/*
 * Copyright (C) 2012 Google Inc. All rights reserved.
 * Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies).
 * Copyright (C) 2015, 2016 Ericsson AB. All rights reserved.
 * Copyright (C) 2017 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer
 *    in the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name of Google Inc. nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#if ENABLE(WEB_RTC)

#include "RTCPeerConnection.h"

#include "DOMError.h"
#include "Document.h"
#include "Event.h"
#include "ExceptionCode.h"
#include "Frame.h"
#include "JSDOMError.h"
#include "MediaEndpointConfiguration.h"
#include "MediaStream.h"
#include "MediaStreamTrack.h"
#include "Page.h"
#include "RTCConfiguration.h"
#include "RTCController.h"
#include "RTCDataChannel.h"
#include "RTCIceCandidate.h"
#include "RTCSessionDescription.h"
#include "RTCTrackEvent.h"
#include "UUID.h"
#include <wtf/MainThread.h>
#include <wtf/text/Base64.h>

namespace WebCore {

using namespace PeerConnection;

PassRefPtr<RTCPeerConnection> RTCPeerConnection::create(ScriptExecutionContext* context)
{
    RefPtr<RTCPeerConnection> peerConnection = adoptRef(new RTCPeerConnection(context));
    peerConnection->suspendIfNeeded();
    // RTCPeerConnection may send events at about any time during its lifetime.
    // Let's make it uncollectable until the pc is closed by JS or the page stops it.
    if (peerConnection->m_signalingState != RTCSignalingState::Closed) {
        peerConnection->setPendingActivity(peerConnection.get());
        peerConnection->registerToController();
    }
    return peerConnection.release();
}

RTCPeerConnection::RTCPeerConnection(ScriptExecutionContext* context)
    : ActiveDOMObject(context)
	, m_isStopped(false)
    , m_signalingState(RTCSignalingState::Stable)
    , m_iceGatheringState(RTCIceGatheringState::New)
    , m_iceConnectionState(RTCIceConnectionState::New)
	, m_connectionState(RTCPeerConnectionState::New)
	, m_transceiverSet(adoptPtr(new RtpTransceiverSet()))
	, m_backend(PeerConnectionBackend::create(*this))
{
}

RTCPeerConnection::~RTCPeerConnection()
{
    stop();
}

void RTCPeerConnection::initializeWith(Document* document, const RTCConfiguration& configuration, ExceptionCode& ec)
{
    if (!document->frame()) {
        ec = NOT_SUPPORTED_ERR;
        return;
    }

    if (!m_backend) {
        ec = NOT_SUPPORTED_ERR;
        return;
    }

    setConfiguration(configuration, ec);
}

PassRefPtr<RTCRtpSender> RTCPeerConnection::addTrack(MediaStreamTrack& track, const Vector<RefPtr<MediaStream>>& streams, ExceptionCode& ec)
{
    if (m_signalingState == RTCSignalingState::Closed) {
        ec = INVALID_STATE_ERR;
        return nullptr;
    }

    // Require at least one stream until https://github.com/w3c/webrtc-pc/issues/288 is resolved
    if (!streams.size()) {
        ec = NOT_SUPPORTED_ERR;
        return nullptr;
    }

    for(int i = 0; i < m_transceiverSet->senders().size(); ++i) {
        if (m_transceiverSet->senders()[i]->trackId() == track.id()) {
            ec = INVALID_ACCESS_ERR;
            return nullptr;
        }
    }

    Vector<String> mediaStreamIds;
    for(int i = 0; i < streams.size(); ++i)
        mediaStreamIds.append(streams[i]->id());

    RTCRtpSender* sender = 0;

    // Reuse an existing sender with the same track kind if it has never been used to send before.
    for(int i = 0; i < m_transceiverSet->list().size(); ++i) {
    	const RefPtr<RTCRtpTransceiver>& transceiver = m_transceiverSet->list()[i];
        RTCRtpSender& existingSender = transceiver->sender();
        if (existingSender.trackKind() == track.kind() && existingSender.trackId().isNull() && !transceiver->hasSendingDirection()) {
            existingSender.setTrack(track);
            existingSender.setMediaStreamIds(WTFMove(mediaStreamIds));
            transceiver->enableSendingDirection();
            sender = &existingSender;

            break;
        }
    }

    if (!sender) {
        String transceiverMid = RTCRtpTransceiver::getNextMid();
        const String& trackKind = track.kind();
        String trackId = createVersion4UUIDString();

        RefPtr<RTCRtpSender> newSender = RTCRtpSender::create(track, WTFMove(mediaStreamIds), *this);
        RefPtr<RTCRtpReceiver> receiver = m_backend->createReceiver(transceiverMid, trackKind, trackId);
        RefPtr<RTCRtpTransceiver> transceiver = RTCRtpTransceiver::create(newSender.release(), receiver.release());

        // This transceiver is not yet associated with an m-line (null mid), but we need a
        // provisional mid if the transceiver is used to create an offer.
        transceiver->setProvisionalMid(transceiverMid);

        sender = &transceiver->sender();
        m_transceiverSet->append(transceiver.release());
    }

#if !USE(LIBWEBRTC)
    m_backend->markAsNeedingNegotiation();
#endif

    m_backend->notifyAddedTrack(*sender);
    return sender;
}

void RTCPeerConnection::removeTrack(RTCRtpSender& sender, ExceptionCode& ec)
{
    if (m_signalingState == RTCSignalingState::Closed) {
        ec = INVALID_STATE_ERR;
        return;
    }

    bool shouldAbort = true;
    for(int i = 0; i < m_transceiverSet->senders().size(); ++i) {
        if (m_transceiverSet->senders()[i] == &sender) {
            shouldAbort = sender.isStopped();
            break;
        }
    }
    if (shouldAbort)
        return;

    sender.stop();

    m_backend->notifyRemovedTrack(sender);
#if !USE(LIBWEBRTC)
    m_backend->markAsNeedingNegotiation();
#endif
}

RefPtr<RTCRtpTransceiver> RTCPeerConnection::addTransceiver(MediaStreamTrack& track, const RTCRtpTransceiverInit& init, ExceptionCode& ec)
{
    if (m_signalingState == RTCSignalingState::Closed) {
        ec = INVALID_STATE_ERR;
        return nullptr;
    }

    String transceiverMid = RTCRtpTransceiver::getNextMid();
    const String& trackKind = track.kind();
    const String& trackId = track.id();

    RefPtr<RTCRtpSender> sender = RTCRtpSender::create(track, Vector<String>(), *this);
    RefPtr<RTCRtpReceiver> receiver = m_backend->createReceiver(transceiverMid, trackKind, trackId);
    RefPtr<RTCRtpTransceiver> transceiver = RTCRtpTransceiver::create(sender.release(), receiver.release());
    transceiver->setProvisionalMid(transceiverMid);

    return completeAddTransceiver(transceiver.release(), init);
}

RefPtr<RTCRtpTransceiver> RTCPeerConnection::addTransceiver(const String& kind, const RTCRtpTransceiverInit& init, ExceptionCode& ec)
{
    if (m_signalingState == RTCSignalingState::Closed) {
        ec = INVALID_STATE_ERR;
        return nullptr;
    }

    if (kind != "audio" && kind != "video") {
        ec = TypeError;
        return nullptr;
    }

    String transceiverMid = RTCRtpTransceiver::getNextMid();
    String trackId = createVersion4UUIDString();

    RefPtr<RTCRtpSender> sender = RTCRtpSender::create(String(kind), Vector<String>(), *this);
    RefPtr<RTCRtpReceiver> receiver = m_backend->createReceiver(transceiverMid, kind, trackId);
    RefPtr<RTCRtpTransceiver> transceiver = RTCRtpTransceiver::create(sender.release(), receiver.release());
    transceiver->setProvisionalMid(transceiverMid);

    return completeAddTransceiver(transceiver.release(), init);
}

RefPtr<RTCRtpTransceiver> RTCPeerConnection::completeAddTransceiver(RefPtr<RTCRtpTransceiver> transceiver, const RTCRtpTransceiverInit& init)
{
    transceiver->setDirection(init.direction);

    m_transceiverSet->append(transceiver);
#if !USE(LIBWEBRTC)
    m_backend->markAsNeedingNegotiation();
#endif
    return transceiver.release();
}

void RTCPeerConnection::queuedCreateOffer(const RTCOfferOptions& options, SessionDescriptionPromise promise)
{
    if (m_signalingState == RTCSignalingState::Closed) {
        promise->reject(DOMError::create("InvalidStateError"));
        return;
    }

    m_backend->createOffer(options, promise);
}

void RTCPeerConnection::queuedCreateAnswer(const RTCAnswerOptions& options, SessionDescriptionPromise promise)
{
    if (m_signalingState == RTCSignalingState::Closed) {
        promise->reject(DOMError::create(ASCIILiteral("InvalidStateError")));
        return;
    }

    m_backend->createAnswer(options, promise);
}

void RTCPeerConnection::queuedSetLocalDescription(RTCSessionDescription& description, PeerConnection::VoidPromise promise)
{
    if (m_signalingState == RTCSignalingState::Closed) {
        promise->reject(DOMError::create(ASCIILiteral("InvalidStateError")));
        return;
    }

    ASSERT(description);
    m_backend->setLocalDescription(description, promise);
}

RefPtr<RTCSessionDescription> RTCPeerConnection::localDescription() const
{
    return m_backend->localDescription();
}

RefPtr<RTCSessionDescription> RTCPeerConnection::currentLocalDescription() const
{
    return m_backend->currentLocalDescription();
}

RefPtr<RTCSessionDescription> RTCPeerConnection::pendingLocalDescription() const
{
    return m_backend->pendingLocalDescription();
}

void RTCPeerConnection::queuedSetRemoteDescription(RTCSessionDescription& description, PeerConnection::VoidPromise promise)
{
    if (m_signalingState == RTCSignalingState::Closed) {
        promise->reject(DOMError::create(ASCIILiteral("InvalidStateError")));
        return;
    }

    ASSERT(description);
    m_backend->setRemoteDescription(description, promise);
}

RefPtr<RTCSessionDescription> RTCPeerConnection::remoteDescription() const
{
    return m_backend->remoteDescription();
}

RefPtr<RTCSessionDescription> RTCPeerConnection::currentRemoteDescription() const
{
    return m_backend->currentRemoteDescription();
}

RefPtr<RTCSessionDescription> RTCPeerConnection::pendingRemoteDescription() const
{
    return m_backend->pendingRemoteDescription();
}

void RTCPeerConnection::queuedAddIceCandidate(RTCIceCandidate& rtcCandidate, VoidPromise promise)
{
    if (m_signalingState == RTCSignalingState::Closed) {
        promise->reject(DOMError::create(ASCIILiteral("InvalidStateError")));
        return;
    }

    ASSERT(rtcCandidate);
    m_backend->addIceCandidate(rtcCandidate, promise);
}

void RTCPeerConnection::setConfiguration(const RTCConfiguration& configuration, ExceptionCode& ec)
{
    if (m_signalingState == RTCSignalingState::Closed) {
        ec = INVALID_STATE_ERR;
        return;
    }

    Vector<MediaEndpointConfiguration::IceServerInfo> servers;
    if (!configuration.iceServers.isEmpty()) {
        servers.reserveInitialCapacity(configuration.iceServers.size());
        for (auto& server : configuration.iceServers) {
            Vector<KURL> serverURLs;
			serverURLs.reserveInitialCapacity(server.urls.size());
			for (auto& string : server.urls)
				serverURLs.uncheckedAppend(KURL(KURL(), string));
            for (auto& serverURL : serverURLs) {
                if (!(serverURL.protocolIs("turn") || serverURL.protocolIs("turns") || serverURL.protocolIs("stun"))) {
                    ec = INVALID_ACCESS_ERR;
                    return;
                }
            }
            servers.uncheckedAppend(MediaEndpointConfiguration::IceServerInfo(WTFMove(serverURLs), server.credential, server.username));
        }
    }

    m_backend->setConfiguration(MediaEndpointConfiguration(WTFMove(servers), configuration.iceTransportPolicy, configuration.bundlePolicy, configuration.iceCandidatePoolSize));
    m_configuration = configuration;
}

void RTCPeerConnection::getStats(MediaStreamTrack* selector, RefPtr<DeferredPromise> promise)
{
    m_backend->getStats(selector, WTFMove(promise));
}

void RTCPeerConnection::getStats(RefPtr<DeferredPromise> promise)
{
    getStats(0, promise.release());
}

PassRefPtr<RTCDataChannel> RTCPeerConnection::createDataChannel(ScriptExecutionContext* context, const String& label, const RTCDataChannelInit& options, ExceptionCode& ec)
{
    if (m_signalingState == RTCSignalingState::Closed) {
        ec = INVALID_STATE_ERR;
        return nullptr;
    }

    if (options.negotiated && !options.negotiated.value() && (label.length() > 65535 || options.protocol.length() > 65535)) {
        ec = TypeError;
        return nullptr;
    }

    if (options.maxPacketLifeTime && options.maxRetransmits) {
        ec = TypeError;
        return nullptr;
    }

    if (options.id && options.id.value() > 65534) {
        ec = TypeError;
        return nullptr;
    }

    OwnPtr<RTCDataChannelHandler> channelHandler = m_backend->createDataChannelHandler(label, options);
    if (!channelHandler) {
    	ec = NOT_SUPPORTED_ERR;
        return nullptr;
    }

    return RTCDataChannel::create(context, channelHandler.release(), label, options);
}

bool RTCPeerConnection::doClose()
{
    if (m_signalingState == RTCSignalingState::Closed)
        return false;

    m_iceConnectionState = RTCIceConnectionState::Closed;
    m_signalingState = RTCSignalingState::Closed;

    for (auto& receiver : m_transceiverSet->receivers())
        receiver->stop();

    for (auto& sender : m_transceiverSet->senders())
        sender->stop();

    m_backend->stop();

    return true;
}

class RTCPeerConnection::CloseTask : public ScriptExecutionContext::Task
{
public:
	CloseTask(RTCPeerConnection& connection)
	: m_RTCPeerConnection(&connection)
	{
	}
    virtual void performTask(ScriptExecutionContext*)
    {
    	m_RTCPeerConnection->doStop();
    }
private:
    RefPtr<RTCPeerConnection> m_RTCPeerConnection;
};

void RTCPeerConnection::close()
{
    if (!doClose())
        return;

    updateConnectionState();
    scriptExecutionContext()->postTask(adoptPtr(new CloseTask(*this)));
}

void RTCPeerConnection::emulatePlatformEvent(const String& action)
{
    m_backend->emulatePlatformEvent(action);
}

void RTCPeerConnection::stop()
{
    if (!doClose())
        return;

    doStop();
}

void RTCPeerConnection::doStop()
{
    if (m_isStopped)
        return;

    m_isStopped = true;
    unregisterFromController();
    unsetPendingActivity(this);
}

RTCController& RTCPeerConnection::rtcController()
{
    ASSERT(scriptExecutionContext());
    ASSERT(scriptExecutionContext()->isDocument());
    auto* page = static_cast<Document*>(scriptExecutionContext())->page();
    return page->rtcController();
}

void RTCPeerConnection::registerToController()
{
    rtcController().add(*this);
}

void RTCPeerConnection::unregisterFromController()
{
    rtcController().remove(*this);
}

const char* RTCPeerConnection::activeDOMObjectName() const
{
    return "RTCPeerConnection";
}

bool RTCPeerConnection::canSuspendForDocumentSuspension() const
{
    // FIXME: We should try and do better here.
    return false;
}

void RTCPeerConnection::addTransceiver(PassRefPtr<RTCRtpTransceiver> transceiver)
{
    m_transceiverSet->append(transceiver);
}

void RTCPeerConnection::setSignalingState(RTCSignalingState newState)
{
    m_signalingState = newState;
}

class RTCPeerConnection::UpdateIceGatheringStateTask : public ScriptExecutionContext::Task
{
public:
	UpdateIceGatheringStateTask(RTCPeerConnection& connection, RTCIceGatheringState newState)
	: m_RTCPeerConnection(&connection)
	, m_newState(newState)
	{
	}
    virtual void performTask(ScriptExecutionContext*)
    {
        if (m_RTCPeerConnection->m_signalingState == RTCSignalingState::Closed || m_RTCPeerConnection->m_iceGatheringState == m_newState)
            return;

    	m_RTCPeerConnection->m_iceGatheringState = m_newState;
    	m_RTCPeerConnection->dispatchEvent(Event::create(eventNames().icegatheringstatechangeEvent, false, false));
    	m_RTCPeerConnection->updateConnectionState();
    }
private:
    RefPtr<RTCPeerConnection> m_RTCPeerConnection;
    RTCIceGatheringState m_newState;
};

void RTCPeerConnection::updateIceGatheringState(RTCIceGatheringState newState)
{
    scriptExecutionContext()->postTask(adoptPtr(new UpdateIceGatheringStateTask(*this, newState)));
}

class RTCPeerConnection::UpdateIceConnectionStateTask : public ScriptExecutionContext::Task
{
public:
	UpdateIceConnectionStateTask(RTCPeerConnection& connection, RTCIceConnectionState newState)
	: m_RTCPeerConnection(&connection)
	, m_newState(newState)
	{
	}
    virtual void performTask(ScriptExecutionContext*)
    {
        if (m_RTCPeerConnection->m_signalingState == RTCSignalingState::Closed || m_RTCPeerConnection->m_iceConnectionState == m_newState)
            return;

    	m_RTCPeerConnection->m_iceConnectionState = m_newState;
    	m_RTCPeerConnection->dispatchEvent(Event::create(eventNames().iceconnectionstatechangeEvent, false, false));
    	m_RTCPeerConnection->updateConnectionState();
    }
private:
    RefPtr<RTCPeerConnection> m_RTCPeerConnection;
    RTCIceConnectionState m_newState;
};

void RTCPeerConnection::updateIceConnectionState(RTCIceConnectionState newState)
{
    scriptExecutionContext()->postTask(adoptPtr(new UpdateIceConnectionStateTask(*this, newState)));
}

void RTCPeerConnection::updateConnectionState()
{
    RTCPeerConnectionState state;

    if (m_iceConnectionState == RTCIceConnectionState::New && m_iceGatheringState == RTCIceGatheringState::New)
        state = RTCPeerConnectionState::New;
    else if (m_iceConnectionState == RTCIceConnectionState::Checking || m_iceGatheringState == RTCIceGatheringState::Gathering)
        state = RTCPeerConnectionState::Connecting;
    else if ((m_iceConnectionState == RTCIceConnectionState::Completed || m_iceConnectionState == RTCIceConnectionState::Connected) && m_iceGatheringState == RTCIceGatheringState::Complete)
        state = RTCPeerConnectionState::Connected;
    else if (m_iceConnectionState == RTCIceConnectionState::Disconnected)
        state = RTCPeerConnectionState::Disconnected;
    else if (m_iceConnectionState == RTCIceConnectionState::Failed)
        state = RTCPeerConnectionState::Failed;
    else if (m_iceConnectionState == RTCIceConnectionState::Closed)
        state = RTCPeerConnectionState::Closed;
    else
        return;

    if (state == m_connectionState)
        return;

    m_connectionState = state;
    dispatchEvent(Event::create(eventNames().connectionstatechangeEvent, false, false));
}

class RTCPeerConnection::ScheduleNegotiationNeededEventTask : public ScriptExecutionContext::Task
{
public:
	ScheduleNegotiationNeededEventTask(RTCPeerConnection& connection)
	: m_RTCPeerConnection(&connection)
	{
	}
    virtual void performTask(ScriptExecutionContext*)
    {
        if (m_RTCPeerConnection->m_backend->isNegotiationNeeded()) {
        	m_RTCPeerConnection->m_backend->clearNegotiationNeededState();
        	m_RTCPeerConnection->dispatchEvent(Event::create(eventNames().negotiationneededEvent, false, false));
        }
    }
private:
    RefPtr<RTCPeerConnection> m_RTCPeerConnection;
};

void RTCPeerConnection::scheduleNegotiationNeededEvent()
{
    scriptExecutionContext()->postTask(adoptPtr(new ScheduleNegotiationNeededEventTask(*this)));
}

void RTCPeerConnection::fireEvent(Event& event)
{
    dispatchEvent(&event);
}

void RTCPeerConnection::enqueueReplaceTrackTask(RTCRtpSender& sender, Ref<MediaStreamTrack>&& withTrack, RefPtr<DeferredPromise>&& promise)
{
	class TaskImpl : public ScriptExecutionContext::Task
	{
	public:
		TaskImpl(RTCRtpSender& sender, RefPtr<DeferredPromise>&& promise, Ref<MediaStreamTrack>&& withTrack)
			: protectedSender(sender)
			, promise(WTFMove(promise))
			, withTrack(WTFMove(withTrack))
		{
		}
        virtual void performTask(ScriptExecutionContext*) OVERRIDE
		{
        	protectedSender->setTrack(WTFMove(withTrack));
        	promise->resolve();
		}
	private:
		Ref<RTCRtpSender> protectedSender;
		RefPtr<DeferredPromise> promise;
		Ref<MediaStreamTrack> withTrack;
	};
    scriptExecutionContext()->postTask(adoptPtr(new TaskImpl(sender, WTFMove(promise), WTFMove(withTrack))));
}

void RTCPeerConnection::replaceTrack(RTCRtpSender& sender, RefPtr<MediaStreamTrack>&& withTrack, RefPtr<DeferredPromise>&& promise)
{
    if (!withTrack) {
    	class TaskImpl : public ScriptExecutionContext::Task
    	{
    	public:
    		TaskImpl(RTCRtpSender& sender, RefPtr<DeferredPromise>&& promise)
    			: protectedSender(sender)
    			, promise(WTFMove(promise))
    		{
    		}
            virtual void performTask(ScriptExecutionContext*) OVERRIDE
    		{
            	protectedSender->setTrackToNull();
            	promise->resolve();
    		}
    	private:
    		Ref<RTCRtpSender> protectedSender;
    		RefPtr<DeferredPromise> promise;
    	};
        scriptExecutionContext()->postTask(adoptPtr(new TaskImpl(sender, WTFMove(promise))));
        return;
    }

    if (!sender.track()) {
        enqueueReplaceTrackTask(sender, withTrack.releaseNonNull(), WTFMove(promise));
        return;
    }

    m_backend->replaceTrack(sender, withTrack.releaseNonNull(), WTFMove(promise));
}

RTCRtpParameters RTCPeerConnection::getParameters(RTCRtpSender& sender) const
{
    return m_backend->getParameters(sender);
}

EventTargetInterface RTCPeerConnection::eventTargetInterface() const
{
    return RTCPeerConnectionEventTargetInterfaceType;
}

ScriptExecutionContext* RTCPeerConnection::scriptExecutionContext() const
{
    return ActiveDOMObject::scriptExecutionContext();
}

} // namespace WebCore

#endif // ENABLE(WEB_RTC)
