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

#include <platform.h>
#include "sdk/common/os/iomux.h"
#include "sdk/common/interface/json_api.h"
#include "sdk/common/interface/webserver_plugin.h"
#include "sdk/common/lib/appwebsocket.h"
#include "sdk/common/lib/app_updates.h"

#if defined DBG_FLAG_APP_UPDATES
#define DBG(x) debug->printf x
#else
#define DBG(x) //debug->printf x
#endif

AppUpdates::AppUpdates(IIoMux * const iomux)
{
    this->iomux = iomux;
    currentFilter = 0;
    nextSessionId = 1;
}

void AppUpdates::StartUpdate(class AppUpdate * update)
{
    DBG(("AppUpdates(%p)::StartUpdate(%p)", this, update));
    if (!updates.front()) {
        iomux->SetExec(this, 0);
    }
    updates.push_back(update);
}

void AppUpdates::IoExec(void * execContext)
{
    DBG(("AppUpdates(%p)::IoExec(%p)", this, currentFilter));
    if (!currentFilter && updates.front()) {
        if (updates.front()->user) {
            currentFilter = updates.front()->user->filters;
        }
        else {
            currentFilter = updates.front()->filters->front();
        }
    }
    if (currentFilter) {
        for (int count = 0; currentFilter && count < 10; currentFilter = currentFilter->goNext(), count++) {
            if (!updates.front()->user || currentFilter->user == updates.front()->user) {
                if (currentFilter->Test(updates.front())) {
                    if (currentFilter->session->sendingUpdate) {
                        DBG(("currentFilter(%p)->session(%p)->queue(new AppUpdatesQueued(updates.front(%x)))", currentFilter, currentFilter->session, updates.front()));
                        class AppUpdatesQueued* u = new AppUpdatesQueued(updates.front(), currentFilter);
                        currentFilter->queue.push_back(u);
                        currentFilter->session->queue.push_back(u);
                    }
                    else {
                        DBG(("currentFilter(%p,session=%p)->Send(updates.front(%x))", currentFilter, currentFilter->session, updates.front()));
                        currentFilter->Send(updates.front());
                    }
                }
            }
            else {
                currentFilter = 0;
                break;
            }
        }
    }
    if (!currentFilter) {
        class AppUpdate * update = updates.pop_front();
        if (update && !update->queuedCount) {
            delete update;
        }
    }
    if (currentFilter || (!currentFilter && updates.front())) iomux->SetExec(this, 0);
}

/*---------------------------------------------------------------------------*/
/* AppUpdatesSession                                                         */
/*---------------------------------------------------------------------------*/

AppUpdatesSession::AppUpdatesSession(class AppUpdates * app, IWebserverPlugin * plugin, class IInstanceLog * const log, class JsonApiContext * jsonApiContext) :
    AppWebsocket(plugin, log, jsonApiContext)
{
    DBG(("AppUpdatesSession(%p)::AppUpdatesSession()", this));
    instance = app;
    sessionId = app->nextSessionId++;
    srcs = 0;
    sendingUpdate = false;
    sendingResponse = false;
    responsePending = 0;
    appUpdatesClosed = false;
    app->sessions.push_back(this);
}

AppUpdatesSession::~AppUpdatesSession()
{
    DBG(("AppUpdatesSession(%p)::~AppUpdatesSession()", this));
    while (srcs) {
        class AppUpdatesFilterY * f = (AppUpdatesFilterY *)srcs;
        f->RemFilter();
        if (instance->currentFilter == f) instance->currentFilter = 0;
        f->AppUpdatesFilterTryDelete();
    }
}

void AppUpdatesSession::AppWebsocketClosed()
{
    DBG(("AppUpdatesSession(%p)::AppWebsocketClosed()", this));
    appUpdatesClosed = true;
    if (sendingResponse) {
        while (responsePending--) ResponseSent();
        sendingResponse = false;
    }
    TryDelete();
}

void AppUpdatesSession::AppWebsocketSendResult()
{
    DBG(("AppUpdatesSession(%p)::AppWebsocketSendResult() sendingResponse=%x, sendingUpdate=%x, responsePending=%u", this, sendingResponse, sendingUpdate, responsePending));
    if (sendingResponse) {
        responsePending--;
        if (!responsePending) sendingResponse = false;
        ResponseSent();
    }
    else if (sendingUpdate) {
        sendingUpdate = false;
        UpdateSent();
    }
}

void AppUpdatesSession::UpdateSent()
{
    DBG(("AppUpdatesSession(%p)::UpdateSent()", this));
    if (queue.front()) {
        class AppUpdatesQueued * queued = (class AppUpdatesQueued *)queue.pop_front();
        DBG(("queued(%p)->filter(%p)->SendQueued(%p)", queued, queued->filter, queued->update));
        queued->filter->Send(queued->update);
        delete queued;
    }
}

void AppUpdatesSession::SendResponse(class json_io & msg, char * buffer)
{
    DBG(("AppUpdateSession(%p)::SendResponse()", this));
    AppWebsocketMessageSend(msg, buffer);
    sendingResponse = true;
    responsePending++;
}

void AppUpdatesSession::SendUpdate(class json_io & msg, char * buffer)
{
    DBG(("AppUpdateSession(%p)::SendUpdate()", this));
    AppWebsocketMessageSend(msg, buffer);
    sendingUpdate = true;
}

void AppUpdatesSession::AppFilterClose(istd::list<AppUpdatesFilterY> & filters, const char * src)
{
    DBG(("AppUpdateSession(%p)::AppFilterClose(filters=%p, src=%s)", this, &filters, src));
    struct filters_ctx ctx((void *)&filters, src);
    class btree * b = srcs->btree_find(&ctx);
    if (b) delete b;
}

class AppUpdatesFilterY * AppUpdatesSession::AppFilterGet(istd::list<AppUpdatesFilterY> & filters, const char * src)
{
    DBG(("AppUpdateSession(%p)::AppFilterGet()", this));
    struct filters_ctx ctx((void *)&filters, src);
    class btree * b = srcs->btree_find(&ctx);
    if (b) {
        DBG(("return %p", (AppUpdatesFilterY *)b));
        return (AppUpdatesFilterY *)b;
    }
    return 0;
}

class AppUpdatesFilterY * AppUpdatesSession::AppFilterGetFirstLeft(istd::list<AppUpdatesFilterY> & filters)
{
    DBG(("AppUpdateSession(%p)::AppFilterGet()", this));
    struct filters_ctx ctx((void *)&filters, 0);
    class btree * b = srcs->btree_find_first_left(&ctx);
    if (b && ((AppUpdatesFilterY *)b)->filters == (void *)&filters) {
        DBG(("return %p", (AppUpdatesFilterY *)b));
        return (AppUpdatesFilterY *)b;
    }
    return 0;
}

/*---------------------------------------------------------------------------*/
/* AppUpdate                                                                 */
/*---------------------------------------------------------------------------*/

AppUpdate::AppUpdate(AppUpdatesFilters & filters, const char * sip)
{
    DBG(("AppUpdate(%p)::AppUpdate(%s)", this, sip ? sip : "-"));
    this->filters = &filters.list;
    user = 0;
    if (sip) {
        class btree * b = filters.usersTree->btree_find(sip);
        if (b) {
            user = (class AppUpdatesUser *)b;
            user->refCount++;
        }
        else {
            user = new AppUpdatesUser(0, filters.usersTree, sip);
        }
    }
    queuedCount = 0;
}

AppUpdate::~AppUpdate()
{
    DBG(("AppUpdate(%p)::~AppUpdate()", this));
    if (user) {
        if (user->refCount) user->refCount--;
        else delete user;
    }
};

/*---------------------------------------------------------------------------*/
/* AppUpdatesUser                                                            */
/*---------------------------------------------------------------------------*/

AppUpdatesUser::AppUpdatesUser(class AppUpdatesFilterY * filter, class btree * & tree, const char * sip) :
tree(tree)
{
    DBG(("AppUpdatesUser(%p)::AppUpdateUser(%s)", this, sip));
    this->filters = filter;
    this->sip = _strdup(sip);
    refCount = 0;
    tree = tree->btree_put(this);
}

AppUpdatesUser::~AppUpdatesUser()
{
    DBG(("AppUpdatesUser(%p):~AppUpdateUser(%s)", this, sip));
    tree = tree->btree_get(this);
    free((void *)sip);
}

/*---------------------------------------------------------------------------*/
/* AppUpdatesFilter                                                          */
/*---------------------------------------------------------------------------*/

AppUpdatesFilterY::AppUpdatesFilterY(istd::list<AppUpdatesFilterY> & filters, class btree * & usersTree, class AppUpdatesSession * session, const char * src, const char * sip)
{
    DBG(("AppUpdatesFilterY(%p)::AppUpdatesFilterY(filters=%p session=%p:%s %s)", this, &filters, session, src, sip ? sip : "-"));
    this->session = session;
    this->filters = (void *)&filters;
    this->src = _strdup(src);

    filters_ctx ctx(this->filters, src);
    class btree * b = session->srcs->btree_find(&ctx);
    if (b) delete b;
    session->srcs = session->srcs->btree_put(this);

    if (sip) {
        class btree * b = usersTree->btree_find(sip);
        if (b) {
            user = (class AppUpdatesUser *)b;
            filters.insertBefore(this, user->filters);
            user->filters = this;
            user->refCount++;
        }
        else {
            user = new AppUpdatesUser(this, usersTree, sip);
            filters.push_back(this);
        }
    }
    else {
        user = 0;
        filters.push_back(this);
    }
}

AppUpdatesFilterY::~AppUpdatesFilterY()
{
    DBG(("AppUpdatesFilterY(%p)::~AppUpdatesFilterY(%p:%s) user=%p", this, session, src, user));
    if (filters) {
        session->srcs = session->srcs->btree_get(this);
    }
    free(src);

    if (user) {
        if (user->filters == this) {
            user->filters = this->goNext();
        }
        if (user->refCount) user->refCount--;
        else delete user;
    }

    while (queue.front()) {
        class AppUpdatesQueuedFilter * update = queue.pop_front();
        DBG(("delete queue(%p).front(%p)", &queue, (AppUpdatesQueued *)update));
        delete update;
    }
}

/*---------------------------------------------------------------------------*/
/* AppUpdatesQueued                                                          */
/*---------------------------------------------------------------------------*/

AppUpdatesQueued::AppUpdatesQueued(class AppUpdate * update, class AppUpdatesFilterY * filter)
{
    this->update = update;
    this->filter = filter;
    update->queuedCount++;
    DBG(("AppUpdatesQueued(%p)::AppUpdatesQueued(update=%p:%u,filter=%p)", this, update, update->queuedCount, filter));
}

AppUpdatesQueued::~AppUpdatesQueued()
{
    DBG(("AppUpdatesQueued(%p)::~AppUpdatesQueued(%p:%u)", this, update, update->queuedCount));
    update->queuedCount--;
    if (!update->queuedCount && !update->getList()) {
        delete update;
    }
}

AppUpdatesQueuedSession::~AppUpdatesQueuedSession()
{
    DBG(("AppUpdatesQueuedSession(%p)::~AppUpdatesQueuedSession()", (AppUpdatesQueued *)this));
}

AppUpdatesQueuedFilter::~AppUpdatesQueuedFilter()
{
    DBG(("AppUpdatesQueuedFilter(%p)::~AppUpdatesQueuedFilter()", (AppUpdatesQueued *)this));
}
