/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.java.sip.communicator.service.protocol;

import java.lang.ref.*;
import java.util.*;

import net.java.sip.communicator.service.protocol.event.*;

/**
 * Represents a default/base implementation of
 * <tt>OperationSetDesktopSharingClient</tt> which attempts to make it easier
 * for implementers to provide complete solutions while focusing on
 * implementation-specific functionality.
 *
 * @param <T>
 *
 * @author Sebastien Vincent
 * @author Lyubomir Marinov
 */
public abstract class AbstractOperationSetDesktopSharingClient
        <T extends ProtocolProviderService>
    implements OperationSetDesktopSharingClient
{
    /**
     * The <tt>CallPeerListener</tt> which listens to modifications in the
     * properties/state of <tt>CallPeer</tt>.
     */
    private final CallPeerListener callPeerListener = new CallPeerAdapter()
    {
        /**
         * Indicates that a change has occurred in the status of the source
         * <tt>CallPeer</tt>.
         *
         * @param evt the <tt>CallPeerChangeEvent</tt> instance containing the
         * source event as well as its previous and its new status
         */
        @Override
        public void peerStateChanged(CallPeerChangeEvent evt)
        {
            CallPeer peer = evt.getSourceCallPeer();
            CallPeerState state = peer.getState();

            if(state != null
                    && (state.equals(CallPeerState.DISCONNECTED)
                        || state.equals(CallPeerState.FAILED)))
            {
                removesNullAndRevokedControlPeer(peer.getPeerID());
                removeRemoteControlListener(getListener(peer));
            }
        }
    };

    /**
     * List of the granted remote control peers for this client. Used to
     * remember granted remote control peers, when the granted event is fired
     * before the corresponding UI listener registration.
     */
    private Vector<CallPeer> grantedRemoteControlPeers = new Vector<CallPeer>();

    /**
     * The list of <tt>RemoteControlListener</tt>s to be notified when a change
     * in remote control access occurs.
     */
    private final List<WeakReference<RemoteControlListener>> listeners
        = new ArrayList<WeakReference<RemoteControlListener>>();

    /**
     * Peers who granted/revoked remote control before it's listeners was
     * added
     */
    private List<String> deferredRemoteControlPeers = new ArrayList<>();
    /**
     * The <tt>ProtocolProviderService</tt> implementation which created this
     * instance and for which telephony conferencing services are being provided
     * by this instance.
     */
    protected final T parentProvider;

    /**
     * Initializes a new <tt>AbstractOperationSetDesktopSharing</tt> instance
     * which is to be provided by a specific <tt>ProtocolProviderService.
     *
     * @param parentProvider the <tt>ProtocolProviderService</tt> implementation
     * which is creating the new instance and for which telephony conferencing
     * services are being provided by this instance
     */
    protected AbstractOperationSetDesktopSharingClient(T parentProvider)
    {
        this.parentProvider = parentProvider;
    }

    /**
     * Adds a <tt>RemoteControlListener</tt> to be notified when the remote peer
     * accepts to give us full control of their desktop.
     * <p>
     * The default implementation of
     * <tt>AbstractOperationSetDesktopSharingClient</tt> adds a
     * <tt>WeakReference</tt> to the specified <tt>RemoteControlListener</tt> in
     * order to avoid memory leaks because of code which calls
     * <tt>addRemoteControlListener</tt> and never calls
     * <tt>removeRemoteControlListener</tt>.
     * </p>
     *
     * @param listener the <tt>RemoteControlListener</tt> to add
     */
    public void addRemoteControlListener(RemoteControlListener listener)
    {
        synchronized (listeners)
        {
            Iterator<WeakReference<RemoteControlListener>> i
                = listeners.iterator();
            boolean contains = false;

            while (i.hasNext())
            {
                RemoteControlListener l = i.next().get();

                if (l == null)
                    i.remove();
                else if (l.equals(listener))
                    contains = true;
            }
            if (!contains)
            {
                listeners.add(
                        new WeakReference<RemoteControlListener>(listener));
                listener.getCallPeer().addCallPeerListener(callPeerListener);
            }
        }

        // Notifies the new listener if the corresponding peer has already been
        // granted to remotely control the shared desktop.
        CallPeer peer = listener.getCallPeer();
        // Removes the null peers from the granted remote control peer list.
        // If the corresponding peer was in the granted list, then this peer has
        // already been granted and we must call the remoteControlGranted
        // function for this listener.
        if(this.removesNullAndRevokedControlPeer(peer.getPeerID()) != -1)
            listener.remoteControlGranted(new RemoteControlGrantedEvent(peer));

        if (deferredRemoteControlPeers.contains(peer.getAddress()))
        {
            fireRemoteControlGranted(peer);
            deferredRemoteControlPeers.remove(peer.getAddress());
        }
    }

    /**
     * Fires a <tt>RemoteControlGrantedEvent</tt> to all registered listeners.
     *
     * @param peer the <tt>CallPeer</tt>
     */
    public void fireRemoteControlGranted(CallPeer peer)
    {
        RemoteControlListener listener = getListener(peer);

        if(listener != null)
        {
            listener.remoteControlGranted(new RemoteControlGrantedEvent(peer));
        }
        // The UI has not created the listener yet, then we need to store the
        // information taht this peer has alreayd been granted.
        else
        {
            // Removes all previous instance of this peer.
            this.removesNullAndRevokedControlPeer(peer.getPeerID());
            // Adds the peer to the granted remote control peer list.
            synchronized(this.grantedRemoteControlPeers)
            {
                this.grantedRemoteControlPeers.add(peer);
            }
        }
    }

    /**
     * Fires a <tt>RemoteControlGrantedEvent</tt> to all registered listeners.
     *
     * @param peer the <tt>CallPeer</tt>
     */
    public void fireRemoteControlRevoked(CallPeer peer)
    {
        RemoteControlListener listener = getListener(peer);

        if(listener != null)
        {
            listener.remoteControlRevoked(new RemoteControlRevokedEvent(peer));
        }

        // Removes the peer from the granted remote control peer list.
        this.removesNullAndRevokedControlPeer(peer.getPeerID());
    }

    public void addAddDeferredRemoteControlPeer(String address)
    {
        if (!deferredRemoteControlPeers.contains(address))
        {
            deferredRemoteControlPeers.add(address);
        }
    }
    /**
     * Gets a list of <tt>RemoteControlListener</tt>s to be notified of remote
     * control access changes.
     *
     * @return a list of <tt>RemoteControlListener</tt>s to be notifed of remote
     * control access changes
     */
    protected List<RemoteControlListener> getListeners()
    {
        List<RemoteControlListener> listeners;

        synchronized (this.listeners)
        {
            Iterator<WeakReference<RemoteControlListener>> i
                = this.listeners.iterator();
            listeners
                = new ArrayList<RemoteControlListener>(this.listeners.size());

            while (i.hasNext())
            {
                RemoteControlListener l = i.next().get();

                if (l == null)
                    i.remove();
                else
                    listeners.add(l);
            }
        }
        return listeners;
    }

    /**
     * Removes a <tt>RemoteControlListener</tt> to be notified when remote peer
     * accept/revoke to give us full control.
     *
     * @param listener <tt>RemoteControlListener</tt> to remove
     */
    public void removeRemoteControlListener(RemoteControlListener listener)
    {
        synchronized (listeners)
        {
            Iterator<WeakReference<RemoteControlListener>> i
                = listeners.iterator();

            while (i.hasNext())
            {
                RemoteControlListener l = i.next().get();

                if ((l == null) || l.equals(listener))
                    i.remove();
            }
        }
    }

    /**
     * Removes null and the peer corresponding to the revokedPeerID from the
     * granted control peer list.
     *
     * @param revokedPeerID The ID of the revoked peer. May be null to only
     * clear null instances from the granted control peer list.
     *
     * @return The index corresponding to the revokedPeerID entry. -1 if the
     * revoked PeerID is null, or if the revokedPeerID is not found and removed.
     */
    private int removesNullAndRevokedControlPeer(String revokedPeerID)
    {
        int index = -1;
        synchronized(this.grantedRemoteControlPeers)
        {
            CallPeer peer;
            for(int i = 0; i < this.grantedRemoteControlPeers.size(); ++i)
            {
                peer = this.grantedRemoteControlPeers.get(i);
                if(peer == null || peer.getPeerID().equals(revokedPeerID))
                {
                    this.grantedRemoteControlPeers.remove(i);
                    index = i;
                    --i;
                }
            }
        }
        return index;
    }

    /**
     * Returns the <tt>RemoteControlListener</tt> corresponding to the given
     * <tt>callPeer</tt>, if it exists.
     *
     * @param callPeer the <tt>CallPeer</tt> to get the corresponding
     * <tt>RemoteControlListener</tt> of
     * @return the <tt>RemoteControlListener</tt> corresponding to the given
     * <tt>callPeer</tt>, if it exists; <tt>null</tt>, otherwise
     */
    protected RemoteControlListener getListener(CallPeer callPeer)
    {
        String peerID = callPeer.getPeerID();

        synchronized (listeners)
        {
            Iterator<WeakReference<RemoteControlListener>> i
                = listeners.iterator();

            while (i.hasNext())
            {
                RemoteControlListener l = i.next().get();

                if (l == null)
                    i.remove();
                else if (peerID.equals(l.getCallPeer().getPeerID()))
                    return l;
            }
        }
        return null;
    }
}
