/*---------------------------------------------------------------------------*/
/* signaling_app.cpp                                                         */
/* copyright (c) innovaphone 2021                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include "platform/platform.h"
#include "common/os/iomux.h"
#include "common/interface/socket.h"
#include "common/interface/time.h"
#include "common/interface/media.h"
#include "common/interface/ringer.h"
#include "common/interface/random.h"
#include "common/interface/json_api.h"
#include "common/interface/webserver_plugin.h"
#include "common/interface/appwebsocket_client.h"
#include "common/interface/websocket_client.h"
#include "common/interface/local_storage.h"
#include "common/interface/notifications-if.h"
#include "common/interface/launcherapi.h"
#include "common/interface/url_handler.h"
#include "common/interface/call_kit.h"
#include "common/interface/remote_services.h"
#include "common/interface/signaling_app.h"
#include "common/interface/task.h"
#include "common/interface/stask.h"
#include "common/interface/search_contact.h"
#include "common/interface/system.h"

#include "common/ilib/json.h"
#include "common/ilib/uri.h"
#include "common/ilib/str.h"
#include "common/ilib/hash.h"

#include "common/interface/guuid.h"
#include "common/interface/http_client.h"

#include "common/lib/appservice.h"
#include "common/lib/appwebsocket.h"
#include "common/lib/app_updates.h"
#include "common/lib/apiwebsocket_client.h"
#include "common/lib/language.h"

#include "client/lang/myapps_languages.h"

#include "signaling/signaling_api.h"
#include "signaling/signaling.h"

#include "signaling_app.h"

SignalingApp::SignalingApp(IIoMux * const iomux, class ULauncherApi* launcherApi, class ILauncherApi* iLauncherApi, class ISocketProvider * localSocketProvider, class ISocketProvider * tcpSocketProvider, class ISocketProvider * tlsSocketProvider, class IDns * dns, class IInstanceLog * const log, class INotifications* notifications, const char * apiRelayUrl, const char * servicePassword, const char * workingPath, const char * apiRelayPath, class IRemoteServices* remoteServices, class ICallKit * callKit, class USignalingApp * signalingAppUser) :
ApiWebsocketClient(iomux, localSocketProvider, tcpSocketProvider, tlsSocketProvider, dns, this, apiRelayUrl, servicePassword, "SignalingApp", apiRelayPath),
defProviderTimer(iomux, this),
startCallTimer(iomux, this)
{
    iomux->RegisterShutdownHandler(this);
    this->closing = false;
    this->closed = false;
    this->iomux = iomux;
    this->log = log;
    this->notifications = notifications;
    this->signaling = nullptr;
    this->signalingAvatarConsumer = nullptr;
    this->signalingPhoneConsumer = nullptr;
    this->signalingLookupConsumer = nullptr;
    this->langId = 0;
    this->launcherApi = launcherApi;
    this->iLauncherApi = iLauncherApi;
    this->remoteServices = remoteServices;
    this->searchContact = nullptr;

    this->callKit = callKit;
    this->signalingAppUser = signalingAppUser;
    this->url = ILocalStorage::GetItem("SignalingServiceUrl");
    this->sec = ILocalStorage::GetItem("SignalingServiceSec");
    this->phys = ILocalStorage::GetItem("SignalingServicePhys");
    this->app = ILocalStorage::GetItem("SignalingServiceApp");
    this->usr = ILocalStorage::GetItem("SignalingServiceUsr");
    this->pwd = ILocalStorage::GetItem("SignalingServicePwd");
    this->domain = ILocalStorage::GetItem("SignalingServiceDomain");
    this->appdomain = ILocalStorage::GetItem("SignalingServiceAppDomain");
    char * t = ILocalStorage::GetItem("SignalingServiceTimeout");
    if (t) {
        this->timeout = atoi(t);
        free(t);
    }
    else this->timeout = 0;

    this->hw = ILocalStorage::GetItem("SignalingServiceHw");
    // Empty string
    if (this->hw) {
        bool delHwString = false;
        if(!this->hw[0]) delHwString = true;         // Empty 
        else {
            delHwString = true;                      // Only whitespaces 
            for (unsigned int i = 0; i < strlen(this->hw); i++) { 
                if (this->hw[i] != 0x20) {
                    delHwString = false;
                    break;
                }
            }
        }
        if (delHwString) {
            free(this->hw);
            this->hw = NULL;
        }
    }
    t = ILocalStorage::GetItem("SignalingServiceHwTimeout");
    if (t) {
        this->hwTimeout = atoi(t);
        free(t);
    }
    else this->hwTimeout = 0;
    this->currentUserSip = NULL;
    this->excludeInterfaces = NULL;
    this->cxSupported = false;
    this->uiRunning = 0;
    this->uuidTerminateNotify = NULL;
    this->uuidTerminateNotifyConf = NULL;
    this->lastMissedCallUuid = NULL;
    this->defaultApiProvider = NULL;
    this->defaultApiProviderRemoved = false;
    this->workingPath = _strdup(workingPath);
    this->dnd = false;
    this->remoteServicesClientConnected = false;
    this->remoteServicesServerConnected = false;
    this->registered = false;
    this->windowTitle = _strdup(innovaphoneMyapps);
    this->showNotifications = this->notifications ? true : false;
    this->startCallSip = NULL;
    this->startCallNum = NULL;
    this->startCallName = NULL;
}

SignalingApp::~SignalingApp()
{
    this->defProviderTimer.Cancel();
    this->startCallTimer.Cancel();
    if (this->url) free(this->url);
    if (this->sec) free(this->sec);
    if (this->phys) free(this->phys);
    if (this->app) free(this->app);
    if (this->usr) free(this->usr);
    if (this->pwd) free(this->pwd);
    if (this->domain) free(this->domain);
    if (this->appdomain) free(this->appdomain);
    if (this->hw) free(this->hw);
    if (this->currentUserSip) free(this->currentUserSip);
    if (this->excludeInterfaces) free(this->excludeInterfaces);
    if (this->uuidTerminateNotify) free(this->uuidTerminateNotify);
    if (this->uuidTerminateNotifyConf) free(this->uuidTerminateNotifyConf);
    if (this->lastMissedCallUuid) free(this->lastMissedCallUuid);
    if (this->defaultApiProvider) free(this->defaultApiProvider);
    if (this->windowTitle) free(this->windowTitle);
    if (this->workingPath) free(this->workingPath);
    if (this->startCallSip) free(this->startCallSip);
    if (this->startCallNum) free(this->startCallNum);
    if (this->startCallName) free(this->startCallName);
    while (!calls.empty()) delete calls.front();
    while (!phoneApps.empty()) delete phoneApps.front();
}

void SignalingApp::Shutdown()
{
    if(log) log->Log(LOG_SIGNALING,"SignalingApp::Shutdown");
    TryClose();
}

void SignalingApp::TryClose()
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::TryClose stopping=%d", this->closing);

    if (!this->closing) {
        this->closing = true;
        if (uuidTerminateNotify) {
            char ref[256];
            _sprintf(ref, "drop:%s", uuidTerminateNotify);
            RemoveNotification(ref);
            _sprintf(ref, "hold:%s", uuidTerminateNotify);
            RemoveNotification(ref);
            free(uuidTerminateNotify);
            uuidTerminateNotify = NULL;
        }
        if (uuidTerminateNotifyConf) {
            char ref[256];
            _sprintf(ref, "drop:%s", uuidTerminateNotifyConf);
            RemoveNotification(ref);
            _sprintf(ref, "hold:%s", uuidTerminateNotifyConf);
            RemoveNotification(ref);
            free(uuidTerminateNotifyConf);
            uuidTerminateNotifyConf = NULL;
        }
        ApiWebsocketClientClose();
        if (searchContact) searchContact->Close();
    }
    if (this->closed && !searchContact) {
        iomux->ShutdownComplete(this);
    }
}

void SignalingApp::ApiWebsocketClientCloseComplete()
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::ApiWebsocketClientCloseComplete");

    this->closed = true;
    TryClose();
}

void SignalingApp::SearchContactCloseResult()
{
    delete searchContact;
    searchContact = NULL;
    TryClose();
}

void SignalingApp::SetSignaling(ISignaling * signaling)
{
    if (!log) log = signaling;
    if(log) log->Log(LOG_SIGNALING, "SignalingApp(%p)::SetSignaling signaling: %p url: %s sec: %s phys: %s app: %s usr: %s pwd: %s domain: %s hw: %s timeout: %d", this, signaling, url ? url : "", sec ? sec : "", phys ? phys : "", app ? app : "", usr ? usr : "", pwd ? pwd : "", domain ? domain : "", hw ? hw : "", timeout);
    this->signaling = signaling;
    this->signalingPhoneConsumer = new class SignalingApiPhoneConsumer(this);
    this->signalingAvatarConsumer = new class SignalingApiAvatarConsumer(this);
    this->signalingLookupConsumer = new class SignalingApiLookupConsumer(this);
    if (url && *url && app && *app && usr && *usr && pwd && *pwd && domain && *domain) {
        signaling->SetClient(url, sec, phys, app, usr, pwd, domain, appdomain, timeout, true);
    }
    signaling->SetWorkingPath(this->workingPath);
}

void SignalingApp::SetExcludeInterfaces(const char * excludeInf)
{
    //if(log) log->Log(LOG_SIGNALING, "SignalingApp(%p)::SetExcludeInterfaces excludeInf=%s", this, excludeInf ? excludeInf : "");
    if (excludeInterfaces) free(excludeInterfaces);
    excludeInterfaces = _strdup(excludeInf);
    if (signaling) signaling->SetExcludeInterfaces(excludeInf);
}

void SignalingApp::SetNoVpnAddresses(bool noVpnAddresses)
{
    if(signaling) signaling->SetNoVpnAddresses(noVpnAddresses);
}

void SignalingApp::SetCurrentUserSip(const char * sip)
{
    if (currentUserSip) free(currentUserSip);
    currentUserSip = _strdup(sip);
    if(signaling) signaling->SetCurrentUserSip(sip);
}

void SignalingApp::SetRecordingParameters(const char* url, bool forceUrl, bool isRecordingByDefaultOn, bool forceDefaultOn, bool recordExternalOnly, bool forceExternalOnly)
{
    if (signaling) signaling->SetRecordingParameters(url, true, forceUrl, isRecordingByDefaultOn, forceDefaultOn, recordExternalOnly, forceExternalOnly);
}

void SignalingApp::SignalingClosing(class ISignaling *signaling)
{
    if (this->log == signaling) this->log = NULL;
    this->signaling = NULL;
}

void SignalingApp::ClientState(bool up, bool localInit)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::ClientState:%s localInit:%d this->hw:%s", up ? "true" : "false", localInit, this->hw ? this->hw : "-");
    if (localInit && up && this->hw) signaling->SetRegistration(this->hw, this->phys, hwTimeout);
}

void SignalingApp::RegistrationState(bool up) 
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::RegistrationState %s", up ? "true" : "false");
    if (signalingAppUser) signalingAppUser->SignalingRegistrationState(up);
    this->registered = up;
}

bool SignalingApp::HasCredentials()
{
    char* tUrl = ILocalStorage::GetItem("SignalingServiceUrl");
    char* tSec = ILocalStorage::GetItem("SignalingServiceSec");
    char* tApp = ILocalStorage::GetItem("SignalingServiceApp");
    char* tUsr = ILocalStorage::GetItem("SignalingServiceUsr");
    char* tPwd = ILocalStorage::GetItem("SignalingServicePwd");
    char* tDomain = ILocalStorage::GetItem("SignalingServiceDomain");

    if(log) log->Log(LOG_SIGNALING, "SignalingApp(%p)::HasCredentials signaling: %p url: %s sec: %s phys: %s app: %s usr: %s pwd: %s domain: %s hw: %s timeout: %d", this, signaling,
        tUrl ? tUrl : "", tSec ? tSec : "",
        phys ? phys : "", tApp ? tApp : "", tUsr ? tUsr : "", tPwd ? tPwd : "",
        tDomain ? tDomain : "", hw ? hw : "", timeout);

    bool hasCredentials = tUrl && *tUrl && tApp && *tApp && tUsr && *tUsr && tPwd && *tPwd && tDomain && *tDomain;

    if (tUrl) free(tUrl);
    if (tSec) free(tSec);
    if (tApp) free(tApp);
    if (tUsr) free(tUsr);
    if (tPwd) free(tPwd);
    if (tDomain) free(tDomain);

    return hasCredentials;
}

void SignalingApp::StartSoftphoneApp()
{
    iLauncherApi->WakePhone();
}

void SignalingApp::SaveCredentials(const char* url, const char* sec, const char* phys, const char* app, const char* usr, const char* pwd, const char * domain, const char * appdomain, unsigned timeout)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::SaveCredentials url=%s sec=%s phys=%s app=%s usr=%s pwd=%s domain=%s appdomain=%s timeout=%d hw:(%s),(%s)", url ? url : "", sec ? sec : "", phys ? phys : "", app ? app : "", usr ? usr : "", pwd ? pwd : "", domain ? domain : "", appdomain ? appdomain : "", timeout, this->hw ? this->hw : "-", this->defaultApiProvider ? this->defaultApiProvider : "-");

    if (url && app && usr && pwd && domain) {
        if (this->url) free(this->url);
        this->url = _strdup(url);
        if (this->sec) free(this->sec);
        this->sec = _strdup(sec);
        if (this->phys) free(this->phys);
        this->phys = _strdup(phys);
        if (this->app) free(this->app);
        this->app = _strdup(app);
        if (this->usr) free(this->usr);
        this->usr = _strdup(usr);
        if (this->pwd) free(this->pwd);
        this->pwd = _strdup(pwd);
        if (this->domain) free(this->domain);
        this->domain = _strdup(domain);
        if (this->appdomain) free(this->appdomain);
        this->appdomain = _strdup(appdomain);
        this->timeout = timeout;
    }
}

void SignalingApp::SaveRegistration(const char* hw, const char* phys, unsigned timeout)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::SaveRegistration hw=(%s),(%s),(%s) phys=%s timeout=%d", hw ? hw : "-", this->hw ? this->hw : "-", this->defaultApiProvider ? this->defaultApiProvider : "-", phys ? phys : "-", timeout);

    const char* tmpDefPhoneApp;
    if (defaultApiProvider && !strncmp(defaultApiProvider, "dev:", 4)) tmpDefPhoneApp = &this->defaultApiProvider[4];
    else tmpDefPhoneApp = this->defaultApiProvider;

    if (this->phys) free(this->phys);
    this->phys = _strdup(phys);

    // SaveRegistration only if hw is default Softphone App
    if (hw && tmpDefPhoneApp && !strcmp(tmpDefPhoneApp, hw)) {
        if (this->hw) free(this->hw);
        this->hw = _strdup(hw);
        ILocalStorage::SetItem("SignalingServiceHw", hw);
        this->hwTimeout = timeout;
        char t[32];
        _sprintf(t, "%d", this->hwTimeout);
        ILocalStorage::SetItem("SignalingServiceHwTimeout", t);
        if (url) ILocalStorage::SetItem("SignalingServiceUrl", url);
        if (sec) ILocalStorage::SetItem("SignalingServiceSec", sec);
        else ILocalStorage::SetItem("SignalingServiceSec", "");
        if (phys) ILocalStorage::SetItem("SignalingServicePhys", phys);
        else ILocalStorage::SetItem("SignalingServicePhys", "");
        if (app) ILocalStorage::SetItem("SignalingServiceApp", app);
        if (usr) ILocalStorage::SetItem("SignalingServiceUsr", usr);
        if (pwd) ILocalStorage::SetItem("SignalingServicePwd", pwd);
        if (domain) ILocalStorage::SetItem("SignalingServiceDomain", domain);
        if (appdomain) ILocalStorage::SetItem("SignalingServiceAppDomain", appdomain);
        _sprintf(t, "%d", this->timeout);
        ILocalStorage::SetItem("SignalingServiceTimeout", t);
    }

    const char * t = ILocalStorage::GetItem("cxSupported");
    if(!t) cxSupported = false;
    else if(!strcmp(t, "1")) cxSupported = true;
    else cxSupported = false;
}

void SignalingApp::LauncherApiSetLangId(const char* langId)
{
    //if(log) log->Log(LOG_SIGNALING, "SignalingApp(%p)::LauncherApiSetLangId lang=%s", this, langId ? langId : "");
    this->langId = Language::GetLanguageId(langId, myappsLanguageTable, MYAPPS_NUM_LANGUAGES, MYAPPS_LANGUAGE_EN);
}

void SignalingApp::SearchContactResult(const char* src, class json_io& msg, word base)
{
    signaling->LookupInfo(src, msg, base);
}

void SignalingApp::TimerOnTimeout(StartCallTimer * timer)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::TimerOnTimeout startCallTimer");
    if (signaling) {
        this->startCallTime += 200;
        if (!this->registered && (this->startCallTime < 2000)) {
            this->startCallTimer.Start(200);
        }
        else {
            signaling->PhoneDialNumber(startCallNum, startCallSip, startCallName, startCallVideoMode == START_CALL_VIDEO_MODE_ON ? true : false, startCallSip ? true : startCallSendingComplete);
        }
    }
}

bool SignalingApp::IsActive()
{
    if (!calls.empty()) return true;
    return false;
}

bool SignalingApp::StartCall(const char * sip, const char * num, const char * name, StartCallVideoMode videoMode, bool sendingComplete)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::StartCall sip:%s num:%s name:%s registered:%d standardApp:%d", sip ? sip: "-", num ? num : "-", name ? name : "-", this->registered, IsSoftPhoneStandardApp());
    if (!IsSoftPhoneStandardApp()) return false;
    if ((num || sip || name) && signaling) {
        this->startCallTimer.Cancel();
        if (this->startCallSip) {
            free(this->startCallSip);
            this->startCallSip = NULL;
        }
        if (this->startCallNum) {
            free(this->startCallNum);
            this->startCallNum = NULL;
        }
        if (this->startCallName) {
            free(this->startCallName);
            this->startCallName = NULL;
        }
        if (this->registered) {
            signaling->PhoneDialNumber(num, sip, name, videoMode == START_CALL_VIDEO_MODE_ON ? true : false, sip ? true : sendingComplete);
        }
        else {
            this->startCallSip = _strdup(sip);
            this->startCallNum = _strdup(num);
            this->startCallName = _strdup(name);
            this->startCallVideoMode = videoMode;
            this->startCallSendingComplete = sendingComplete;
            this->startCallTime = 0;
            this->startCallTimer.Start(200);
        }
        return true;
    }
    return false;
}

void SignalingApp::CxSupported(bool supported)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::CxSupported cxSupported: %s", supported ? "true" : "false");
    cxSupported = supported;
    const char * cxSupportedString = ILocalStorage::GetItem("cxSupported");
    if (cxSupportedString && strcmp(cxSupportedString, cxSupported ? "1" : "0")) ILocalStorage::SetItem("cxSupported", cxSupported ? "1" : "0");
    else if (!cxSupportedString) ILocalStorage::SetItem("cxSupported", cxSupported ? "1" : "0");
}

void SignalingApp::CxStartCallAction(const char * uuid, const char * number, const char * name, bool video)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::CxStartCallAction");
    if(signaling) signaling->PhoneStartCallAction(uuid, number, name, video, true);
}

void SignalingApp::CxAnswerCallAction(const char * uuid)
{
    if(signaling) signaling->PhoneAnswerCallAction(uuid);
}

void SignalingApp::CxEndCallAction(const char * uuid, enum CxCallError callError)
{
    if(signaling) signaling->PhoneEndCallAction(uuid, callError);
}

void SignalingApp::CxSetHeldCallAction(const char * uuid, bool held)
{
    if(signaling){
        if(held) signaling->PhoneHoldCallAction(uuid);
        else signaling->PhoneRetrieveCallAction(uuid);
    }
}

void SignalingApp::CxSetMutedCallAction(const char * uuid, bool muted)
{
    if(signaling) signaling->PhoneSetMutedCallAction(uuid, muted);
}

void SignalingApp::CxAudioRouteChanged(const char *uuid, const char * deviceId)
{
    if(signaling) signaling->PhoneAudioRouteChanged(uuid, deviceId);
}

void SignalingApp::CxSetGroupCallAction(const char * uuid, const char * with)
{
}

void SignalingApp::CxPlayDTMFCallAction(const char * uuid, const char * digits)
{
    if(signaling) signaling->PhonePlayDtmfCallAction(uuid, digits);
}

void SignalingApp::CxCallError(const char * uuid, enum CxCallError callError)
{
    if(signaling) signaling->PhoneEndCallAction(uuid, callError);
}

void SignalingApp::CxShowIncomingCallUi(const char * uuid)
{
    if (signaling) signaling->PhoneStartRinging(uuid);
    SignalingAppCall *c = NULL;
    if (!calls.empty()) {
        for (c = calls.front(); c; c = c->goNext()) {
            if (uuid && c->uuid && !strcmp(uuid, c->uuid)) break;
        }
    }
    if (c) {
        c->showIncomingCallUI = true;
    }
}

void SignalingApp::CxCheckIncomingCall(const char * uuid)
{
    if (signaling) signaling->PhoneCheckIncomingCall(uuid);
}

void SignalingApp::Lookup(const char * src, const char * number, const char * prefixIntl, const char * prefixNtl, const char * prefixExt, const char * country, const char * area, const char * subscriber)
{
    if (searchContact) searchContact->SearchContact(src, number, prefixIntl, prefixNtl, prefixExt, country, area, subscriber);
    else if(signalingLookupConsumer) signalingLookupConsumer->Lookup(src, number, prefixIntl, prefixNtl, prefixExt, country, area, subscriber);
}

char* SignalingApp::GetAvatarUrl(const char* sip, const char* dn, const char* domain, const char* appdomain)
{
    return signalingAvatarConsumer->GetAvatarUrl(sip, dn, domain, appdomain, currentUserSip);
}

void SignalingApp::PhoneAppAvailable(const char * hw, const char * app, const char * title, const char * url)
{
    if (!phoneApps.empty()) {
        class SignalingAppPhoneApps* phoneApp = phoneApps.front();
        while (phoneApp) {
            if (((phoneApp->hw && hw && !strcmp(phoneApp->hw, hw))             || (!phoneApp->hw && !hw)) &&
                ((phoneApp->app && app && !strcmp(phoneApp->app, app))         || (!phoneApp->app && !app)) &&
                ((phoneApp->title && title && !strcmp(phoneApp->title, title)) || (!phoneApp->title && !title)) &&
                ((phoneApp->url && url && !strcmp(phoneApp->url, url))         || (!phoneApp->url && !url))) return;
            phoneApp = phoneApp->goNext();
        }
    }

    if(log) log->Log(LOG_SIGNALING, "SignalingApp::PhoneAppAvailable hw=%s app=%s title=%s urls=%s", hw ? hw : "-", app ? app : "-", title ? title : "-", url ? url : "-");

    phoneApps.push_back(new class SignalingAppPhoneApps(hw, app, title, url));
}

void SignalingApp::DefaultApiProvider(const char * defaultApiProvider)
{
    if(!this->defaultApiProvider || !defaultApiProvider || strcmp(defaultApiProvider, this->defaultApiProvider)) if(log) log->Log(LOG_SIGNALING, "SignalingApp::DefaultApiProvider provider: %s old: %s", defaultApiProvider ? defaultApiProvider : "-", this->defaultApiProvider ? this->defaultApiProvider : "-");

    if (defaultApiProvider) {
        if (!phoneApps.empty()) {
            class SignalingAppPhoneApps* phoneApp = phoneApps.front();
            while (phoneApp) {
                // Ignore phoneApps
                if (phoneApp->app && !strcmp(phoneApp->app, "softphone")) {
                    // DefaultApiProvider as softphoneApp
                    if (phoneApp->hw && !strcmp(phoneApp->hw, defaultApiProvider)) {
                        if(log) log->Log(LOG_SIGNALING, "SignalingApp::DefaultApiProvider provider: %s hw: %s (%s) phoneApp: %s registered=%d uiRunning=%d", defaultApiProvider, phoneApp->hw, this->hw ? this->hw : "-", phoneApp->app, this->registered, uiRunning);
                        const char* tmpDefPhoneApp;
                        if (defaultApiProvider && !strncmp(defaultApiProvider, "dev:", 4)) tmpDefPhoneApp = &defaultApiProvider[4];
                        else tmpDefPhoneApp = defaultApiProvider;
                        // No hw still set or different provider (provider changed or new user logged in)
                        if (!this->hw || strcmp(this->hw, tmpDefPhoneApp)) {
                            // uiRunning false. Otherwise we opened a second softphone App instance
                            if (!uiRunning) {
                                signalingPhoneConsumer->ConfigureSoftphone(defaultApiProvider);
                                if (registered) {
                                    signaling->CloseClient();
                                    registered = false;
                                }
                            }
                            if (this->defaultApiProvider) free(this->defaultApiProvider);
                            this->defaultApiProvider = _strdup(defaultApiProvider);
                            this->defaultApiProviderRemoved = false;
                            // DefaultApiProvider is set when the Softphone App already runs and is registered
                            // hw must be set to avoid signaling being disconnect if the user closes the softphone App
                            if (uiRunning && registered && !this->hw) SaveRegistration(tmpDefPhoneApp, this->phys, this->timeout);
                        }
                        else if (this->hw && !strcmp(this->hw, tmpDefPhoneApp) && !this->defaultApiProvider) {
                            if (url && app && usr && pwd && hw && domain) {
                                signaling->SetClient(url, sec, phys, app, usr, pwd, domain, appdomain, timeout, true);
                                this->defaultApiProvider = _strdup(defaultApiProvider);
                                this->defaultApiProviderRemoved = false;
                            }
                        }
                        break;
                    }
                }
                // Check if a phoneApp is the new provider
                else if (phoneApp->app && phoneApp->hw && !strcmp(phoneApp->app, "phone") && !strcmp(phoneApp->hw, defaultApiProvider)) {
                    if(log) log->Log(LOG_SIGNALING, "SignalingApp::DefaultApiProvider provider changed: %s -> %s uiRunning: %d calls: %d registered: %d", this->defaultApiProvider ? this->defaultApiProvider : "-", defaultApiProvider, uiRunning, signaling->NumActiveCalls(), this->registered);
                    if (this->defaultApiProvider || this->registered) {
                        if (!uiRunning && !signaling->NumActiveCalls()) {
                            signaling->CloseClient();
                            if (this->defaultApiProvider) free(this->defaultApiProvider);
                            this->defaultApiProvider = NULL;
                            this->defaultApiProviderRemoved = false;
                        }
                        else this->defaultApiProviderRemoved = true;
                        // Remove settings to avoid a registration during myApps start if standard app was changed
                        ILocalStorage::SetItem("SignalingServiceUrl", "");
                        ILocalStorage::SetItem("SignalingServiceSec", "");
                        ILocalStorage::SetItem("SignalingServiceUsr", "");
                        ILocalStorage::SetItem("SignalingServicePwd", "");
                        ILocalStorage::SetItem("SignalingServiceHw", "");
                        ILocalStorage::SetItem("SignalingServicePhys", "");
                        if (this->hw) free(this->hw);
                        this->hw = NULL;
                    }
                }
                phoneApp = phoneApp->goNext();
            }
        }
    }
    else if (this->defaultApiProvider || this->registered) {
        if(log) log->Log(LOG_SIGNALING, "SignalingApp::DefaultApiProvider provider removed: %s uiRunning: %d calls: %d registered: %d", this->defaultApiProvider ? this->defaultApiProvider : "-", uiRunning, signaling->NumActiveCalls(), this->registered);
        if (!uiRunning && !signaling->NumActiveCalls()) {
            signaling->CloseClient();
            if (this->defaultApiProvider) free(this->defaultApiProvider);
            this->defaultApiProvider = NULL;
            this->defaultApiProviderRemoved = false;
        }
        else this->defaultApiProviderRemoved = true;
        // Remove settings to avoid a registration during myApps start if standard app was removed
        ILocalStorage::SetItem("SignalingServiceUrl", "");
        ILocalStorage::SetItem("SignalingServiceSec", "");
        ILocalStorage::SetItem("SignalingServiceUsr", "");
        ILocalStorage::SetItem("SignalingServicePwd", "");
        ILocalStorage::SetItem("SignalingServiceHw", "");
        ILocalStorage::SetItem("SignalingServicePhys", "");
        if (this->hw) free(this->hw);
        this->hw = NULL;
    }
}

void SignalingApp::LookupInfo(const char * src, class json_io& msg, word base)
{
    signaling->LookupInfo(src, msg, base);
}

void SignalingApp::SelectRingtone(const char* ringtoneId)
{
    if (cxSupported) {
        if (callKit) callKit->CxUse(true, ringtoneId, true, 2, 1);
    }
}

void SignalingApp::ReportNewIncomingCall(const char * uuid, const char * number, int numOffset, const char * sip, const char * name, const char * diverting, const char * called,
    bool hasVideo, bool supportsDTMF, bool supportsHolding, bool supportsGrouping, bool supportsUngrouping)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::ReportNewIncomingCall uuid: %s number: %s(%d) sip: %s name: %s dummy: %d", uuid, number ? number : "", numOffset, sip ? sip : "", name ? name : "");

    SignalingAppCall *c = NULL;
    if (!calls.empty()) {
        for (c = calls.front(); c; c = c->goNext()) {
            if (uuid && c->uuid && !strcmp(uuid, c->uuid)) break;
        }
    }
    if (!c) {
        if (calls.empty()) {
            if (signalingAppUser) signalingAppUser->SignalingCallsActive(true);
        }
        calls.push_back(new class SignalingAppCall(uuid, number, numOffset, sip, name, diverting, called, true));
    }

    bool noAccept = false; // Mantis 416009: do not offer ACCEPT if there is an Outbound Not Connected Call pending
    if (!calls.empty()) {
        for (c = calls.front(); c; c = c->goNext()) {
            if (!c->incoming && !c->connected) noAccept = true;
        }
    }

    if (!this->dnd) {
        if (cxSupported) {
            if (callKit) callKit->CxReportNewIncomingCall(uuid, number, name, diverting, called, hasVideo, supportsDTMF, supportsHolding, supportsGrouping, supportsUngrouping);
        }
        else ReportIncomingCall(uuid, name, sip, number, diverting, called, this->app, this->domain, this->appdomain, noAccept);
    }
}

void SignalingApp::ReportMissedCall(const char * uuid, const char * number, int numOffset, const char * sip, const char * name, int reason)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::ReportMissedCall uuid: %s number: %s(%d) sip: %s name: %s reason: %d", uuid, number ? number : "", numOffset, sip ? sip : "", name ? name : "", reason);

    if (reason != CALL_ENDED_REASON_ANSWERED_ELSEWHERE && !this->dnd) {
        // Mantis #398895: Do not report same call again
        if (lastMissedCallUuid) {
            if (uuid && !strcmp(uuid, lastMissedCallUuid)) return;
            free(lastMissedCallUuid);
        }
        lastMissedCallUuid = _strdup(uuid);
        ReportMissedCall(uuid, name, sip, number, numOffset, NULL, NULL, this->app, this->domain, this->appdomain);
    }
}

void SignalingApp::ReportCallEnded(const char* uuid, int reason, bool ignoreCallkit, const char* reconnectUuid)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::ReportCallEnded uuid: %s reason: %d ignoreCallkit: %d reconnectUuid: (%s)", uuid, reason, ignoreCallkit, reconnectUuid ? reconnectUuid : "");

    if (cxSupported && callKit && !ignoreCallkit) {
        callKit->CxReportCallEnded(reconnectUuid ? reconnectUuid : uuid, (CxCallEndedReason)reason);
    }

    if (!calls.empty()) {
        SignalingAppCall *c;
        for (c = calls.front(); c; c = c->goNext()) {
            if (c->uuid && uuid && !strcmp(c->uuid, uuid)) {
                if (c->connected == false) {
                    if (c->incoming) {
                        if (!cxSupported) RemoveNotification(c->uuid);
                        if (c->hold == false && reason != CALL_ENDED_REASON_ANSWERED_ELSEWHERE && !this->dnd) ReportMissedCall(c->uuid, c->name, c->sip, c->number, c->numOffset, c->diverting, c->called, this->app, this->domain, this->appdomain);
                    }
                }
                else if (!cxSupported && uuidTerminateNotify && !strcmp(uuidTerminateNotify, uuid)) {
                    char ref[256];
                    _sprintf(ref, "drop:%s", uuidTerminateNotify);
                    RemoveNotification(ref);
                    _sprintf(ref, "hold:%s", uuidTerminateNotify);
                    RemoveNotification(ref);
                    free(uuidTerminateNotify);
                    uuidTerminateNotify = NULL;
                }
                else if (!cxSupported && uuidTerminateNotifyConf && !strcmp(uuidTerminateNotifyConf, uuid)) {
                    char ref[256];
                    _sprintf(ref, "drop:%s", uuidTerminateNotifyConf);
                    RemoveNotification(ref);
                    _sprintf(ref, "hold:%s", uuidTerminateNotifyConf);
                    RemoveNotification(ref);
                    free(uuidTerminateNotifyConf);
                    uuidTerminateNotifyConf = NULL;
                }
                c->remove();
                delete c;
                if (calls.empty()) {
                    if (signalingAppUser) signalingAppUser->SignalingCallsActive(false);
                }
                break;
            }
        }
    }
    if (!cxSupported && uuid) {
        if(uuidTerminateNotify && !strcmp(uuidTerminateNotify, uuid)) {
            free(uuidTerminateNotify); uuidTerminateNotify = NULL;
        }
        else if(uuidTerminateNotifyConf && !strcmp(uuidTerminateNotifyConf, uuid)) {
            free(uuidTerminateNotifyConf); uuidTerminateNotifyConf = NULL;
        }
    }
    if (defaultApiProviderRemoved && !uiRunning && (signaling && (signaling->NumActiveCalls() == 1))) this->defProviderTimer.Start(500);
}

void SignalingApp::ReportNameIdentification(const char* uuid, const char* number, const char* sip, const char* name, const char* diverting, const char* called,
                                            bool hasVideo, bool supportsDTMF, bool supportsHolding, bool supportsGrouping, bool supportsUngrouping, bool ignoreCallkit)
{
    if(cxSupported && callKit && !ignoreCallkit) callKit->CxReportCallUpdated(uuid, number, name, diverting, called, hasVideo, supportsDTMF, supportsHolding, supportsGrouping, supportsUngrouping);

    if (!calls.empty()) {
        SignalingAppCall* c = NULL;
        for (c = calls.front(); c; c = c->goNext()) {
            if (uuid && c->uuid && !strcmp(uuid, c->uuid)) {
                if (c->incoming && c->hold == false && c->connected == false) {
                    if(log) log->Log(LOG_SIGNALING, "SignalingApp::ReportNameIdentification uuid=%s old=%s new=%s", uuid, c->name ? c->name : "-", name);

                    if (c->name) free(c->name);
                    c->name = _strdup(name);
                    if (!cxSupported) {
#if !defined _WINDOWS
                        RemoveNotification(c->uuid);
#endif
                        ReportIncomingCall(uuid, name, sip, number, diverting, called, this->app, this->domain, this->appdomain, false);
                    }
                }
                return;
            }
        }
        if(log) log->Log(LOG_SIGNALING, "SignalingApp::ReportNameIdentification no call found for uuid=%s name=%s", uuid, name);
    }
}

void SignalingApp::TimerOnTimeout(DefaultApiProviderTimer * timer)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::TimerOnTimeout defaultApiProviderRemoved=%d", defaultApiProviderRemoved);

    if (defaultApiProviderRemoved) {
        if (!uiRunning && !signaling->NumActiveCalls()) {
            signaling->CloseClient();
            free(this->defaultApiProvider);
            this->defaultApiProvider = NULL;
            defaultApiProviderRemoved = false;
        }
        else this->defProviderTimer.Start(500);
    }
}

void SignalingApp::SoftphoneAppWindowTitle(const char* title)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::SoftphoneAppWindowTitle title:%s new:%s", windowTitle ? windowTitle : "", title ? title : "");
    if (windowTitle) free(windowTitle);
    windowTitle = _strdup(title);
}

void SignalingApp::BringWindowToForeground()
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::BringWindowToForeground cxSupported=%d app=%s windowTitle=%s", cxSupported, app ? app : "", windowTitle ? windowTitle : "");

    if (!cxSupported && windowTitle) launcherApi->LauncherApiSetFocus(app, windowTitle);
}

void SignalingApp::LocalNotificationClicked(const char* ref, enum LocalNotificationAction action, const char* replyText)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::LocalNotificationClicked ref: %s action: %u", ref ? ref : "", action);

    if (ref) {
        if (action == LOCAL_NOTIFICATION_ACTION_REJECT) {
            if (strstr(ref, "hold:")) {
                const char* tUuid = &ref[5];
                signaling->PhoneEndCallAction(tUuid, 0);
                ReportCallEnded(ref, 0);
            }
            else signaling->PhoneEndCallAction(ref, 0);
        }
        else if (action == LOCAL_NOTIFICATION_ACTION_ACCEPT) {
            if (strstr(ref, "sip:")) {
                if (windowTitle) launcherApi->LauncherApiSetFocus(app, windowTitle);
                PhoneAnswerCallAction(ref);
            }
            else if (strstr(ref, "numOff:") || strstr(ref, "num:")) {
                if (windowTitle) launcherApi->LauncherApiSetFocus(app, windowTitle);
                PhoneAnswerCallAction(ref);
            }
            else if (strstr(ref, "drop:")) {
                const char* tUuid = &ref[5];
                signaling->PhoneEndCallAction(tUuid, 0);
                ReportCallEnded(ref, 0);
            }
            else if (strstr(ref, "hold:")) {
                const char* tUuid = &ref[5];
                signaling->PhoneRetrieveCallAction(tUuid);
                PhoneAnswerCallAction(ref);
            }
            else {
                if (windowTitle) launcherApi->LauncherApiSetFocus(app, windowTitle);
                PhoneAnswerCallAction(ref);
            }
        }
        else if (action == LOCAL_NOTIFICATION_ACTION_DEFAULT) {
            if (windowTitle) launcherApi->LauncherApiSetFocus(app, windowTitle);
        }
    }
}

void SignalingApp::ReportCallConnected(const char * uuid, bool connected, const char* number, int numOffset, const char* sip, const char* name, const char* diverting, const char* called,
                                       bool hasVideo, bool supportsDTMF, bool supportsHolding, bool supportsGrouping, bool supportsUngrouping, bool ignoreCallkit)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::ReportCallConnected uuid: %s connected: %s ignoreCallkit:%d", uuid, connected ? "true" : "false", ignoreCallkit);

    if(cxSupported && callKit && connected && !ignoreCallkit) {
        callKit->CxReportCallUpdated(uuid, number, name, diverting, called, hasVideo, supportsDTMF, supportsHolding, supportsGrouping, supportsUngrouping);
        callKit->CxReportOutgoingCallConnected(uuid);
    }

    bool found = false;
    if (!calls.empty()) {
        SignalingAppCall *c;
        for (c = calls.front(); c; c = c->goNext()) {
            if (c->uuid && uuid && !strcmp(c->uuid, uuid)) {
                if (c->incoming && c->hold == false) RemoveNotification(c->uuid);
                c->connected = true;
                c->hold = false;
                if (!cxSupported && !uiRunning) {
                    NotificationTerminateCall(c->uuid, c->name, c->sip, c->number, c->diverting, c->called, this->app, this->domain, this->appdomain);
                    if(uuidTerminateNotify == NULL) uuidTerminateNotify = _strdup(c->uuid);
                    else if (strcmp(c->uuid, uuidTerminateNotify)) uuidTerminateNotifyConf = _strdup(c->uuid);
                }
                found = true;
                break;
            }
        }
    }
    if (!found) {
        if (calls.empty()) {
            if (signalingAppUser) signalingAppUser->SignalingCallsActive(true);
        }
        SignalingAppCall * c = new class SignalingAppCall(uuid, number, numOffset, sip, name, nullptr, nullptr, false);
        calls.push_back(c);
        c->connected = true;
        if (!cxSupported && !uiRunning) {
            NotificationTerminateCall(uuid, name, sip, number, nullptr, nullptr, this->app, this->domain, this->appdomain);
            if (uuidTerminateNotify == NULL) uuidTerminateNotify = _strdup(uuid);
            else if (uuid && strcmp(uuid, uuidTerminateNotify)) uuidTerminateNotifyConf = _strdup(uuid);
        }
    }
}

void SignalingApp::ReportCallHold(const char * uuid)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::ReportCallHold uuid: %s", uuid);

    if (cxSupported && callKit) callKit->CxRequestSetHeldCall(uuid, true);

    if (!calls.empty()) {
        SignalingAppCall *c;
        for (c = calls.front(); c; c = c->goNext()) {
            if (c->uuid && uuid && !strcmp(c->uuid, uuid)) {
                c->hold = true;
                c->connected = false;
                break;
            }
        }
    }
}

void SignalingApp::ReportCallRetrieve(const char * uuid)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::ReportCallRetrieve uuid: %s", uuid);

    if (cxSupported && callKit) callKit->CxRequestSetHeldCall(uuid, false);
}

void SignalingApp::PhoneAnswerCallAction(const char * uuid)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::PhoneAnswerCallAction %s", uuid);

    // Call answered with the notification. Set other calls on hold.
    if (!calls.empty()) {
        SignalingAppCall *c;
        for (c = calls.front(); c; c = c->goNext()) {
            if (c->connected) {
                signaling->PhoneHoldCallAction(c->uuid);
                c->connected = false;
                c->hold = true;
            }
            else if (!c->incoming) {
                // there is an outbound (not-connected) call pending
                // MUST NOT answer inbound call in this case
                // otherwise we end up in unwanted local conference
                return;
            }
            else if (!uiRunning) {
                c->connected = true;
                c->hold = false;
            }
        }
    }
    if (strstr(uuid, "sip:")) {
        const char* sip = &uuid[4];
        signaling->PhoneDialNumber(nullptr, sip, nullptr, false, true);
    }
    else if (strstr(uuid, "numOff:")) {
        const char* num = &uuid[7];
        signaling->PhoneDialNumber(num, nullptr, nullptr, false, false);
    }
    else if (strstr(uuid, "num:")) {
        const char* num = &uuid[4];
        signaling->PhoneDialNumber(num, nullptr, nullptr, false, false, false);
    }
    else if (strstr(uuid, "hold:")) {
    }
    else signaling->PhoneAnswerCallAction(uuid);
}

void SignalingApp::RequestStartCall(const char * uuid, const char * number, const char * name, bool video, bool sendingComplete)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::RequestStartCall uuid: %s number: %s name: %s", uuid, number, name );
    if(cxSupported && callKit) callKit->CxRequestStartCall( uuid, number, name, video );
    else signaling->PhoneStartCallAction(uuid, number, name, video, sendingComplete);
}

void SignalingApp::RequestStartRinging(const char* uuid)
{
    SignalingAppCall *c = NULL;
    if (!calls.empty()) {
        for (c = calls.front(); c; c = c->goNext()) {
            if (uuid && c->uuid && !strcmp(uuid, c->uuid)) break;
        }
    }
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::RequestStartRinging uuid: %s %u %u", uuid, cxSupported, (!c || c->showIncomingCallUI));
    if ((cxSupported == false) || !c || c->showIncomingCallUI) signaling->StartRinging(uuid);
}

bool SignalingApp::IsSoftPhoneStandardApp()
{
    return (ILocalStorage::GetItem("SignalingServiceUrl") && (strlen(ILocalStorage::GetItem("SignalingServiceUrl")) > 1));
}

void SignalingApp::ReportOutgoingCallAlerting(const char *uuid, const char * name, const char * sip, const char * num, int numOffset, bool ignoreCallkit)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::ReportOutgoingCallAlerting %s ignoreCallkit %d", uuid, ignoreCallkit);

    if(cxSupported && !ignoreCallkit) {
        if(callKit) callKit->CxReportOutgoingCallConnecting(uuid);
    }
    else if (!uiRunning) {
        NotificationTerminateCall(uuid, name, sip, num, nullptr, nullptr, this->app, this->domain, this->appdomain);
        if(uuidTerminateNotify == NULL) uuidTerminateNotify = _strdup(uuid);
        else if(uuid && strcmp(uuid, uuidTerminateNotify)) uuidTerminateNotifyConf = _strdup(uuid);
    }

    SignalingAppCall *c = NULL; 
    if (!calls.empty()) {
        for (c = calls.front(); c; c = c->goNext()) {
            if (uuid && c->uuid && !strcmp(uuid, c->uuid)) break;
        }
    }
    if (!c) {
        if (calls.empty()) {
            if (signalingAppUser) signalingAppUser->SignalingCallsActive(true);
        }
        calls.push_back(new class SignalingAppCall(uuid, num, numOffset, sip, name, nullptr, nullptr, false));
    }
}

void SignalingApp::UserSession(bool up)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::UserSession %d(%d) (%s) (%s) hw:%s defaultApi:%s calls:%d", up, uiRunning, uuidTerminateNotify ? uuidTerminateNotify : "-", uuidTerminateNotifyConf ? uuidTerminateNotifyConf : "-", hw ? hw : "-", this->defaultApiProvider ? this->defaultApiProvider : "-", !calls.empty());
    if (up) uiRunning++;
    else if (uiRunning) uiRunning--;
    if (uiRunning) {
        if(uuidTerminateNotify) {
            char ref[256];
            _sprintf(ref, "drop:%s", uuidTerminateNotify);
            RemoveNotification(ref);
            _sprintf(ref, "hold:%s", uuidTerminateNotify);
            RemoveNotification(ref);
            free(uuidTerminateNotify);
            uuidTerminateNotify = NULL;
        }
        if(uuidTerminateNotifyConf) {
            char ref[256];
            _sprintf(ref, "drop:%s", uuidTerminateNotifyConf);
            RemoveNotification(ref);
            _sprintf(ref, "hold:%s", uuidTerminateNotifyConf);
            RemoveNotification(ref);
            free(uuidTerminateNotifyConf);
            uuidTerminateNotifyConf = NULL;
        }
    }
    else {
        if (!calls.empty()) {
            SignalingAppCall *c;
            for (c = calls.front(); c; c = c->goNext()) {
                if (c->uuid) {
                    if (c->hold) NotificationHoldTerminateCall(c->uuid, c->name, c->sip, c->number, c->diverting, c->called, this->app, this->domain, this->appdomain);
                    else NotificationTerminateCall(c->uuid, c->name, c->sip, c->number, c->diverting, c->called, this->app, this->domain, this->appdomain);
                    if(uuidTerminateNotify == NULL) uuidTerminateNotify = _strdup(c->uuid);
                    else if (strcmp(c->uuid, uuidTerminateNotify)) uuidTerminateNotifyConf = _strdup(c->uuid);
                }
            }
        }
        if ((defaultApiProviderRemoved || !hw) && signaling && !signaling->NumActiveCalls()) {
            signaling->CloseClient();
            if(this->defaultApiProvider) free(this->defaultApiProvider);
            this->defaultApiProvider = NULL;
            defaultApiProviderRemoved = false;
        }
        if (windowTitle) {
            free(windowTitle);
            windowTitle = _strdup(innovaphoneMyapps);
        }
    }
}

void SignalingApp::SetDnd(bool dnd)
{
    this->dnd = dnd;
}

void SignalingApp::SendEvent(int code, char* error, char* src)
{
    char hostname[256];
    if (!ISystem::GetHostname(hostname, sizeof(hostname))) {
        hostname[0] = '\0';
    }
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::SendEvent code=%x error=%s src=%s,%s,%s", code, error ? error : "-", hostname, currentUserSip ? currentUserSip : "-", src ? src : "-");

    char tSrc[2048];
    _sprintf(tSrc, "%s / %s / %s", hostname, currentUserSip ? currentUserSip : "", src ? src : "");

    if (iLauncherApi) iLauncherApi->SendEvent(code, 0x1, ITime::TimeStampMilliseconds() / 1000, error, nullptr, tSrc);
}

SignalingApiPhoneConsumer::SignalingApiPhoneConsumer(class SignalingApp * client) :
    ApiConsumer(client, "com.innovaphone.phone")
{
    this->signaling = client;
}

SignalingApiPhoneConsumer::~SignalingApiPhoneConsumer()
{
    if(signaling) signaling->Log(LOG_SIGNALING, "SignalingApiPhoneConsumer::~SignalingApiPhoneConsumer");
}

void SignalingApiPhoneConsumer::ApiConsumerUpdate(class json_io & model, word base)
{
    //if(log) log->Log(LOG_SIGNALING, "SignalingApiPhoneConsumer::ApiConsumerUpdate");
    if (base != JSON_ID_NONE) {
        word last = 0, t, end = 0xFFFF;
        while ((t = model.get_object(base, last)) != end) {
            const char * hw = model.get_name(t);
            const char * title = model.get_string(t, "title");
            const char * url = model.get_string(t, "url");
            word info = model.get_object(t, "info");
            if (info != JSON_ID_NONE) {
                const char * app = model.get_string(info, "type");
                if (app) {
                    signaling->PhoneAppAvailable(hw, app, title, url);
                }
            }
        }
    }
}

void SignalingApiPhoneConsumer::ApiConsumerDefaultProvider(const char * defaultApiProvider)
{
    signaling->DefaultApiProvider(defaultApiProvider);
}

void SignalingApiPhoneConsumer::ConfigureSoftphone(const char * appname)
{
    if(signaling) signaling->Log(LOG_SIGNALING, "SignalingApiPhoneConsumer::ConfigureSoftphone %s", appname);
    char buffer[1024];
    json_io json(0);
    word base = json.add_object(JSON_ID_ROOT, 0);
    json.add_string(base, "mt", "ConfigureSoftphone");
    ApiConsumerSend(appname, 0, json, base, buffer);
}

SignalingApiLookupConsumer::SignalingApiLookupConsumer(class SignalingApp* client) :
    ApiConsumer(client, "com.innovaphone.phonelookup")
{
    this->signaling = client;
}

SignalingApiLookupConsumer::~SignalingApiLookupConsumer()
{
    if(signaling) signaling->Log(LOG_SIGNALING, "SignalingApiLookUpConsumer::~SignalingApiLookupConsumer");
}

void SignalingApiLookupConsumer::Lookup(const char * src, const char * number, const char * prefixIntl, const char * prefixNtl, const char * prefixExt, const char * country, const char * area, const char * subscriber)
{
    if(signaling) signaling->Log(LOG_SIGNALING, "SignalingApiLookupConsumer::LookUp %s src %s", number ? number : "", src ? src : "");
    if (!number) return;

    char buffer[1024];
    json_io json(0);
    word base = json.add_object(JSON_ID_ROOT, 0);
    json.add_string(base, "mt", "Lookup");
    json.add_string(base, "lookup", number);
    json.add_string(base, "prefixIntl", prefixIntl);
    json.add_string(base, "prefixNtl", prefixNtl);
    json.add_string(base, "prefixExt", prefixExt);
    json.add_string(base, "country", country);
    json.add_string(base, "area", area);
    json.add_string(base, "subscriber", subscriber);
    ApiConsumerSend("*", src, json, base, buffer);
}

void SignalingApiLookupConsumer::ApiConsumerRecv(const char* provider, const char* src, class json_io& msg, word base)
{
    const char* mt = msg.get_string(base, "mt");

    if(signaling) signaling->Log(LOG_SIGNALING, "SignalingApiLookupConsumer::ApiConsumerRecv mt:%s", mt ? mt : "");

    if(mt && !strcmp(mt, "LookupInfo")) signaling->LookupInfo(src, msg, base);
}

SignalingApiAvatarConsumer::SignalingApiAvatarConsumer(class SignalingApp * client) :
    ApiConsumer(client, "com.innovaphone.avatar")
{
    this->url = nullptr;
    this->base = nullptr;
    this->domain = nullptr;
    this->filename = nullptr;
    this->token = nullptr;
    this->client = client;
    IRandom::GetChars(this->salt, 16, random_chars_url); this->salt[16] = 0;
}

SignalingApiAvatarConsumer::~SignalingApiAvatarConsumer()
{
    client->Log(LOG_SIGNALING, "SignalingApiAvatarConsumer::~SignalingApiAvatarConsumer");
    if (this->url) free(this->url);
    if (this->base) free(this->base);
    if (this->domain) free(this->domain);
    if (this->filename) free(this->filename);
    if (this->token) free(this->token);
}

void SignalingApiAvatarConsumer::ApiConsumerUpdate(class json_io & model, word base)
{
    word usersappi = model.get_object(base, "usersapis");
    if (usersappi != JSON_ID_NONE) {
        if (this->url) free(this->url);
        this->url = _strdup(model.get_string(usersappi, "url"));
        word info = model.get_object(usersappi, "info");
        if (info != JSON_ID_NONE) {
            if (domain) free(domain);
            if (filename) free(filename);
            if (token) free(token);
            domain = _strdup(model.get_string(info, "domain"));
            filename = _strdup(model.get_string(info, "filename"));
            token = _strdup(model.get_string(info, "token"));
        }
    }

    if (url && filename) {
        if (this->base)
            free(this->base);
        this->base = (char *)malloc(strlen(this->url)+strlen(filename)+32);
        memcpy(this->base, url, strlen(url));
        this->base[strlen(url)] = 0;
        const char * slash = strrchr(this->base, 0x2f); // last occurrence of "/"
        if (slash) {
            int baseLen = slash - this->base + 1;
            this->base[baseLen] = 0;
            strcat(this->base, filename);
        }
    }

    client->Log(LOG_SIGNALING, "SignalingApiAvatarConsumer::ApiConsumerUpdate url=%s domain=%s filename=%s token=%s", this->base ? this->base : "", domain ? domain : "", filename ? filename : "", token ? token : "");
}

char * SignalingApiAvatarConsumer::GetAvatarUrl(const char * sip, const char * dn, const char * domain, const char * appdomain, const char * currentUserSip)
{
    client->Log(LOG_SIGNALING, "SignalingApiAvatarConsumer::GetAvatarUrl base=%s sip=%s dn=%s domain=%s appdomain=%s token=%s user=%s", this->base ? this->base : "", sip ? sip : "", dn ? dn : "", domain ? domain : "", appdomain ? appdomain : "", token ? token : "", currentUserSip ? currentUserSip : "");

    char * avatarUrl = NULL;
    if (this->base && token && sip && dn && currentUserSip) {
        class hash hash;
        byte digestBin[HASH_SIZE_SHA256];
        const char *at = strchr(sip, '@');
        size_t sipLen = at ? at - sip : strlen(sip);
        char * sipBuf = (char*)alloca(sipLen + 1);
        memcpy(sipBuf, sip, sipLen);
        sipBuf[sipLen] = '\0';

        hash.init(HASH_SHA256);
        hash.update(token, strlen(token));
        hash.update(salt, strlen(salt));
        hash.update(sipBuf, sipLen);
        hash.final(digestBin);

        char* digestCheck = (char*)alloca(HASH_SIZE_SHA256 * 2 + 1);
        for (int i = 0; i < HASH_SIZE_SHA256; i++) sprintf(&digestCheck[i * 2], "%02x", digestBin[i]);

        avatarUrl = (char *)malloc(strlen(base)+(sipLen*3)+(strlen((char *)digestCheck)*3)+(strlen(dn)*3)+256);
        strcpy(avatarUrl, base);

        strcat(avatarUrl, "?sip=");
        dword lenEncoded = (dword)sipLen * 3 + 1;
        char * sipEncoded = (char *)malloc(lenEncoded);
        str::to_url(sipBuf, sipEncoded, lenEncoded);
        strcat(avatarUrl, sipEncoded);
        free(sipEncoded);

        strcat(avatarUrl, "&salt=");
        strcat(avatarUrl, salt);
        strcat(avatarUrl, "&auth=");

        lenEncoded = strlen(digestCheck)+1;
        char * digestEncoded = (char *)malloc(lenEncoded);
        str::to_url((char *)digestCheck, digestEncoded, lenEncoded);
        strcat(avatarUrl, digestEncoded);
        free(digestEncoded);

        strcat(avatarUrl, "&size=240");
        if (dn) {
            lenEncoded = strlen(dn) * 3 + 1;
            char * dnEncoded = (char *)malloc(lenEncoded);
            str::to_url(dn, dnEncoded, lenEncoded);

            strcat(avatarUrl, "&dn=");
            strcat(avatarUrl, dnEncoded);

            free(dnEncoded);
        }
        strcat(avatarUrl, "&user=");
        strcat(avatarUrl, currentUserSip);
        strcat(avatarUrl, "&dom=@");
        strcat(avatarUrl, domain);
        if(!strcmp(sip, currentUserSip)) strcat(avatarUrl, "&no-cache");

        debug->printf("SignalingApiAvatarConsumer::GetAvatarUrl %s", avatarUrl);
    }
    return avatarUrl;
}

void SignalingApp::ReportIncomingCall(const char* uuid, const char * name, const char * sip, const char * num, const char * diverting, const char * called, const char * app, const char * domain, const char * appdomain, bool noAccept)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::ReportIncomingCall uuid=%s", uuid);

    char title[1024];
    int tlen = _sprintf(title, "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_CALL_FROM, langId));
    _sprintf(&title[tlen], "%s", name ? name : num ? num : sip ? sip : "anonymous");

    int bodyLen = 32;
    bodyLen += called ? strlen(called) : 0;
    bodyLen += diverting ? strlen(diverting) : 0;
    bodyLen += strlen(MYAPPS_LANG_STRING(TXT_SIGNALING_VIA, langId));
    bodyLen += strlen(MYAPPS_LANG_STRING(TXT_SIGNALING_AND, langId));
    bodyLen += app ? strlen(app) : 0;
    char * body = (char *)malloc(bodyLen);
    if (called && diverting) {
        int tlen = _sprintf(body, "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_VIA, langId));
        tlen += _sprintf(&body[tlen], "%s ", called);
        tlen += _sprintf(&body[tlen], "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_AND, langId));
        tlen += _sprintf(&body[tlen], "%s", diverting);
    }
    else if (diverting) {
        int tlen = _sprintf(body, "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_VIA, langId));
        tlen += _sprintf(&body[tlen], "%s", diverting);
    }
    else {
        _sprintf(body, "%s ", app);
    }

    char * avatarUrl = GetAvatarUrl(sip, name, domain, appdomain);
    if (avatarUrl) {
        if(log) log->Log(LOG_SIGNALING, "avatarUrl %s", avatarUrl);
    }

    const char * buttonTitle = MYAPPS_LANG_STRING(TXT_SIGNALING_SHOW, langId);
    const char * acceptTitle = MYAPPS_LANG_STRING(TXT_SIGNALING_CONNECT, langId);
    const char * rejectTitle = MYAPPS_LANG_STRING(TXT_SIGNALING_REJECT, langId);
    if (noAccept) acceptTitle = nullptr;
    if (showNotifications) notifications->AddLocalNotification(uuid, LOCAL_NOTIFICATION_TYPE_PHONE_CALL, title, body, nullptr, avatarUrl ? avatarUrl : nullptr, nullptr, buttonTitle, acceptTitle, rejectTitle, nullptr, nullptr, this);

    free(body);
    if (avatarUrl) free(avatarUrl);
}

void SignalingApp::RemoveNotification(const char * ref)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::RemoveNotification ref=%s", ref);

    if (showNotifications) notifications->RemoveLocalNotification(ref);
}

void SignalingApp::ReportMissedCall(const char* uuid, const char * name, const char * sip, const char * num, int numOffset, const char * diverting, const char * called, const char * app, const char * domain, const char * appdomain)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::ReportMissedCall uuid=%s sip=%s num=%s(%d)", uuid, sip ? sip : "", num ? num : "", numOffset);

    char ref[256];
    if (sip) _sprintf(ref, "sip:%s", sip);
    else if (num) {
        if(numOffset) _sprintf(ref, "numOff:%s", num);
        else _sprintf(ref, "num:%s", num);
    }
    else ref[0] = 0;

    char title[256];
    int tlen = _sprintf(title, "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_MISSED_CALL_FROM, langId));
    _sprintf(&title[tlen], "%s", name ? name : num ? num : sip ? sip : "anonymous");

    int bodyLen = 32;
    bodyLen += called ? strlen(called) : 0;
    bodyLen += diverting ? strlen(diverting) : 0;
    bodyLen += strlen(MYAPPS_LANG_STRING(TXT_SIGNALING_VIA, langId));
    bodyLen += strlen(MYAPPS_LANG_STRING(TXT_SIGNALING_AND, langId));
    bodyLen += app ? strlen(app) : 0;
    char * body = (char *)malloc(bodyLen);
    if (called && diverting) {
        int tlen = _sprintf(body, "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_VIA, langId));
        tlen += _sprintf(&body[tlen], "%s ", called);
        tlen += _sprintf(&body[tlen], "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_AND, langId));
        tlen += _sprintf(&body[tlen], "%s", diverting);
    }
    else if (diverting) {
        int tlen = _sprintf(body, "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_VIA, langId));
        tlen += _sprintf(&body[tlen], "%s", diverting);
    }
    else {
        _sprintf(body, "%s ", app);
    }

    if (showNotifications) {
        char* avatarUrl = GetAvatarUrl(sip, name, domain, appdomain);
        notifications->AddLocalNotification(ref, LOCAL_NOTIFICATION_TYPE_END_PHONE_CALL, title, body, nullptr, avatarUrl ? avatarUrl : nullptr, nullptr, MYAPPS_LANG_STRING(TXT_SIGNALING_CALL_BACK, langId), nullptr, nullptr, nullptr, nullptr, this);
        if (avatarUrl) free(avatarUrl);
    }
    free(body);
}

void SignalingApp::NotificationTerminateCall(const char* uuid, const char * name, const char * sip, const char * num, const char * diverting, const char * called, const char * app, const char * domain, const char * appdomain)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApiConsumer::NotificationTerminateCall uuid=%s", uuid);

    char ref[256];
    _sprintf(ref, "drop:%s", uuid);

    char title[256];
    int tlen = _sprintf(title, "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_CALL_FROM, langId));
    _sprintf(&title[tlen], "%s", name ? name : num ? num : sip ? sip : "anonymous");

    int bodyLen = 32;
    bodyLen += called ? strlen(called) : 0;
    bodyLen += diverting ? strlen(diverting) : 0;
    bodyLen += strlen(MYAPPS_LANG_STRING(TXT_SIGNALING_VIA, langId));
    bodyLen += strlen(MYAPPS_LANG_STRING(TXT_SIGNALING_AND, langId));
    bodyLen += app ? strlen(app) : 0;
    char * body = (char *)malloc(bodyLen);
    if (called && diverting) {
        int tlen = _sprintf(body, "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_VIA, langId));
        tlen += _sprintf(&body[tlen], "%s ", called);
        tlen += _sprintf(&body[tlen], "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_AND, langId));
        tlen += _sprintf(&body[tlen], "%s", diverting);
    }
    else if (diverting) {
        int tlen = _sprintf(body, "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_VIA, langId));
        tlen += _sprintf(&body[tlen], "%s", diverting);
    }
    else {
        _sprintf(body, "%s ", app);
    }

#if 0
    char * avatarUrl = signalingApp->GetAvatarUrl(sip, name, domain, appdomain);
    if (avatarUrl) {
        json.add_string(base, "largeIcon", avatarUrl);
        free(avatarUrl);
    }
#endif

    if (showNotifications) notifications->AddLocalNotification(ref, LOCAL_NOTIFICATION_TYPE_END_PHONE_CALL, title, body, nullptr, nullptr, nullptr, MYAPPS_LANG_STRING(TXT_SIGNALING_TERMINATE_CALL, langId), nullptr, nullptr, nullptr, nullptr, this);

    free(body);
}

void SignalingApp::NotificationHoldTerminateCall(const char* uuid, const char * name, const char * sip, const char * num, const char * diverting, const char * called, const char * app, const char * domain, const char * appdomain)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApiConsumer::NotificationHoldTerminateCall uuid=%s", uuid);


    char ref[256];
    _sprintf(ref, "hold:%s", uuid);

    char title[256];
    int tlen = _sprintf(title, "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_CALL_FROM, langId));
    _sprintf(&title[tlen], "%s", name ? name : num ? num : sip ? sip : "anonymous");

    int bodyLen = 32;
    bodyLen += called ? strlen(called) : 0;
    bodyLen += diverting ? strlen(diverting) : 0;
    bodyLen += strlen(MYAPPS_LANG_STRING(TXT_SIGNALING_VIA, langId));
    bodyLen += strlen(MYAPPS_LANG_STRING(TXT_SIGNALING_AND, langId));
    bodyLen += app ? strlen(app) : 0;
    char * body = (char *)malloc(bodyLen);
    if (called && diverting) {
        int tlen = _sprintf(body, "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_VIA, langId));
        tlen += _sprintf(&body[tlen], "%s ", called);
        tlen += _sprintf(&body[tlen], "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_AND, langId));
        tlen += _sprintf(&body[tlen], "%s", diverting);
    }
    else if (diverting) {
        int tlen = _sprintf(body, "%s ", MYAPPS_LANG_STRING(TXT_SIGNALING_VIA, langId));
        tlen += _sprintf(&body[tlen], "%s", diverting);
    }
    else {
        _sprintf(body, "%s ", app);
    }

#if 0
    char * avatarUrl = signalingApp->GetAvatarUrl(sip, name, domain, appdomain);
    if (avatarUrl) {
        json.add_string(base, "largeIcon", avatarUrl);
        free(avatarUrl);
    }
#endif

    if (showNotifications) notifications->AddLocalNotification(ref, LOCAL_NOTIFICATION_TYPE_HOLD_PHONE_CALL, title, body, nullptr, nullptr, nullptr, nullptr, MYAPPS_LANG_STRING(TXT_SIGNALING_CONNECT, langId), MYAPPS_LANG_STRING(TXT_SIGNALING_TERMINATE_CALL, langId), nullptr, nullptr, this);

    free(body);
}

void SignalingApp::PhoneEndCallAction(const char* uuid, int callError)
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp::PhoneEndCallAction uuid=%s error=%d", uuid, callError);
    if (strstr(uuid, "hold:")) {
        const char * tUuid = &uuid[5];
        signaling->PhoneEndCallAction(tUuid, 0);
        ReportCallEnded(uuid, 0);
    }
    else signaling->PhoneEndCallAction(uuid, callError);
}

void SignalingApp::ClientConnected()
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp(%p)::ClientConnected", this);
    this->remoteServicesClientConnected = true;
}

void SignalingApp::ClientDisconnected()
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp(%p)::ClientDisconnected calls=%d", this, !calls.empty());
    this->remoteServicesClientConnected = false;
    if (!calls.empty()) {
        SignalingAppCall* c = calls.front();
        while (c) {
            class SignalingAppCall* n = c->goNext();
            // PhoneEndCallAction will probably remove and delete c!
            signaling->PhoneEndCallAction(c->uuid, 0);
            c = n;
        }
    }
}

void SignalingApp::ServerConnected()
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp(%p)::ServerConnected", this);
    this->remoteServicesServerConnected = true;
    this->showNotifications = false;
}

void SignalingApp::ServerDisconnected()
{
    if(log) log->Log(LOG_SIGNALING, "SignalingApp(%p)::ServerDisconnected", this);
    remoteServicesServerConnected = false;
    this->showNotifications = this->notifications ? true : false;
}

void SignalingApp::RegistrationCheck()
{
    if (signaling) signaling->RegistrationCheck();
}
