///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2018 F4EXB                                                      //
// written by Edouard Griffiths                                                  //
//                                                                               //
// This program is free software; you can redistribute it and/or modify          //
// it under the terms of the GNU General Public License as published by          //
// the Free Software Foundation as version 3 of the License, or                  //
//                                                                               //
// This program is distributed in the hope that it will be useful,               //
// but WITHOUT ANY WARRANTY; without even the implied warranty of                //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                  //
// GNU General Public License V3 for more details.                               //
//                                                                               //
// You should have received a copy of the GNU General Public License             //
// along with this program. If not, see .          //
///////////////////////////////////////////////////////////////////////////////////
#include "rtpsink.h"
#include "dsp/dsptypes.h"
#include 
RTPSink::RTPSink(const QString& address, uint16_t port, PayloadType payloadType) :
    m_payloadType(payloadType),
    m_sampleRate(48000),
    m_sampleBytes(0),
    m_packetSamples(0),
    m_bufferSize(0),
    m_sampleBufferIndex(0),
    m_byteBuffer(0),
    m_destport(port),
    m_rtpTransmitter(&m_rtpMemoryManager),
    m_mutex(QMutex::Recursive)
{
	// Here we use JRTPLIB in a bit funny way since we do not want the socket to bind because we are only sending
	// data to a remote party and we don't want to waste a port on the local machine for each possible connection that may not be used.
	// Therefore we create a socket and assign it through the SetUseExistingSockets method of the RTPUDPv4TransmissionParams object
	// By doing this the socket is left unbound but sending RTP packets with the library is still possible. Other functions may
	// not work but we don't care
	m_rtpSessionParams.SetOwnTimestampUnit(1.0 / (double) m_sampleRate);
    m_rtpTransmissionParams.SetRTCPMultiplexing(true); // do not allocate another socket for RTCP
    int status = m_rtpTransmitter.Init(false);
    if (status < 0) {
        qCritical("RTPSink::RTPSink: cannot initialize transmitter: %s", jrtplib::RTPGetErrorString(status).c_str());
        m_valid = false;
    } else {
        qDebug("RTPSink::RTPSink: initialized transmitter: %s", jrtplib::RTPGetErrorString(status).c_str());
    }
    m_rtpTransmitter.Create(m_rtpSessionParams.GetMaximumPacketSize(), &m_rtpTransmissionParams);
    qDebug("RTPSink::RTPSink: created transmitter: %s", jrtplib::RTPGetErrorString(status).c_str());
    status = m_rtpSession.Create(m_rtpSessionParams, &m_rtpTransmitter);
    if (status < 0) {
        qCritical("RTPSink::RTPSink: cannot create session: %s", jrtplib::RTPGetErrorString(status).c_str());
        m_valid = false;
    } else {
        qDebug("RTPSink::RTPSink: created session: %s", jrtplib::RTPGetErrorString(status).c_str());
    }
    setPayloadType(payloadType);
    m_valid = true;
    uint32_t endianTest32 = 1;
    uint8_t *ptr = (uint8_t*) &endianTest32;
    m_endianReverse = (*ptr == 1);
    m_destip = inet_addr(address.toStdString().c_str());
    m_destip = ntohl(m_destip);
}
RTPSink::~RTPSink()
{
    jrtplib::RTPTime delay = jrtplib::RTPTime(10.0);
    m_rtpSession.BYEDestroy(delay, "Time's up", 9);
    if (m_byteBuffer) {
        delete[] m_byteBuffer;
    }
}
void RTPSink::setPayloadType(PayloadType payloadType)
{
    QMutexLocker locker(&m_mutex);
    qDebug("RTPSink::setPayloadType: %d", payloadType);
    switch (payloadType)
    {
    case PayloadL16Stereo:
        m_sampleRate = 48000;
        m_sampleBytes = sizeof(AudioSample);
        m_rtpSession.SetDefaultPayloadType(96);
        break;
    case PayloadL16Mono:
    default:
        m_sampleRate = 48000;
        m_sampleBytes = sizeof(int16_t);
        m_rtpSession.SetDefaultPayloadType(96);
        break;
    }
    m_packetSamples = m_sampleRate/50; // 20ms packet samples
    m_bufferSize = m_packetSamples * m_sampleBytes;
    if (m_byteBuffer) {
        delete[] m_byteBuffer;
    }
    m_byteBuffer = new uint8_t[m_bufferSize];
    m_sampleBufferIndex = 0;
    m_payloadType = payloadType;
    int status = m_rtpSession.SetTimestampUnit(1.0 / (double) m_sampleRate);
    if (status < 0) {
        qCritical("RTPSink::setPayloadType: cannot set timestamp unit: %s", jrtplib::RTPGetErrorString(status).c_str());
    } else {
        qDebug("RTPSink::setPayloadType: timestamp unit set to %f: %s",
               1.0 / (double) m_sampleRate,
               jrtplib::RTPGetErrorString(status).c_str());
    }
    status = m_rtpSession.SetDefaultMark(false);
    if (status < 0) {
        qCritical("RTPSink::setPayloadType: cannot set default mark: %s", jrtplib::RTPGetErrorString(status).c_str());
    } else {
        qDebug("RTPSink::setPayloadType: set default mark to false: %s", jrtplib::RTPGetErrorString(status).c_str());
    }
    status = m_rtpSession.SetDefaultTimestampIncrement(m_packetSamples);
    if (status < 0) {
        qCritical("RTPSink::setPayloadType: cannot set default timestamp increment: %s", jrtplib::RTPGetErrorString(status).c_str());
    } else {
        qDebug("RTPSink::setPayloadType: set default timestamp increment to %d: %s", m_packetSamples, jrtplib::RTPGetErrorString(status).c_str());
    }
    status = m_rtpSession.SetMaximumPacketSize(m_bufferSize+40);
    if (status < 0) {
        qCritical("RTPSink::setPayloadType: cannot set maximum packet size: %s", jrtplib::RTPGetErrorString(status).c_str());
    } else {
        qDebug("RTPSink::setPayloadType: set maximum packet size to %d bytes: %s", m_bufferSize+40, jrtplib::RTPGetErrorString(status).c_str());
    }
}
void RTPSink::setDestination(const QString& address, uint16_t port)
{
    m_rtpSession.ClearDestinations();
    m_rtpSession.DeleteDestination(jrtplib::RTPIPv4Address(m_destip, m_destport));
    m_destip = inet_addr(address.toStdString().c_str());
    m_destip = ntohl(m_destip);
    m_destport = port;
    int status = m_rtpSession.AddDestination(jrtplib::RTPIPv4Address(m_destip, m_destport));
    if (status < 0) {
        qCritical("RTPSink::setDestination: cannot set destination address: %s", jrtplib::RTPGetErrorString(status).c_str());
    }
}
void RTPSink::deleteDestination(const QString& address, uint16_t port)
{
    uint32_t destip = inet_addr(address.toStdString().c_str());
    destip = ntohl(m_destip);
    int status =  m_rtpSession.DeleteDestination(jrtplib::RTPIPv4Address(destip, port));
    if (status < 0) {
        qCritical("RTPSink::deleteDestination: cannot delete destination address: %s", jrtplib::RTPGetErrorString(status).c_str());
    }
}
void RTPSink::addDestination(const QString& address, uint16_t port)
{
    uint32_t destip = inet_addr(address.toStdString().c_str());
    destip = ntohl(m_destip);
    int status = m_rtpSession.AddDestination(jrtplib::RTPIPv4Address(destip, port));
    if (status < 0) {
        qCritical("RTPSink::addDestination: cannot add destination address: %s", jrtplib::RTPGetErrorString(status).c_str());
    } else {
        qDebug("RTPSink::addDestination: destination address set to %s:%d: %s",
                address.toStdString().c_str(),
                port,
                jrtplib::RTPGetErrorString(status).c_str());
    }
}
void RTPSink::write(const uint8_t *sampleByte)
{
    QMutexLocker locker(&m_mutex);
    if (m_sampleBufferIndex < m_packetSamples)
    {
        writeNetBuf(&m_byteBuffer[m_sampleBufferIndex*m_sampleBytes],
                sampleByte,
                elemLength(m_payloadType),
                m_sampleBytes,
                m_endianReverse);
        m_sampleBufferIndex++;
    }
    else
    {
        int status = m_rtpSession.SendPacket((const void *) m_byteBuffer, (std::size_t) m_bufferSize);
        if (status < 0) {
            qCritical("RTPSink::write: cannot write packet: %s", jrtplib::RTPGetErrorString(status).c_str());
        }
        writeNetBuf(&m_byteBuffer[0], sampleByte,  elemLength(m_payloadType), m_sampleBytes, m_endianReverse);
        m_sampleBufferIndex = 1;
    }
}
void RTPSink::write(const uint8_t *samples, int nbSamples)
{
    int samplesIndex = 0;
    QMutexLocker locker(&m_mutex);
    // fill remainder of buffer and send it
    if (m_sampleBufferIndex + nbSamples > m_packetSamples)
    {
        writeNetBuf(&m_byteBuffer[m_sampleBufferIndex*m_sampleBytes],
                &samples[samplesIndex*m_sampleBytes],
                elemLength(m_payloadType),
                (m_packetSamples - m_sampleBufferIndex)*m_sampleBytes,
                m_endianReverse);
        m_rtpSession.SendPacket((const void *) m_byteBuffer, (std::size_t) m_bufferSize);
        nbSamples -= (m_packetSamples - m_sampleBufferIndex);
        m_sampleBufferIndex = 0;
    }
    // send complete packets
    while (nbSamples > m_packetSamples)
    {
        writeNetBuf(m_byteBuffer,
                samples,
                elemLength(m_payloadType),
                m_bufferSize,
                m_endianReverse);
        m_rtpSession.SendPacket((const void *) m_byteBuffer, (std::size_t) m_bufferSize);
        samplesIndex += m_packetSamples;
        nbSamples -= m_packetSamples;
    }
    // copy remainder of input to buffer
    writeNetBuf(&m_byteBuffer[m_sampleBufferIndex*m_sampleBytes],
            &samples[samplesIndex*m_sampleBytes],
            elemLength(m_payloadType),
            nbSamples*m_sampleBytes,m_endianReverse);
}
void RTPSink::writeNetBuf(uint8_t *dest, const uint8_t *src, unsigned int elemLen, unsigned int bytesLen, bool endianReverse)
{
    for (unsigned int i = 0; i < bytesLen; i += elemLen)
    {
        memcpy(&dest[i], &src[i], elemLen);
        if (endianReverse) {
            std::reverse(&dest[i], &dest[i+elemLen]);
        }
    }
}
unsigned int RTPSink::elemLength(PayloadType payloadType)
{
    switch (payloadType)
    {
    case PayloadL16Stereo:
        return sizeof(int16_t);
        break;
    case PayloadL16Mono:
    default:
        return sizeof(int16_t);
        break;
    }
}