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

#include "platform/platform.h"
#include <ctype.h>
#include "common/os/iomux.h"
#include "common/interface/socket.h"
#include "common/ilib/str.h"
#include "common/interface/media.h"
#include "common/interface/guuid.h"
#include "common/interface/task.h"
#include "common/interface/stask.h"
#include "common/interface/video_format_conversion.h"
#include "common/lib/tasks_file.h"
#include "client/logflags/debug_logflags.h"
#include "client/config/debug_readlog.h"
#include "client/linux/client_main/appclient_main.h"
#include "webcam.h"

extern class UAppClientMain * appClientMainUser;


Webcam::Webcam(class WebcamProvider * const provider, class UWebcam * const webcamUser, void * context, class IInstanceLog * const log) :
    log(log),
    webcamUser(webcamUser),
    provider(provider)
{
    this->context = context;
    numOutputFormats = 0;
    webCamLost = false;
    width = 0;
    height = 0;
    defStride = 0;
    sampleSize = 0;

    id = NULL;
    utf8FriendlyName = NULL;
    shutDown = false;
    memset(&outputFormat, 0, sizeof(struct VideoFrameFormat));
}

Webcam::~Webcam()
{
    log->Log(LOG_VIDEO, "Webcam(%p)::~Webcam", this);
    if (id) free(id);
    if (utf8FriendlyName) free(utf8FriendlyName);
}

struct VideoFrameFormat * Webcam::Start(int width, int height, bool hdVideo)
{
    char buf[20];
    void * context = NULL;

    log->Log(LOG_VIDEO, "WebcamDevice(%p)::Start(%s)", this, utf8FriendlyName);
    _sprintf(buf, "%p", context);
    appClientMainUser->AppClientMainStartWebcam(id, buf, width, height, hdVideo);

    outputFormat.captureWidth = width;
    outputFormat.captureHeight = height;
    outputFormat.width = width;
    outputFormat.height = height;
    outputFormat.interlace = VideoInterlaceMode::VIDEO_INTERLACE_Unknown; 
    outputFormat.format = VideoFormat::VIDEO_FORMAT_UNKNOWN;
    outputFormat.stride = (outputFormat.format == VideoFormat::VIDEO_FORMAT_YUY2 ? outputFormat.width<<1 : 1);
    outputFormat.sampleSize = sampleSize;
    outputFormat.frameRate.numerator = 30;
    outputFormat.frameRate.denominator = 1;
    if (width * 9 == height * 16) {
        outputFormat.aspectRatio.numerator = 16;
        outputFormat.aspectRatio.denominator = 9;
    }
    else {
        outputFormat.aspectRatio.numerator = 4;
        outputFormat.aspectRatio.denominator = 3;
    }
    outputFormat.targetBitrate = hdVideo ? 700 : 350;
    outputFormat.averageBitrate = outputFormat.targetBitrate * 1000;
    outputFormat.hdVideo = hdVideo;
    outputFormat.appSharing = false;
    struct VideoFrameFormat * f = (struct VideoFrameFormat *)malloc(sizeof(struct VideoFrameFormat));
    memcpy(f, &outputFormat, sizeof(struct VideoFrameFormat));
    return f;
}

void Webcam::Stop()
{
    appClientMainUser->AppClientMainStopWebcam(id);
}

void Webcam::Close()
{
    log->Log(LOG_VIDEO, "Webcam(%p)::Close", this);

    shutDown = true;
    provider->execCnt++;
    provider->iomux->SetExec(provider, this);
}

bool Webcam::Initialize(const char * id, bool facingFront, int orientation, const char * description)
{
    bool ret = true;

    this->id = _strdup(id);
    this->facingFront = facingFront;
    this->orientation = orientation;
    this->utf8FriendlyName = _strdup(description);
    return ret;
}

void Webcam::FullIntraRequest()
{
    appClientMainUser->AppClientMainWebcamFullIntraRequest(id);
}

static istd::list<class WebcamProvider> webcamProviderList;

class IWebcamProvider * IWebcamProvider::Create(class IIoMux * const iomux, class UWebcamProvider * const user, class UWebcam * const webcamUser, class IInstanceLog * log)
{
    return new class WebcamProvider(iomux, user, webcamUser, log);
}

WebcamProvider::WebcamProvider(class IIoMux * const iomux, class UWebcamProvider * const webcamProviderUser, class UWebcam * const webcamUser, class IInstanceLog * const log) :
    iomux(iomux),
    webcamUser(webcamUser),
    webcamProviderUser(webcamProviderUser),
    log(log)
{
    this->queryDevicesPending = false;
    this->execCnt = 0;
    this->shutDown = false;
    webcamProviderList.push_back(this);
}

WebcamProvider::~WebcamProvider()
{
}

void WebcamProvider::Close()
{
    this->shutDown = true;
    // Wait for the webcams to stop
    if(!webcams.empty()) {
        class WebcamActive * w;
        for (w = webcams.front(); w; w = w->goNext()) {
            w->webcam->Close(); 
        }
    }
    else {
        this->execCnt++;
        iomux->SetExec(this, (class Webcam *)NULL);
    }
}

void WebcamProvider::QueryDevices(void * context)
{
    char buf[20];
    log->Log(LOG_VIDEO, "WebcamProvider(%p)::QueryDevices context=%p devices=%d context=%u", this, context, !webcams.empty(), queryDevicesPending);

    if(shutDown || queryDevicesPending) return;

    // Clean webcams and the make the query!
    if(!webcams.empty()) {
        queryDevicesPending = true;
        queryDevicesContext = context;
        class WebcamActive * w;
        for (w = webcams.front(); w; w = w->goNext()) {
            w->webcam->Close();
        }
        return;
    }
    log->Log(LOG_VIDEO, "WebcamProvider(%p)::QueryWebcams", this);
    _sprintf(buf, "%p", context);
    appClientMainUser->AppClientMainDetectWebcams(buf);
}

void WebcamProvider::IoExec(WebcamProviderClose * context)
{
    class Webcam * webcam = static_cast<class Webcam *>(context);
    log->Log(LOG_VIDEO, "WebcamProvider(%p)::IoExec Shutdown webcam=%p", this, webcam);

    if(!webcams.empty()) {
        class WebcamActive * a;
        a = webcams.front();
        while (a && (a->webcam != webcam)) {
            a = a->goNext();
        }
        if (a) {
            webcamProviderUser->WebcamClosed(webcam);
            delete webcam;
            delete a;
        }
    }
    if(webcams.empty()) {
        if(!shutDown && queryDevicesPending) {
            queryDevicesPending = false;
            QueryDevices(queryDevicesContext);
        }
    }

    execCnt--;
    if(shutDown && !execCnt && webcams.empty()) {
        webcamProviderUser->WebcamProviderCloseResult();
    }
}

void WebcamProvider::WebcamAdded(const char * id, bool facing_front, int orientation, void * context, const char * description)
{
    class WebcamActive * a;

    a = NULL;
    if(!webcams.empty()) {
        a = webcams.front();
        while (a && (strcmp(a->webcam->id, id) != 0)) {
            a = a->goNext();
        }
    }
    if (!a) {
        class Webcam * w = new class Webcam(this, webcamUser, context, log);
        if (w->Initialize(id, facing_front, orientation, description) == false) {
            delete w;
        }
        else {
            webcams.push_back(new class WebcamActive(w));
            webcamProviderUser->WebcamAdded(w, context);
        }
    }
}

void WebcamProvider::WebcamRemoved(const char * id)
{
    class WebcamActive * a;
    if(!webcams.empty()) {
        a = webcams.front();
        while (a && (strcmp(a->webcam->id, id) != 0)) {
            a = a->goNext();
        }
        if (a) {
            webcamProviderUser->WebcamLost(a->webcam, NULL);
            delete a->webcam;
            delete a;
        }
    }
}

void WebcamProvider::WebcamQueryResult(void * context)
{
    webcamProviderUser->WebcamQueryResult(context);
}

void WebcamProvider::WebcamFailed(const char * id, void * context)
{
    class WebcamActive * a;
    if(!webcams.empty()) {
        a = webcams.front();
        while (a && (strcmp(a->webcam->id, id) != 0)) {
            a = a->goNext();
        }
        if (a) {
            webcamProviderUser->WebcamFailed(a->webcam, context);
        }
    }
}

void WebcamProvider::WebcamSample(const char * id, dword timestamp, int length, const byte *data, const struct VideoFrameFormat *format)
{
    class WebcamActive * a;
    if(!webcams.empty()) {
        a = webcams.front();
        while (a && (strcmp(a->webcam->id, id) != 0)) {
            a = a->goNext();
        }
        if (a) {
            a->webcam->webcamUser->WebcamSample(a->webcam, data, length, timestamp, 0, format);
        }
    }
}

void phone_webcam_added(const char * id, bool facing_front, int orientation, const char * context, const char * description)
{
    WebcamProvider *p;
    uintp ctx;
    const char *c;

    ctx = 0;
    if(context) {
        c = context;
        while (*c) {
            if (isxdigit(*c)) {
                ctx = (ctx << 4) | (isdigit(*c) ? *c - '0' : tolower(*c) - 'a' + 10);
            }
            c++;
        }
    }
    for (p = webcamProviderList.front(); p; p = p->goNext()) {
        p->WebcamAdded(id, facing_front, orientation, (void *) ctx, description);
    }
}

void phone_webcam_removed(const char * id)
{
    WebcamProvider *p;
    for (p = webcamProviderList.front(); p; p = p->goNext()) {
        p->WebcamRemoved(id);
    }
}

void phone_webcam_query_result(const char * context)
{
    WebcamProvider *p;
    uintp ctx;
    const char *c;

    ctx = 0;
    if(context) {
        c = context;
        while (*c) {
            if (isxdigit(*c)) {
                ctx = (ctx << 4) | (isdigit(*c) ? *c - '0' : tolower(*c) - 'a' + 10);
            }
            c++;
        }
    }
    for (p = webcamProviderList.front(); p; p = p->goNext()) {
        p->WebcamQueryResult((void *) ctx);
    }
}

void phone_webcam_failed(const char * id, const char * context)
{
    WebcamProvider *p;
    uintp ctx;
    const char *c;

    ctx = 0;
    c = context;
    while (*c) {
        if (isxdigit(*c)) {
            ctx = (ctx << 4) | (isdigit(*c) ? *c - '0' : tolower(*c) - 'a' + 10);
        }
        c++;
    }
    for (p = webcamProviderList.front(); p; p = p->goNext()) {
        p->WebcamFailed(id, (void *) ctx);
    }
}

void phone_webcam_data(const char * id, dword timestamp, int length, const byte *data, int previewLength, const byte *previewData)
{
}

void phone_webcam_sample(const char * id, dword timestamp, int length, const byte *sample, int width, int height, int stride, int videoFormat)
{
    struct VideoFrameFormat format;
    WebcamProvider *p;

    format.captureWidth = width;
    format.captureHeight = height;
    format.width = width;
    format.height = height;
    format.interlace = VideoInterlaceMode::VIDEO_INTERLACE_Unknown; 
    format.format = (enum VideoFormat) videoFormat;
    format.stride = stride;
    format.sampleSize = length;
    format.frameRate.numerator = 30;
    format.frameRate.denominator = 1;
    if (width * 9 == height * 16) {
        format.aspectRatio.numerator = 16;
        format.aspectRatio.denominator = 9;
    }
    else {
        format.aspectRatio.numerator = 4;
        format.aspectRatio.denominator = 3;
    }
    format.hdVideo = ((width >= 1080) || (height >= 960));
    format.targetBitrate = format.hdVideo ? 700 : 350;
    format.averageBitrate = format.targetBitrate * 1000;
    format.appSharing = false;
    for (p = webcamProviderList.front(); p; p = p->goNext()) {
        p->WebcamSample(id, timestamp, length, sample, &format);
    }
}

extern "C" void IVideoFormatConversionRGB32ToYUV(unsigned char* dest, int destStride, const byte* src, int srcStride, int width, int height)
{
    IVideoFormatConversion::RGB32ToYUV(dest, destStride, src, srcStride, width, height, false);
}

extern "C" void IVideoFormatConversionARGBToYUV(unsigned char* dest, int destStride, const byte* src, int srcStride, int width, int height)
{
    IVideoFormatConversion::ARGBToYUV(dest, destStride, src, srcStride, width, height, false);
}

extern "C" void IVideoFormatConversionRGBAToYUV(unsigned char* dest, int destStride, const byte* src, int srcStride, int width, int height)
{
    IVideoFormatConversion::RGBAToYUV(dest, destStride, src, srcStride, width, height, false);
}

extern "C" void IVideoFormatConversionYUY2ToYUV(unsigned char* dest, int destStride, const byte* src, int srcStride, int width, int height)
{
    IVideoFormatConversion::YUY2ToYUV(dest, destStride, src, srcStride, width, height, false);
}
