/*
 * 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.media;

import java.awt.*;
import java.beans.*;
import java.text.*;
import java.util.List;

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

import org.jitsi.service.neomedia.*;
import org.jitsi.util.event.*;
import org.jitsi.utils.*;

/**
 * Represents a default implementation of <tt>OperationSetVideoTelephony</tt> in
 * order to make it easier for implementers to provide complete solutions while
 * focusing on implementation-specific details.
 *
 * @param <T> the implementation specific telephony operation set class like for
 * example <tt>OperationSetBasicTelephonySipImpl</tt>.
 * @param <U> the implementation specific provider class like for example
 * <tt>ProtocolProviderServiceSipImpl</tt>.
 * @param <V> the <tt>MediaAwareCall</tt> implementation like
 * <tt>CallSipImpl</tt> or <tt>CallJabberImpl</tt>.
 * @param <W> the <tt>MediaAwarePeerCall</tt> implementation like
 * <tt>CallPeerSipImpl</tt> or <tt>CallPeerJabberImpl</tt>.
 *
 * @author Emil Ivov
 * @author Sebastien Vincent
 */
public abstract class AbstractOperationSetVideoTelephony<
                                    T extends OperationSetBasicTelephony<U>,
                                    U extends ProtocolProviderService,
                                    V extends MediaAwareCall<W, T, U>,
                                    W extends MediaAwareCallPeer<V, ?, U> >
    implements OperationSetVideoTelephony
{
    /**
     * The SIP <tt>ProtocolProviderService</tt> implementation which created
     * this instance and for which telephony conferencing services are being
     * provided by this instance.
     */
    protected final U parentProvider;

    /**
     * The telephony-related functionality this extension builds upon.
     */
    protected final T basicTelephony;

    /**
     * Initializes a new <tt>AbstractOperationSetVideoTelephony</tt> instance
     * which builds upon the telephony-related functionality of a specific
     * <tt>OperationSetBasicTelephony</tt> implementation.
     *
     * @param basicTelephony the <tt>OperationSetBasicTelephony</tt>
     * the new extension should build upon
     */
    public AbstractOperationSetVideoTelephony(T basicTelephony)
    {
        this.basicTelephony = basicTelephony;
        this.parentProvider = basicTelephony.getProtocolProvider();
    }

    /**
     * Delegates to the <tt>CallPeerMediaHandler</tt> of the specified
     * <tt>CallPeer</tt> because the video is provided by it. Because other
     * <tt>OperationSetVideoTelephony</tt> implementations may not provide their
     * video through the <tt>CallPeerMediaHandler</tt>, this implementation
     * promotes itself as the provider of the video by replacing the
     * <tt>CallPeerMediaHandler</tt> in the <tt>VideoEvents</tt> it fires.
     *
     * @param peer the <tt>CallPeer</tt> that we will be registering
     * <tt>listener</tt> with.
     * @param listener the <tt>VideoListener</tt> that we'd like to register.
     */
    @SuppressWarnings("unchecked") // work with MediaAware* in media package
    public void addVideoListener(CallPeer peer, VideoListener listener)
    {
        if (listener == null)
            throw new NullPointerException("listener");

        ((W)peer).getMediaHandler().addVideoListener(listener);
    }

    /**
     * Implements
     * {@link OperationSetVideoTelephony#createLocalVisualComponent(CallPeer)}.
     *
     * @param peer the <tt>CallPeer</tt> that we are sending our local video to.
     * @return the <tt>Component</tt> containing the local video.
     * @throws OperationFailedException if we fail extracting the local video.
     */
    @SuppressWarnings("unchecked") // work with MediaAware* in media package
    public Component getLocalVisualComponent(CallPeer peer)
        throws OperationFailedException
    {
        return ((W)peer).getMediaHandler().getLocalVisualComponent();
    }

    /**
     * Gets the visual/video <tt>Component</tt> available in this telephony for
     * a specific <tt>CallPeer</tt>.
     *
     * @param peer the <tt>CallPeer</tt> whose video is to be retrieved
     * @return the visual/video <tt>Component</tt> available in this telephony
     * for the specified <tt>peer</tt> if any; otherwise, <tt>null</tt>
     */
    @Deprecated
    public Component getVisualComponent(CallPeer peer)
    {
        List<Component> visualComponents = getVisualComponents(peer);

        return visualComponents.isEmpty() ? null : visualComponents.get(0);
    }

    /**
     * Gets the visual/video <tt>Component</tt>s available in this telephony for
     * a specific <tt>CallPeer</tt>.
     *
     * @param peer the <tt>CallPeer</tt> whose videos are to be retrieved
     * @return the visual/video <tt>Component</tt>s available in this telephony
     * for the specified <tt>peer</tt>
     */
    @SuppressWarnings("unchecked") // work with MediaAware* in media package
    public List<Component> getVisualComponents(CallPeer peer)
    {
        return ((W)peer).getMediaHandler().getVisualComponents();
    }

    /**
     * Returns the <tt>ConferenceMember</tt> corresponding to the given
     * <tt>visualComponent</tt>.
     *
     * @param peer the parent <tt>CallPeer</tt>
     * @param visualComponent the visual <tt>Component</tt>, which corresponding
     * <tt>ConferenceMember</tt> we're looking for
     * @return the <tt>ConferenceMember</tt> corresponding to the given
     * <tt>visualComponent</tt>.
     */
    public ConferenceMember getConferenceMember(CallPeer peer,
                                                Component visualComponent)
    {
        @SuppressWarnings("unchecked")
        W w = (W) peer;
        VideoMediaStream videoStream
            = (VideoMediaStream) w.getMediaHandler().getStream(MediaType.VIDEO);

        if (videoStream != null)
        {
            for (ConferenceMember member : peer.getConferenceMembers())
            {
                Component memberComponent
                    = videoStream.getVisualComponent(member.getVideoSsrc());

                if (visualComponent.equals(memberComponent))
                    return member;
            }
        }
        return null;
    }

    /**
     * Delegates to the <tt>CallPeerMediaHandler</tt> of the specified
     * <tt>CallPeer</tt> because the video is provided by it.
     *
     * @param peer the <tt>CallPeer</tt> that we'd like to unregister our
     * <tt>VideoListener</tt> from.
     * @param listener the <tt>VideoListener</tt> that we'd like to unregister.
     */
    @SuppressWarnings("unchecked") // work with MediaAware* in media package
    public void removeVideoListener(CallPeer peer, VideoListener listener)
    {
        if (listener != null)
            ((W)peer).getMediaHandler().removeVideoListener(listener);
    }

    /**
     * Implements OperationSetVideoTelephony#setLocalVideoAllowed(Call,
     * boolean). Modifies the local media setup to reflect the requested setting
     * for the streaming of the local video and then re-invites all
     * CallPeers to re-negotiate the modified media setup.
     *
     * @param call    the call where we'd like to allow sending local video.
     * @param allowed <tt>true</tt> if local video transmission is allowed and
     * <tt>false</tt> otherwise.
     *
     * @throws OperationFailedException if video initialization fails.
     */
    public void setLocalVideoAllowed(Call call, boolean allowed)
        throws OperationFailedException
    {
        MediaAwareCall<?, ?, ?> mediaAwareCall = (MediaAwareCall<?, ?, ?>) call;
        MediaUseCase useCase = MediaUseCase.CALL;

        mediaAwareCall.setLocalVideoAllowed(allowed, useCase);
    }

    /**
     * Determines whether the streaming of local video in a specific
     * <tt>Call</tt> is currently allowed. The setting does not reflect
     * the availability of actual video capture devices, it just expresses the
     * desire of the user to have the local video streamed in the case the
     * system is actually able to do so.
     *
     * @param call the <tt>Call</tt> whose video transmission properties we are
     * interested in.
     *
     * @return <tt>true</tt> if the streaming of local video for the specified
     * <tt>Call</tt> is allowed; otherwise, <tt>false</tt>
     */
    @SuppressWarnings("unchecked") // work with MediaAware* in media package
    public boolean isLocalVideoAllowed(Call call)
    {
        return ((V)call).isLocalVideoAllowed(MediaUseCase.CALL);
    }

    /**
     * Determines whether a specific <tt>Call</tt> is currently streaming the
     * local video (to a remote destination).
     *
     * @param call the <tt>Call</tt> whose video transmission we are interested
     * in.
     *
     * @return <tt>true</tt> if the specified <tt>Call</tt> is currently
     * streaming the local video (to a remote destination); otherwise,
     * <tt>false</tt>
     */
    @SuppressWarnings("unchecked") // work with MediaAware* in media package
    public boolean isLocalVideoStreaming(Call call)
    {
        return ((V)call).isLocalVideoStreaming();
    }

    /**
     * Adds a specific <tt>PropertyChangeListener</tt> to the list of
     * listeners which get notified when the properties (e.g.
     * {@link #LOCAL_VIDEO_STREAMING}) associated with a specific
     * <tt>Call</tt> change their values.
     *
     * @param call the <tt>Call</tt> to start listening to the changes of
     * the property values of
     * @param listener the <tt>PropertyChangeListener</tt> to be notified
     * when the properties associated with the specified <tt>Call</tt> change
     * their values
     */
    @SuppressWarnings("unchecked") // work with MediaAware* in media package
    public void addPropertyChangeListener(
            Call call,
            PropertyChangeListener listener)
    {
        ((V)call).addVideoPropertyChangeListener(listener);
    }

    /**
     * Removes a specific <tt>PropertyChangeListener</tt> from the list of
     * listeners which get notified when the properties (e.g.
     * {@link #LOCAL_VIDEO_STREAMING}) associated with a specific
     * <tt>Call</tt> change their values.
     *
     * @param call the <tt>Call</tt> to stop listening to the changes of the
     * property values of
     * @param listener the <tt>PropertyChangeListener</tt> to no longer be
     * notified when the properties associated with the specified <tt>Call</tt>
     * change their values
     */
    @SuppressWarnings("unchecked") // work with MediaAware* in media package
    public void removePropertyChangeListener(
            Call call,
            PropertyChangeListener listener)
    {
        ((V)call).removeVideoPropertyChangeListener(listener);
    }

    /**
     * Get the <tt>MediaUseCase</tt> of a video telephony operation set.
     *
     * @return <tt>MediaUseCase.CALL</tt>
     */
    public MediaUseCase getMediaUseCase()
    {
        return MediaUseCase.CALL;
    }

    /**
     * Returns the quality control for video calls if any.
     * Return null so protocols who supports it to override it.
     * @param peer the peer which this control operates on.
     * @return the implemented quality control.
     */
    public QualityControl getQualityControl(CallPeer peer)
    {
        return null;
    }

    /**
     * Create a new video call and invite the specified CallPeer to it with
     * initial video setting.
     *
     * @param uri the address of the callee that we should invite to a new
     * call.
     * @param qualityPreferences the quality preset we will use establishing
     * the video call, and we will expect from the other side. When establishing
     * call we don't have any indications whether remote part supports quality
     * presets, so this setting can be ignored.
     * @return CallPeer the CallPeer that will represented by the
     * specified uri. All following state change events will be delivered
     * through that call peer. The Call that this peer is a member
     * of could be retrieved from the CallParticipatn instance with the use
     * of the corresponding method.
     * @throws OperationFailedException with the corresponding code if we fail
     * to create the video call.
     * @throws java.text.ParseException if <tt>callee</tt> is not a valid sip address
     * string.
     */
    public Call createVideoCall(String uri, QualityPreset qualityPreferences)
        throws OperationFailedException,
            ParseException
    {
        return createVideoCall(uri);
    }

    /**
     * Create a new video call and invite the specified CallPeer to it with
     * initial video setting.
     *
     * @param callee the address of the callee that we should invite to a new
     * call.
     * @param qualityPreferences the quality preset we will use establishing
     * the video call, and we will expect from the other side. When establishing
     * call we don't have any indications whether remote part supports quality
     * presets, so this setting can be ignored.
     * @return CallPeer the CallPeer that will represented by the
     * specified uri. All following state change events will be delivered
     * through that call peer. The Call that this peer is a member
     * of could be retrieved from the CallParticipatn instance with the use
     * of the corresponding method.
     * @throws OperationFailedException with the corresponding code if we fail
     * to create the video call.
     */
    public Call createVideoCall(
            Contact callee, QualityPreset qualityPreferences)
        throws OperationFailedException
    {
        return createVideoCall(callee);
    }
}
