/*---------------------------------------------------------------------------*/
/* recording.cpp                                                             */
/* copyright (c) innovaphone 2013                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include <platform.h>
#include "sdk/common/interface/local_storage.h"
#include "sdk/common/interface/time.h"
#include "sdk/common/interface/socket.h"
#include "sdk/common/interface/ethernet.h"
#include "sdk/common/interface/media.h"
#include "sdk/common/interface/http_client.h"
#include "common/ilib/str.h"

#include "recording.h"

#define DBG(x) //debug->printf x

USING_NAMESPACE;

const char *MediaRecording::sdp =
        "v=0\n"
        "o=- 1 1 IN IP4 %A\n"
        "s=-\n"
        "c=IN IP4 %A\n"
        "t=0 0\n"
        "m=audio %u RTP/AVP %u %u\n";

const char *MediaRecording::invite =
        "INVITE sip:100@innovaphone.com SIP/2.0\n"
        "Via: SIP/2.0/UDP %A;branch=z9hG4bK-B89D3277;rport\n"
        "From: <sip:100@innovaphone.com>;tag=3002661604\n"
        "To: <sip:200@innovaphone.com>\n"
        "Call-ID: 1\n"
        "CSeq: 372 INVITE\n"
        "Contact: <sip:100@innovaphone.com>\n"
        "Content-Length: %u\n"
        "Content-Type: application/sdp\n"
        "Max-Forwards: 70\n\n";

const char *MediaRecording::invite_ok =
        "SIP/2.0 200 OK\n"
        "Via: SIP/2.0/UDP %A;branch=z9hG4bK-B89D3277;rport\n"
        "From: <sip:100@innovaphone.com>;tag=3002661604\n"
        "To: <sip:200@innovaphone.com>;tag=3424734827\n"
        "Call-ID: 1\n"
        "CSeq: 372 INVITE\n"
        "Contact: <sip:200@innovaphone.com>\n"
        "Content-Length: %u\n"
        "Content-Type: application/sdp\n\n";

const char *MediaRecording::ack =
        "ACK sip:100@innovaphone.com SIP/2.0\n"
        "Via: SIP/2.0/UDP %A;branch=z9hG4bK-B89D3278;rport\n"
        "From: <sip:100@innovaphone.com>;tag=3002661604\n"
        "To: <sip:200@innovaphone.com>;tag=3424734827\n"
        "Call-ID: 1\n"
        "CSeq: 372 ACK\n"
        "Contact: <sip:100@innovaphone.com>\n"
        "Content-Length: 0\n"
        "Max-Forwards: 70\n\n";

const char *MediaRecording::bye =
        "BYE sip:100@innovaphone.com SIP/2.0\n"
        "Via: SIP/2.0/UDP %A;branch=z9hG4bK-C898346E;rport\n"
        "From: <sip:100@innovaphone.com>;tag=3002661604\n"
        "To: <sip:200@innovaphone.com>;tag=3424734827\n"
        "Call-ID: 1\n"
        "CSeq: 13 BYE\n"
        "Contact: <sip:100@innovaphone.com>\n"
        "Content-Length: 0\n"
        "Max-Forwards: 70\n\n";

const char *MediaRecording::bye_ok =
        "SIP/2.0 200 OK\n"
        "Via: SIP/2.0/UDP %A;branch=z9hG4bK-C898346E;rport\n"
        "From: <sip:100@innovaphone.com>;tag=3002661604\n"
        "To: <sip:200@innovaphone.com>;tag=3424734827\n"
        "Call-ID: 1\n"
        "CSeq: 13 BYE\n"
        "Contact: <sip:200@innovaphone.com>\n"
        "Content-Length: 0\n\n";


MediaRecording::MediaRecording(IIoMux * iomux, ISocketProvider * tcpSocketProvider, class ISocketProvider * tlsSocketProvider, IPaddr addr, const char * url, OS_GUID * guid, enum MediaType type, class IInstanceLog * const log, bool recordOn, const char * currentUserSip, const char * recordingUsername, const char * recordingPassword) {
    log->Log(LOG_MEDIA_CHANNEL,"MediaRecording(%p)::MediaRecording iomux: %p tcpSocketProvider: %p tlsSocketProvider: %p url: %s recordOn: %d", this, iomux, tcpSocketProvider, tlsSocketProvider, url ? url : "", recordOn);

    this->iomux = iomux;
    this->tcpSocketProvider = tcpSocketProvider;
    this->tlsSocketProvider = tlsSocketProvider;
    this->log = log;

    this->recording_user = 0;
    this->error_reported = false;
    this->state = 0;
    this->http = 0;
    this->buffer = 0;
    this->fileUrl = 0;
    this->closing = false;
    this->type = type;
    this->is_on = recordOn;
    this->recordingUsername = _strdup(recordingUsername);
    this->recordingPassword = _strdup(recordingPassword);

    if(type != MediaType::AUDIO) return;

    if (is_ip4(addr)) {
        local = *IPaddr2IP4addr_local(&addr);
    } else {
        local = ip4_anyaddr;
    }
    remote = ip4_anyaddr;

    unsigned lenUrl = url ? strlen(url) : 0;
    if (lenUrl && strstr(url, "//") && strncmp(url + lenUrl - 2, "//", 2) && guid) {
        char b[256];
        byte mac[13];
        IEthernet::GetMACAddress(mac, log);
        while (lenUrl && (url[lenUrl - 1] == '/')) lenUrl--;
        isnprintf(b, sizeof(b), "%.*s/%32H-%12H-%u-%s%E.pcap", lenUrl, url, guid->b, mac,  (dword)(ITime::TimeStampMilliseconds() / 1000), currentUserSip ? "-" : "", currentUserSip ? currentUserSip : "");

        this->fileUrl = _strdup(b);

        char *fileUrlParts[2];
        char *fileUrlCopy = _strdup(this->fileUrl);
        str::split(fileUrlCopy, fileUrlParts, 2, "//");
        unsigned pos = 0;
        while (fileUrlParts[1][pos] != '/' && pos < strlen(fileUrlParts[1])) pos++;
        _sprintf(recordingPath, "%s", fileUrlParts[1] + pos);
    }
}

MediaRecording::~MediaRecording(){
    log->Log(LOG_MEDIA_CHANNEL,"MediaRecording(%p)::~MediaRecording", this);

    if(fileUrl){
        free(this->fileUrl);
        fileUrl = 0;
    }

    if(recordingUsername) {
        memset(this->recordingUsername, 0, strlen(this->recordingUsername));
        free(this->recordingUsername);
        this->recordingUsername = 0;
    }
    if(recordingPassword) {
        memset(this->recordingPassword, 0, strlen(this->recordingPassword));
        free(this->recordingPassword);
        this->recordingPassword = 0;
    }
}

void MediaRecording::Start(IPaddr remote, word media_type, word media_pt, word localDtmfPt, word remoteDtmfPt, class MediaRecordingUser *recording_user) {
    log->Log(LOG_MEDIA_CHANNEL, "MediaRecording(%p)::Start media_type: %d media_pt: %u localDtmf_pt: %u remoteDtmfPt: %u recordOn: %d", this, media_type, media_pt, localDtmfPt, remoteDtmfPt, is_on);

    if(type != MediaType::AUDIO) return;

    if (is_ip4(remote)) {
        this->remote = *IPaddr2IP4addr_local(&remote);
    } else {
        this->remote = ip4_anyaddr;
    }
    this->media_type = media_type;
    this->media_pt = media_pt;
    this->localDtmfPt = localDtmfPt;
    this->remoteDtmfPt = remoteDtmfPt;
    this->recording_user = recording_user;
    if (is_on) Start();
}

void MediaRecording::Start() {
    log->Log(LOG_MEDIA_CHANNEL, "MediaRecording(%p)::Start()",this);
    if (!http && fileUrl && iomux && tcpSocketProvider && tlsSocketProvider) {
        http = IHTTPClient::Create(iomux, tcpSocketProvider, tlsSocketProvider, this, log);
        if (http) {
            struct pcap_global_header g_header;
            WRITE_DWORD_NET(&g_header.magic_number, PCAP_MAGIC_NUMBER);
            WRITE_WORD_NET(&g_header.version_major, PCAP_VERSION_MAJOR);
            WRITE_WORD_NET(&g_header.version_minor, PCAP_VERSION_MINOR);
            WRITE_DWORD_NET(&g_header.this_zone, 0);
            WRITE_DWORD_NET(&g_header.sig_figs, 0x00000000);
            WRITE_DWORD_NET(&g_header.snap_len, 0x0FFFF);
            WRITE_DWORD_NET(&g_header.network, PCAP_NETWORK);
            char *temp_buffer = reinterpret_cast<char *>(&g_header);
            buffer = new std::vector<char>();
            buffer->insert(buffer->end(), temp_buffer, temp_buffer + sizeof(g_header));

            log->Log(LOG_MEDIA_CHANNEL, "MediaRecording(%p)::Start http->Connect to url: %s",this, fileUrl);
            if (recordingUsername && recordingPassword) {
                http->Connect(fileUrl, recordingUsername, recordingPassword);
            } else {
                http->Connect(fileUrl);
            }

            state = RECORD_OPENING;
            free(this->fileUrl);
            fileUrl = 0;
        }
    }
    if(http) {
        char b[512];

        word opus_pt = (media_type == AudioCoder::OPUS_NB || media_type == AudioCoder::OPUS_WB) ? media_pt : 0;

        std::vector<char> data(b, b + isnprintf(b, sizeof(b), MediaRecording::sdp, &local, &local, RECORD_LOCAL_PORT, media_pt, localDtmfPt));

        if(opus_pt) data.insert(data.end(), b, b + isnprintf(b, sizeof(b),"a=rtpmap:%u opus/48000/2\n", opus_pt));
        if(localDtmfPt) data.insert(data.end(), b, b + isnprintf(b, sizeof(b),"a=rtpmap:%u telephone-event/8000\n", localDtmfPt));
        data.insert(data.begin(), b,b + isnprintf(b, sizeof(b), MediaRecording::invite, &local, data.size()));

        SipXmit(&data);

        data.clear();
        data.insert(data.begin(), b, b + isnprintf(b, sizeof(b), MediaRecording::sdp, &remote, &remote, RECORD_REMOTE_PORT, media_pt, remoteDtmfPt));

        if(opus_pt) data.insert(data.end(), b, b + isnprintf(b, sizeof(b), "a=rtpmap:%u opus/48000/2\n", opus_pt));
        if(remoteDtmfPt) data.insert(data.end(), b, b + isnprintf(b, sizeof(b), "a=rtpmap:%u telephone-event/8000\n", remoteDtmfPt));
        data.insert(data.begin(), b,b + isnprintf(b, sizeof(b), MediaRecording::invite_ok, &remote, data.size()));

        SipRecv(&data);

        data.clear();
        data.insert(data.begin(), b, b + isnprintf(b, sizeof(b), MediaRecording::ack, &local));

        SipXmit(&data);

        Write();
    }
}

void MediaRecording::RtpRecv(char *data, size_t len) {
    DBG(("MediaRecording(%p)::rtp_recv data size: %d",this, len));
    if(!closing && is_on) {

        if(buffer && buffer->size() > RECORD_BUFFER_LIMIT) {
            if(recording_user && !error_reported) {
                recording_user->RecordingStopped("HTTP_WRITING_TO_SLOW");
                error_reported = true;
            }
            return; // skip (http writing to slow)
        }

        std::vector<char> pcap(data, data + len);

        AddUdpHeader(&pcap, RECORD_LOCAL_PORT, RECORD_REMOTE_PORT);
        AddIpHeader(&pcap, local, remote, 0x11);
        AddEthernetHeader(&pcap);
        AddPcapHeader(&pcap);

        char *temp_buffer = pcap.data();
        if(!buffer) buffer = new std::vector<char>();
        buffer->insert(buffer->end(), temp_buffer, temp_buffer + pcap.size());

        Write();
    }
}

void MediaRecording::RtpXmit(char *data, size_t len) {
    if(!closing && is_on) {

        if(buffer && buffer->size() > RECORD_BUFFER_LIMIT) {
            return; // skip (file writing too slow)
        }
        std::vector<char> pcap(data, data + len);

        AddUdpHeader(&pcap, RECORD_REMOTE_PORT, RECORD_LOCAL_PORT);
        AddIpHeader(&pcap, remote, local, 0x11);
        AddEthernetHeader(&pcap);
        AddPcapHeader(&pcap);

        char *temp_buffer = pcap.data();
        if(!buffer) buffer = new std::vector<char>();
        buffer->insert(buffer->end(), temp_buffer, temp_buffer + pcap.size());

        Write();
    }
}


void MediaRecording::HTTPClientConnectComplete(IHTTPClient *const httpClient) {
    log->Log(LOG_MEDIA_CHANNEL, "MediaRecording(%p)::HTTPClientConnectComplete connected: %s", this, httpClient->Connected() ? "true" : "false");

    http->SetRequestType(HTTP_PUT, recordingPath, HTTP_CLIENT_CHUNKED_TRANSFER, "application/octet-stream");
    if (recording_user) recording_user->RecordingStarted();
    if(state == RECORD_OPENING) {
        state = RECORD_OPENED;
        if(closing) {
            http->Shutdown();
        } else {
            if(buffer) Write();
        }
    }
};

void MediaRecording::HTTPClientSendResult(IHTTPClient *const httpClient) {
    log->Log(LOG_MEDIA_CHANNEL, "MediaRecording(%p)::HTTPClientSendResult", this);

    if(state == RECORD_WRITING) {
        state = RECORD_OPENED;
        if(closing) {
            if(buffer) {
                http->Send(reinterpret_cast<const byte *>(buffer->data()), buffer->size());
                buffer->clear();
                buffer = 0;
                state = RECORD_WRITING;
            } else {
                http->Send(NULL, 0, true);
            }
        } else {
            if(buffer) Write();
        }
    }
};

void MediaRecording::HTTPClientRequestComplete(IHTTPClient *const httpClient) {
    log->Log(LOG_MEDIA_CHANNEL, "MediaRecording(%p)::HTTPClientRequestComplete", this);
    if (closing) http->Shutdown();
};

void MediaRecording::HTTPClientShutdown(IHTTPClient *const httpClient, http_shutdown_reason_t reason) {
    log->Log(LOG_MEDIA_CHANNEL, "MediaRecording(%p)::HTTPClientShutdown reason=%s recording_user=%p", this, HTTPShutdownReasonToStr(reason), recording_user);

    if(http) {
        if(reason != HTTP_SHUTDOWN_NORMAL) {
            if(recording_user && !error_reported) {
                recording_user->RecordingStopped(HTTPShutdownReasonToStr(reason));
                error_reported = true;
            }
        }
        delete http;
        http = 0;
        delete buffer;
        buffer = 0;
    }
    if (recording_user) {
        class MediaRecordingUser * user = recording_user;
        recording_user = 0;
        user->RecordingCloseComplete();
    }
};

void MediaRecording::HTTPClientRecvResult(IHTTPClient *const httpClient, byte *buffer, size_t len, bool transferComplete) {
    DBG(("%p MediaRecording::HTTPClientRecvResult complete: %s buffer: %s", this, transferComplete ? "true" : "false", buffer));
};

void MediaRecording::HTTPClientRecvCanceled(IHTTPClient *const httpClient, byte *buffer) {
    DBG(("%p MediaRecording::HTTPClientRecvCanceled buffer: %s", this, buffer));
    free(buffer);
};

void MediaRecording::Close() {
    log->Log(LOG_MEDIA_CHANNEL, "MediaRecording(%p)::Close()", this);
    if(http) {

        char b[512];
        std::vector<char> data(b, b + _sprintf(b, MediaRecording::bye, &local));
        SipXmit(&data);

        data.clear();
        data.insert(data.begin(), b, b + _sprintf(b, MediaRecording::bye_ok, &remote));

        SipRecv(&data);

        if(state == RECORD_OPENED) {
            http->Send(reinterpret_cast<const byte *>(buffer->data()), buffer->size());
            buffer->clear();
            buffer = 0;
            state = RECORD_WRITING;
        }
        closing = true;

        // channel is closed on FILE_OPEN_RESULT and FILE_WRITE_RESULT
        ASSERT(state == RECORD_OPENING || state == RECORD_WRITING, "Invalid state");
    }
    if(fileUrl) {
        free(this->fileUrl);
        fileUrl = 0;
    }
}

void MediaRecording::Recording(bool on) {
    log->Log(LOG_MEDIA_CHANNEL, "MediaRecording(%p)::Recording on: %d",this, on);
    is_on = on;
    if(on && !http && !error_reported) Start();
    if (on) recording_user->RecordingStarted();
    if (!on) recording_user->RecordingStopped(nullptr);
}


void MediaRecording::SipXmit(std::vector<char> *data) {
    AddUdpHeader(data, 1000, 5060);
    AddIpHeader(data, local, remote, 0x11);
    AddEthernetHeader(data);
    AddPcapHeader(data);

    char *temp_buffer = data->data();
    if(!buffer) buffer = new std::vector<char>();
    buffer->insert(buffer->end(), temp_buffer, temp_buffer + data->size());
}


void MediaRecording::SipRecv(std::vector<char> *data) {
    AddUdpHeader(data, 5060, 1000);
    AddIpHeader(data, remote, local, 0x11);
    AddEthernetHeader(data);
    AddPcapHeader(data);

    char *temp_buffer = data->data();
    if(!buffer) buffer = new std::vector<char>();
    buffer->insert(buffer->end(), temp_buffer, temp_buffer + data->size());
}


void MediaRecording::AddPcapHeader(std::vector<char> *data) {
    pcap_packet_header p_header;
    dword timestamp = (dword) (ITime::TimeStampMilliseconds() << 3);                                 //TODO verify if this is okay - GetTimeStamp8kHz

    WRITE_DWORD_NET(&p_header.incl_len, data->size());
    WRITE_DWORD_NET(&p_header.orig_len, data->size());
    WRITE_DWORD_NET(&p_header.ts_sec, timestamp / 8000);
    WRITE_DWORD_NET(&p_header.ts_usec, ((timestamp >> 3) % 1000) * 1000 +
                                       (timestamp & 7) * 125); // resolution is 250 micro seconds

    char *temp_buffer = reinterpret_cast<char *>(&p_header);
    data->insert(data->begin(), temp_buffer, temp_buffer + sizeof(p_header));
}


void MediaRecording::AddEthernetHeader(std::vector<char> *data) {
    static byte hdr[14] = {0x00, 0x90, 0x33, 0x00, 0x00, 0x00, 0x00, 0x90, 0x33, 0x00, 0x00, 0x00, 0x08, 0x00};
    data->insert(data->begin(), &hdr[0], &hdr[0] + sizeof(hdr));
}


void MediaRecording::AddIpHeader(std::vector<char> *data, IP4addr src, IP4addr dst, byte protocol) {
    struct ip_header ip;
    ip.ip_verlen = 0x45;    // 4=Version 4 5=5*32 bit header len
    ip.ip_tos = 0x0;
    WRITE_WORD_NET(&ip.ip_len, (word) (data->size() + sizeof(ip)));
    ip.ip_id = 0;
    WRITE_WORD_NET(&ip.ip_fragoff, 0x4000);
    ip.ip_ttl = 127;
    ip.ip_proto = protocol;
    ip.ip_src = src;
    ip.ip_dst = dst;
    ip.ip_chksum = 0;
    WRITE_WORD_NET(&ip.ip_chksum, IpChecksum((byte *) &ip, sizeof(ip)));

    char *temp_buffer = reinterpret_cast<char *>(&ip);
    data->insert(data->begin(), temp_buffer, temp_buffer + sizeof(ip));
}


void MediaRecording::AddUdpHeader(std::vector<char> *data, word src_port, word dst_port) {
    // UPD-Header
    struct udp_header udp;
    WRITE_WORD_NET(&udp.dst_port, dst_port);
    WRITE_WORD_NET(&udp.src_port, src_port);
    udp.chksum = 0;
    WRITE_WORD_NET(&udp.len, (word) (data->size() + sizeof(udp)));

    char *temp_buffer = reinterpret_cast<char *>(&udp);
    data->insert(data->begin(), temp_buffer, temp_buffer + sizeof(udp));
}


short MediaRecording::IpChecksum(byte *addr, byte len) {
    int i;
    dword sum;

    for (i = 0, sum = 0; i < len; i++) {
        sum += i & 1 ? addr[i] : addr[i] << 8;
    }

    sum = (sum >> 16) + (sum & 0xffff);     // add in carry
    sum += (sum >> 16);                     // maybe one more
    return (short) ~sum;
}

void MediaRecording::Write() {
    if(buffer->size() > RECORD_BUFFER_SIZE) {
        if(state == RECORD_OPENED) {
            DBG(("%p MediaRecording::Write() to path: %s", this, recordingPath));
            http->Send(reinterpret_cast<const byte *>(buffer->data()), buffer->size());
            buffer->clear();
            buffer = 0;
            state = RECORD_WRITING;
        }
    }
}