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

#include "platform/platform.h"
#include "common/os/iomux.h"
#include "common/interface/socket.h"
#include "common/interface/websocket_client.h"
#include "common/interface/webserver_plugin.h"
#include "common/interface/notifications-if.h"
#include "common/interface/system.h"
#include "common/interface/launcherapi.h"
#include "common/interface/guuid.h"
#include "common/ilib/json.h"
#include "common/lib/appservice.h"
#include "common/lib/apiwebsocket_client.h"
#include "launcherapihandler.h"

/*---------------------------------------------------------------------------*/
/* class LauncherApiHandlerService                                                 */
/*---------------------------------------------------------------------------*/

LauncherApiHandlerService::LauncherApiHandlerService(class IIoMux * const iomux, IWebserverPluginProvider * const webserverPluginProvider, class ISocketProvider * localSocketProvider, class ISocketProvider * tcp, class ISocketProvider * tls, class IDns * dns, AppServiceArgs * args, const char * url, const char * pwd, const char * dn, const char * localSocketPath) :
    AppService(iomux, localSocketProvider, args)
{
    this->iomux = iomux;
    this->webserverPluginProvider = webserverPluginProvider;
    this->localSocketProvider = localSocketProvider;
    this->tcp = tcp;
    this->tls = tls;
    this->dns = dns;
    this->url = _strdup(url);
    this->pwd = _strdup(pwd);
    this->dn = _strdup(dn);
    this->localSocketPath = _strdup(localSocketPath);
    this->launcherApi = 0;
}

LauncherApiHandlerService::~LauncherApiHandlerService()
{
    if (url) free(url);
    if (pwd) free(pwd);
    if (dn) free(dn);
    if (localSocketPath) free(localSocketPath);
}

class AppInstance * LauncherApiHandlerService::CreateInstance(AppInstanceArgs * args)
{
    return new LauncherApiHandler(this, args);
}

/*---------------------------------------------------------------------------*/
/* class LauncherApiHandler                                                  */
/*---------------------------------------------------------------------------*/

LauncherApiHandler::LauncherApiHandler(class LauncherApiHandlerService * service, AppInstanceArgs * args) :
    AppInstance(service, args),
    ApiWebsocketClient(service->iomux, service->localSocketProvider, service->tcp, service->tls, service->dns, service, service->url, service->pwd, service->dn, service->localSocketPath)
{
    this->stopping = false;
    this->closed = false;
    this->service = service;
    this->webserverPlugin = 0;
    if (service->webserverPluginProvider && service->localSocketProvider && args->webserverPath) {
        this->Log("Websocket: listen to %s", args->webserverPath);
        this->webserverPlugin = service->webserverPluginProvider->CreateWebserverPlugin(service->iomux, service->localSocketProvider, this, args->webserver, args->webserverPath, service);
        this->webserverPlugin->WebsocketListen();
    }
    this->phone = new PhoneApiConsumer(this);
    this->chat = new ChatApiConsumer(this);
    this->events = new EventsApiConsumer(this);
    this->clientApi = new ClientApiConsumer(this);
    this->cacheableApi = new CacheableApiConsumer(this);
    this->launcherUser = new LauncherUserApiConsumer(this);
    this->launcher = new LauncherApiProvider(this);
    service->launcherApi = this;
}

LauncherApiHandler::~LauncherApiHandler()
{
    if (webserverPlugin) delete webserverPlugin;
}

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

int LauncherApiHandler::NameId(LauncherApiIdNameItem *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;
}

static NotificationIdNameItem IdNameLocalNotificationType[] =
{
    { LOCAL_NOTIFICATION_TYPE_PHONE_CALL,      "PhoneCall" },
    { LOCAL_NOTIFICATION_TYPE_HOLD_PHONE_CALL, "HoldPhoneCall" },
    { LOCAL_NOTIFICATION_TYPE_END_PHONE_CALL,  "EndPhoneCall" },
    { LOCAL_NOTIFICATION_TYPE_CHAT_MESSAGE,    "ChatMessage" },
    { LOCAL_NOTIFICATION_TYPE_DECISION,        "decision" },
    { LOCAL_NOTIFICATION_TYPE_REPLY,           "reply" },
    { LOCAL_NOTIFICATION_TYPE_APP_NOTIFY,      "AppNotify" },
    { LOCAL_NOTIFICATION_TYPE_DEFAULT,         NULL },
};

int LauncherApiHandler::NotificationNameId(NotificationIdNameItem* 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;
}

void LauncherApiHandler::Stop()
{
    TryStop();
}

void LauncherApiHandler::AppInstanceShutdownTimeout()
{
    debug->printf("LauncherApiHandler::AppInstanceShutdownTimeout %d %d %d",
        (webserverPlugin != NULL), !websocketConnections.empty(), !closed);
}

void LauncherApiHandler::TryStop()
{
    if (!stopping) {
        stopping = true;
        for (LauncherApiUsers::iterator i = launcherApiUsers.begin(); i != launcherApiUsers.end(); i++) {
            (*i)->LauncherApiClosing(this);
        }
        service->launcherApi = NULL;
        phone = 0;
        chat = 0;
        clientApi = 0;
        cacheableApi = 0;
        launcherUser = 0;
        launcher = 0;
        ApiWebsocketClientClose();
        for (WebsocketConnections::iterator i = websocketConnections.begin(); i != websocketConnections.end(); i++) {
            (*i)->Close();
        }
        if (webserverPlugin) {
            webserverPlugin->Close();
        }
    }
    if (!webserverPlugin && websocketConnections.empty() && closed) {
        appService->AppStopped(this);
    }
}

void LauncherApiHandler::ApiWebsocketClientCloseComplete()
{
    closed = true;
    phone = 0;
    chat = 0;
    clientApi = 0;
    cacheableApi = 0;
    launcherUser = 0;
    if (stopping) TryStop();
}

void LauncherApiHandler::WebserverPluginWebsocketListenResult(IWebserverPlugin * plugin, const char * path, const char * registeredPathForRequest, const char * host)
{
    if (stopping) {
        plugin->Cancel(WSP_CANCEL_UNAVAILABLE);
    }
    else {
        websocketConnections.push_back(new WebsocketConnection(this, plugin));
    }
}

void LauncherApiHandler::WebserverPluginClose(IWebserverPlugin * plugin, wsp_close_reason_t reason, bool lastUser)
{
    if (lastUser) {
        delete this->webserverPlugin;
        this->webserverPlugin = 0;
        TryStop();
    }
}

void LauncherApiHandler::WebsocketConnectionClosed(class WebsocketConnection * connection)
{
    websocketConnections.remove(connection);
    delete connection;
    if (stopping) TryStop();
}

void LauncherApiHandler::Message(char * buffer)
{
    if (!stopping && buffer) {
        json_io json(buffer);
        if (json.decode()) {
            word base = json.get_object(JSON_ID_ROOT, 0);
            const char * mt = json.get_string(base, "mt");
            if (mt) {
                if (!strcmp(mt, "SetPushToken")) {
                    SetPushToken(json.get_string(base, "pushToken"));
                }
            }
        }
    }
}

void LauncherApiHandler::AddLauncherApiUser(class ULauncherApi * launcherApiUser)
{
    launcherApiUsers.push_back(launcherApiUser);
}

void LauncherApiHandler::RemoveLauncherApiUser(class ULauncherApi * launcherApiUser)
{
    launcherApiUsers.remove(launcherApiUser);
}

void LauncherApiHandler::StartCall(const char * sip, const char * num, const char * name, StartCallVideoMode videoMode)
{
    if (phone) phone->StartCall(sip, num, true, name, videoMode);
}

void LauncherApiHandler::PrepareCall(const char * text)
{
    if (phone) phone->PrepareCall(text);
}

void LauncherApiHandler::StartChat(const char * sip)
{
    if (chat) chat->StartChat(sip);
}

void LauncherApiHandler::SendEvent(dword code, int severtiy, ulong64 time, const char * text, const char * details, const char * src)
{
    if (events) events->SendEvent(code, severtiy, time, text, details, src);
}

void LauncherApiHandler::SetPushToken(const char * pushToken)
{
    if (launcher) launcher->SetPushToken(pushToken);
}

void LauncherApiHandler::SetClose(bool closeSupported)
{
    if (launcher) launcher->SetClose(closeSupported);
}

void LauncherApiHandler::SetDocking(bool dockingSupported, bool docked)
{
    if (launcher) launcher->SetDocking(dockingSupported, docked);
}

void LauncherApiHandler::SetAutoOffline(bool offlineSupported, bool offline)
{
    if (launcher) launcher->SetAutoOffline(offlineSupported, offline);
}

void LauncherApiHandler::SetAutostart(bool autostartSupported, bool autostart)
{
    if(launcher) launcher->SetAutostart(autostartSupported, autostart);
}

void LauncherApiHandler::SetHotkeyAction(bool hotkeySupported, int action, const char * dialNumber)
{
    if (action == 0) {
        if (phone) phone->PrepareCall(dialNumber);
    }
    else if (action == 1) {
        if (phone) phone->ConnectCall();
    }
    else if (action == 2) {
        if (phone) phone->DisconnectCall();
    }
}

void LauncherApiHandler::SetGsmState(bool gsmSupported, enum GsmCallState gsmCallState)
{
    if (launcher) launcher->SetGsmState(gsmSupported, gsmCallState);
}

void LauncherApiHandler::SetDetach(bool detach)
{
    if (launcher) launcher->SetDetach(detach);
}

void LauncherApiHandler::SetConfigApp(const char * url)
{
    if (launcher) launcher->SetConfigApp(url);
}

void LauncherApiHandler::SetStatusHeader(const char* url)
{
    if (launcher) launcher->SetStatusHeader(url);
}

void LauncherApiHandler::SetRingerServiceUrl(const char * url)
{
    if(launcher) launcher->SetRingerServiceUrl(url);
}

void LauncherApiHandler::SetAudioServiceUrl(const char * url)
{
    if(launcher) launcher->SetAudioServiceUrl(url);
}

void LauncherApiHandler::SetVideoServiceUrl(const char * url)
{
    if(launcher) launcher->SetVideoServiceUrl(url);
}

void LauncherApiHandler::SetAppSharingServiceUrl(const char * url)
{
    if(launcher) launcher->SetAppSharingServiceUrl(url);
}

void LauncherApiHandler::SetSignalingServiceUrl(const char* url)
{
    if (launcher) launcher->SetSignalingServiceUrl(url);
}

void LauncherApiHandler::SetRemoteControlServiceUrl(const char* url)
{
    if (launcher) launcher->SetRemoteControlServiceUrl(url);
}

void LauncherApiHandler::SetServicePassword(const char * url)
{
    if(launcher) launcher->SetServicePassword(url);
}

void LauncherApiHandler::SetExpectTouches(bool expectTouchesSupported, bool expectTouches)
{
    if (launcher) launcher->SetExpectTouches(expectTouchesSupported, expectTouches);
}

void LauncherApiHandler::SetShownLocked(bool shownLockedSupported, bool shownLocked)
{
    if (launcher) launcher->SetShownLocked(shownLockedSupported, shownLocked);
}

void LauncherApiHandler::SetProximity(bool proximitySupported, bool proximity)
{
    if (launcher) launcher->SetProximity(proximitySupported, proximity);
}

void LauncherApiHandler::SetNoPushAutostart(bool noPushAutostartSupported)
{
    if (launcher) launcher->SetNoPushAutostart(noPushAutostartSupported);
}

void LauncherApiHandler::SetRunningClientServer(bool runningClientServer)
{
    if (launcher) launcher->SetRunningClientServer(runningClientServer);
}

void LauncherApiHandler::SetBusylightHttpPort(int port)
{
    if (launcher) launcher->SetBusylightHttpPort(port);
}

void LauncherApiHandler::LauncherApiLocalNotificationClicked(const char * ref, LocalNotificationAction action, const char * replyText)
{
    if (launcherUser) launcherUser->LocalNotificationClicked(ref, action, replyText);
}

void LauncherApiHandler::WakePhone()
{
    if (clientApi) clientApi->WakePhone();
}

void LauncherApiHandler::OpenPermalink(const char * link)
{
    if (clientApi) clientApi->OpenPermalink(link);
}

/*---------------------------------------------------------------------------*/
/* class WebsocketConnection                                                 */
/*---------------------------------------------------------------------------*/

WebsocketConnection::WebsocketConnection(LauncherApiHandler * instance, IWebserverPlugin * plugin)
{
    this->instance = instance;
    this->websocket = 0;
    this->closing = false;
    plugin->WebsocketAccept(this);
}

WebsocketConnection::~WebsocketConnection()
{
}

void WebsocketConnection::Close()
{
    if (!this->closing) {
        this->closing = true;
        if (this->websocket) this->websocket->Close();
    }
}

void WebsocketConnection::WebsocketAcceptComplete(class IWebsocket * websocket)
{
    this->websocket = websocket;
    if (this->closing) {
        this->websocket->Close();
    }
    else {
        this->websocket->Recv();
    }
}

void WebsocketConnection::WebsocketSendResult(class IWebsocket * websocket)
{

}

void WebsocketConnection::WebsocketRecvResult(class IWebsocket * websocket, void * buffer, size_t len, bool text, bool isFragmented)
{
    ASSERT(buffer, "WebsocketConnection::WebsocketRecvResult");
    if (text && !isFragmented) {
        instance->Message((char *)buffer);
    }
    free(buffer);
    this->websocket->Recv();
}

void * WebsocketConnection::WebsocketRecvBuffer(size_t len)
{
    char * recvBuffer = (char *)malloc(len + 1);
    recvBuffer[len] = 0;
    return recvBuffer;
}

void WebsocketConnection::WebsocketRecvCanceled(class IWebsocket * websocket, void * buffer)
{
    free(buffer);
}

void WebsocketConnection::WebsocketCloseComplete(class IWebsocket * websocket, ws_close_reason_t reason)
{
    delete websocket;
    this->websocket = 0;
    instance->WebsocketConnectionClosed(this);
}

/*---------------------------------------------------------------------------*/
/* class PhoneApiConsumer                                                    */
/*---------------------------------------------------------------------------*/

PhoneApiConsumer::PhoneApiConsumer(LauncherApiHandler * client) : ApiConsumer(client, "com.innovaphone.phone")
{
}

PhoneApiConsumer::~PhoneApiConsumer()
{
}

void PhoneApiConsumer::StartCall(const char * sip, const char * num, bool adjust, const char *dn, StartCallVideoMode videoMode)
{
    char buffer[1024];
    json_io json(0);
    word base = json.add_object(JSON_ID_ROOT, 0);
    json.add_string(base, "mt", "StartCall");
    json.add_string(base, "num", num);
    json.add_string(base, "sip", sip);
    if (adjust) json.add_bool(base, "adjust", adjust);
    json.add_string(base, "dn", dn);
    if (videoMode != START_CALL_VIDEO_MODE_DEFAULT) json.add_bool(base, "video", (videoMode == START_CALL_VIDEO_MODE_ON));
    ApiConsumerSend(0, 0, json, base, buffer);
}

void PhoneApiConsumer::PrepareCall(const char * text, bool adjust)
{
    char buffer[1024];
    json_io json(0);
    word base = json.add_object(JSON_ID_ROOT, 0);
    json.add_string(base, "mt", "PrepareCall");
    json.add_string(base, "text", text);
    if (adjust) json.add_bool(base, "adjust", adjust);
    ApiConsumerSend(0, 0, json, base, buffer);
}

void PhoneApiConsumer::ConnectCall()
{
    char buffer[1024];
    json_io json(0);
    word base = json.add_object(JSON_ID_ROOT, 0);
    json.add_string(base, "mt", "ConnectCall");
    ApiConsumerSend(0, 0, json, base, buffer);
}

void PhoneApiConsumer::DisconnectCall()
{
    char buffer[1024];
    json_io json(0);
    word base = json.add_object(JSON_ID_ROOT, 0);
    json.add_string(base, "mt", "DisconnectCall");
    ApiConsumerSend(0, 0, json, base, buffer);
}

/*---------------------------------------------------------------------------*/
/* class ChatApiConsumer                                                     */
/*---------------------------------------------------------------------------*/

ChatApiConsumer::ChatApiConsumer(LauncherApiHandler * client) : ApiConsumer(client, "com.innovaphone.chat")
{
}

ChatApiConsumer::~ChatApiConsumer()
{
}

void ChatApiConsumer::StartChat(const char * sip)
{
    char buffer[1024];
    json_io json(0);
    word base = json.add_object(JSON_ID_ROOT, 0);
    json.add_string(base, "mt", "StartChat");
    json.add_string(base, "sip", sip);
    ApiConsumerSend(0, 0, json, base, buffer);
}

/*---------------------------------------------------------------------------*/
/* class EventsApiConsumer                                                     */
/*---------------------------------------------------------------------------*/

EventsApiConsumer::EventsApiConsumer(LauncherApiHandler * client) : ApiConsumer(client, "com.innovaphone.events")
{
}

EventsApiConsumer::~EventsApiConsumer()
{
}

void EventsApiConsumer::SendEvent(dword code, int severtiy, ulong64 time, const char * text, const char * details, const char * src)
{
    //<dd>Example: <code>{ mt: "AddAlarm", type : "alarm", severity : 1, active : true, code : "0x00010002", date : "1580559884", host : "IP0011-42-04-a7", serial : "009033000000", source : "RELAY/RECH", text : "Registration down", xsl : "fault_relay.xsl", details : "&lt;relay prot=\"SH323\"/&gt;" }< / code>< / dd>

    char buffer[8192];
    json_io json(buffer);
    char b[128];
    char * tmp = b;
    word base = json.add_object(JSON_ID_ROOT, 0);
    json.add_string(base, "mt", "AddEvent");
    json.add_string(base, "type", "error");
    json.add_int(base, "severity", severtiy, tmp);
    char tmpStr[128];
    int l = _sprintf(tmpStr, "0x%x", code);
    json.add_string(base, "code", tmpStr);
    _sprintf(&tmpStr[l+1], "%llu", (unsigned long long)time);
    json.add_string(base, "date", &tmpStr[l+1]);
    json.add_string(base, "host", src);
    json.add_string(base, "source", "myApps");
    json.add_string(base, "text", text);
    json.add_string(base, "xsl", "fault_apps.xsl");
    json.add_string(base, "details", details);

    ApiConsumerSend(0, 0, json, base, buffer);
}

/*---------------------------------------------------------------------------*/
/* class ClientApiConsumer                                                   */
/*---------------------------------------------------------------------------*/

ClientApiConsumer::ClientApiConsumer(LauncherApiHandler * client) : ApiConsumer(client, "com.innovaphone.client")
{
    this->client = client;
}

ClientApiConsumer::~ClientApiConsumer()
{
}

void ClientApiConsumer::ApiConsumerUpdate(class json_io & model, word base)
{
    word n = base;
    word p = model.get_object(base, n);
    word m = model.get_object(p, "model");
    const char * scheme = model.get_string(m, "scheme");
    if (scheme) {
        if(strcmp(scheme, "light") == 0) {
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiStatusBarAppearance(false, true, 0xffffff);
            }
        }
        else {
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiStatusBarAppearance(false, false, 0x161616);
            }
        }
    }
    n = model.get_object(base, "@client");
    bool loggedIn = false;
    const char * appClientUrl = NULL;
    if (n != JSON_ID_NONE) {
        appClientUrl = model.get_string(n, "url");
        n = model.get_object(n, "model");
        if (n != JSON_ID_NONE) {
            word userBase = model.get_object(n, "user");
            const char* sip = NULL;
            if (userBase != JSON_ID_NONE) {
                sip = model.get_string(userBase, "sip");
                if (sip && *sip) loggedIn = true;
            }
            if (loggedIn) {
                const char* lang = model.get_string(n, "lang");
                if (lang) {
                    for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                        (*i)->LauncherApiSetLangId(lang);
                    }
                }
                word baseSettings = model.get_object(n, "adminSettings");
                if (baseSettings) {
                    for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                        (*i)->LauncherApiSetAdminSettings(model, baseSettings);
                    }
                }
                for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                    (*i)->LauncherApiSetUser(sip);
                }
            }
            else {
                for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                    (*i)->LauncherApiSetUser(NULL);
                }
            }
            word connInfo = model.get_object(n, "connInfo");
            if (connInfo != JSON_ID_NONE) {
                bool up = model.get_bool(connInfo, "up");
                bool standby = model.get_bool(connInfo, "standby");
                const char * cur = model.get_string(connInfo, "cur");
                const char * pri = model.get_string(connInfo, "pri");
                const char * sec = model.get_string(connInfo, "sec");
                for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                    (*i)->LauncherApiSetConnInfo(up, standby, cur, pri, sec);
                }
            }
        }
    }
    for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
        (*i)->LauncherApiAppClientUrl(appClientUrl);
    }
}

void ClientApiConsumer::WakePhone()
{
    char buffer[8192];
    json_io json(buffer);
    word base = json.add_object(JSON_ID_ROOT, 0);
    json.add_string(base, "mt", "Wake");
    json.add_string(base, "type", "phone");
    json.add_bool(base, "outgoing", false);
    ApiConsumerSend(0, 0, json, base, buffer);
}

void ClientApiConsumer::OpenPermalink(const char * link)
{
    char buffer[8192];
    json_io json(buffer);
    word base = json.add_object(JSON_ID_ROOT, 0);
    json.add_string(base, "mt", "ShowApp");
    json.add_string(base, "app", link);
    ApiConsumerSend(0, 0, json, base, buffer);
}


/*---------------------------------------------------------------------------*/
/* class CacheableApiConsumer                                                */
/*---------------------------------------------------------------------------*/

CacheableApiConsumer::CacheableApiConsumer(LauncherApiHandler * client) : ApiConsumer(client, "com.innovaphone.cacheable")
{
    this->client = client;
}

CacheableApiConsumer::~CacheableApiConsumer()
{
}

void CacheableApiConsumer::ApiConsumerUpdate(class json_io & model, word base)
{
    word n = base;
    word p = model.get_object(base, n);
    const char * url = model.get_string(p, "url");
    word info = model.get_object(p, "info");
    const char * filename = model.get_string(info, "filename");
    for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
        (*i)->LauncherApiCacheable(url, filename);
    }
}

/*---------------------------------------------------------------------------*/
/* class LauncherUserApiConsumer                                             */
/*---------------------------------------------------------------------------*/

LauncherUserApiConsumer::LauncherUserApiConsumer(LauncherApiHandler * client) : ApiConsumer(client, "com.innovaphone.notificationhandler")
{
}

LauncherUserApiConsumer::~LauncherUserApiConsumer()
{
}

bool LauncherUserApiConsumer::ExtractRef(char * p, const char ** consumer, const char ** src)
{
    *consumer = (p[0] == ' ') ? p + 1 : 0;
    p = strchr(p, '\n');
    // Signaling App notifications may arrive here due to a Windows issue
    // We know this ref does not belong here because it does not contain a '\n'
    if (!p) return false;
    *(p++) = '\0';
    *src = (p[0] == ' ') ? p + 1 : 0;
    return true;
}

void LauncherUserApiConsumer::LocalNotificationClicked(const char * ref, enum LocalNotificationAction action, const char * replyText)
{
    char buffer[64000]; // replyText might be big ...
    json_io json(0);
    char * ref_buffer = _strdup(ref);
    const char * consumer;
    const char * r;
    if (!ExtractRef(ref_buffer, &consumer, &r)) {
        free(ref_buffer);
        return;
    }

    word base = json.add_object(JSON_ID_ROOT, 0);
    json.add_string(base, "mt", "LocalNotificationClicked");
    json.add_string(base, "ref", r);
    if (action != LOCAL_NOTIFICATION_ACTION_DEFAULT) {
        const char * actionStr = nullptr;
        switch (action)
        {
        case LOCAL_NOTIFICATION_ACTION_ACCEPT: actionStr = "accept"; break;
        case LOCAL_NOTIFICATION_ACTION_REJECT: actionStr = "reject"; break;
        case LOCAL_NOTIFICATION_ACTION_REPLY: 
            actionStr = "reply"; 
            if (replyText) {
                json.add_string(base, "replyText", replyText, (word)MIN(strlen(replyText), 63000));   // we cut at 63000 to ensure no bufferoverflow ...
            }
            break;
        default:
            break;
        }
        json.add_string(base, "action", actionStr);
    }
    ApiConsumerSend(consumer, NULL, json, base, buffer);
    free(ref_buffer);
}

/*---------------------------------------------------------------------------*/
/* class LauncherApiProvider                                                 */
/*---------------------------------------------------------------------------*/


LauncherApiProvider::LauncherApiProvider(LauncherApiHandler * client) : ApiProvider(client, "com.innovaphone.launcher")
{
    this->client = client;
    this->pushToken = 0;
    this->dockingSupported = false;
    this->docked = false;
    this->gsmSupported = false;
    this->gsmCallState = GSM_CALL_STATE_IDLE;
    this->detach = false;
    this->configApp = 0;
    this->statusHeader = 0;
    this->ringerService = 0;
    this->audioService = 0;
    this->videoService = 0;
    this->appSharingService = 0;
    this->signalingService = 0;
    this->remoteControlService = 0;
    this->servicePassword = 0;
    this->closeSupported = false;
    this->offline = false;
    this->offlineSupported = false;
    this->autostart = false;
    this->autostartSupported = false;
    this->expectTouchesSupported = false;
    this->expectTouches = false;
    this->shownLockedSupported = false;
    this->shownLocked = false;
    this->proximitySupported = false;
    this->proximity = false;
    this->noPushAutostartSupported = false;
    this->cxSupported = false;
    this->runningClientServer = false;
    this->busylightHttpPort = 0;
}

LauncherApiProvider::~LauncherApiProvider()
{
    class KnownConsumerItem * item = NULL;
    if (!knownConsumers.empty()) {
        item = knownConsumers.front();
        while (item) {
            class KnownConsumerItem * nextItem = item->goNext();
            delete item;
            item = nextItem;
        }
    }
    if (pushToken) free(pushToken);
    if (configApp) free(configApp);
    if (statusHeader) free(statusHeader);
    if (ringerService) free(ringerService);
    if (audioService) free(audioService);
    if (videoService) free(videoService);
    if (appSharingService) free(appSharingService);
    if (signalingService) free(signalingService);
    if (remoteControlService) free(remoteControlService);
    if (servicePassword) free(servicePassword);
}

void LauncherApiProvider::Update()
{
    char buffer[2048];
    json_io json(0);
    char b[128];
    char* tmp = b;
    word base = json.add_object(JSON_ID_ROOT, 0);
    json.add_string(base, "version", _VERSION_STR_);
    json.add_string(base, "build", _BUILD_STRING_);
    json.add_string(base, "label", _RELEASE_STATE_);
    char hostname[256];
    if (ISystem::GetHostname(hostname, sizeof(hostname))) {
        json.add_string(base, "computername", hostname);
    }
    char username[256];
    if (ISystem::GetUsername(username, sizeof(username))) {
        json.add_string(base, "username", username);
    }
    char clientname[256];
    if (ISystem::GetClientname(clientname, sizeof(clientname))) {
        json.add_string(base, "clientname", clientname);
    }
    char os[256];
    if (ISystem::GetOperatingSystem(os, sizeof(os))) {
        json.add_string(base, "os", os);
    }
    json.add_string(base, "pushToken", pushToken);
    if(closeSupported) json.add_bool(base, "close", true);
    if(dockingSupported) {
        word docking = json.add_object(base, "docking");
        json.add_bool(docking, "docked", docked);
    }
    if(offlineSupported) {
        word autoOffline = json.add_object(base, "autoOffline");
        json.add_bool(autoOffline, "offline", offline);
    }
    if(autostartSupported) {
        word autostart = json.add_object(base, "autostart");
        json.add_bool(autostart, "autostart", this->autostart);
    }
    if(gsmSupported) {
        word gsm = json.add_object(base, "gsm");
        json.add_string(gsm, "state", (gsmCallState == GSM_CALL_STATE_OFFHOOK) ? "Offhook" : ((gsmCallState == GSM_CALL_STATE_RINGING) ? "Ringing" : "Idle"));
    }
    if (detach) json.add_bool(base, "detach", detach);
    json.add_string(base, "configApp", configApp);
    json.add_string(base, "statusHeader", statusHeader);
    json.add_string(base, "ringerService", ringerService);
    json.add_string(base, "audioService", audioService);
    json.add_string(base, "videoService", videoService);
    json.add_string(base, "appSharingService", appSharingService);
    json.add_string(base, "signalingService", signalingService);
    json.add_string(base, "remoteControlService", remoteControlService);
    json.add_string(base, "servicePwd", servicePassword);
    if(expectTouchesSupported) {
        word touches = json.add_object(base, "expectTouches");
        json.add_bool(touches, "touches", expectTouches);
    }
    if (runningClientServer) {
        word clientServer = json.add_object(base, "runningClientServer");
        json.add_bool(clientServer, "runningClientServer", runningClientServer);
    }
    if (busylightHttpPort) {
        word httpPort = json.add_object(base, "Busylight");
        json.add_int(httpPort, "httpPort", busylightHttpPort, tmp);
    }
    if (shownLockedSupported) {
        word locked = json.add_object(base, "shownLocked");
        json.add_bool(locked, "locked", shownLocked);
    }
    if (proximitySupported) {
        word nearby = json.add_object(base, "proximity");
        json.add_bool(nearby, "nearby", proximity);
    }
    if (noPushAutostartSupported) {
        json.add_object(base, "noPushAutostart");
    }
    if (cxSupported) {
        json.add_object(base, "callKit");
    }
    ApiProviderUpdate(json, base, buffer);
}

int LauncherApiProvider::CreateRef(char * ref, const char * consumer, const char * src)
{
    if (!ref) {
        return ((consumer ? 2 + strlen(consumer) : 1) + (src ? 2 + strlen(src) : 1));
    }
    int i = 0;
    if (consumer) {
        ref[i++] = ' ';
        i += _sprintf(&ref[i], "%s", consumer);
    }
    ref[i++] = '\n';
    if (src) {
        ref[i++] = ' ';
        i += _sprintf(&ref[i], "%s", src);
    }
    ref[i] = '\0';
    return i;
}

void LauncherApiProvider::ApiProviderRecv(const char * client, const char * consumer, const char * src, class json_io & msg, word base)
{
    class KnownConsumerItem * item = NULL;
    if (!knownConsumers.empty()) {
        item = knownConsumers.front();
        while (item && (strcmp(consumer, item->consumer) != 0)) {
            item = item->goNext();
        }
    }
    if (!item) {
        item = new KnownConsumerItem(client, consumer);
        knownConsumers.push_back(item);
    }
    const char * mt = msg.get_string(base, "mt");
    if (mt) {
        if (!strcmp(mt, "SetDocked")) {
            bool docked = msg.get_bool(base, "docked");
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiSetDocked(docked);
            }
        }
        else if (!strcmp(mt, "DisablePowerSaving")) {
            bool active = msg.get_bool(base, "active");
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiDisablePowerSaving(active);
            }
            item->disablePowerSaving = active;
        }
        else if(!strcmp(mt, "AppWindowOpened")) {
            const char * app = msg.get_string(base, "app");
            const char * title = msg.get_string(base, "windowTitle");
            for(LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiAppWindowOpened(app, title);
            }
        }
        else if(!strcmp(mt, "Focus")) {
            const char * app = msg.get_string(base, "app");
            const char * title = msg.get_string(base, "windowTitle");
            for(LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiSetFocus(app, title);
            }
        }
        else if (!strcmp(mt, "Position")) {
            bool present = false;
            const char* obj = msg.get_string(base, "obj");
            int x = msg.get_int(base, "x");
            int y = msg.get_int(base, "y");
            int width = msg.get_int(base, "width");
            int height = msg.get_int(base, "height");
            int screenX = msg.get_int(base, "screenX", &present);
            if (!present) screenX = INT_MIN; // only newer versions provide "screenY"
            int screenY = msg.get_int(base, "screenY", &present);
            if (!present) screenY = INT_MIN; // only newer versions provide "screenY"
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiJavascriptObjPosition(obj, x, y, width, height, screenX, screenY);
            }
        }
        else if(!strcmp(mt, "Resize")) {
            bool isWidthPresent, isHeightPresent;
            const char * app = msg.get_string(base, "windowTitle");
            int width = msg.get_int(base, "width", &isWidthPresent);
            int height = msg.get_int(base, "height", &isHeightPresent);
            if(isWidthPresent && isHeightPresent) {
                for(LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                    (*i)->LauncherApiSetResize(app, width, height);
                }
            }
        }
        else if(!strcmp(mt, "MaximizeWindow")) {
            const char * app = msg.get_string(base, "windowTitle");
            bool on = msg.get_bool(base, "on");
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiSetMaximized(app, on);
            }
        }
        else if(!strcmp(mt, "RegistrationState")) {
            bool up = msg.get_bool(base, "up");
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiRegistrationState(up);
            }
        }
        else if(!strcmp(mt, "DialGsm")) {
            const char * number = msg.get_string(base, "number");
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiDialGsm(number);
            }
        }
        else if (!strcmp(mt, "AddLocalNotification")) {
            const char * r = msg.get_string(base, "ref");
            char * ref = (char *)(malloc(CreateRef(NULL, consumer, r) + 1));
            LocalNotificationType type = (LocalNotificationType)(LauncherApiHandler::NotificationNameId(IdNameLocalNotificationType, msg.get_string(base, "type")));
            const char * title = msg.get_string(base, "title");
            const char * text = msg.get_string(base, "text");
            const char * smallIcon = msg.get_string(base, "smallIcon");
            const char * largeIcon = msg.get_string(base, "largeIcon");
            const char * soundUrl = msg.get_string(base, "soundUrl");
            const char * buttonTitle = msg.get_string(base, "buttonTitle");
            const char * acceptTitle = msg.get_string(base, "acceptTitle");
            const char * rejectTitle = msg.get_string(base, "rejectTitle");
            const char * replyTitle = msg.get_string(base, "replyTitle");
            const char * link = msg.get_string(base, "link");
            CreateRef(ref, consumer, r);
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiAddLocalNotification(ref, type, title, text, smallIcon, largeIcon, soundUrl, buttonTitle, acceptTitle, rejectTitle, replyTitle, link, nullptr);
            }
            free(ref);
        }
        else if (!strcmp(mt, "AddNotification")) {
            char uuid[48];
            word payload = msg.get_object(base, "payload");
            const char * ref = msg.get_string(payload, "ref");
            if (!ref) {
                IGuuid::GenerateUUID(uuid);
                ref = uuid;
            }
            LocalNotificationType type = (LocalNotificationType)(LauncherApiHandler::NotificationNameId(IdNameLocalNotificationType, msg.get_string(payload, "type")));
            const char * title = msg.get_string(payload, "title");
            const char * text = msg.get_string(payload, "text");
            const char * smallIcon = msg.get_string(payload, "smallIcon");
            const char * largeIcon = msg.get_string(payload, "largeIcon");
            const char * soundUrl = msg.get_string(payload, "soundUrl");
            const char * buttonTitle = msg.get_string(payload, "buttonTitle");
            const char * acceptTitle = msg.get_string(payload, "acceptTitle");
            const char * rejectTitle = msg.get_string(payload, "rejectTitle");
            const char * replyTitle = msg.get_string(payload, "replyTitle");
            const char * link = msg.get_string(payload, "link");
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiAddLocalNotification(ref, type, title, text, smallIcon, largeIcon, soundUrl, buttonTitle, acceptTitle, rejectTitle, replyTitle, link, nullptr);
            }
        }
        else if (!strcmp(mt, "RemoveLocalNotification")) {
            const char * r = msg.get_string(base, "ref");
            char * ref = (char *)(malloc(CreateRef(NULL, consumer, r) + 1));
            CreateRef(ref, consumer, r);
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiRemoveLocalNotification(ref);
            }
            free(ref);
        }
        else if (!strcmp(mt, "RemoveNotification")) {
            word payload = msg.get_object(base, "payload");
            const char * ref = msg.get_string(payload, "ref");
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiRemoveLocalNotification(ref);
            }
        }
        else if (!strcmp(mt, "SetBadgeCount")) {
            const char * r = msg.get_string(base, "ref");
            char * ref = (char *)(malloc(CreateRef(NULL, consumer, r) + 1));
            unsigned count = msg.get_unsigned(base, "count");
            const char * text = msg.get_string(base, "text");
            CreateRef(ref, consumer, r);
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiSetBadgeCount(ref, count, text);
            }
            free(ref);
        }
        else if (!strcmp(mt, "ShowIfLocked")) {
            const char * r = msg.get_string(base, "ref");
            char * ref = (char *)(malloc(CreateRef(NULL, consumer, r) + 1));
            bool show = msg.get_bool(base, "show");
            CreateRef(ref, consumer, r);
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiShowIfLocked(ref, show);
            }
            free(ref);
        }
        else if (!strcmp(mt, "ShowUnlock")) {
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiShowUnlock();
            }
        }
        else if (!strcmp(mt, "SetPushPassword")) {
            const char * pwd = msg.get_string(base, "pwd");
            for(LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiSetPushPassword(pwd);
            }
        }
        else if (!strcmp(mt, "NoPushAutostart")) {
            bool noPushAutostart = msg.get_bool(base, "noPushAutostart");
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiNoPushAutostart(noPushAutostart);
            }
        }
        else if (!strcmp(mt, "Close")) {
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiClose();
            }
        }
        else if(!strcmp(mt, "RccAppConnected")) {
            bool up = msg.get_bool(base, "up");
            for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                (*i)->LauncherApiRccAppConnected(up);
            }
        }
        else {
            debug->printf("LauncherApiProvider::ApiProviderRecv() Unknown mt: %s", mt);
        }
    }
}

void LauncherApiProvider::ApiProviderConsumerClosed(const char * client, const char * consumer)
{
    if (!knownConsumers.empty()) {
        class KnownConsumerItem * item = knownConsumers.front();
        while (item) {
            class KnownConsumerItem * nextItem = item->goNext();
            if((!client || !strcmp(client, item->client)) && (!consumer || !strcmp(consumer, item->consumer))) {
                char * ref = (char *)(malloc(CreateRef(NULL, item->consumer, NULL) + 1));
                CreateRef(ref, item->consumer, NULL);
                for (LauncherApiUsers::iterator i = this->client->launcherApiUsers.begin(); i != this->client->launcherApiUsers.end(); i++) {
                    (*i)->LauncherApiRemoveLocalNotification(ref);
                    (*i)->LauncherApiSetBadgeCount(ref, 0, NULL);
                    (*i)->LauncherApiShowIfLocked(ref, false);
                    if(item->disablePowerSaving) (*i)->LauncherApiDisablePowerSaving(false);
                }
                free(ref);
                delete item;
            }
            item = nextItem;
        }
    }
}

void LauncherApiProvider::SetPushToken(const char * pushToken)
{
    if (this->pushToken) free(this->pushToken);
    this->pushToken = _strdup(pushToken);
    Update();
}

void LauncherApiProvider::SetClose(bool closeSupported)
{
    this->closeSupported = closeSupported;
    Update();
}

void LauncherApiProvider::SetDocking(bool dockingSupported, bool docked)
{
    this->dockingSupported = dockingSupported;
    this->docked = docked;
    Update();
}

void LauncherApiProvider::SetAutostart(bool autostartSupported, bool autostart)
{
    this->autostartSupported = autostartSupported;
    this->autostart = autostart;
    Update();
}

void LauncherApiProvider::SetAutoOffline(bool offlineSupported, bool offline)
{
    this->offlineSupported = offlineSupported;
    this->offline = offline;
    Update();
}

void LauncherApiProvider::SetGsmState(bool gsmSupported, enum GsmCallState gsmCallState)
{
    this->gsmSupported = gsmSupported;
    this->gsmCallState = gsmCallState;
    Update();
}

void LauncherApiProvider::SetDetach(bool detach)
{
    this->detach = detach;
    Update();
}

void LauncherApiProvider::SetConfigApp(const char * url)
{
    if (this->configApp) free(this->configApp);
    this->configApp = _strdup(url);
    Update();
}

void LauncherApiProvider::SetStatusHeader(const char* url)
{
    if (this->statusHeader) free(this->statusHeader);
    this->statusHeader = _strdup(url);
    Update();
}

void LauncherApiProvider::SetRingerServiceUrl(const char * url)
{
    if(this->ringerService) free(this->ringerService);
    this->ringerService = _strdup(url);
    Update();
}

void LauncherApiProvider::SetAudioServiceUrl(const char * url)
{
    if(this->audioService) free(this->audioService);
    this->audioService = _strdup(url);
    Update();
}

void LauncherApiProvider::SetVideoServiceUrl(const char * url)
{
    if(this->videoService) free(this->videoService);
    this->videoService = _strdup(url);
    Update();
}

void LauncherApiProvider::SetAppSharingServiceUrl(const char * url)
{
    if(this->appSharingService) free(this->appSharingService);
    this->appSharingService = _strdup(url);
    Update();
}

void LauncherApiProvider::SetSignalingServiceUrl(const char* url)
{
    if (this->signalingService) free(this->signalingService);
    this->signalingService = _strdup(url);
    Update();
}

void LauncherApiProvider::SetRemoteControlServiceUrl(const char* url)
{
    if (this->remoteControlService) free(this->remoteControlService);
    this->remoteControlService = _strdup(url);
    Update();
}

void LauncherApiProvider::SetServicePassword(const char * url)
{
    if(this->servicePassword) free(this->servicePassword);
    this->servicePassword = _strdup(url);
    Update();
}

void LauncherApiProvider::SetExpectTouches(bool expectTouchesSupported, bool expectTouches)
{
    this->expectTouchesSupported = expectTouchesSupported;
    this->expectTouches = expectTouches;
    Update();
}

void LauncherApiProvider::SetShownLocked(bool shownLockedSupported, bool shownLocked)
{
    this->shownLockedSupported = shownLockedSupported;
    this->shownLocked = shownLocked;
    Update();
}

void LauncherApiProvider::SetProximity(bool proximitySupported, bool proximity)
{
    this->proximitySupported = proximitySupported;
    this->proximity = proximity;
    Update();
}

void LauncherApiProvider::SetNoPushAutostart(bool noPushAutostartSupported)
{
    this->noPushAutostartSupported = noPushAutostartSupported;
    Update();
}

void LauncherApiProvider::SetRunningClientServer(bool runningClientServer)
{
    this->runningClientServer = runningClientServer;
    Update();
}

void LauncherApiProvider::SetBusylightHttpPort(int port)
{
    this->busylightHttpPort = port;
    Update();
}

void LauncherApiProvider::CxSupported(bool supported)
{
    this->cxSupported = supported;
    Update();
}

