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

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

#include "common/interface/time.h"
#include "common/interface/socket.h"
#include "common/interface/dns.h"
#include "common/interface/guuid.h"
#include "common/interface/random.h"
#include "common/interface/task.h"
#include "common/interface/stask.h"
#include "common/interface/dtls.h"
#include "common/interface/media.h"
#include "common/interface/ringer.h"
#include "common/interface/certificates.h"
#include "common/interface/webserver_plugin.h"
#include "common/interface/json_api.h"
#include "common/interface/remote_services.h"
#include "common/ilib/json.h"
#include "common/ilib/http_query_args.h"
#include "common/lib/appservice.h"
#include "common/lib/appwebsocket.h"

#include "common/service/openssl_shared/ssl.h"
#include "common/service/dtls/dtls.h"

#include "appclient_ringer.h"

static RingerIdNameItem IdNameRingerType[] =
{
    { RingerDeviceType::RingerSpeaker,     "RingerSpeaker" },
    { RingerDeviceType::RingerBaseStation, "RingerBaseStation" },
    { 0,                                   NULL },
};

static RingerIdNameItem IdNameRingerMode[] =
{
    { RingerDeviceMode::RingerModeAllowVibrate,    "AllowVibrate" },
    { RingerDeviceMode::RingerModeAudioOnly,       "AudioOnly" },
    { RingerDeviceMode::RingerModeOnlyBaseStation, "OnlyBaseStation" },
    { RingerDeviceMode::RingerModeWithBaseStation, "WithBaseStation" },
    { 0,                                           NULL },
};


AppClientRinger::AppClientRinger(IIoMux * const iomux, IWebserverPluginProvider * webserverPluginProvider, ISocketProvider * localSocketProvider, AppInstanceArgs * args, class MediaConfiguration* mediaConfiguration)
{
    iomux->RegisterShutdownHandler(this);
    this->logFlags = args->logFlags;
    stopping = false;
    stoppingRinger = false;
    pwd = _strdup(args->appPassword);
    this->iomux = iomux;
    this->appName = _strdup(args->appName);
    this->appDomain = _strdup(args->appDomain);
    if (args->webserver) {
        this->webserverPlugin = webserverPluginProvider->CreateWebserverPlugin(iomux, localSocketProvider, this, args->webserver, args->webserverPath, this);
        this->webserverPlugin->WebsocketListen();
    }
    else this->webserverPlugin = NULL;
    if (mediaConfiguration->isRunningTS) ringer = NULL;
    else ringer = IRinger::Create(iomux, this, mediaConfiguration);
    ringerIo = IRingerIo::Create(iomux, ringer, this, mediaConfiguration);
    this->ringerIoInitialized = false;
    this->mediaConfiguration = mediaConfiguration;
    this->myAppsPlugingConnected = false;
    this->remoteRingerIo = NULL;
    this->Log(LOG_APP, "AppClientRinger(%p)::AppClientRinger started", this);
}

AppClientRinger::~AppClientRinger()
{
    this->Log(LOG_APP, "AppClientRinger(%p)::~AppClientRinger %p,%p", this, ringer, webserverPlugin);
    if(ringer) delete ringer;
    if(ringerIo) delete ringerIo;
    if(webserverPlugin) delete webserverPlugin;
    if(appName) free(appName);
    if(appDomain) free(appDomain);
    if(pwd) free(pwd);
}

void AppClientRinger::TryStop()
{
    if (!stopping) {
        this->Log(LOG_APP, "AppClientRinger(%p)::Stop() stopping", this);
        stopping = true;
        if (webserverPlugin) webserverPlugin->Close();
    }
    this->Log(LOG_APP, "AppClientRinger(%p)::Stop() sessions %d %d %p", this, !sessions.empty(), stoppingRinger, ringerIo);
    if (!sessions.empty()) return;
    if (!stoppingRinger && ringerIo) {
        stoppingRinger = true;
        if(!ringerIoInitialized) ringerIo->Initialize(this, this, this);
        ringerIo->Close(this);
    }
}

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

    TryStop();
    if (!webserverPlugin && !ringerIo) {
        this->iomux->ShutdownComplete(this);
    }
}

void AppClientRinger::ShutdownTimeout()
{
    debug->printf("AppClientRinger(%p)::ShutdownTimeout %d %d %d  %d %d", this, (webserverPlugin != NULL), (ringer != NULL), !sessions.empty(), stoppingRinger, (ringerIo != NULL));
}

void AppClientRinger::CloseRingerIoComplete()
{
    this->Log(LOG_APP, "AppClientRinger(%p)::CloseRingerIoComplete %d", this, stopping);

    if(ringerIo) delete ringerIo;
    ringerIo = NULL;
    Shutdown();
}

void AppClientRinger::RemoveSession(class AppClientRingerSession * session)
{
    if(!sessions.empty()) {
        AppClientRingerSession *s;
        for(s = sessions.front(); s; s = s->goNext()) {
            if(s == session) {
                this->Log(LOG_APP, "AppClientRinger(%p)::RemoveSession %p", this, session);
                s->remove();
                break;  
            }
        }
    }
    if(stopping) TryStop();
}

const char *AppClientRinger::IdName(RingerIdNameItem *map, int id)
{
    unsigned i = 0;
    while (map[i].name && (map[i].id != id)) {
        i++;
    }
    return map[i].name;
}

int AppClientRinger::NameId(RingerIdNameItem *map, const char *name)
{
    unsigned i = 0;
    if (!name) {
        while (map[i].name) {
            i++;
        }
    }
    else {
        while (map[i].name && (strcmp(map[i].name, name) != 0)) {
            i++;
        }
    }
    return map[i].id;
}

// ******************** UDeviceIo ******************** //

void AppClientRinger::MediaIoDeviceAdded(IDeviceIo * deviceIo, void * context, const char *deviceId, int deviceType, unsigned deviceCapabilities, const char *deviceName, bool selected)
{
    class AppClientRingerSrc * src = (class AppClientRingerSrc *)context;

    Log(LOG_APP, "AppClientRinger(%p)::MediaIoDeviceAdded id=%s type=%d capabilities=%x name=%s src=%p session=%p selected=%s", this, deviceId, deviceType, deviceCapabilities, deviceName, src, src ? src->session : NULL, selected ? "true" : "false");
    if (!sessions.empty()) {
        AppClientRingerSession* session;
        for (session = sessions.front(); session; session = session->goNext()) {
            if ((session->deviceIo == deviceIo) && (!src || (session == src->session))) {
                char temp[128];
                char * tmp = temp;
                char cfg[2000];
                class json_io send(cfg);

                word sendBase = 0xffff;
                sendBase = send.add_object(0xffff, 0);
                send.add_string(sendBase, "mt", "AddRingerDevice");
                if (src && src->src) {
                    send.add_string(sendBase, "src", src->src);
                }
                send.add_string(sendBase, "device", deviceId);
                send.add_string(sendBase, "type", AppClientRinger::IdName(IdNameRingerType, deviceType));
                send.add_unsigned(sendBase, "capabilities", deviceCapabilities, tmp);
                send.add_string(sendBase, "name", deviceName);
                if (selected) send.add_bool(sendBase, "selected", selected);
                session->AppWebsocketMessageSend(send, cfg);
            }
        }
    }
}

void AppClientRinger::MediaIoDeviceFailed(IDeviceIo* deviceIo, void* context, const char* deviceId) 
{

}

void AppClientRinger::MediaIoDeviceRemoved(IDeviceIo * deviceIo, void * context, const char *deviceId) 
{
    class AppClientRingerSrc * src = (class AppClientRingerSrc *)context;
    AppClientRingerSession * session;

    Log(LOG_APP, "AppClientRingerSession(%p)::MediaIoDeviceRemoved id=%s", this, deviceId);
    if (!sessions.empty()) {
        for (session = sessions.front(); session; session = session->goNext()) {
            if ((session->ringerIo == deviceIo) && (!src || (session == src->session))) {
                char cfg[2000];
                class json_io send(cfg);

                word sendBase = 0xffff;
                sendBase = send.add_object(0xffff, 0);
                send.add_string(sendBase, "mt", "RemoveRingerDevice");
                if (src && src->src) {
                    send.add_string(sendBase, "src", src->src);
                }
                send.add_string(sendBase, "device", deviceId);
                session->AppWebsocketMessageSend(send, cfg);
            }
        }
    }
}

void AppClientRinger::MediaIoQueryDevicesResult(IDeviceIo * deviceIo, void * context)
{
    class AppClientRingerSrc * src = (class AppClientRingerSrc *)context;
    AppClientRingerSession * session;

    Log(LOG_APP, "AppClientRinger(%p)::MediaIoQueryDevicesResult src=%p", this, context);
    if (!sessions.empty()) {
        for (session = sessions.front(); session; session = session->goNext()) {
            if (session->ringerIo == deviceIo) {
                char cfg[2000];
                class json_io send(cfg);

                word sendBase = 0xffff;
                sendBase = send.add_object(0xffff, 0);
                send.add_string(sendBase, "mt", "QueryDevicesResult");
                if (src->src) {
                    send.add_string(sendBase, "src", src->src);
                }
                session->AppWebsocketMessageSend(send, cfg);
            }
        }
    }
}

// ******************** URingerIo *************************** //

void AppClientRinger::RingerIoRingtoneAdded(IRingerIo * ringerIo, void * context, const char *ident, const char *title, bool selected)
{
    class AppClientRingerSrc * src = (class AppClientRingerSrc *)context;

    Log(LOG_APP, "AppClientRinger(%p)::RingerIoRingtoneAdded ident=%s title=%s src=%p session=%p selected=%s", this, ident, title, src, src ? src->session : NULL, selected ? "true" : "false");
    if (!sessions.empty()) {
        AppClientRingerSession* session;
        for (session = sessions.front(); session; session = session->goNext()) {
            if ((session->ringerIo == ringerIo) && (!src || (session == src->session))) {
                char cfg[2000];
                class json_io send(cfg);

                word sendBase = 0xffff;
                sendBase = send.add_object(0xffff, 0);
                send.add_string(sendBase, "mt", "AddRingtone");
                if (src && src->src) {
                    send.add_string(sendBase, "src", src->src);
                }
                send.add_string(sendBase, "ident", ident);
                send.add_string(sendBase, "title", title);
                if (selected) send.add_bool(sendBase, "selected", selected);
                session->AppWebsocketMessageSend(send, cfg);
            }
        }
    }
}

void AppClientRinger::RingerIoRingtoneRemoved(IRingerIo * ringerIo, void * context, const char *ident, const char *title)
{
    class AppClientRingerSrc * src = (class AppClientRingerSrc *)context;
    AppClientRingerSession * session;

    Log(LOG_APP, "AppClientRingerSession(%p)::MediaIoDeviceRemoved ident=%s title=%s", this, ident, title);
    if (!sessions.empty()) {
        for (session = sessions.front(); session; session = session->goNext()) {
            if ((session->ringerIo == ringerIo) && (!src || (session == src->session))) {
                char cfg[2000];
                class json_io send(cfg);

                word sendBase = 0xffff;
                sendBase = send.add_object(0xffff, 0);
                send.add_string(sendBase, "mt", "RemoveRingtone");
                send.add_string(sendBase, "ident", ident);
                send.add_string(sendBase, "title", title);
                session->AppWebsocketMessageSend(send, cfg);
            }
        }
    }
}

void AppClientRinger::RingerIoQueryRingtonesResult(IRingerIo * ringerIo, void * context)
{
    class AppClientRingerSrc * src = (class AppClientRingerSrc *)context;
    AppClientRingerSession * session;

    Log(LOG_APP, "AppClientRinger(%p)::RingerIoQueryRingtonesResult src=%p", this, context);
    if (!sessions.empty()) {
        for (session = sessions.front(); session; session = session->goNext()) {
            if (session->ringerIo == ringerIo) {
                char cfg[2000];
                class json_io send(cfg);

                word sendBase = 0xffff;
                sendBase = send.add_object(0xffff, 0);
                send.add_string(sendBase, "mt", "QueryRingtonesResult");
                if (src->src) {
                    send.add_string(sendBase, "src", src->src);
                }
                session->AppWebsocketMessageSend(send, cfg);
            }
        }
    }
}

void AppClientRinger::RingerIoNotificationToneAdded(IRingerIo * ringerIo, void * context, const char *ident, const char *title, bool selected)
{

}

void AppClientRinger::RingerIoNotificationToneRemoved(IRingerIo * ringerIo, void * context, const char *ident, const char *title)
{

}

void AppClientRinger::RingerIoQueryNotificationTonesResult(IRingerIo * ringerIo, void * context)
{

}

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

void AppClientRinger::WebserverPluginHttpListenResult(IWebserverPlugin * plugin, ws_request_type_t requestType, char * resourceName, const char * registeredPathForRequest, ulong64 dataSize)
{
    this->Log(LOG_APP, "AppClientRinger(%p)::WebserverPluginHttpListenResult (%s) (%s)", this, (requestType == WS_REQUEST_GET ? "GET" : (requestType == WS_REQUEST_POST ? "POST" : "Unknown Request")), (resourceName ? resourceName : "empty!"));

    plugin->Cancel(WSP_CANCEL_NOT_FOUND);
}

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

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

void AppClientRinger::QueryDevices(bool cleanDevices)
{
    if (ringerIo) {
        if (cleanDevices) ringerIo->CleanRemoteDevices();
        ringerIo->QueryDevices(NULL);
    }
}

// ******************** AppClientSession ******************** //

AppClientRingerSession::AppClientRingerSession(class AppClientRinger * appclient, IIoMux * const iomux, class IRingerIo * ringerIo, class IDeviceIo * deviceIo):
AppWebsocket(appclient->webserverPlugin, appclient)
{
    this->iomux = iomux;
    this->appclient = appclient;
    this->ringerIo = ringerIo;
    this->deviceIo = deviceIo;
    log = appclient;
    closed = false;
    ringerDevicesSessionContext = NULL;
    ringerRingtonesSessionContext = NULL;
    ringerNotificationsTonesSessionContext = NULL;

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

AppClientRingerSession::~AppClientRingerSession()
{
    log->Log(LOG_APP, "AppClientRingerSession(%p)::~AppClientRingerSession", this);

    if (ringerDevicesSessionContext) delete ringerDevicesSessionContext;
    if (ringerRingtonesSessionContext) delete ringerRingtonesSessionContext;
    if (ringerNotificationsTonesSessionContext) delete ringerNotificationsTonesSessionContext;

    appclient->RemoveSession(this);
}

bool AppClientRingerSession::AppWebsocketConnectComplete(class json_io & msg, word info)
{
    return true;
}

void AppClientRingerSession::AppWebsocketMessage(class json_io & msg, word base, const char * mt, const char * src)
{
    log->Log(LOG_APP, "AppClientRingerSession(%p)::AppWebsocketMessage msg=%s app=%s src=%s sip=%s", this, mt, app, src, getSip());

    if(!strcmp(mt, "QueryDevices")) {
        if (ringerDevicesSessionContext && ((src && !ringerDevicesSessionContext->src) || (!src && ringerDevicesSessionContext->src) || (src && ringerDevicesSessionContext->src && (strcmp(src, ringerDevicesSessionContext->src) != 0)))) {
            delete ringerDevicesSessionContext;
            ringerDevicesSessionContext = NULL;
        }
        if (!ringerDevicesSessionContext) {
            ringerDevicesSessionContext = new class AppClientRingerSrc(this, src, msg.get_string(base, "app"));
        }
        ringerIo->QueryDevices(ringerDevicesSessionContext);
    }
    else if(!strcmp(mt, "QueryRingtones")) {
        if (ringerRingtonesSessionContext && ((src && !ringerRingtonesSessionContext->src) || (!src && ringerRingtonesSessionContext->src) || (src && ringerRingtonesSessionContext->src && (strcmp(src, ringerRingtonesSessionContext->src) != 0)))) {
            delete ringerRingtonesSessionContext;
            ringerRingtonesSessionContext = NULL;
        }
        if (!ringerRingtonesSessionContext) {
            ringerRingtonesSessionContext = new class AppClientRingerSrc(this, src, msg.get_string(base, "app"));
        }
        ringerIo->QueryRingtones(ringerRingtonesSessionContext);
    }
    else if(!strcmp(mt, "StartRinging")) {
        if (appclient->mediaConfiguration->isRunningTS) {
            if (appclient->myAppsPlugingConnected && appclient->GetRemoteRingerIo()) {
                appclient->GetRemoteRingerIo()->RingtoneStart(msg.get_string(base, "device"),
                    (enum RingerDeviceMode)(AppClientRinger::NameId(IdNameRingerMode, msg.get_string(base, "mode"))),
                    msg.get_string(base, "ringtone"));
            }
        }
        else {
            ringerIo->RingtoneStart(msg.get_string(base, "device"),
                (enum RingerDeviceMode)(AppClientRinger::NameId(IdNameRingerMode, msg.get_string(base, "mode"))),
                msg.get_string(base, "ringtone"));
        }
    }
    else if(!strcmp(mt, "StopRinging")) {
        if (appclient->mediaConfiguration->isRunningTS) {
            if (appclient->myAppsPlugingConnected && appclient->GetRemoteRingerIo()) appclient->GetRemoteRingerIo()->RingtoneStop(msg.get_string(base, "device"));
        }
        else ringerIo->RingtoneStop(msg.get_string(base, "device"));
    }
    else if(!strcmp(mt, "QueryNotificationTones")) {
        if (ringerNotificationsTonesSessionContext && ((src && !ringerNotificationsTonesSessionContext->src) || (!src && ringerNotificationsTonesSessionContext->src) || (src && ringerNotificationsTonesSessionContext->src && (strcmp(src, ringerNotificationsTonesSessionContext->src) != 0)))) {
            delete ringerNotificationsTonesSessionContext;
            ringerNotificationsTonesSessionContext = NULL;
        }
        if (!ringerNotificationsTonesSessionContext) {
            ringerRingtonesSessionContext = new class AppClientRingerSrc(this, src, msg.get_string(base, "app"));
        }
        ringerIo->QueryNotificationTones(ringerNotificationsTonesSessionContext);
    }
    else if(!strcmp(mt, "PlayNotificationTone")) {
        if (appclient->mediaConfiguration->isRunningTS) {
            if (appclient->myAppsPlugingConnected && appclient->GetRemoteRingerIo()) {
                appclient->GetRemoteRingerIo()->NotificationTonePlay(msg.get_string(base, "device"),
                    (enum RingerDeviceMode)(AppClientRinger::NameId(IdNameRingerMode, msg.get_string(base, "mode"))),
                    msg.get_string(base, "tone"));
            }
        }
        else {
            ringerIo->NotificationTonePlay(msg.get_string(base, "device"),
                (enum RingerDeviceMode)(AppClientRinger::NameId(IdNameRingerMode, msg.get_string(base, "mode"))),
                msg.get_string(base, "tone"));
        }
    }
    else if (!strcmp(mt, "SelectDevice")) {
        ringerIo->SelectDevice(msg.get_string(base, "app"), msg.get_string(base, "device"));
    }
    else if (!strcmp(mt, "SelectRingtone")) {
        const char * ident = msg.get_string(base, "ident");
        ringerIo->SelectRingtone(msg.get_string(base, "app"), ident && ident[0] ? ident : "No Ringtone");
    }
    AppWebsocketMessageComplete();
    return;
}

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

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


AppClientRingerSrc::AppClientRingerSrc(class AppClientRingerSession * session, const char * src, const char * app)
{
    this->session = session;
    this->src = _strdup(src);
    this->app = _strdup(app);
}

AppClientRingerSrc::~AppClientRingerSrc()
{
    if (this->src) free(this->src);
    if (this->app) free(this->app);
}
