/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "EP_AudioProvider.h"
#include "EP_Engine.h"
#include "../media/MT_Box.h"
#include "../media/MT_AudioStream.h"
#include "../media/MT_SrtpHelper.h"
#include "../media/MT_Stream.h"
#include "../helper/HL_Rtp.h"
#include "../helper/HL_StreamState.h"
#include "../helper/HL_Log.h"
#include "../helper/HL_String.h"

#define LOG_SUBSYSTEM "engine"

AudioProvider::AudioProvider(UserAgent& agent, MT::Terminal& terminal)
    :mUserAgent(agent), mTerminal(terminal), mState(0),
      mRemoteTelephoneCodec(0), mRemoteNoSdp(false)
{
    mActive = mfActive;
    mRemoteState = msSendRecv;
    mActiveStream = mTerminal.createStream(MT::Stream::Audio, mUserAgent.config());
    if (mUserAgent.config().exists(CONFIG_CODEC_PRIORITY))
        mCodecPriority.setupFrom(mUserAgent.config()[CONFIG_CODEC_PRIORITY].asVMap());
    mSrtpSuite = SRTP_NONE;
    setStateImpl((int)StreamState::SipRecv | (int)StreamState::SipSend | (int)StreamState::Receiving | (int)StreamState::Sending);
}

AudioProvider::~AudioProvider()
{
}

std::string AudioProvider::streamName()
{
    return "audio";
}

std::string AudioProvider::streamProfile()
{
    if (mState & (int)StreamState::Srtp)
        return "RTP/SAVP";
    else
        return "RTP/AVP";
}

// Sets destination IP address
void  AudioProvider::setDestinationAddress(const RtpPair<InternetAddress>& addr)
{
    if (!mActiveStream)
        return;

    mActiveStream->setDestination(addr);
}

void AudioProvider::configureMediaObserver(MT::Stream::MediaObserver *observer, void* userTag)
{
    mMediaObserver = observer;
    mMediaObserverTag = userTag;
    if (mActiveStream)
        mActiveStream->configureMediaObserver(observer, userTag);
}

// Processes incoming data
void  AudioProvider::processData(const PDatagramSocket& s, const void* dataBuffer, int dataSize, InternetAddress& source)
{
    if (!mActiveStream)
        return;

    if (RtpHelper::isRtpOrRtcp(dataBuffer, dataSize))
    {
        ICELogMedia(<<"Adding new data to stream processing");
        mActiveStream->dataArrived(s, dataBuffer, dataSize, source);
    }
}

// This method is called by user agent to send ICE packet from mediasocket
void AudioProvider::sendData(const PDatagramSocket& s, InternetAddress& destination, const void* buffer, unsigned int size)
{
    s->sendDatagram(destination, buffer, size);
}

// Create SDP offer
void AudioProvider::updateSdpOffer(resip::SdpContents::Session::Medium& sdp, SdpDirection direction)
{
    if (mRemoteNoSdp)
        return;

    if (mState & (int)StreamState::Srtp)
    {
        // Check if SRTP suite is found already or not
        if (mSrtpSuite == SRTP_NONE)
        {
            // RFC 4568 requires a unique tag per crypto attribute; use the suite id.
            for (int suite = SRTP_AES_128_AUTH_80; suite <= SRTP_LAST; suite++)
                sdp.addAttribute("crypto", resip::Data(createCryptoAttribute((SrtpSuite)suite, suite)));
        }
        else
            // Answer/re-offer: echo the tag of the negotiated attribute.
            sdp.addAttribute("crypto", resip::Data(createCryptoAttribute(mSrtpSuite, mSrtpTag)));
    }

    // Use CodecListPriority mCodecPriority adapter to work with codec priorities
    if (mAvailableCodecs.empty())
    {
        for (int i=0; i<mCodecPriority.count(mTerminal.codeclist()); i++)
            mCodecPriority.codecAt(mTerminal.codeclist(), i).updateSdp(sdp.codecs(), direction);
        sdp.addCodec(resip::SdpContents::Session::Codec::TelephoneEvent);
    }
    else
    {
        mAvailableCodecs.front().mFactory->updateSdp(sdp.codecs(), direction);
        if (mRemoteTelephoneCodec)
            sdp.addCodec(resip::SdpContents::Session::Codec::TelephoneEvent);
    }


    // Publish stream state
    const char* attr = nullptr;
    switch (mActive)
    {
    case mfActive:
        switch(mRemoteState)
        {
        case msSendonly: attr = "recvonly"; break;
        case msInactive: attr = "recvonly"; break;
        case msRecvonly:
        case msSendRecv: break; // Do nothing here
        }
        break;

    case mfPaused:
        switch (mRemoteState)
        {
        case msRecvonly: attr = "sendonly"; break;
        case msSendonly: attr = "inactive"; break;
        case msInactive: attr = "inactive"; break;
        case msSendRecv: attr = "sendonly"; break;
        }
        break;
    }
    if (attr)
        sdp.addAttribute(attr);
}

void AudioProvider::sessionDeleted()
{
    sessionTerminated();
}

void AudioProvider::sessionTerminated()
{
    ICELogDebug(<< "sessionTerminated() for audio provider");
    setState(state() & ~((int)StreamState::Sending | (int)StreamState::Receiving));

    if (mActiveStream)
    {
        ICELogDebug(<< "Copy statistics from existing stream before freeing.");

        // Copy statistics - maybe it will be requested later
        mBackupStats = mActiveStream->statistics();

        ICELogDebug(<< "Remove stream from terminal");
        mTerminal.freeStream(mActiveStream);

        // Retrieve final statistics
        MT::AudioStream* audio_stream = dynamic_cast<MT::AudioStream*>(mActiveStream.get());
        if (audio_stream)
            audio_stream->setFinalStatisticsOutput(&mBackupStats);

        ICELogDebug(<< "Reset reference to stream.");
        mActiveStream.reset();
    }
}

void AudioProvider::sessionEstablished(int conntype)
{
    // Start media streams
    setState(state() | (int)StreamState::Receiving | (int)StreamState::Sending);

    // Available codec list can be empty in case of no-sdp offers.
    if (conntype == EV_SIP && !mAvailableCodecs.empty() && mActiveStream)
    {
        RemoteCodec& rc = mAvailableCodecs.front();
        mActiveStream->setTransmittingCodec(*rc.mFactory, rc.mRemotePayloadType);
        auto codec = dynamic_cast<MT::AudioStream*>(mActiveStream.get())->transmittingCodec();
        dynamic_cast<MT::AudioStream*>(mActiveStream.get())->setTelephoneCodec(mRemoteTelephoneCodec);
    }
}

void AudioProvider::setSocket(const RtpPair<PDatagramSocket>& p4, const RtpPair<PDatagramSocket>& p6)
{
    mSocket4 = p4;
    mSocket6 = p6;
    mActiveStream->setSocket(p4);
}

RtpPair<PDatagramSocket>& AudioProvider::socket(int family)
{
    switch (family)
    {
    case AF_INET:
        return mSocket4;

    case AF_INET6:
        return mSocket6;
    }
    return mSocket4;
}


bool AudioProvider::processSdpOffer(const resip::SdpContents::Session::Medium& media, SdpDirection sdpDirection)
{
    // Check if there is compatible codec
    mAvailableCodecs.clear();
    mRemoteTelephoneCodec = 0;

    // Check if there is SDP at all
    mRemoteNoSdp = media.codecs().empty();
    if (mRemoteNoSdp)
        return true;

    // Update RFC2833 related information
    findRfc2833(media.codecs());

    // Use CodecListPriority mCodecPriority to work with codec priorities
    int pt;
    for (int localIndex=0; localIndex<mCodecPriority.count(mTerminal.codeclist()); localIndex++)
    {
        MT::Codec::Factory& factory = mCodecPriority.codecAt(mTerminal.codeclist(), localIndex);
        if ((pt = factory.processSdp(media.codecs(), sdpDirection)) != -1)
            mAvailableCodecs.push_back(RemoteCodec(&factory, pt));
    }

    if (!mAvailableCodecs.size())
        return false;

    // Iterate SRTP crypto: attributes
    if (media.exists("crypto"))
    {
        // Find the most strong crypt suite
        const std::list<resip::Data>& vl = media.getValues("crypto");
        SrtpSuite ss = SRTP_NONE;
        ByteBuffer key;
        for (std::list<resip::Data>::const_iterator attrIter = vl.begin(); attrIter != vl.end(); attrIter++)
        {
            const resip::Data& attr = *attrIter;
            ByteBuffer tempkey;
            int tag = 1;
            SrtpSuite suite = processCryptoAttribute(attr, tempkey, &tag);
            if (srtpSuiteStrength(suite) > srtpSuiteStrength(ss))
            {
                ss = suite;
                mSrtpSuite = suite;
                mSrtpTag = tag;
                key = tempkey;
            }
        }

        // If SRTP suite is agreed
        if (ss != SRTP_NONE)
        {
            ICELogInfo(<< "Found SRTP suite " << ss);
            mActiveStream->srtp().open(key, ss);
            setState(state() | (int)StreamState::Srtp);
        }
        else
            ICELogInfo(<< "Did not find valid SRTP suite");
    }

    DataProvider::processSdpOffer(media, sdpDirection);

    return true;
}


void AudioProvider::setState(unsigned state)
{
    setStateImpl(state);
}

unsigned AudioProvider::state()
{
    return mState;
}

MT::Statistics AudioProvider::getStatistics()
{
    if (mActiveStream)
        return mActiveStream->statistics();
    else
        return mBackupStats;
}

MT::PStream AudioProvider::activeStream()
{
    return mActiveStream;
}

std::string AudioProvider::createCryptoAttribute(SrtpSuite suite, int tag)
{
    if (!mActiveStream)
        return "";

    // Print key to base64 string
    PByteBuffer keyBuffer = mActiveStream->srtp().outgoingKey(suite).first;
    if (!keyBuffer)
        return "";
    resip::Data d(keyBuffer->data(), keyBuffer->size());
    resip::Data keyText = d.base64encode();

    return std::format("{} {} inline:{}", tag, toString(suite), keyText.c_str());
}

SrtpSuite AudioProvider::processCryptoAttribute(const resip::Data& value, ByteBuffer& key, int* tag)
{
    int srtpTag = 0;
    char suite[64], keyChunk[256];
    int components = sscanf(value.c_str(), "%d %63s inline: %255s", &srtpTag, suite, keyChunk);
    if (components != 3)
        return SRTP_NONE;
    if (tag)
        *tag = srtpTag;

    const char* delimiter = strchr(keyChunk, '|');
    resip::Data keyText;
    if (delimiter)
        keyText = resip::Data(keyChunk, delimiter - keyChunk);
    else
        keyText = resip::Data(keyChunk);

    resip::Data rawkey = keyText.base64decode();
    key = ByteBuffer(rawkey.c_str(), rawkey.size());

    return toSrtpSuite(suite);
}

void AudioProvider::findRfc2833(const resip::SdpContents::Session::Medium::CodecContainer& codecs)
{
    resip::SdpContents::Session::Medium::CodecContainer::const_iterator codecIter;
    for (codecIter = codecs.begin(); codecIter != codecs.end(); codecIter++)
    {
        if (strcmp("TELEPHONE-EVENT", codecIter->getName().c_str()) == 0 ||
                strcmp("telephone-event", codecIter->getName().c_str()) == 0)
            mRemoteTelephoneCodec = codecIter->payloadType();
    }
}

void AudioProvider::readFile(const Audio::PWavFileReader& stream, MT::Stream::MediaDirection direction)
{
    // Iterate stream list
    if (mActiveStream)
        mActiveStream->readFile(stream, direction);
}

void AudioProvider::writeFile(const Audio::PWavFileWriter& stream, MT::Stream::MediaDirection direction)
{
    if (mActiveStream)
        mActiveStream->writeFile(stream, direction);
}

void AudioProvider::setupMirror(bool enable)
{
    if (mActiveStream)
        mActiveStream->setupMirror(enable);
}

void AudioProvider::setStateImpl(unsigned int state) {
    mState = state;
    if (mActiveStream)
        mActiveStream->setState(state);

}
