/*---------------------------------------------------------------------------*/
/* screen_buffer.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 "screen_sink.h"
#include "screen_buffer.h"

IScreenBuffer * IScreenBuffer::Create(class IInstanceLog * const log)
{
    return new ScreenBuffer(log);
}

ScreenBuffer::ScreenBuffer(class IInstanceLog * const log) : log(log)
{
    for(int i = 0; i < MAX_X_BLOCKS; i++) {
        for(int j = 0; j < MAX_Y_BLOCKS; j++) {
            blocks[i][j] = NULL;
        }
    }
    this->w = 0;
    this->h = 0;
    this->lowQualityImage = NULL;
}

ScreenBuffer::~ScreenBuffer()
{
    debug->printf("ScreenBuffer(%p)::~ScreenBuffer b=%p", this, this->blocks);

    CleanScreenBufferAppBlocks();

    if(lowQualityImage) delete lowQualityImage;
}

void ScreenBuffer::RegisterScreenSink(class IScreenSink * sink)
{
    debug->printf("ScreenBuffer(%p)::RegisterScreenSink sink=%p", this, sink);

    sinks.push_back(new class ScreenSinkHelper(sink));
    if(w && h) sink->UpdateAppResolution(w, h);

    for(int i = 0; i < MAX_X_BLOCKS; i++) {
        for(int j = 0; j < MAX_Y_BLOCKS; j++) {
            if(blocks[i][j]) {
                sink->ImageAdded(blocks[i][j]->x, blocks[i][j]->y, blocks[i][j]->version);
                for(int l = 1; l < blocks[i][j]->update; l++) {
                    sink->ImageUpdated(blocks[i][j]->x, blocks[i][j]->y, l);
                }
                if(blocks[i][j]->update) sink->ImageUpdated(blocks[i][j]->x, blocks[i][j]->y, blocks[i][j]->update);
            }
        }
    }
}

void ScreenBuffer::UnregisterScreenSink(class IScreenSink * sink)
{
    debug->printf("ScreenBuffer(%p)::UnregisterScreenSink sink=%p", this, sink);

    if(!sinks.empty()) {
        class ScreenSinkHelper * h;
        for(h = sinks.front(); h; h = h->goNext()) {
            if(h->sink == sink) {
                h->remove();
                delete h;
                return;
            }
        }
    }
}

//void ScreenBuffer::Set(byte x, byte y, class IImage * image, byte version, byte update)
void ScreenBuffer::Set(class IImage * image)
{
    //debug->printf("ScreenBuffer(%p)::Set (%d,%d) (%d,%d %d,%d) type=%d v=%d u=%d", this, image->x, image->y, image->posX, image->posY, image->dimX, image->dimY, image->type, image->version, image->update);

    if(AddImageUpdate(image) == false) {
        delete image;
        return;
    }
    if(!sinks.empty()) {
        class ScreenSinkHelper * h;
        for(h = sinks.front(); h; h = h->goNext()) {
            if(image->update) h->sink->ImageUpdated(image->x, image->y, image->update);
            else h->sink->ImageAdded(image->x, image->y, image->version);
        }
    }
}

void ScreenBuffer::Clear()
{
    for (int i = 0; i < MAX_X_BLOCKS; i++) {
        for (int j = 0; j < MAX_Y_BLOCKS; j++) {
            if (blocks[i][j]) blocks[i][j]->DeleteUpdates();
        }
    }
}

void ScreenBuffer::Set(class ILowQualityImage * image)
{
    debug->printf("ScreenBuffer(%p)::Set LowQualityImage", this);

    if(lowQualityImage) delete lowQualityImage;
    lowQualityImage = image;

    if(!sinks.empty()) {
        class ScreenSinkHelper * h;
        for(h = sinks.front(); h; h = h->goNext()) {
            h->sink->LowQualityImageAdded();
        }
    }
}

byte ScreenBuffer::GetNumUpdates(byte x, byte y)
{
    return 0;
}

class ILowQualityImage * ScreenBuffer::GetLowQuality()
{
    return lowQualityImage;
}

class IImage * ScreenBuffer::Get(byte x, byte y)
{
    if(blocks[x][y]) return blocks[x][y]->GetImageUpdate(0);
    return NULL;
}

class IImage * ScreenBuffer::GetUpdate(byte x, byte y, byte update)
{
    if(blocks[x][y]) return blocks[x][y]->GetImageUpdate(update);
    return NULL;
}

void ScreenBuffer::UpdateAppResolution(int w, int h)
{
    debug->printf("ScreenBuffer(%p)::UpdateAppResolution (w=%d,h=%d) -> (w=%d,h=%d)", this, this->w, this->h, w, h);
    if(!sinks.empty()) {
        class ScreenSinkHelper * t;
        for(t = sinks.front(); t; t = t->goNext()) {
            t->sink->UpdateAppResolution(w, h);
        }
    }
    CleanScreenBufferAppBlocks();
    this->w = w;
    this->h = h;
}

void ScreenBuffer::CleanScreenBufferAppBlocks()
{
    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;
        }
    }
}

bool ScreenBuffer::AddImageUpdate(class IImage * image)
{
    if(blocks[image->x][image->y] == NULL) {
        blocks[image->x][image->y] = new class ScreenBufferAppBlock(image->x, image->y);
        //debug->printf("ScreenBuffer(%p)::StoreImageUpdate add screen block (%d,%d)", this, image->x, image->y);
    }
    return blocks[image->x][image->y]->AddImageUpdate(image);
}

bool ScreenBufferAppBlock::AddImageUpdate(class IImage * image)
{
    // delete all previous updates and old version
    if(image->version > this->version) {
        if(!this->updates.empty()) {
            while(this->updates.front()) {
                delete this->updates.front();
            }
        }
    }
    // Same version/update or same version/old update arrives, SCTP ACK got lost or arrived late and block was resend
    if(this->version == image->version && ((!this->updates.empty() && (this->update == image->update)) || (this->update > image->update))) {
        if (this->update == image->update) {
            class IImage* img = GetImageUpdate(image->update);
            if(img && (img->dimX != image->dimX || img->dimY != image->dimY || img->posX != image->posX || img->posY != image->posY)) {
                debug->printf("ScreenBufferAppBlock(%p)::AddImageUpdate version/update already available but different dimensions (%d,%d) v=%d u=%d dim=%d,%d pos=%d,%d  dim=%d,%d pos=%d,%d", 
                             this, this->x, this->y, this->version, this->update, 
                             img->dimX, img->dimY, img->posX, img->posY,
                             image->dimX, image->dimY, image->posX, image->posY);
            }
        }              
        return false;
    }
    this->version = image->version;
    this->update = image->update;
    this->updates.push_back(new class ImageUpdateBlock(image));
    //debug->printf("ScreenBlock(%p)::AddImageUpdate (%d,%d) (%d,%d) (%d,%d) v=%d,u=%d", this, this->x, this->y, image->posX, image->posY, image->dimX, image->dimY, this->version, this->update);
    return true;
}

class IImage * ScreenBufferAppBlock::GetImageUpdate(byte update)
{
    if(!this->updates.empty()) {
        class ImageUpdateBlock * i;
        if(!update) return this->updates.front()->img;
        for(i = this->updates.front(); i; i = i->goNext()) {
            if(i->img->update == update) return i->img;
        }
    }
    return NULL;
}
