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

#include "platform/platform.h"
#include "common/os/iomux.h"
#include "common/interface/task.h"
#include "common/interface/webserver_plugin.h"
#include "common/interface/json_api.h"
#include "common/interface/stask.h"
#include "common/interface/client.h"
#include "common/ilib/str.h"
#include "common/ilib/json.h"
#include "common/lib/tasks_file.h"
#include "common/lib/appwebsocket.h"
#include "debug_readlog.h"
#include "client.h"

#define DBG_GET(x) //debug->printf x

#define BYTE_TRAILING_ZEROES(x)\
    (((x) & 0xf) ? (((x) & 0x3) ? (((x) & 0x1) ? 0 : 1) : (((x) & 0x4) ? 2 : 3)) : (((x) & 0x30) ? (((x) & 0x10) ? 4 : 5) : (((x) & 0x40) ? 6 : 7)))

#define WORD_TRAILING_ZEROES(x)\
    (((x) & 0xff) ? BYTE_TRAILING_ZEROES(x) : 8 + BYTE_TRAILING_ZEROES((x) >> 8))

#define DWORD_TRAILING_ZEROES(x)\
    (((x) & 0xffff) ? WORD_TRAILING_ZEROES(x) : 16 + WORD_TRAILING_ZEROES((x) >> 16))

#define QWORD_TRAILING_ZEROES(x)\
    (((x) & 0xffffffff) ? DWORD_TRAILING_ZEROES(x) : 32 + DWORD_TRAILING_ZEROES((x) >> 32))

/*---------------------------------------------------------------------------*/

ulong64 IClient::LogFlagTable[ICLIENT_LOG_FLAG_COUNT] = {
    LOG_MEDIA,
    LOG_MEDIA_CHANNEL,
    LOG_ICE,
    LOG_DTLS,
    LOG_TURN,
    LOG_APP,
    LOG_VIDEO,
    LOG_AUDIO,
    LOG_APPSHARING,
    LOG_BROWSER,
    LOG_CONSOLE,
    LOG_WEBSERVER,
    LOG_APPPROXY,
    LOG_APP_WEBSOCKET,
    LOG_TCP,
    LOG_LDS,
    LOG_UDP,
    LOG_TLS,
    LOG_HTTP_CLIENT,
    LOG_SIGNALING,
    LOG_VDI_PLUGIN
};

/*---------------------------------------------------------------------------*/

Client::Client(IIoMux * const iomux, IWebserverPluginProvider * webserverPluginProvider, ISocketProvider * localSocketProvider, const char * webserver, const char * webserverPath,
               class UDebugReadLog * debugReadLogUser, class UClient * clientUser, const char * password) :
    STaskContext(iomux)
{
    iomux->RegisterShutdownHandler(this);
    //this->logFlags = 0x0;
    pendingWebRequests = 0;
    pendingGetLogs = 0;
    stopping = false;
    this->password = _strdup(password);
    this->iomux = iomux;
    this->debugReadLogUser = debugReadLogUser;
    this->clientUser = clientUser;
    if (webserver) {
        this->webserverPlugin = webserverPluginProvider->CreateWebserverPlugin(iomux, localSocketProvider, this, webserver, webserverPath, this);
        this->webserverPlugin->WebsocketListen();
        this->webserverPlugin->HttpListen(NULL, NULL, NULL, NULL, _BUILD_STRING_);
    }
    else this->webserverPlugin = NULL;
}

Client::~Client()
{
    this->Log(LOG_APP, "Client(%p)::~Client %p", this, webserverPlugin);
    if (password) free(password);
    if (webserverPlugin) delete webserverPlugin;
}

void Client::TryStop()
{
    if (stopping && !webserverPlugin && !pendingWebRequests) {
        this->iomux->ShutdownComplete(this);
    }
}

void Client::Stop()
{
    if (!stopping) {
        this->Log(LOG_APP, "Client(%p)::Stop stopping", this);
        stopping = true;
        if (webserverPlugin) webserverPlugin->Close();
    }
    TryStop();
}

void Client::Shutdown()
{
    this->Log(LOG_APP, "Client(%p)::Shutdown", this);

    Stop();
}

void Client::ShutdownTimeout()
{
    debug->printf("Client(%p)::ShutdownTimeout %d %d", this, (webserverPlugin != NULL), (pendingWebRequests != 0));
}

// ******************** Webserver Plugin ******************** //

void Client::WebserverPluginHttpListenResult(IWebserverPlugin * plugin, ws_request_type_t requestType, char * resourceName, const char * registeredPathForRequest, ulong64 dataSize)
{
    this->Log(LOG_APP, "Client(%p)::WebserverPluginHttpListenResult", this);

    if (requestType == WS_REQUEST_GET) {
        UWebserverGet * webserverGet = clientUser->ClientWebserverGetAccept(resourceName);
        if (webserverGet) {
            pendingWebRequests++;
            plugin->Accept(webserverGet);
            return;
        }
        size_t len = strlen(_BUILD_STRING_) + 128;
        char * tmp = (char *)alloca(len);
        _snprintf(tmp, len, "/%s/log.txt", _BUILD_STRING_);
        if (!str::n_casecmp(resourceName, tmp, strlen(tmp))) {
            plugin->Accept(new class TaskGetLog(this, NULL));
            return;
        }
        _snprintf(tmp, len, "/%s/log", _BUILD_STRING_);
        if (!str::n_casecmp(resourceName, "/log", strlen("/log"))
         || !str::n_casecmp(resourceName, tmp, strlen(tmp))) {
            plugin->Accept(new class TaskGetLog(this, "log.txt"));
            return;
        }
        if (plugin->BuildRedirect(resourceName, _BUILD_STRING_, strlen(_BUILD_STRING_))) {
            return;
        }
    }
    plugin->Cancel(WSP_CANCEL_NOT_FOUND);
}

void Client::WebserverPluginWebsocketListenResult(IWebserverPlugin * plugin, const char * path, const char * registeredPathForRequest, const char * host)
{
    this->Log(LOG_APP, "Client(%p)::WebserverWebsocketListenResult %s %s (%s)", this, path, registeredPathForRequest, host);
    
    AppClientSession * session = new AppClientSession(this, iomux);
    sessions.push_back(session);
}

void Client::WebserverPluginClose(IWebserverPlugin * plugin, wsp_close_reason_t reason, bool lastUser)
{
    this->Log(LOG_APP, "Client(%p)::WebserverPluginClose %d", this, lastUser);
    if (lastUser) {
        delete this->webserverPlugin;
        this->webserverPlugin = NULL;
        Shutdown();
    }
}

void Client::WebserverRequestClosed()
{
    pendingWebRequests--;
    TryStop();
}

void Client::ClientConfigChanged(class UClient * user, void * context)
{
    class AppClientSrc * src = (class AppClientSrc *) context;
    AppClientSession * session;

    //Log(LOG_APP, "Client(%p)::ClientConfigChanged", this);
    if(!sessions.empty()) {
        for (session = sessions.front(); session; session = session->goNext()) {
            char temp[1024];
            char * tmp = temp;
            char cfg[10000];
            class json_io send(cfg);

            word sendBase = 0xffff;
            sendBase = send.add_object(0xffff, 0);
            send.add_string(sendBase, "mt", "ClientConfig");
            if (src && src->src && (session == src->session)) {
                send.add_string(sendBase, "src", src->src);
            }
            user->ClientConfigToJson(send, sendBase, tmp);
            session->AppWebsocketMessageSend(send, cfg);
        }
    }
    if (src) delete src;
}


void Client::ClientStatusChanged(const char* name, int value)
{
    AppClientSession* session;
    Log(LOG_APP, "Client(%p)::ClientStatusChanged %s %i", this, name, value);
    if (!sessions.empty()) {
        for (session = sessions.front(); session; session = session->goNext()) {
            char temp[1024];
            char* tmp = temp;
            char cfg[10000];
            class json_io send(cfg);
            word sendBase = 0xffff;
            sendBase = send.add_object(0xffff, 0);
            send.add_string(sendBase, "mt", "ClientStatus");
            send.add_int(sendBase, name, value,tmp);
            if(session->monitorstatuschanges) session->AppWebsocketMessageSend(send, cfg);
        }
    }
}

void Client::ClientStatusChanged(const char* name, const char* value)
{
    AppClientSession* session;
    Log(LOG_APP, "Client(%p)::ClientStatusChanged %s %s", this, name, value);
    if (!sessions.empty()) {
        for (session = sessions.front(); session; session = session->goNext()) {
            char cfg[10000];
            class json_io send(cfg);
            word sendBase = 0xffff;
            sendBase = send.add_object(0xffff, 0);
            send.add_string(sendBase, "mt", "ClientStatus");
            send.add_string(sendBase, name, value);
            if (session->monitorstatuschanges) session->AppWebsocketMessageSend(send, cfg);
        }
    }
} 

// ******************** AppAppClientSession ******************** //

AppClientSession::AppClientSession(class Client * client, IIoMux * const iomux):
AppWebsocket(client->webserverPlugin, client)
{
    this->iomux = iomux;
    this->client = client;
    this->log = client;
    this->closed = false;
    this->monitorstatuschanges = false;

    log->Log(LOG_APP, "AppClientSession(%p)::AppClientSession", this);
}

AppClientSession::~AppClientSession()
{
    log->Log(LOG_APP, "AppClientSession(%p)::~AppClientSession", this);
    client->clientUser->ClientSessionDelete();
}

bool AppClientSession::AppWebsocketConnectComplete(class json_io & msg, word info)
{
    char temp[1024];
    char * tmp = temp;
    char cfg[10000];
    word base;

    class json_io names(cfg);
    word namesBase = 0xffff;
    namesBase = names.add_object(0xffff, 0);
    names.add_string(namesBase, "mt", "LogFlagNames");
    word arrayBase = names.add_array(namesBase, "names");
    int i, l;
    for (i = 0; i < ICLIENT_LOG_FLAG_COUNT; i++) {
        word nameBase = names.add_object(arrayBase, 0);
        l = _sprintf(tmp, "%016llx", (unsigned long long)IClient::LogFlagTable[i]);
        names.add_string(nameBase, "flag", tmp);
        tmp += l + 1;
        names.add_string(nameBase, "name", TraceFlags[QWORD_TRAILING_ZEROES(IClient::LogFlagTable[i])]);
    }
    AppWebsocketMessageSend(names, cfg);
    if (client->clientUser->ClientEMailLogAvailable()) {
        tmp = temp;
        class json_io emailLog(cfg);
        base = 0xffff;
        base = emailLog.add_object(0xffff, 0);
        emailLog.add_string(base, "mt", "EMailLogAvailable");
        AppWebsocketMessageSend(emailLog, cfg);
    }

    tmp = temp;
    class json_io configOptions(cfg);
    base = 0xffff;
    base = configOptions.add_object(0xffff, 0);
    configOptions.add_string(base, "mt", "ConfigOptions");
    client->clientUser->ClientConfigOptionsToJson(configOptions, base, tmp);
    AppWebsocketMessageSend(configOptions, cfg);

    tmp = temp;
    class json_io send(cfg);
    word sendBase = 0xffff;
    sendBase = send.add_object(0xffff, 0);
    send.add_string(sendBase, "mt", "ClientConfig");
    client->clientUser->ClientConfigToJson(send, sendBase, tmp);
    AppWebsocketMessageSend(send, cfg);

    char autostartSettingsAppName[128] = { 0 };
    client->clientUser->ClientAutostartSettingsApp(autostartSettingsAppName, sizeof(autostartSettingsAppName));
    if (autostartSettingsAppName[0]) {
        tmp = temp;
        class json_io autostartApp(cfg);
        base = 0xffff;
        base = autostartApp.add_object(0xffff, 0);
        autostartApp.add_string(base, "mt", "AutostartSettingsApp");
        autostartApp.add_string(base, "name", autostartSettingsAppName);
        AppWebsocketMessageSend(autostartApp, cfg);
    }

    return true;
}

char * AppClientSession::AppWebsocketPassword() {
    return client->password;
};

void AppClientSession::AppWebsocketMessage(class json_io & msg, word base, const char * mt, const char * src)
{
    log->Log(LOG_APP, "AppClientSession(%p)::AppWebsocketMessage msg=%s app=%s src=%s sip=%s", this, mt, app, src, getSip());
    if (!(strcmp(mt, "ClientConfig"))) {
        client->clientUser->ClientJsonToConfig(msg, base, new class AppClientSrc(this, src));
    }
    else if (!(strcmp(mt, "FixCert"))) {
        client->clientUser->ClientFixCert();
    }
    else if (!(strcmp(mt, "StartAutostartSettingsApp"))) {
        const char *name = msg.get_string(base, "name");
        client->clientUser->ClientStartAutostartSettingsApp(name);
    }
    else if (!(strcmp(mt, "EMailLog"))) {
        client->clientUser->ClientEMailLog();
    }
    else if(!(strcmp(mt, "ClearLog"))) {
        client->clientUser->ClientClearLog();
    }
    else if (!(strcmp(mt, "MonitorStatusChanges"))) {
        monitorstatuschanges = true;
    }
    else {
        client->clientUser->ClientJSONMessage(msg, base, mt, this);
    }
    AppWebsocketMessageComplete();
    return;
}

void AppClientSession::AppWebsocketSendResult()
{
    log->Log(LOG_APP, "AppClientSession(%p)::AppWebsocketSendResult", this);
}

void AppClientSession::AppWebsocketClosed()
{
    log->Log(LOG_APP, "AppClientSession(%p)::AppWebsocketClosed", this);
    closed = true;
    delete this;
}

AppClientSrc::AppClientSrc(class AppClientSession * session, const char * src)
{
    this->session = session;
    this->src = _strdup(src);
}

AppClientSrc::~AppClientSrc()
{
    if (src) free(src);
}

/*---------------------------------------------------------------------------*/
/* TaskGetLog                                                                */
/*---------------------------------------------------------------------------*/

TaskGetLog::TaskGetLog(class Client * client, const char * filename)
{
    DBG_GET(("TaskGetLog::TaskGetLog"));
    this->client = client;
    this->filename = _strdup(filename);
    get = NULL;
    client->pendingWebRequests++;
    if (client->pendingGetLogs == 0) {
        client->clientUser->ClientGetLogActive(true);
    }
    client->pendingGetLogs++;
}

TaskGetLog::~TaskGetLog()
{
    if (filename) free(filename);
    client->pendingWebRequests--;
    client->pendingGetLogs--;
    if (client->pendingGetLogs == 0) {
        client->clientUser->ClientGetLogActive(false);
    }
    client->TryStop();
}

void TaskGetLog::WebserverGetRequestAcceptComplete(IWebserverGet * const webserverGet)
{
    DBG_GET(("TaskGetLog::WebserverGetRequestAcceptComplete"));
    get = webserverGet;
    get->SetTransferInfo(WSP_RESPONSE_TEXT, WS_RESPONSE_CHUNKED);
    if (filename) get->ForceDownloadResponse(filename);
    readLog = IDebugReadLog::Create(client->debugReadLogUser, client, 8000, false, false);
    readLog->Start(this);
    reading = true;
}

void TaskGetLog::WebserverGetSendResult(IWebserverGet * const webserverGet)
{
    DBG_GET(("TaskGetLog::WebserverGetSendResult"));
    if (readLog) {
        readLog->read(8000, false);
        reading = true;
    }
}

void TaskGetLog::WebserverGetCloseComplete(IWebserverGet * const webserverGet)
{
    delete webserverGet;
    get = 0;
    if (readLog) {
        if (!reading) {
            readLog->read(0, true);
            reading = true;
        }
    }
    else {
        delete this;
    }
}

void TaskGetLog::TaskComplete(class ITask * const task)
{
    DBG_GET(("TaskGetLog::TaskComplete"));
    delete readLog;
    readLog = 0;
    if (get) get->Close();
    else delete this;
}

void TaskGetLog::TaskFailed(class ITask * const task)
{
    DBG_GET(("TaskGetLog::TaskFailed"));
    delete readLog;
    readLog = 0;
    if (get) get->Close();
    else delete this;
}

void TaskGetLog::TaskProgress(class ITask * const task, dword progress)
{
    unsigned len = readLog->getLength();
    if (get && len) {
        byte * data = (byte *)alloca(len);
        readLog->getData(data, len);
        DBG_GET(("TaskGetLog::TaskProgress(%u)", len));
        get->Send((void *)data, len);
        reading = false;
    }
    else {
        readLog->read(0, true);
    }
}

/*---------------------------------------------------------------------------*/
