/*---------------------------------------------------------------------------*/
/* screen_sink.cpp                                                           */
/* copyright (c) innovaphone 2018                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include "platform/platform.h"
#include "common/os/iomux.h"

#include "common/interface/time.h"
#include "common/interface/socket.h"
#include "common/interface/media.h"
#include "common/interface/appsharing.h"
#include "common/ilib/json.h"

#include "screen_sink.h"

IScreenSink * IScreenSink::Create(class IIoMux * const iomux, class UScreenSink * const user, class IScreenBuffer * const screenBuffer, byte sessionId, int bitrate, int waitingMsForAck, const char * appName, const char * appDesc, const char * senderName, class IInstanceLog * const log)
{
    return new ScreenSink(iomux, user, screenBuffer, sessionId, bitrate, waitingMsForAck, appName, appDesc, senderName, log);
}

ScreenSink::ScreenSink(class IIoMux * const iomux, class UScreenSink * const user, class IScreenBuffer * const screenBuffer, byte sessionId, int bitrate, int waitingMsForAck, const char * appName, const char * appDesc, const char * senderName, class IInstanceLog * const log) :
log(log), 
iomux(iomux),
screenBuffer(screenBuffer),
user(user)
{
    this->bitrate = bitrate;
    this->waitingMsForAck = waitingMsForAck;
    this->w = 0;
    this->h = 0;
    this->seq = 0;
    for(int i = 0; i < MAX_X_BLOCKS; i++) {
        for(int j = 0; j < MAX_Y_BLOCKS; j++) {
            blocks[i][j] = NULL;
        }
    }
    this->lastX = 0;
    this->lastY = 0;
    this->sessionId = sessionId;
    this->totalDataSent = 0;
    this->totDataSent = 0;
    this->indexBitrate = 0;
    this->maxSendBytes = (bitrate * 1000) / 8;
    for(int i = 0; i<BITRATE_SLOTS; i++) bitrateSlots[i] = 0;
    this->sessionCreated = (screenBuffer == NULL ? true : false);
    this->waitUpdateSessionAck = false;
    this->expectedWidth = 0;
    this->expectedHeight = 0;
    this->sessionTimer = new ITimer(iomux, this);
    this->bitrateTimer = new ITimer(iomux, this);
    this->createSession = NULL;
    this->appName = _strdup(appName);
    this->appDesc = _strdup(appDesc);
    this->senderName = _strdup(senderName);
    this->closing = false;
    this->waitLowQualityImageAck = false;
    this->numDeleteLowQualityMsgs = 0;

    debug->printf("ScreenSink(%p)::ScreenSink user=%p screenBuffer=%p bitrate=%d waitingMsForAck=%d maxSendBytes=%d appName=(%s) appDesc=(%s) senderName=(%s)", this, user, screenBuffer, bitrate, waitingMsForAck, maxSendBytes, this->appName ? (char *)this->appName : "", this->appDesc ? (char *)this->appDesc : "", this->senderName ? (char *)this->senderName : "");
}

ScreenSink::~ScreenSink()
{
    debug->printf("ScreenSink(%p)::~ScreenSink user=%p screenBuffer=%p", this, user, screenBuffer);

    CleanScreenSinkAppBlocks();

    if(this->appName) free(this->appName);
    if(this->appDesc) free(this->appDesc);
    if(this->senderName) free(this->senderName);
    if(this->createSession) free(this->createSession);

    delete this->sessionTimer;
    delete this->bitrateTimer;
}

void ScreenSink::Stop() 
{
    debug->printf("ScreenSink(%p)::Stop", this);
    this->bitrateTimer->Cancel();
    this->sessionTimer->Cancel();
    this->closing = true;
}

void ScreenSink::RecvAck(unsigned num)
{
    //debug->printf("ScreenSink(%p)::RecvAck num=%x", this, num);

    switch(num >> 24) {
        case AppSharingMessages::IMAGE_MESSAGE:
            {
                word seq = num & 0xffff;
                if(seq > MAX_SEQUENCE_SINK) return;
                int x = (sequenceTable[seq] >> 8) & 0xff;
                int y = (sequenceTable[seq] >> 0) & 0xff;
                if(x < MAX_X_BLOCKS && y < MAX_Y_BLOCKS && blocks[x][y]) {
                    class ImageUpdateSent * img = blocks[x][y]->GetImageUpdate();
                    // img->sequence != seq means block was retransmitted but an old version was acknowledged.
                    // this should not happen often, avoid having a queue with old transmitted sequences
                    if(img && img->sequence == seq) {
                        //debug->printf("ScreenSink(%p)::RecvAck (%d,%d) img=%p v=%d u=%d seq=%d", this, x, y, img, img->version, img->update, seq);
                        img->remove();
                        delete img;
                    }
                }
            }  
            break;
    }
}

void ScreenSink::Recv(void * buf, int len)
{
    char * b = (char *)buf;

    //debug->printf("ScreenSink(%p)::Recv len=%d msg=%d", this, len, b[0]);

    switch(b[0]) {
        case AppSharingMessages::CREATE_SESSION_ACK:
            sessionCreated = true;
            sessionTimer->Cancel();
            bitrateTimer->Start(BITRATE_TIMER);
            lastBitrateTimerStart = ITime::TimeStampMilliseconds();
            lastBitrateTimerValue = BITRATE_TIMER;
            break;
        case AppSharingMessages::UPDATE_SESSION_ACK:
            {
                word w = READ_WORD_NET(&b[2]);
                word h = READ_WORD_NET(&b[4]);
                if(w == expectedWidth && h == expectedHeight) {
                    waitUpdateSessionAck = false;
                    sessionTimer->Cancel();
                    bitrateTimer->Start(BITRATE_TIMER);
                    lastBitrateTimerStart = ITime::TimeStampMilliseconds();
                    lastBitrateTimerValue = BITRATE_TIMER;
                }
                debug->printf("ScreenSink(%p)::Recv update session ack %dx%d == %dx%d", this, w, h, expectedWidth, expectedHeight);
            }
            break;
        case AppSharingMessages::DELETE_IMAGE_MESSAGE_ACK:
            {
#ifdef SIG_TRACE_
                debug->printf("ScreenSink(%p)::Recv delete image ack", this);
#endif
                if(screenBuffer && waitLowQualityImageAck) {
                    waitLowQualityImageAck = false;
                    numDeleteLowQualityMsgs = 0;
                    sessionTimer->Cancel();
                    class ILowQualityImage * lowQualityImage;
                    if((lowQualityImage = screenBuffer->GetLowQuality()) != NULL) {
#ifdef SIG_TRACE_
                        debug->printf("ScreenSink(%p)::TimerOnTimeout SendLowQualityImage len=%i sessionId=%i lowQualityImage=%p", this, lowQualityImage->len, sessionId, lowQualityImage);
#endif
                        char * lowQualityImageMessage = (char *)malloc(3 + lowQualityImage->len);
                        lowQualityImageMessage[0] = AppSharingMessages::LOW_QUALITY_IMAGE_MESSAGE;
                        lowQualityImageMessage[1] = sessionId;
                        lowQualityImageMessage[2] = lowQualityImage->type;
                        memcpy(&lowQualityImageMessage[3], lowQualityImage->buf, lowQualityImage->len);
                        user->SendSinkMessage(lowQualityImageMessage, 3 + lowQualityImage->len, (((unsigned)LOW_QUALITY_IMAGE_MESSAGE << 24) & 0xff000000) | (((unsigned)sessionId << 16) & 0xff0000) | 0);
                        free(lowQualityImageMessage);

                        bitrateSlots[indexBitrate] += 3 + lowQualityImage->len;
                        totDataSent += 3 + lowQualityImage->len;
                        totalDataSent += 3 + lowQualityImage->len;
                    }
                    indexBitrate = ((indexBitrate + 1) == BITRATE_SLOTS) ? 0 : indexBitrate + 1;
                    bitrateTimer->Start(BITRATE_TIMER);
                } 
            }
            break;       
        default: break;
    }
}

int ScreenSink::SendImage(int x, int y)
{
    int dataSent = 0;
    if(blocks[x][y]) {
        class ImageUpdateSent * img = blocks[x][y]->GetImageUpdate();

        //if(img) debug->printf("ScreenSink(%p)::SendImage packet (%i,%i) img=%p ack=%i (v=%i,u=%i) indexBitrate=%i (bytesSent=%i totalDataSent=%i) slot=%i", this, blocks[x][y]->x, blocks[x][y]->y, img, img ? img->waitingMsAck : 0, img ? img->version : 0, img ? img->update : 0, indexBitrate, totDataSent, totalDataSent, bitrateSlots[indexBitrate]);

        if(img) {
            class IImage * block;
            if(!img->update) block = screenBuffer->Get(blocks[x][y]->x, blocks[x][y]->y);
            else block = screenBuffer->GetUpdate(blocks[x][y]->x, blocks[x][y]->y, img->update);
            if(block) {  // Can this be NULL???
                //if(block->version != img->version || block->update != img->update || block->type != CompressionType::JPEG) debug->printf("version-update are different! (v=%d,u=%d) != (v=%d,u=%d)", block->version, block->update, img->version, img->update);
                char * imageMessage = (char *)malloc(11 + block->len);
                imageMessage[0] = AppSharingMessages::IMAGE_MESSAGE;
                imageMessage[1] = sessionId;
                imageMessage[2] = blocks[x][y]->x;
                imageMessage[3] = blocks[x][y]->y;
                imageMessage[4] = img->version;
                imageMessage[5] = img->update;
                imageMessage[6] = block->posX;
                imageMessage[7] = block->posY;
                imageMessage[8] = block->dimX;
                imageMessage[9] = block->dimY;
                imageMessage[10] = block->type;
                memcpy(&imageMessage[11], block->buf, block->len);
                user->SendSinkMessage(imageMessage, 11 + block->len, (((unsigned)imageMessage[0] << 24) & 0xff000000) | (((unsigned)imageMessage[1] << 16) & 0xff0000) | seq);
                free(imageMessage);
                sequenceTable[seq] = (((unsigned)blocks[x][y]->x << 8) & 0xff00) | (((unsigned)blocks[x][y]->y << 0) & 0xff);
                img->sequence = seq;
                //debug->printf("ScreenSink(%p)::SendImage (%d,%d) dim=(%d,%d) pos=(%d,%d) img=%p v=%d u=%d seq=%d", this, x, y, block->dimX, block->dimY, block->posX, block->posY, img, img->version, img->update, seq);
                seq = (seq + 1 == MAX_SEQUENCE_SINK) ? 0 : seq + 1;
                img->waitingMsAck = waitingMsForAck;
                bitrateSlots[indexBitrate] += 11 + block->len;
                totDataSent += 11 + block->len;
                totalDataSent += 11 + block->len;
                dataSent = 11 + block->len;
            }
        }
    }
    return dataSent;
}

void ScreenSink::TimerOnTimeout(ITimer * timer)
{
    //debug->printf("ScreenSink(%p)::TimerOnTimeout s=%d %p,%p indexBitrate=%d", this, sessionCreated, lastBlock, blocks, indexBitrate);

    if(closing) return;

    if(timer == sessionTimer) {
        if(waitLowQualityImageAck) {
            if(numDeleteLowQualityMsgs--) {
                user->SendSinkMessage(deleteLowQualityImage, 2, (((unsigned)deleteLowQualityImage[0] << 24) & 0xff000000) | (((unsigned)deleteLowQualityImage[1] << 16) & 0xff0000) | seq);
                seq = (seq + 1 == MAX_SEQUENCE_SINK) ? 0 : seq + 1;
            } 
            else {
                waitLowQualityImageAck = false;
                sessionTimer->Cancel();
                class ILowQualityImage * lowQualityImage;
                if((lowQualityImage = screenBuffer->GetLowQuality()) != NULL) {
#ifdef SIG_TRACE_
                    debug->printf("ScreenSink(%p)::TimerOnTimeout SendLowQualityImage len=%i sessionId=%i lowQualityImage=%p", this, lowQualityImage->len, sessionId, lowQualityImage);
#endif
                    char * lowQualityImageMessage = (char *)malloc(3 + lowQualityImage->len);
                    lowQualityImageMessage[0] = AppSharingMessages::LOW_QUALITY_IMAGE_MESSAGE;
                    lowQualityImageMessage[1] = sessionId;
                    lowQualityImageMessage[2] = lowQualityImage->type;
                    memcpy(&lowQualityImageMessage[3], lowQualityImage->buf, lowQualityImage->len);
                    user->SendSinkMessage(lowQualityImageMessage, 3 + lowQualityImage->len, (((unsigned)LOW_QUALITY_IMAGE_MESSAGE << 24) & 0xff000000) | (((unsigned)sessionId << 16) & 0xff0000) | 0);
                    free(lowQualityImageMessage);

                    bitrateSlots[indexBitrate] += 3 + lowQualityImage->len;
                    totDataSent += 3 + lowQualityImage->len;
                    totalDataSent += 3 + lowQualityImage->len;
                }
                indexBitrate = ((indexBitrate + 1) == BITRATE_SLOTS) ? 0 : indexBitrate + 1;
                bitrateTimer->Start(BITRATE_TIMER);
                return; 
            }
        }
        else if(waitUpdateSessionAck) {
            user->SendSinkMessage(startSessionMsg, 6, (((unsigned)startSessionMsg[0] << 24) & 0xff000000) | (((unsigned)startSessionMsg[1] << 16) & 0xff0000) | seq);
            seq = (seq + 1 == MAX_SEQUENCE_SINK) ? 0 : seq + 1;
        }
        else if(!sessionCreated) {
            user->SendSinkMessage(createSession, createSessionLen, (((unsigned)createSession[0] << 24) & 0xff000000) | (((unsigned)createSession[1] << 16) & 0xff0000) | seq);
            seq = (seq + 1 == MAX_SEQUENCE_SINK) ? 0 : seq + 1;
        }
        sessionTimer->Start(SESSION_TIMER);
    }
    else if(timer == bitrateTimer) {
        int nexTimer = BITRATE_TIMER;
        ulong64 actTime = ITime::TimeStampMilliseconds();
        int msElapsed = (int)(actTime - lastBitrateTimerStart);
        if(msElapsed > (lastBitrateTimerValue + 1)) {
            nexTimer -= (msElapsed - lastBitrateTimerValue);
            if(nexTimer < 0) nexTimer = BITRATE_TIMER;
        }
        //debug->printf("[%d] lastBitrateTimer=%d nexTimer=%d ms. (%llu-%llu)=elapsed=%d", indexBitrate, lastBitrateTimerValue, nexTimer, actTime, lastBitrateTimerStart, msElapsed);
        bool canSend = CalculateBitrate();
        bitrateSlots[indexBitrate] = 0;

        // reduce waiting ms for ack
        for(int i = 0; i < MAX_X_BLOCKS; i++) {
            for(int j = 0; j < MAX_Y_BLOCKS; j++) {
                if(blocks[i][j]) {
                    class ImageUpdateSent * img = blocks[i][j]->GetImageUpdate();
                    if(img && img->waitingMsAck) {
                        img->waitingMsAck -= msElapsed;
                    }
                }
            }
        }

        if(canSend) {
            int sent = 0;
            // start at last position 
            int i = lastX;
            int j = lastY;
            int oldLastX = lastX;
            int oldLastY = lastY;

            //debug->printf("ScreenSink(%p)::TimerOnTimeout (%i,%i)", this, lastX, lastY);

            // (lastX -> MAX_X_BLOCKS, lastY)
            for(; i < MAX_X_BLOCKS; i++) {
                sent += SendImage(i, lastY);
                if(totDataSent > maxSendBytes) {
                    canSend = false;
                    // End X reached?
                    lastX = (i + 1 == MAX_X_BLOCKS ? 0 : i + 1);
                    if(lastX == 0) {  // newline
                        // End Y reached?
                        lastY = (j + 1 == MAX_Y_BLOCKS ? 0 : j + 1);
                    }
                    break;
                }
            }

            // (0 -> MAX_X_BLOCKS, lastY + 1 -> MAX_Y_BLOCKS)
            if(canSend) {
                lastY = (j + 1 == MAX_Y_BLOCKS ? 0 : j + 1);
                for(j = lastY; j < MAX_Y_BLOCKS; j++) {
                    for(i = 0; i < MAX_X_BLOCKS; i++) {
                        sent += SendImage(i, j);
                        if(totDataSent > maxSendBytes) {
                            canSend = false;
                            // End X reached?
                            lastX = (i + 1 == MAX_X_BLOCKS ? 0 : i + 1);
                            if(lastX == 0) {
                                lastY = (j + 1 == MAX_Y_BLOCKS ? 0 : j + 1);
                            }
                            else lastY = j;
                            break;
                        }
                    }
                    if(canSend == false) break;
                }
            }

            // Either position (MAX_X_BLOCKS, MAX_Y_BLOCKS) was reached or data cannot be sent
            if(canSend) {
                // Use oldLastX, oldLastY to avoid resend same block twice inside same loop
                for(j = 0; j < oldLastY; j++) {
                    for(i = 0; i < MAX_X_BLOCKS; i++) {
                        sent += SendImage(i, j);
                        if(totDataSent > maxSendBytes) {
                            canSend = false;
                            // End X reached?
                            lastX = (i + 1 == MAX_X_BLOCKS ? 0 : i + 1);
                            if(lastX == 0) {
                                lastY = (j + 1 == MAX_Y_BLOCKS ? 0 : j + 1);
                            }
                            else lastY = j;
                            break;
                        }
                    }
                    if(canSend == false) break;
                }
            }

            if(canSend) {
                // Use oldLastX, oldLastY to avoid resend same block twice inside same loop
                for(i = 0; i < oldLastX; i++) {
                    sent += SendImage(i, oldLastY);
                    if(totDataSent > maxSendBytes) {
                        canSend = false;
                        // End X reached?
                        lastX = (i + 1 == MAX_X_BLOCKS ? 0 : i + 1);
                        if(lastX == 0) {
                            lastY = (j + 1 == MAX_Y_BLOCKS ? 0 : j + 1);
                        }
                        else lastY = j;
                        break;
                    }
                }
            }

            // we could have sent data but there was nothing to send, reset to (0,0)
            if(sent == 0) {
                lastX = 0;
                lastY = 0;
            }
        }
        indexBitrate = ((indexBitrate + 1) == BITRATE_SLOTS) ? 0 : indexBitrate + 1;
        bitrateTimer->Start(nexTimer);
        lastBitrateTimerStart = ITime::TimeStampMilliseconds();
        lastBitrateTimerValue = nexTimer;
    }
}

void ScreenSink::LowQualityImageAdded()
{
    debug->printf("ScreenSink(%p)::LowQualityImageAdded canSend=%d sessionCreated=%d", this, CalculateBitrate(), sessionCreated);

    if(!sessionCreated) return;

    CleanScreenSinkAppBlocks();

    numDeleteLowQualityMsgs = 20;
    if(waitLowQualityImageAck) return;

    bitrateTimer->Cancel();
    bitrateSlots[indexBitrate] = 0;

    waitLowQualityImageAck = true;
    deleteLowQualityImage[0] = AppSharingMessages::DELETE_IMAGE_MESSAGE;
    deleteLowQualityImage[1] = sessionId;
    user->SendSinkMessage(deleteLowQualityImage, 2, (((unsigned)deleteLowQualityImage[0] << 24) & 0xff000000) | (((unsigned)deleteLowQualityImage[1] << 16) & 0xff0000) | seq);
    seq = (seq + 1 == MAX_SEQUENCE_SINK) ? 0 : seq + 1;

    sessionTimer->Start(SESSION_TIMER);
}

void ScreenSink::ImageAdded(byte x, byte y, byte version)
{
    if(blocks[x][y] == NULL) {
        blocks[x][y] = new class ScreenSinkAppBlock(x, y, version);
        //debug->printf("ScreenSink(%p)::ImageAdded add screen block (%d,%d) b=%p c=%p", this, x, y, blocks, sb);
    }

    //debug->printf("ScreenSink(%p)::ImageAdded (%d,%d) last version=%d new version=%d", this, x, y, sb->version, version);

    // new image version in ScreenBuffer, drop the past
    blocks[x][y]->CleanImageUpdateList();
    blocks[x][y]->AddImage(version);
}

void ScreenSink::ImageUpdated(byte x, byte y, byte update)
{
    if(blocks[x][y] == NULL) {  // should not happen...
        blocks[x][y] = new class ScreenSinkAppBlock(x, y);
        //debug->printf("ScreenBuffer(%p)::ImageUpdated add screen block (%d,%d) b=%p c=%p", this, x, y, blocks, sb);
    }
    //debug->printf("ScreenSink(%p)::ImageUpdated (%d,%d) v=%d u=%d", this, x, y, blocks[x][y]->version, update);
    blocks[x][y]->AddImageUpdate(update);
}

void ScreenSink::UpdateAppResolution(int w, int h)
{
    debug->printf("ScreenSink(%p)::UpdateAppResolution (%d) (%d,%d, %d,%d) -> (%d,%d)", this, sessionCreated, this->w, this->h, expectedWidth, expectedHeight, w, h);

    bitrateTimer->Cancel();
    sessionTimer->Cancel();

    CleanScreenSinkAppBlocks();

    this->w = w;
    this->h = h;

    if(sessionCreated) {
        waitUpdateSessionAck = true;

        startSessionMsg[0] = AppSharingMessages::UPDATE_SESSION;
        startSessionMsg[1] = sessionId;
        WRITE_WORD_NET(&startSessionMsg[2], this->w);
        WRITE_WORD_NET(&startSessionMsg[4], this->h);
        user->SendSinkMessage(startSessionMsg, 6, (((unsigned)startSessionMsg[0] << 24) & 0xff000000) | (((unsigned)startSessionMsg[1] << 16) & 0xff0000) | seq);
        seq = (seq + 1 == MAX_SEQUENCE_SINK) ? 0 : seq + 1;
    }
    else {
        if(appName && appDesc)
            createSession = (char *)malloc((strlen(appName) + strlen(appDesc) + 128) << 1);
        else
            createSession = (char *)malloc(6);
        createSession[0] = AppSharingMessages::CREATE_SESSION;
        createSession[1] = sessionId;
        WRITE_WORD_NET(&createSession[2], this->w);
        WRITE_WORD_NET(&createSession[4], this->h);
        createSessionLen = 6;
        if(appName && appDesc && senderName) {
            json_io json(0);
            word base = json.add_object(JSON_ID_ROOT, 0);
            json.add_string(base, "name", appName);
            json.add_string(base, "description", appDesc);
            json.add_string(base, "sender", senderName);
            createSessionLen += json.encode(base, &createSession[createSessionLen]);
        }
        user->SendSinkMessage(createSession, createSessionLen, (((unsigned)createSession[0] << 24) & 0xff000000) | (((unsigned)createSession[1] << 16) & 0xff0000) | seq);
        seq = (seq + 1 == MAX_SEQUENCE_SINK) ? 0 : seq + 1;
        debug->hexdump(createSession, createSessionLen);
    }

    expectedWidth = this->w;
    expectedHeight = this->h;

    sessionTimer->Start(SESSION_TIMER);
}

void ScreenSink::CleanScreenSinkAppBlocks()
{
    for(int i = 0; i < MAX_X_BLOCKS; i++) {
        for(int j = 0; j < MAX_Y_BLOCKS; j++) {
            if(blocks[i][j]) delete blocks[i][j];
            blocks[i][j] = NULL;
        }
    }
    this->lastX = 0;
    this->lastY = 0;
}

bool ScreenSink::CalculateBitrate()
{
    totDataSent = 0;
    for(int i = 0; i < BITRATE_SLOTS; i++) {
        totDataSent += bitrateSlots[i];
    }
    //debug->printf("ScreenSink(%p)::CalculateBitrate[%d] sent=%u(%u) max=%u", this, indexBitrate, totDataSent, totalDataSent, maxSendBytes);
    if(totDataSent > maxSendBytes) return false;
    return true;
}
