/*---------------------------------------------------------------------------*/
/* video_encoder.cpp                                                         */
/* copyright (c) innovaphone 2017                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include "platform/platform.h"

#include "platform/phone_linux.h"
#include "common/os/iomux.h"
#include "common/interface/socket.h"
#include "common/ilib/str.h"
#include "common/interface/media.h"
#include "video_transcoder.h"

#include "video_encoder.h"

class IMediaEncoder * appSharingEncoder = NULL;
class UMediaEncoder * appSharingEncoderUser;
unsigned appSharingFullIntraRequest = 0;

class IMediaEncoder * IMediaEncoder::Create(class IIoMux * const iomux, class UMediaEncoder * const user, class IInstanceLog * log, enum VideoCoder coder)
{
    switch (coder) {
    case VideoCoder::VP8:
        return new VideoEncoderVPX(iomux, user, log, false);
    case VideoCoder::VP9:
        return new VideoEncoderVPX(iomux, user, log, true);
    default:
        return new VideoEncoderH264(iomux, user, log);
    }
}

VideoEncoderH264::VideoEncoderH264(class IIoMux * const iomux, class UMediaEncoder * const user, class IInstanceLog * log) :
    iomux(iomux),
    user(user),
    log(log)
{
    log->Log(LOG_VIDEO, "VideoEncoderH264(%p)::VideoEncoderH264", this);

    shutDown = false;
    execCnt = 0;
    fullIntraRequest = 0;
    fullIntraAck = 0;
    transcoder = IMediaTranscoder::Create(iomux, this, log, (enum VideoCoder) 0, VideoCoder::H264);
}

VideoEncoderH264::~VideoEncoderH264()
{
    log->Log(LOG_VIDEO, "VideoEncoderH264(%p)::~VideoEncoderH264", this);
}

void VideoEncoderH264::Close()
{
    if (appSharingEncoder == this) {
        appSharingEncoder = NULL;
    }
    shutDown = true;
    transcoder->Close();
}

void VideoEncoderH264::MediaTranscoderCloseResult(class IMediaTranscoder * transcoder)
{
    delete transcoder;
    transcoder = NULL;
    user->MediaEncoderCloseResult(this);
}

bool VideoEncoderH264::Initialize(const struct VideoFrameFormat * const format)
{
    if (format->appSharing) {
        appSharingEncoder = this;
        appSharingEncoderUser = user;
    }
    debug->printf("VideoEncoderH264(%p)::Initialize (%s) resolution=%dx%d stride=%d format=%d fps=%d bitrate=%d", this,
        "H264", format->width, format->height, format->stride, format->format,
        (format->frameRate.numerator / format->frameRate.denominator), format->targetBitrate);
    return true;
}

void VideoEncoderH264::Encode(const void * buf, int len, dword timestamp, dword duration, const struct VideoFrameFormat * format)
{
    dword flags;

    if(shutDown) return;
    if(!buf) {
        unsigned req = fullIntraRequest;
        if (req != fullIntraAck) {
            fullIntraAck = req;
            appSharingFullIntraRequest++;
        }
        return;
    }
    flags = 0;
    unsigned req = fullIntraRequest;
    if (req != fullIntraAck) {
        flags |= FORMS_VIDEO_TRANSCODER_KEY_FRAME;
    }
    flags |= format->targetBitrate << FORMS_VIDEO_TRANSCODER_TARGET_KBIT_SHIFT;
    transcoder->Transcode(buf, len, timestamp, duration, 0, format->width, format->height, format->stride, flags);
    fullIntraAck = req;
}

void VideoEncoderH264::TranscodeResult(class IMediaTranscoder * transcoder, const void * buf, int len, dword timestamp, dword duration, short sequenceDiff, int width, int height, int stride, dword flags)
{
    user->EncodeResult(this, buf, len, timestamp, duration);
}

bool VideoEncoderH264::FullIntraRequest()
{
    log->Log(LOG_VIDEO, "VideoEncoderH264(%p)::FullIntraRequest", this);

    fullIntraRequest++;
    appSharingFullIntraRequest++;
    return true;
}


VideoEncoderVPX::VideoEncoderVPX(class IIoMux * const iomux, class UMediaEncoder * const user, class IInstanceLog * log, bool vp9) :
    iomux(iomux),
    user(user),
    log(log),
    keyFrameTimer(iomux, this),
    qualityTimer(iomux, this)
{
    log->Log(LOG_VIDEO, "VideoEncoderVPX(%p)::VideoEncoderVPX", this);

    this->vp9 = vp9;
    shutDown = false;
    execCnt = 0;
    appSharing = false;
    activeMap = false;
    indexBitrate = 0;
    lastTimestamp = 0;
    lastKeyFrameSize = 0;
    forceKeyFrame = 0;
    qualityTimerTimeout = 5000;
    sendKeyFrame = false;
    startKeyFrameSeq = 0;
    endKeyFrameSeq = 0;
    transcoder = IMediaTranscoder::Create(iomux, this, log, (enum VideoCoder) 0, vp9 ? VideoCoder::VP9 : VideoCoder::VP8);
}

VideoEncoderVPX::~VideoEncoderVPX()
{
    log->Log(LOG_VIDEO, "VideoEncoderVPX(%p)::~VideoEncoderVPX", this);
}

void VideoEncoderVPX::Close()
{
    if (appSharingEncoder == this) {
        appSharingEncoder = NULL;
    }
    shutDown = true;
    transcoder->Close();
}

void VideoEncoderVPX::MediaTranscoderCloseResult(class IMediaTranscoder * transcoder)
{
    delete transcoder;
    transcoder = NULL;
    user->MediaEncoderCloseResult(this);
}

bool VideoEncoderVPX::Initialize(const struct VideoFrameFormat * const format)
{
    if (format->appSharing) {
        appSharingEncoder = this;
        appSharingEncoderUser = user;
    }
    appSharing = format->appSharing;
    activeMap = appSharing;
    nextRcMinQuantizer = appSharing ? 12 : 4;
    nextRcMaxQuantizer = appSharing ? 45 : 63;
    debug->printf("VideoEncoderVPX(%p)::Initialize (%s) resolution=%dx%d stride=%d format=%d fps=%d bitrate=%d appSharing=%d", this,
        vp9 ? "vp9" : "vp8", format->width, format->height, format->stride, format->format,
        (format->frameRate.numerator / format->frameRate.denominator), format->targetBitrate, appSharing);
    return true;
}

void VideoEncoderVPX::Encode(const void * buf, int len, dword timestamp, dword duration, const struct VideoFrameFormat * format)
{
    dword flags;

    if(shutDown) return;
    if(!buf) {
        if (sendKeyFrame) {
            sendKeyFrame = 0;
            appSharingFullIntraRequest++;
        }
        return;
    }
    flags = 0;
    maxSendBytes = (format->targetBitrate * 1000) / 8;
    if (appSharing) {
        if (CalculateBitrate() == false) {
            if (lastTimestamp) durationSlots[indexBitrate] = (timestamp - lastTimestamp);
            bitrateSlots[indexBitrate] = 0;
            lastTimestamp = timestamp;
            indexBitrate = ((indexBitrate + 1) == BITRATE_SLOTS) ? 0 : indexBitrate + 1;
            return;
        }
        if (activeMap) {
            flags |= FORMS_VIDEO_TRANSCODER_ACTIVE_MAP;
        }
        activeMap = true;
    }
    if (sendKeyFrame) {
        appSharingFullIntraRequest++;
        flags |= FORMS_VIDEO_TRANSCODER_KEY_FRAME;
        if (appSharing) debug->printf("VideoEncoderVPX(%p)::EncodeSample keyframe flags=%d last=%d q=%d,%d", this, flags, lastKeyFrameSize, nextRcMinQuantizer, nextRcMaxQuantizer);
        sendKeyFrame = 0;
        // KeyFrames are forced due to packet loss reported by remote side
        // Lower the quality to generate a small key frame
        // The qualityTimer will raise the quality if no new keyframes are requested
        if (appSharing) {
            // Force to use something over 60!
            // This ensures small keyframes
            if (lastKeyFrameSize < 50000 && nextRcMinQuantizer > 42) {
                nextRcMinQuantizer = 42;
                nextRcMaxQuantizer = 50;
            }
            else if (lastKeyFrameSize < 60000 && nextRcMinQuantizer > 47) {
                nextRcMinQuantizer = 47;
                nextRcMaxQuantizer = 53;
            }
            else if (lastKeyFrameSize < 70000 && nextRcMinQuantizer > 50) {
                nextRcMinQuantizer = 50;
                nextRcMaxQuantizer = 57;
            }
            else if (lastKeyFrameSize < 80000 && nextRcMinQuantizer > 54) {
                nextRcMinQuantizer = 54;
                nextRcMaxQuantizer = 61;
            }
            else if (lastKeyFrameSize > 80000) {
                nextRcMinQuantizer = 60;
                nextRcMaxQuantizer = 63;
            }
            qualityTimerTimeout = 5000;
            qualityTimer.Start(qualityTimerTimeout);
        }
    }
    flags |= nextRcMinQuantizer << FORMS_VIDEO_TRANSCODER_MIN_QUANT_SHIFT;
    flags |= nextRcMaxQuantizer << FORMS_VIDEO_TRANSCODER_MAX_QUANT_SHIFT;
    flags |= format->targetBitrate << FORMS_VIDEO_TRANSCODER_TARGET_KBIT_SHIFT;
    transcoder->Transcode(buf, len, timestamp, duration, 0, format->width, format->height, format->stride, flags);
}

void VideoEncoderVPX::TranscodeResult(class IMediaTranscoder * transcoder, const void * buf, int len, dword timestamp, dword duration, short sequenceDiff, int width, int height, int stride, dword flags)
{
    if (appSharing) {
        if (flags & FORMS_VIDEO_TRANSCODER_KEY_FRAME) {
            debug->printf("VideoEncoderVP8(%p)::EncodeSample (%s) len=%d q=%d,%d", this, (flags & FORMS_VIDEO_TRANSCODER_KEY_FRAME) ? "KEYFRAME!" : "", len, nextRcMinQuantizer, nextRcMaxQuantizer);
            // Small keyframe could generate poor quality
            lastKeyFrameSize = len;
        }
        if (lastTimestamp) durationSlots[indexBitrate] = (timestamp - lastTimestamp);
        bitrateSlots[indexBitrate] = len;
        lastTimestamp = timestamp;
        indexBitrate = ((indexBitrate + 1) == BITRATE_SLOTS) ? 0 : indexBitrate + 1;
        if (len < 2000) {
            if (nextRcMinQuantizer > 40) nextRcMinQuantizer--;
            if (nextRcMaxQuantizer > 52) nextRcMaxQuantizer--;
        }
    }
    user->EncodeResult(this, buf, len, timestamp, duration);
}

// Check if sequences inside message belongs to last keyframe sent
void VideoEncoderVPX::RtpNackMessage(void* ctx)
{
    if (ctx) {
        char* b = (char*)ctx;
        word nackSeq = READ_WORD_NET(b);
        word bMask = READ_WORD_NET(&b[2]);
        debug->printf("VideoEncoderVPX(%p)::RtpNackMessage seq=%d bitmask=%x (%d -> %d)", this, nackSeq, bMask, startKeyFrameSeq, endKeyFrameSeq);
        if (startKeyFrameSeq == endKeyFrameSeq) return;
        if (endKeyFrameSeq > startKeyFrameSeq) {
            if ((nackSeq >= startKeyFrameSeq) && (nackSeq <= endKeyFrameSeq)) {
                FullIntraRequest();
                return;
            }
            for (int i = 0; i < 16; i++) {
                if ((bMask & (1<<i)) && ((nackSeq + (i+1)) >= startKeyFrameSeq) && ((nackSeq + (i + 1)) <= endKeyFrameSeq)) {
                    FullIntraRequest();
                    return;
                }
            }
        }
        else {
            if ((nackSeq >= startKeyFrameSeq) || (nackSeq <= endKeyFrameSeq)) {
                FullIntraRequest();
                return;
            }
            for (int i = 0; i < 16; i++) {
                if ((bMask & (1 << i)) && (((nackSeq + (i + 1)) >= startKeyFrameSeq) || ((nackSeq + (i + 1)) <= endKeyFrameSeq))) {
                    FullIntraRequest();
                    return;
                }
            }
        }
    }
}

void VideoEncoderVPX::KeyFrameSequences(void* ctx)
{
    if (ctx) {
        dword* b = (dword*)ctx;
        dword sSeq = READ_DWORD_NET(&b[1]);
        startKeyFrameSeq = sSeq & 0xffff;
        endKeyFrameSeq = (sSeq & 0xffff0000) >> 16;
        debug->printf("VideoEncoderVPX(%p)::KeyFrameSequences len=%d %d -> %d", this, READ_DWORD_NET(b), startKeyFrameSeq, endKeyFrameSeq);
    }
}

void VideoEncoderVPX::PacketLostIndication()
{
    debug->printf("VideoEncoderVPX(%p)::PacketLostIndication", this);
}

bool VideoEncoderVPX::FullIntraRequest()
{
    if(appSharing && keyFrameTimer.IsRunning() == false) log->Log(LOG_VIDEO, "VideoEncoderVPX(%p)::FullIntraRequest vp9=%d forceKeyFrame=%d appSharing=%d running=%d", this, vp9, forceKeyFrame, appSharing, keyFrameTimer.IsRunning());

    if (keyFrameTimer.IsRunning() == false) {
        if (appSharing) debug->printf("VideoEncoderVPX(%p)::FullIntraRequest forceKeyFrame", this);
        keyFrameTimer.Start(3000);  // 3 seconds
        sendKeyFrame = true;
    }
    else forceKeyFrame++;
    return true;
}

void VideoEncoderVPX::TimerOnTimeout(VideoEncoderQualityTimer * timer)
{
    // qualityTimer was fired -> no problem in the last 5 seconds, raise quality
    if (qualityTimerTimeout == 5000) {
        nextRcMinQuantizer = 50;
        nextRcMaxQuantizer = 60;
        activeMap = false;
    }
    else if (qualityTimerTimeout == 7500) {
        nextRcMinQuantizer = 40;
        nextRcMaxQuantizer = 55;
        activeMap = false;
    }
    else if (qualityTimerTimeout == 10000) {
        nextRcMinQuantizer = 25;
        nextRcMaxQuantizer = 50;
        activeMap = false;
        sendKeyFrame = true;
    }
    else if (qualityTimerTimeout == 12500) {  // 10 seconds with no problem, good quality again
        nextRcMinQuantizer = 15;
        nextRcMaxQuantizer = 45;
        activeMap = false;
        sendKeyFrame = true;
    }
    else if (qualityTimerTimeout == 15000) {  // 10 seconds with no problem, good quality again
        nextRcMinQuantizer = 20;
        nextRcMaxQuantizer = 40;
        activeMap = false;
        sendKeyFrame = true;
    }
    else if (qualityTimerTimeout == 17500) {  // 10 seconds with no problem, good quality again
        nextRcMinQuantizer = 20;
        nextRcMaxQuantizer = 30;
        activeMap = false;
        sendKeyFrame = true;
        return;
    }
    qualityTimerTimeout += 2500;
    qualityTimer.Start(qualityTimerTimeout);
}

void VideoEncoderVPX::TimerOnTimeout(VideoEncoderKeyFrameTimer * timer)
{
    if(forceKeyFrame) {
        if (appSharing) debug->printf("VideoEncoderVPX(%p)::TimerOnTimeout forceKeyFrame=%d", this, forceKeyFrame);

        sendKeyFrame = true;
        // Key will be sent
        // Start the timer to avoid sending a new keyframe if a FullIntraRequest arrives
        keyFrameTimer.Start(3000);
    }
    forceKeyFrame = 0;
}

// The VP8 Encoder must respect the target bitrate but if not we add some restrictions to avoid large amount of data
// BITRATE_SLOTS has 200 entries
// AppSharing sends a new image approximately each 50 msec.
// 200 entries means around 10 seconds
// Bitrate must also be kept over the last 10 seconds
bool VideoEncoderVPX::CalculateBitrate()
{
    dword timeElapsed = 0;
    int totDataSent = 0;
    int startIndex = indexBitrate == 0 ? BITRATE_SLOTS - 1 : indexBitrate - 1;
    for (int i = startIndex; i >= 0 && timeElapsed < 1000; i--) {
        timeElapsed += durationSlots[i];
        totDataSent += bitrateSlots[i];
        //debug->printf("CalculateBitrate[%d] %d,%d %d,%d", i, durationSlots[i], bitrateSlots[i], timeElapsed, totDataSent);
    }
    for (int i = BITRATE_SLOTS - 1; i > startIndex && timeElapsed < 1000; i--) {
        timeElapsed += durationSlots[i];
        totDataSent += bitrateSlots[i];
        //debug->printf("CalculateBitrate[%d] %d,%d %d,%d", i, durationSlots[i], bitrateSlots[i], timeElapsed, totDataSent);
    }
    int ttimeElapsed = 0;
    int ttotDataSent = 0;
    for (int i = 0; i < BITRATE_SLOTS; i++) {
        ttimeElapsed += durationSlots[i];
        ttotDataSent += bitrateSlots[i];
    }
    int tBitrate = ttimeElapsed ? ttotDataSent * 8 / ttimeElapsed : 0;
    //debug->printf("VideoEncoderVPX(%p)::CalculateBitrate[%d] time=%d sent=%d max=%u tBitrate(%d,%d)=%d", this, indexBitrate, timeElapsed, totDataSent, ((maxSendBytes * 3) >> 1)), ttotDataSent, ttimeElapsed, tBitrate);
    if ((totDataSent > ((maxSendBytes*3)>>1)) && tBitrate > 1250) {
        //debug->printf("VideoEncoderVPX(%p)::CalculateBitrate[%d] time=%d sent=%d max=%u tBitrate(%d,%d)=%d DROP!", this, indexBitrate, timeElapsed, totDataSent, ((maxSendBytes * 3) >> 1), ttotDataSent, ttimeElapsed, tBitrate);
        return false;
    }
    return true;
}
