
/*---------------------------------------------------------------------------*/
/* plugin_main.cpp                                                           */
/* copyright (c) innovaphone 2012                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#define PLUGIN_MAIN_CPP_

#include <ctype.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "platform/platform.h"

#include "common/build/release.h"
#include "common/os/iomux.h"
#include "common/interface/time.h"
#include "common/interface/socket.h"
#include "common/interface/random.h"
#include "common/interface/task.h"
#include "common/interface/stask.h"
#include "common/interface/audio.h"
#include "common/interface/ringer.h"
#include "common/interface/media.h"
#include "common/interface/appsharing.h"
#include "common/interface/webserver_plugin.h"
#include "common/interface/json_api.h"
#include "common/interface/file.h"
#include "common/interface/process.h"
#include "common/interface/local_storage.h"
#include "common/interface/ethernet.h"
#include "common/interface/notifications-if.h"
#include "common/interface/launcherapi.h"
#include "common/interface/remote_services.h"

#include "common/ilib/json.h"
#include "common/ilib/base64.h"
#include "common/ilib/str.h"

#include "common/lib/tasks_file.h"
#include "common/lib/appservice.h"
#include "common/lib/appwebsocket.h"

#include "common/lib/language.h"
#include "client/lang/myapps_languages.h"

#include "client/logflags/debug_logflags.h"
#include "client/media/appclient_media.h"
#include "client/ringer/appclient_ringer.h"
#include "client/config/debug_readlog.h"

#include "client/remote_services/remote_services_media_if.h"
#include "client/remote_services/remote_services.h"

#include "common/linux/udp/udpsocket.h"
#include "common/linux/tcp/tcpsocket.h"
#include "common/linux/uds/udsocket.h"
#include "client_sockets.h"
#include "appclient_main.h"
#include "plugin_main.h"

extern const char *get_files_dir();
extern char get_separator_char();
extern void copy_to_external(const char *file_name);

USING_NAMESPACE

#if defined(__ANDROID__)
const char * _PRODUCT_NAME_ = "myApps for Android";
#elif defined(_IOS_)
#if TARGET_OS_IPHONE
const char * _PRODUCT_NAME_ = "myApps for iOS";
#elif _MACOS_PLUGIN_
const char * _PRODUCT_NAME_ = "myApps Plugin for MacOS";
#else
const char * _PRODUCT_NAME_ = "myApps for macOS";
#endif
#elif defined(_WINDOWS)
const char * _PRODUCT_NAME_ = "myApps for Windows";
#else
const char * _PRODUCT_NAME_ = "myApps for Linux";
#endif

#define CONFIG_FILE_NAME "config.json"
#define MAX_CONFIG_LENGTH 10000

// To be fixed
#if 0
//const char *default_tunes = 0;
//const char *default_tones = 0;
//onVideoStart
//onVideoEnd
//onHandsetStart();
//onHandsetEnd();
//onHeadsetStart
//onHeadsetEnd
//onSpeakerphoneStart
//onSpeakerphoneEnd
//ios_main_sleep
//onCallPresentationStart
//onCallPresentationEnd
//launchSenncomSDK
//mouseCursorImage
//mouseCursorHotSpot
//phone_ios_ios_phone
//GetMouseLocation
//getIconFromPID

extern "C" void ns_log(const char *format, ...)
{
}

extern "C" void request_restart_dsp()
{
}
#endif

int IProcess::System(class IInstanceLog * const log, const char * command, ...) { return 0; }

bool IEthernet::GetMACAddress(byte * mac, class IInstanceLog * const log)
{
    mac[0] = 0x02;
    mac[1] = 0x00;
    mac[2] = 0x00;
    mac[3] = 0x00;
    mac[4] = 0x00;
    mac[5] = 0x00;
    return true;
}

int phoneLanguageId = MYAPPS_LANGUAGE_EN;

#if 0
class Browser : public ULauncherApi, public IInstanceLog, public IShutdownHandler, public UTask, public STaskContext, public UClient, public USignalingApp,
    public ICallKit, public INotifications, public UNotifications, public UAppProxy,
    public UIoExecSubclass<AppClientWriteConfig>,
    public UTimerSubclass<AppClientConnectingTimer>,
    public UClientConfigItems,
    public ClientConfigUser
{
    void SetExec(IIoMux * iomux, UIoExecSubclass<AppClientWriteConfig> * ioContext, AppClientWriteConfig * execContext) { iomux->SetExec(ioContext, execContext); };
    void IoExec(AppClientWriteConfig * context);

    IIoMux * iomux;
    UAppPlugintMain * appClientMainUser;
    class DebugLogFlags * debugLogFlags;
    class IClient * client;
    class IAppProxy * appproxy;
    class IAppClientMedia * mediaClient;
    class ISignalingConfig * signalingConfig;
    char *url;
    char *webserverUrl;
    char *webserverPassword;
    char *proxy;
    unsigned proxyLen;
    char *startFixCertUrl;
    byte *fixCert;
    size_t fixCertLen;
    char *autostartSettingsApp;
    char *appClientConnectionUrl;
    AppClientConnectingTimer appClientConnectingTimer;
    bool appClientConnected;
    unsigned registrationCheckRequests;
    class TaskFileWrite * write;
    bool writeConfig;
    bool writeConfigScheduled;
    bool stopping;
    word readConfigBase;
    char readConfigBuffer[MAX_CONFIG_LENGTH];
    class json_io readConfigJson;

    ClientConfig clientConfig;
    void ClientConfigItemAdded(IClientConfigItem * item) override;
    void ClientConfigItemSet(IClientConfigItem * item) override;
    void Apply(ClientConfigLogFlags * item) override;
    void Apply(ClientConfigNoHexdump * item) override;
    void Apply(ClientConfigOrientationAdaptationOff * item) override;
    void Apply(ClientConfigDisplayOnIfCharging * item) override;
    void Apply(ClientConfigNoPushAutostart * item) override;
    void Apply(ClientConfigAudioApi * item) override;
    void Apply(ClientConfigBluetoothHeadsetMode * item) override;
    void Apply(ClientConfigPhoneAccountEnabled * item) override;
    void Apply(ClientConfigRingInHeadset * item) override;
    void Apply(ClientConfigActiveAfterClose * item) override;
    void Apply(ClientConfigActiveAfterCloseAsked * item) override;
    void Apply(ClientConfigAddressBookInaccessibleWarned * item) override;
    void Apply(ClientConfigPushNotifications * item) override;
    void Apply(ClientConfigPushPassword * item) override;
    void Apply(ClientConfigDownloadedBuild * item) override;
    void Apply(ClientConfigNoVpnAddresses * item) override;
    void Apply(ClientConfigShowDesktopNotifications * item) override;
    void Apply(ClientConfigAutostart * item) override;
    void Apply(ClientConfigDocking * item) override;
    void Apply(ClientConfigAutoOffline * item) override;
    void Apply(ClientConfigHotkeyDial * item) override;
    void Apply(ClientConfigHotkeyAccept * item) override;
    void Apply(ClientConfigHotkeyReject * item) override;
    void Apply(ClientConfigHotkeyAppLink0 * item) override;
    void Apply(ClientConfigHotkeyAppLink1 * item) override;
    void Apply(ClientConfigHotkeyAppLink2 * item) override;
    void Apply(ClientConfigRecordingUrl * item) override;
    void Apply(ClientConfigIsRecordingByDefaultOn * item) override;
    void Apply(ClientConfigRecordExternalOnly * item) override;
    void Apply(ClientConfigHideFromNativeCallList * item) override;
    void Apply(ClientConfigCurrentUserSip * item) override;
    void Apply(ClientConfigLocalStorage * item) override;

    istd::list<class ExternalApp> externalApps;
    class ExternalApps * externalAppsApi;
    char * externalAppsStr;

    const char * GetAppName() const { return "browser"; }
    const char * GetAppDomain() const { return ""; }

    void TimerOnTimeout(AppClientConnectingTimer * timer);

    void TaskComplete(class ITask * const task) override;
    void TaskFailed(class ITask * const task) override;
    void TaskProgress(class ITask * const task, dword progress) override;

    void Shutdown() override;
    void ShutdownTimeout() override;
    void TryStop();

    void ReadConfig();
    void WriteConfig();

    void AppProxyReady() override;
    void AppProxyConnectionMonitor(const char *url, bool up) override;

    void CxUse(bool use, const char *ringtone, bool supportsVideo, int maximumCallGroups, int maximumCallsPerCallGroup) override;
    void CxReportNewIncomingCall(const char *uuid, const char *number, const char *name, const char *transfer, const char *called,
        bool hasVideo, bool supportsDTMF, bool supportsHolding, bool supportsGrouping, bool supportsUngrouping) override;
    void CxReportOutgoingCallConnecting(const char *uuid) override;
    void CxReportOutgoingCallConnected(const char *uuid) override;
    void CxReportCallUpdated(const char *uuid, const char *number, const char *name, const char *transfer, const char *called,
        bool hasVideo, bool supportsDTMF, bool supportsHolding, bool supportsGrouping, bool supportsUngrouping) override;
    void CxReportCallEnded(const char *uuid, CxCallEndedReason reason) override;
    void CxRequestStartCall(const char *uuid, const char *number, const char *name, bool video) override;
    void CxRequestEndCall(const char *uuid) override;
    void CxRequestSetHeldCall(const char *uuid, bool held) override;
    void CxRequestSetMutedCall(const char *uuid, bool muted) override;
    void CxRequestSetGroupCall(const char *uuid, const char *with) override;
    void CxRequestPlayDTMF(const char *uuid, const char *digits) override;

    void AddLocalNotification(const char* ref, LocalNotificationType type, const char* title, const char* text, const char* smallIcon, const char* largeIcon,
        const char* soundUrl, const char* buttonTitle, const char* acceptTitle, const char* rejectTitle, const char* replyTitle, UNotifications * user) override;
    void RemoveLocalNotification(const char* ref) override;
    void ShowUpdateNotification(const char * title, const char * text, bool showAtOnce) override {}
    void SetShowDesktopNotifications(bool showDesktopNotifications) override {}

    void LocalNotificationClicked(const char* ref, enum LocalNotificationAction action, const char* replyText);

    void LauncherApiClosing(class ILauncherApi * launcherApi) override;
    void LauncherApiDialGsm(const char * number) override;
    void LauncherApiDisablePowerSaving(bool active);
    void LauncherApiAppWindowOpened(const char * app, const char * title);
    void LauncherApiSetResize(const char * app, int width, int height);
    void LauncherApiSetMaximized(const char * app, bool on);
    void LauncherApiRegistrationState(bool up) override;
    void LauncherApiShowIfLocked(const char * ref, bool show) override;
    void LauncherApiShowUnlock() override;
    void LauncherApiAddLocalNotification(const char * ref, LocalNotificationType type, const char * title, const char * text, const char * smallIcon, const char * largeIcon,
        const char * soundUrl, const char * buttonTitle, const char * acceptTitle, const char * rejectTitle, const char * replyTitle, void * ctx) override { AddLocalNotification(ref, type, title, text, smallIcon, largeIcon, soundUrl, buttonTitle, acceptTitle, rejectTitle, replyTitle, this); }
    void LauncherApiRemoveLocalNotification(const char * ref) override                                              { RemoveLocalNotification(ref); }
    void LauncherApiSetBadgeCount(const char * ref, unsigned count, const char * text) override;
    void LauncherApiStatusBarAppearance(bool hidden, bool blackText, dword color) override;
    void LauncherApiCacheable(const char * url, const char * filename) override;
    void LauncherApiSetPushPassword(const char * pwd) override;
    void LauncherApiSetDownloadedBuild(const char * build) override;
    void LauncherApiSoftwareUpdate() override;
    void LauncherApiNoPushAutostart(bool noPushAutostart) override;
    void LauncherApiSetLangId(const char* langId) override;
    void LauncherApiSetAdminSettings(class json_io& model, word baseSettings) override;
    void LauncherApiSetUser(const char * sip) override;
    void LauncherApiAppClientUrl(const char * url) override;
    void LauncherApiClose() override;
    void LauncherApiRccAppConnected(bool up) override;
    void SetAdminSettings(class json_io & json, word base, bool pbx, void * context);
    void SetClose(bool closeSupported);
    void SetDocking(bool docked);

    UWebserverGet * ClientWebserverGetAccept(const char * resourceName) override;
    void ClientConfigOptionsToJson(class json_io & json, word base, char * & tmp) override;
    void ClientConfigToJson(class json_io & json, word base, char * & tmp) override;
    void ClientJsonToConfig(class json_io & json, word base, void * context) override;
    void ClientGetLogActive(bool active) override;
    void ClientFixCert() override;
    bool ClientEMailLogAvailable() override;
    void ClientEMailLog() override;
    void ClientClearLog() override;
    void ClientAutostartSettingsApp(char * name, size_t name_len) override;
    void ClientStartAutostartSettingsApp(const char *name) override;

    void SignalingRegistrationState(bool up) override;
    void SignalingCallsActive(bool active) override;

    void SetExternalApp(int id, const char * name, const char * path, const char * param, bool autostart);
    void DelExternalApp(int id);
    void UpdateExternalApps();
    
public:
    Browser(class IIoMux * iomux, class UAppClientMain * appClientMainUser, class DebugLogFlags * debugLogFlags);
    ~Browser();

    UCallKit * callKitUser;
    LauncherApis launcherApis;
    class ISignalingApp * signalingApp;
    NotificationsUsers notificationsUsers;

    void SetClient(class IClient * client) { this->client = client; };
    void SetAppProxy(class IAppProxy * appproxy, const char * proxy);
    void SetUrl(const char *url, const char *webserverUrl, const char *webserverPassword, const char *startFixCertUrl, const byte *fixCert, size_t fixCertLen);
    void SetApiClientParameters(class ISocketProvider * localSocketProvider, class ISocketProvider * tcp, class ISocketProvider * tls, class IDns * dns, const char * apiRelayUrl, const char * servicePassword, const char * apiRelayPath);
    void ExternalAppsApiClientCloseComplete();
    void AddIAppClientMedia(class IAppClientMedia * const mediaClient);
    void AddISignalingConfig(class ISignalingConfig* const signalingConfig);

    void SetAutostartSettingsApp(const char *name);
    void SetActiveAfterClose(bool active);
    void SetActiveAfterCloseAsked(bool asked);
    void SetAddressBookInaccessibleWarned(bool warned);
    void PushNotification();
    void SetPhoneAccountEnabled(bool enabled);
    void SetLocalStorageItem(const char * id, const char * val);
    char * GetLocalStorageItem(const char * id);
    void DeleteLocalStorageItem(const char * id);
    void AddCallKitUser(class UCallKit * callKitUser) { this->callKitUser = callKitUser; }
    void AddNotificationsUser(class UNotifications * notificationsUser) { notificationsUsers.push_back(notificationsUser); }
    void AddILauncherApi(class ILauncherApi * const launcherApi) { launcherApis.push_back(launcherApi); };
    void SetSignalingApp(class ISignalingApp * signalingApp) { this->signalingApp = signalingApp; }

    void LauncherApiSetFocus(const char * app, const char * title);

    void RegistrationCheck();
};

ClientConfig * appclientConfig;

Browser::Browser(class IIoMux * iomux, class UAppClientMain * appClientMainUser, class DebugLogFlags * debugLogFlags) :
    STaskContext(iomux),
    appClientConnectingTimer(iomux, this),
    readConfigBase(JSON_ID_NONE),
    readConfigBuffer(""),
    readConfigJson(readConfigBuffer),
    clientConfig(this, this)
{
    this->iomux = iomux;
    this->appClientMainUser = appClientMainUser;
    this->debugLogFlags = debugLogFlags;
    this->client = 0;
    this->appproxy = 0;
    this->mediaClient = NULL;
    this->callKitUser = NULL;
    this->signalingApp = NULL;
    this->signalingConfig = NULL;
    this->externalAppsApi = NULL;
    this->externalAppsStr = NULL;
    url = NULL;
    webserverUrl = NULL;
    webserverPassword = NULL;
    proxy = NULL;
    startFixCertUrl = NULL;
    fixCert = NULL;
    autostartSettingsApp = NULL;
    appClientConnectionUrl = NULL;
    appClientConnected = false;
    registrationCheckRequests = 0;
    write = NULL;
    writeConfig = false;
    writeConfigScheduled = false;
    stopping = false;
    appclientConfig = &clientConfig;

    iomux->RegisterShutdownHandler(this);
    clientConfig.signalingApp = true;
    
    ReadConfig();
    debugLogFlags->Set(NULL, NULL, clientConfig.logFlags.value, clientConfig.noHexdump.value, this);

#ifdef _IOS_
    appClientMainUser->AppClientMainActiveAfterCloseAsked(clientConfig.activeAfterCloseAsked.value);
#endif
    if (clientConfig.pushPassword.value) appClientMainUser->AppClientMainSetPushPassword(clientConfig.pushPassword.value);
    appClientMainUser->AppClientMainPushNotifications(clientConfig.pushNotifications.value);
}

Browser::~Browser()
{
    if (url) free(url);
    if (webserverUrl) free(webserverUrl);
    if (webserverPassword) free(webserverPassword);
    if (proxy) free(proxy);
    if (startFixCertUrl) free(startFixCertUrl);
    if (fixCert) free(fixCert);
    if (autostartSettingsApp) free(autostartSettingsApp);
    if (appClientConnectionUrl) free(appClientConnectionUrl);
    if (externalAppsStr) free(externalAppsStr);
}

void Browser::SetAppProxy(class IAppProxy * appproxy, const char * proxy)
{
    this->appproxy = appproxy;
    if (this->proxy) free(this->proxy);
    this->proxyLen = strlen(proxy);
    this->proxy = (char *)(malloc(proxyLen + 1));
    memcpy(this->proxy, proxy, this->proxyLen + 1);
};

UWebserverGet * Browser::ClientWebserverGetAccept(const char * resourceName)
{
    size_t len = strlen(_BUILD_STRING_) + 128;
    char * tmp = (char *)alloca(len);
    _snprintf(tmp, len, "/%s/cert.mobileconfig", _BUILD_STRING_);
    if (!str::n_casecmp(resourceName, tmp, strlen (tmp))) {
        return (new class TaskGetCertMobileconfig(client, appClientMainUser, fixCert, fixCertLen));
    }
    if (!str::n_casecmp(resourceName, "/contactsphoto", strlen("/contactsphoto"))) {
        return (new class TaskWebserverGet(client, appClientMainUser, resourceName));
    }
    return NULL;
}

void Browser::ClientConfigOptionsToJson(class json_io & json, word base, char * & tmp)
{
#ifdef __ANDROID__
    word arr = json.add_array(base, "audioApis");
    unsigned i = 0;
    while (clientConfig.audioApi.map[i].name) {
        word api = json.add_object(arr, 0);
        json.add_string(api, "id", clientConfig.audioApi.map[i].name);
        json.add_string(api, "name", clientConfig.audioApi.map[i].name);
        i++;
    }
#endif
}

void Browser::ClientConfigToJson(class json_io & json, word base, char * & tmp)
{
    word baseObject;

    clientConfig.items.ToJson(json, base, tmp);
    baseObject = json.add_object(base, "signalingApp");
    json.add_bool(baseObject, "value", clientConfig.signalingApp);
    json.add_bool(baseObject, "force", false);
    if(!externalApps.empty()) {
        baseObject = json.add_array(base, "externalApps");
        ExternalApp * app = externalApps.front();
        while(app) {
            word e = json.add_object(baseObject, "app");
            json.add_int(e, "id", app->id, tmp);
            json.add_string(e, "name", app->name);
            json.add_string(e, "path", app->path);
            if(app->param) json.add_string(e, "param", app->param);
            json.add_bool(e, "autostart", app->autostart);
            app = app->goNext();
        }
    }
}

void Browser::ClientConfigItemAdded(IClientConfigItem * item)
{
    if (readConfigBase != JSON_ID_NONE) {
        clientConfig.items.SetItemAdminSettings(item, readConfigJson, readConfigBase, false);
    }
}

void Browser::ClientConfigItemSet(IClientConfigItem * item)
{
    WriteConfig();
}

void Browser::Apply(ClientConfigLogFlags * item)
{
    unsigned i;

    ulong64 mask = 0;
    for (i = 0; i < ICLIENT_LOG_FLAG_COUNT; i++) {
        mask |= IClient::LogFlagTable[i];
    }
    item->value = item->value & mask;
    debugLogFlags->Set(NULL, NULL, clientConfig.logFlags.value, clientConfig.noHexdump.value, this);
}

void Browser::Apply(ClientConfigNoHexdump * item)
{
    debugLogFlags->Set(NULL, NULL, clientConfig.logFlags.value, clientConfig.noHexdump.value, this);
}

void Browser::Apply(ClientConfigOrientationAdaptationOff * item)
{
    appClientMainUser->AppClientMainOrientationAdaptationOff(item->value);
}

void Browser::Apply(ClientConfigDisplayOnIfCharging * item)
{
    appClientMainUser->AppClientMainDisplayOnIfCharging(item->value);
}

void Browser::Apply(ClientConfigNoPushAutostart * item)
{
}

void Browser::Apply(ClientConfigAudioApi * item)
{
}

void Browser::Apply(ClientConfigBluetoothHeadsetMode * item)
{
#ifdef __ANDROID__
    appClientMainUser->AppClientMainBluetoothHeadsetMode(item->value);
#endif
}

void Browser::Apply(ClientConfigPhoneAccountEnabled * item)
{
}

void Browser::Apply(ClientConfigRingInHeadset * item)
{
#ifdef _IOS_
    appClientMainUser->AppClientMainRingInHeadset(item->value);
#endif
}

void Browser::Apply(ClientConfigActiveAfterClose * item)
{
#ifdef _IOS_
    appClientMainUser->AppClientMainActiveAfterClose(item->value);
#endif
}

void Browser::Apply(ClientConfigActiveAfterCloseAsked * item)
{
}

void Browser::Apply(ClientConfigAddressBookInaccessibleWarned * item)
{
}

void Browser::Apply(ClientConfigPushNotifications * item)
{
}

void Browser::Apply(ClientConfigPushPassword * item)
{
}

void Browser::Apply(ClientConfigDownloadedBuild * item)
{
}

void Browser::Apply(ClientConfigNoVpnAddresses * item)
{
    if (this->mediaClient) {
        this->mediaClient->SetNoVpnAddresses(item->value);
    }
    if (this->signalingConfig) {
        this->signalingConfig->SetNoVpnAddresses(item->value);
    }
}

void Browser::Apply(ClientConfigShowDesktopNotifications * item)
{
    appClientMainUser->AppClientMainSetShowDesktopNotifications(item->value);
}

void Browser::Apply(ClientConfigAutostart * item)
{
    appClientMainUser->AppClientMainSetAutostart(item->value);
}

void Browser::Apply(ClientConfigDocking * item)
{
    appClientMainUser->AppClientMainSetDocking(item->value);
}

void Browser::Apply(ClientConfigAutoOffline * item)
{
    appClientMainUser->AppClientMainSetOffline(item->value);
}

void Browser::Apply(ClientConfigHotkeyDial * item)
{
    appClientMainUser->AppClientMainSetHotkey(HotkeyType::HOTKEY_DIAL, item->value);
}

void Browser::Apply(ClientConfigHotkeyAccept * item)
{
    appClientMainUser->AppClientMainSetHotkey(HotkeyType::HOTKEY_ACCEPT, item->value);
}

void Browser::Apply(ClientConfigHotkeyReject * item)
{
    appClientMainUser->AppClientMainSetHotkey(HotkeyType::HOTKEY_REJECT, item->value);
}

void Browser::Apply(ClientConfigHotkeyAppLink0 * item)
{
    appClientMainUser->AppClientMainSetHotkey(HotkeyType::HOTKEY_APPLINK0, item->value);
}

void Browser::Apply(ClientConfigHotkeyAppLink1 * item)
{
    appClientMainUser->AppClientMainSetHotkey(HotkeyType::HOTKEY_APPLINK1, item->value);
}

void Browser::Apply(ClientConfigHotkeyAppLink2 * item)
{
    appClientMainUser->AppClientMainSetHotkey(HotkeyType::HOTKEY_APPLINK2, item->value);
}

void Browser::Apply(ClientConfigRecordingUrl * item)
{
    if (this->mediaClient) {
        this->mediaClient->SetRecordingUrl(item->value, item->force);
    }
    if (this->signalingConfig) {
        this->signalingConfig->SetRecordingParameters(clientConfig.recordingUrl.value, clientConfig.recordingUrl.force, clientConfig.isRecordingByDefaultOn.value, clientConfig.isRecordingByDefaultOn.force, clientConfig.recordExternalOnly.value, clientConfig.recordExternalOnly.force);
    }
}

void Browser::Apply(ClientConfigIsRecordingByDefaultOn * item)
{
    if (this->signalingConfig) {
        this->signalingConfig->SetRecordingParameters(clientConfig.recordingUrl.value, clientConfig.recordingUrl.force, clientConfig.isRecordingByDefaultOn.value, clientConfig.isRecordingByDefaultOn.force, clientConfig.recordExternalOnly.value, clientConfig.recordExternalOnly.force);
    }
}

void Browser::Apply(ClientConfigRecordExternalOnly * item)
{
    if (this->signalingConfig) {
        this->signalingConfig->SetRecordingParameters(clientConfig.recordingUrl.value, clientConfig.recordingUrl.force, clientConfig.isRecordingByDefaultOn.value, clientConfig.isRecordingByDefaultOn.force, clientConfig.recordExternalOnly.value, clientConfig.recordExternalOnly.force);
    }
}

void Browser::Apply(ClientConfigHideFromNativeCallList * item)
{
    appClientMainUser->AppClientMainHideFromNativeCallList(item->value);
}

void Browser::Apply(ClientConfigCurrentUserSip * item)
{
}

void Browser::Apply(ClientConfigLocalStorage * item)
{
}

void Browser::SetAdminSettings(class json_io & json, word base, bool pbx, void * context)
{
    word baseStart;
    bool present;

    clientConfig.items.SetAdminSettings(json, base, pbx);
    baseStart = json.get_object(base, "signalingApp");
    if (baseStart != JSON_ID_ROOT) {
        clientConfig.signalingApp = json.get_bool(baseStart, "value", &present);
    }
}

void Browser::ClientJsonToConfig(class json_io & json, word base, void * context)
{
    bool present;
    int extAppId = json.get_int(base, "externalAppId", &present);
    const char * extAppName = json.get_string(base, "externalAppName");
    const char * extAppPath = json.get_string(base, "externalAppPath");
    if(present) {
        if(extAppName && extAppPath) SetExternalApp(extAppId, extAppName, extAppPath, json.get_string(base, "externalAppParam"), json.get_bool(base, "externalAutostartApp"));
        else DelExternalApp(extAppId);
    }
    word baseSettings = json.get_object(base, "adminSettings");
    SetAdminSettings(json, (baseSettings != JSON_ID_ROOT) ? baseSettings : base, false, context);
    WriteConfig();
}

void Browser::ClientGetLogActive(bool active)
{
    debugLogFlags->GetLogActive(active);
}

void Browser::DelExternalApp(int id)
{
    this->Log(LOG_BROWSER, "Browser(%p)::DelExternalApp id=(%d)", this, id);
    if(!externalApps.empty()) {
        class ExternalApp * a = externalApps.front();
        while(a) {
            if(a->id == id) {
                a->remove();
                delete a;
                break;
            }
            a = a->goNext();
        }
    }
    UpdateExternalApps();
}

void Browser::SetExternalApp(int id, const char * name, const char * path, const char * param, bool autostart)
{
    this->Log(LOG_BROWSER, "Browser(%p)::SetExternalApp id=(%d) app=(%s) path=(%s) param=(%s) autostart=%d", this, id, name, path, param, autostart);
    class ExternalApp * a = NULL;
    if(!externalApps.empty()) {
        a = externalApps.front();
        while(a) {
            if(a->id == id) {
                a->Update(name, path, param, autostart);
                break;
            }
            a = a->goNext();
        }
    }
    if(!a) externalApps.push_back(new class ExternalApp(id, name, path, param, autostart));
    UpdateExternalApps();
}

void Browser::UpdateExternalApps()
{
    int jsonSize = 128;
    int intSize = 0;
    ExternalApp * app = externalApps.front();
    while(app) {
        jsonSize += strlen(app->name) + strlen(app->path) + (app->param ? strlen(app->param) : 0) + 64;
        intSize += 32;
        app = app->goNext();
    }
    char b[32];
    char * tmp = b;
    if(externalAppsStr) free(externalAppsStr);
    externalAppsStr = (char *)malloc(jsonSize);
    class json_io jsonApps(externalAppsStr);
    word base = jsonApps.add_object(0xffff, 0);
    word a = jsonApps.add_array(base, "externalApps");
    app = externalApps.front();
    while(app) {
        word e = jsonApps.add_object(a, "app");
        jsonApps.add_int(e, "id", app->id, tmp);
        jsonApps.add_string(e, "name", app->name);
        jsonApps.add_string(e, "path", app->path);
        if(app->param) jsonApps.add_string(e, "param", app->param);
        jsonApps.add_bool(e, "autostart", app->autostart);
        app = app->goNext();
    }
    jsonApps.encode();
    debug->printf("Browser(%p)::SetExternalApp Apps json sizes=%d,%d (%s)", this, jsonSize, intSize, externalAppsStr);
    if(externalAppsApi) externalAppsApi->UpdateExternalApps(externalAppsStr);
}

void Browser::SetApiClientParameters(class ISocketProvider * localSocketProvider, class ISocketProvider * tcp, class ISocketProvider * tls, class IDns * dns, const char * apiRelayUrl, const char * servicePassword, const char * apiRelayPath)
{
    this->externalAppsApi = new class ExternalApps(this, iomux, this, localSocketProvider, servicePassword, "ExternalApps", apiRelayPath);
    if(this->externalAppsStr) this->externalAppsApi->UpdateExternalApps(this->externalAppsStr);
}

void Browser::ExternalAppsApiClientCloseComplete()
{
    debug->printf("Browser(%p)::ExternalAppsApiClientCloseComplete", this);

    delete externalAppsApi;
    externalAppsApi = NULL;
}

class Webserver * webserver;
char fixCertUrl[512];

void Browser::ClientFixCert()
{
    webserver->SetServerCertificate(webserverPassword, fixCert, fixCertLen, NULL);
    ISSLProvider::SharedInstance()->SetDefaultClientCertificate(webserver, fixCert, fixCertLen);
    char * certString = (char *)(malloc(((fixCertLen + 2) / 3) * 4 + 1));
    encode_base64(fixCert, certString, fixCertLen);
    appClientMainUser->AppClientMainCertificate(certString);
    free(certString);
    appClientMainUser->AppClientMainFixCert(fixCertUrl, fixCert, fixCertLen);
}

bool Browser::ClientEMailLogAvailable()
{
    return true;
}

void Browser::ClientEMailLog()
{
    appClientMainUser->AppClientMainEMailLog();
}

void Browser::ClientClearLog()
{
    appClientMainUser->AppClientMainClearLog();
}

void Browser::ClientAutostartSettingsApp(char * name, size_t name_len)
{
    str::to_str(autostartSettingsApp, name, name_len);
}

void Browser::ClientStartAutostartSettingsApp(const char *name)
{
    appClientMainUser->AppClientMainStartAutostartSettingsApp(name);
}

void Browser::SignalingRegistrationState(bool up)
{
    appClientMainUser->AppClientMainRegistrationState(up);
}

void Browser::SignalingCallsActive(bool active)
{
    appClientMainUser->AppClientMainShowIfLocked("", active);
}

void Browser::TryStop()
{
    if (stopping && !write && !writeConfigScheduled) {
        iomux->ShutdownComplete(this);
    }
}

void Browser::Shutdown()
{
    stopping = true;
    TryStop();
}

void Browser::ShutdownTimeout()
{
    debug->printf("Browser::ShutdownTimeout %d", (write != NULL));
}

void Browser::ReadConfig()
{
    char configFilePath[2048];
    
    _sprintf(configFilePath, "%s%c%s", get_files_dir(), get_separator_char(), CONFIG_FILE_NAME);
    
    debug->printf("Browser::ReadConfig %s", configFilePath);
    
    IFileRead * read = FileSystem::OpenReadFile(configFilePath, NULL);
    if (read && read->IsOpen()) {
        size_t l = read->Read(readConfigBuffer, MAX_CONFIG_LENGTH - 1);
        readConfigBuffer[l] = '\0';
        char * tmpExtAppsStr = strstr(readConfigBuffer, "\"externalApps");
        if(tmpExtAppsStr) {
            char * tmpExtAppsStrEnd = strstr(tmpExtAppsStr, "]");
            if(tmpExtAppsStrEnd) {
                if(this->externalAppsStr) free(this->externalAppsStr);
                this->externalAppsStr = (char *)malloc(1+(tmpExtAppsStrEnd-tmpExtAppsStr)+2+1);
                strcpy(this->externalAppsStr, "{");
                memcpy(&this->externalAppsStr[1], tmpExtAppsStr, (tmpExtAppsStrEnd-tmpExtAppsStr)+1);
                strcpy(&this->externalAppsStr[1+(tmpExtAppsStrEnd-tmpExtAppsStr)+1], "}");
                if(externalAppsApi) externalAppsApi->UpdateExternalApps(this->externalAppsStr);
            }
        }
        if (readConfigJson.decode()) {
            word last = 0, t, end = 0xFFFF;
            readConfigBase = readConfigJson.get_object(JSON_ID_ROOT, 0);
            word appsArray = readConfigJson.get_array(readConfigBase, "externalApps");
            if(appsArray != end) {
                while((t = readConfigJson.get_object(appsArray, last)) != end) {
                    debug->printf("app=%s t=%d", readConfigJson.get_string(t, "name"), t);
                    if(readConfigJson.get_string(t, "name") && readConfigJson.get_string(t, "path")) {
                        bool present;
                        int appId = readConfigJson.get_int(t, "id", &present);
                        const char * appName = readConfigJson.get_string(t, "name");
                        class ExternalApp * app = new class ExternalApp(appId, appName, readConfigJson.get_string(t, "path"), readConfigJson.get_string(t, "param"), readConfigJson.get_bool(t, "autostart"));
                        this->externalApps.push_back(app);
                    }
                }
            }
            SetAdminSettings(readConfigJson, readConfigBase, false, NULL);
        }
        read->Close();
    }
}

void Browser::TaskComplete(class ITask * const task)
{
    if (task == write) {
        delete write;
        write = 0;
        copy_to_external(CONFIG_FILE_NAME);
        if (writeConfig) {
            writeConfig = false;
            WriteConfig();
        }
        if (stopping && !write) {
            TryStop();
        }
    }
}

void Browser::TaskFailed(class ITask * const task)
{
    if (task == write) {
        delete write;
        write = 0;
        if (writeConfig) {
            writeConfig = false;
            WriteConfig();
        }
        if (stopping && !write) {
            TryStop();
        }
    }
}

void Browser::TaskProgress(class ITask * const task, dword progress)
{
    if (task == write) {
        char temp[1024];
        char * tmp = temp;
        char buffer[MAX_CONFIG_LENGTH];
        class json_io json(buffer);
        word base = json.add_object(0xffff, 0);
        ClientConfigToJson(json, base, tmp);
        json.encode();
        write->write((byte *)buffer, strlen(buffer), true);
        writeConfig = false;
    }
}

void Browser::WriteConfig()
{
    if (write) {
        writeConfig = true;
    }
    else if (!writeConfigScheduled && !stopping) {
        writeConfigScheduled = true;
        SetExec(iomux, this, (AppClientWriteConfig *) NULL);
    }
}

void Browser::IoExec(AppClientWriteConfig * context)
{
    char configFilePath[2048];
    _sprintf(configFilePath, "%s%c", get_files_dir(), get_separator_char());
    write = new TaskFileWrite(this, configFilePath, CONFIG_FILE_NAME, false);
    write->Start(this);
    if (client) {
        client->ClientConfigChanged(this, NULL);
    }
    writeConfigScheduled = false;
}

void Browser::SetUrl(const char *url, const char *webserverUrl, const char *webserverPassword, const char *startFixCertUrl, const byte *fixCert, size_t fixCertLen)
{
    if (this->url) free(this->url);
    this->url = _strdup(url);
    if (this->webserverUrl) free(this->webserverUrl);
    this->webserverUrl = _strdup(webserverUrl);
    if (this->webserverPassword) free(this->webserverPassword);
    this->webserverPassword = _strdup(webserverPassword);
    if (this->startFixCertUrl) free(this->startFixCertUrl);
    this->startFixCertUrl = _strdup(startFixCertUrl);
    if (this->fixCert) free(this->fixCert);
    this->fixCert = (byte *)(malloc(fixCertLen));
    memcpy(this->fixCert, fixCert, fixCertLen);
    this->fixCertLen = fixCertLen;
}

void Browser::AddISignalingConfig(class ISignalingConfig* const signalingConfig)
{
    this->signalingConfig = signalingConfig;
    if (this->signalingConfig) {
        this->signalingConfig->SetNoVpnAddresses(clientConfig.noVpnAddresses.value);
        this->signalingConfig->SetCurrentUserSip(clientConfig.currentUserSip.value);
        this->signalingConfig->SetRecordingParameters(clientConfig.recordingUrl.value, clientConfig.recordingUrl.force, clientConfig.isRecordingByDefaultOn.value, clientConfig.isRecordingByDefaultOn.force, clientConfig.recordExternalOnly.value, clientConfig.recordExternalOnly.force);
    }
}

void Browser::AddIAppClientMedia(class IAppClientMedia * const mediaClient)
{
    this->mediaClient = mediaClient;
    if (this->mediaClient) {
        this->mediaClient->SetNoVpnAddresses(clientConfig.noVpnAddresses.value);
        this->mediaClient->SetCurrentUserSip(clientConfig.currentUserSip.value);
        this->mediaClient->SetRecordingUrl(clientConfig.recordingUrl.value, clientConfig.recordingUrl.force);
    }
}

void Browser::SetAutostartSettingsApp(const char *name)
{
    if (autostartSettingsApp) free(autostartSettingsApp);
    autostartSettingsApp = _strdup(name);
}

void Browser::SetActiveAfterClose(bool active)
{
#ifdef _IOS_
    clientConfig.activeAfterClose.Set(active);
#endif
}

void Browser::SetActiveAfterCloseAsked(bool asked)
{
#ifdef _IOS_
    clientConfig.activeAfterCloseAsked.Set(asked);
#endif
}

void Browser::SetAddressBookInaccessibleWarned(bool warned)
{
#ifdef _IOS_
    clientConfig.addressBookInaccessibleWarned.Set(warned);
#endif
}

void Browser::PushNotification()
{
    if ((clientConfig.pushNotifications.value == PUSH_NOTIFICATIONS_AUTO) || (clientConfig.pushNotifications.value == PUSH_NOTIFICATIONS_ON)) {
        clientConfig.pushNotifications.Set(PUSH_NOTIFICATIONS_ACTIVE);
        appClientMainUser->AppClientMainPushNotifications(clientConfig.pushNotifications.value);
    }
}

void Browser::SetPhoneAccountEnabled(bool enabled)
{
#ifdef __ANDROID__
    clientConfig.phoneAccountEnabled.Set(enabled);
#endif
}

void Browser::SetLocalStorageItem(const char * id, const char * val)
{
    clientConfig.localStorage.SetItem(id, val);
}

char * Browser::GetLocalStorageItem(const char * id)
{
    return clientConfig.localStorage.GetItem(id);
}

void Browser::DeleteLocalStorageItem(const char * id)
{
    return clientConfig.localStorage.DeleteItem(id);
}

void Browser::AppProxyReady()
{
    debug->printf("client ready");
    appClientMainUser->AppClientMainListening(url, webserverUrl, startFixCertUrl);
}

void Browser::AppProxyConnectionMonitor(const char *url, bool up)
{
    if (appClientConnectionUrl && (strcmp(url, appClientConnectionUrl) == 0)) {
        appClientConnected = up;
        while (registrationCheckRequests != 0) {
            appClientMainUser->AppClientMainRegistrationCheckConfirm();
            registrationCheckRequests--;
        }
        appClientConnectingTimer.Start(2000);
    }
}

void Browser::TimerOnTimeout(AppClientConnectingTimer * timer)
{
}

void Browser::RegistrationCheck()
{
    registrationCheckRequests++;
    if (!appClientConnectionUrl || appClientConnected || appClientConnectingTimer.IsRunning()) {
        while (registrationCheckRequests != 0) {
            appClientMainUser->AppClientMainRegistrationCheckConfirm();
            registrationCheckRequests--;
        }
    }
}

void Browser::LauncherApiClosing(class ILauncherApi * launcherApi)
{
    launcherApis.remove(launcherApi);
}

void Browser::LauncherApiDisablePowerSaving(bool active)
{
    appClientMainUser->AppClientMainDisablePowerSaving(active);
}

void Browser::LauncherApiSetFocus(const char * app, const char * title)
{
    appClientMainUser->AppClientMainSetFocus(app, title);
}

void Browser::LauncherApiAppWindowOpened(const char * app, const char * title)
{
    appClientMainUser->AppClientMainAppWindowOpened(app, title);
}

void Browser::LauncherApiSetResize(const char * app, int width, int height)
{
    appClientMainUser->AppClientMainSetResize(app, width, height);
}

void Browser::LauncherApiSetMaximized(const char * app, bool on)
{
}

void Browser::LauncherApiRegistrationState(bool up)
{
    appClientMainUser->AppClientMainRegistrationState(up);
}

void Browser::LauncherApiDialGsm(const char * number)
{
    appClientMainUser->AppClientMainDialGSM(number);
}

void Browser::LauncherApiShowIfLocked(const char * ref, bool show)
{
    appClientMainUser->AppClientMainShowIfLocked(ref, show);
}

void Browser::LauncherApiShowUnlock()
{
    appClientMainUser->AppClientMainShowUnlock();
}

void Browser::AddLocalNotification(const char * ref, LocalNotificationType type, const char * title, const char * text, const char * smallIcon, const char * largeIcon,
    const char * soundUrl, const char * buttonTitle, const char * acceptTitle, const char * rejectTitle, const char * replyTitle, UNotifications * user)
{
    NotificationsUsers::iterator i = notificationsUsers.begin();
    while ((i != notificationsUsers.end()) && ((*i) != user)) {
        i++;
    }
    if (i == notificationsUsers.end()) {
        notificationsUsers.push_back(user);
    }
    appClientMainUser->AppClientMainAddLocalNotification(ref, type, title, text, smallIcon, largeIcon, soundUrl, buttonTitle, acceptTitle, rejectTitle);
}

void Browser::RemoveLocalNotification(const char * ref)
{
    appClientMainUser->AppClientMainRemoveLocalNotification(ref);
}

void Browser::LocalNotificationClicked(const char* ref, enum LocalNotificationAction action, const char* replyText)
{
    for (LauncherApis::iterator i = launcherApis.begin(); i != launcherApis.end(); i++) {
        (*i)->LauncherApiLocalNotificationClicked(ref, action);
    }
}

void Browser::LauncherApiSetBadgeCount(const char * ref, unsigned count, const char * text)
{
    appClientMainUser->AppClientMainSetBadgeCount(ref, count, text);
}

void Browser::LauncherApiStatusBarAppearance(bool hidden, bool blackText, dword color)
{
    appClientMainUser->AppClientMainStatusBarAppearance(hidden, blackText, color);
}

void Browser::LauncherApiCacheable(const char * url, const char * filename)
{
    appproxy->Cacheable(url, filename);
}

void Browser::LauncherApiSetPushPassword(const char * pwd)
{
    clientConfig.pushPassword.Set(pwd);
    appClientMainUser->AppClientMainSetPushPassword(pwd);
}

void Browser::LauncherApiSetDownloadedBuild(const char * build)
{
    clientConfig.downloadedBuild.Set(build);
}

void Browser::LauncherApiSoftwareUpdate()
{
    appClientMainUser->AppClientMainSoftwareUpdate();
}

void Browser::LauncherApiNoPushAutostart(bool noPushAutostart)
{
    clientConfig.noPushAutostart.Set(noPushAutostart);
}

int phoneLanguageId = MYAPPS_LANGUAGE_EN;

void Browser::LauncherApiSetLangId(const char* langId)
{
    this->Log(LOG_BROWSER, "Browser(%p)::LauncherApiSetLangId lang=%s", this, langId ? langId : "");
    phoneLanguageId = Language::GetLanguageId(langId, myappsLanguageTable, MYAPPS_NUM_LANGUAGES, MYAPPS_LANGUAGE_EN);
    appClientMainUser->AppClientMainSetLanguageId(phoneLanguageId);
    if (this->mediaClient) this->mediaClient->SetLangId(langId);
}

void Browser::LauncherApiSetAdminSettings(class json_io& model, word baseSettings)
{
    this->Log(LOG_BROWSER, "Browser(%p)::LauncherApiSetAdminSettings", this);
    SetAdminSettings(model, baseSettings, true, NULL);
    WriteConfig();
}

void Browser::LauncherApiSetUser(const char * sip)
{
    this->Log(LOG_BROWSER, "Browser(%p)::LauncherApiSetUser sip (%s)", this, sip ? sip : "");
    clientConfig.currentUserSip.Set(sip);
    if (this->mediaClient) {
        this->mediaClient->SetCurrentUserSip(clientConfig.currentUserSip.value);
    }
    if (this->signalingConfig) {
        this->signalingConfig->SetCurrentUserSip(clientConfig.currentUserSip.value);
    }
}

void Browser::LauncherApiAppClientUrl(const char * url)
{
    unsigned i, j;
    appClientConnected = false;
    if (appClientConnectionUrl) free(appClientConnectionUrl);
    if (url) {
        i = strlen(url);
        while ((i != 0) && (url[i-1] != '/')) {
            i--;
        }
        if ((i > proxyLen) && (memcmp(url, proxy, proxyLen) == 0)) {
            appClientConnectionUrl = (char *)(malloc(i + (i - proxyLen + 6 + sizeof("websocket"))));
            j = 0;
            appClientConnectionUrl[j++] = 'w';
            appClientConnectionUrl[j++] = 's';
            if (proxy[4] == 's') appClientConnectionUrl[j++] = 's';
            appClientConnectionUrl[j++] = ':';
            appClientConnectionUrl[j++] = '/';
            appClientConnectionUrl[j++] = '/';
            memcpy(&(appClientConnectionUrl[j]), &(url[proxyLen]), i - proxyLen);
            memcpy(&(appClientConnectionUrl[j + i - proxyLen]), "websocket", sizeof("websocket"));
            appproxy->ConnectionMonitor(true);
        }
        else {
            appClientConnectionUrl = NULL;
            appproxy->ConnectionMonitor(false);
        }
    }
    else {
        appClientConnectionUrl = NULL;
        appproxy->ConnectionMonitor(false);
    }
}

void Browser::LauncherApiRccAppConnected(bool up)
{
    appClientMainUser->AppClientMainRccAppConnected(up);
}

void Browser::SetClose(bool closeSupported)
{
    for (LauncherApis::iterator i = launcherApis.begin(); i != launcherApis.end(); i++) {
        (*i)->SetClose(closeSupported);
    }
}

void Browser::SetDocking(bool docked)
{
    for (LauncherApis::iterator i = launcherApis.begin(); i != launcherApis.end(); i++) {
        (*i)->SetDocking(true, docked);
    }
}

void Browser::CxUse(bool use, const char *ringtone, bool supportsVideo, int maximumCallGroups, int maximumCallsPerCallGroup)
{
    appClientMainUser->AppClientMainCxUse(use, ringtone, supportsVideo, maximumCallGroups, maximumCallsPerCallGroup);
}

void Browser::CxReportNewIncomingCall(const char *uuid, const char *number, const char *name, const char *transfer, const char *called,
    bool hasVideo, bool supportsDTMF, bool supportsHolding, bool supportsGrouping, bool supportsUngrouping)
{
    appClientMainUser->AppClientMainCxReportNewIncomingCall(uuid, number, name, transfer, called,
        hasVideo, supportsDTMF, supportsHolding, supportsGrouping, supportsUngrouping);
}

void Browser::CxReportOutgoingCallConnecting(const char *uuid)
{
    appClientMainUser->AppClientMainCxReportOutgoingCallConnecting(uuid);
}

void Browser::CxReportOutgoingCallConnected(const char *uuid)
{
    appClientMainUser->AppClientMainCxReportOutgoingCallConnected(uuid);
}

void Browser::CxReportCallUpdated(const char *uuid, const char *number, const char *name, const char *transfer, const char *called,
    bool hasVideo, bool supportsDTMF, bool supportsHolding, bool supportsGrouping, bool supportsUngrouping)
{
    appClientMainUser->AppClientMainCxReportCallUpdated(uuid, number, name, transfer, called,
        hasVideo, supportsDTMF, supportsHolding, supportsGrouping, supportsUngrouping);
}

void Browser::CxReportCallEnded(const char *uuid, CxCallEndedReason reason)
{
    appClientMainUser->AppClientMainCxReportCallEnded(uuid, reason);
}

void Browser::CxRequestStartCall(const char *uuid, const char *number, const char *name, bool video)
{
    appClientMainUser->AppClientMainCxRequestStartCall(uuid, number, name, video);
}

void Browser::CxRequestEndCall(const char *uuid)
{
    appClientMainUser->AppClientMainCxRequestEndCall(uuid);
}

void Browser::CxRequestSetHeldCall(const char *uuid, bool held)
{
    appClientMainUser->AppClientMainCxRequestSetHeldCall(uuid, held);
}

void Browser::CxRequestSetMutedCall(const char *uuid, bool muted)
{
    appClientMainUser->AppClientMainCxRequestSetMutedCall(uuid, muted);
}

void Browser::CxRequestSetGroupCall(const char *uuid, const char *with)
{
    appClientMainUser->AppClientMainCxRequestSetGroupCall(uuid, with);
}

void Browser::CxRequestPlayDTMF(const char *uuid, const char *digits)
{
    appClientMainUser->AppClientMainCxRequestPlayDTMF(uuid, digits);
}

void Browser::LauncherApiClose()
{
    appClientMainUser->AppClientMainShutdown();
}
#endif

void phone_set_push_token(const char *token)
{
}

void phone_push_notification()
{
}

void phone_set_expect_touches(bool expectTouchesSupported, bool expectTouches)
{
}

void phone_set_shown_locked(bool shownLockedSupported, bool shownLocked)
{
}

void phone_set_proximity(bool proximitySupported, bool proximity)
{
}

void phone_local_notification_clicked(const char *ref)
{
}

void phone_local_notification_accept(const char *ref)
{
}

void phone_local_notification_reject(const char *ref)
{
}

void phone_local_notification_reply(const char *ref, const char *replyText)
{
}

void phone_dial_number(const char *number, const char *name, int video_mode)
{

}

void phone_dial_sip(const char *sip, const char *name, int video_mode)
{
}

void phone_chat(const char *sip)
{
}

void phone_external_call_idle(const char *number)
{
}

void phone_external_call_ringing(const char *number)
{
}

void phone_external_call_offhook(const char *number)
{
}

void phone_cx_supported(bool supported)
{
}

void phone_cx_start_call_action(const char * uuid, const char * number, const char * name, bool video)
{
}

void phone_cx_answer_call_action(const char * uuid)
{
}

void phone_cx_end_call_action(const char * uuid, int call_error)
{
}

void phone_cx_set_held_call_action(const char * uuid, bool held)
{
}

void phone_cx_set_muted_call_action(const char * uuid, bool muted)
{
}

void phone_cx_audio_route_changed(const char * uuid, const char * deviceId)
{
}

void phone_cx_set_group_call_action(const char * uuid, const char * with)
{
}

void phone_cx_play_dtmf_call_action(const char * uuid, const char * digits)
{
}

void phone_cx_call_error(const char * uuid, int call_error)
{
}

void phone_cx_show_incoming_call_ui(const char * uuid)
{
}

void phone_cx_check_incoming_call(const char * uuid)
{
}

void phone_wake_phone()
{
}

void phone_open_permalink(const char * link)
{
}

void myApps_set_offline(bool offline)
{
}

void myApps_set_hotkey_action(int type, const char * dialNumber)
{
}

void phone_registration_check()
{
}

void phone_active_after_close(bool active)
{
}

void phone_active_after_close_asked(bool asked)
{
}

void phone_address_book_inaccessible_warned(bool warned)
{
}

void phone_set_phone_account_enabled(bool enabled)
{
}

void phone_contacts_search(const char *ref, const char *info)
{
}

void phone_contacts_lookup(const char *ref, const char *info)
{
}

void phone_webserver_get(const char *context, bool complete, int length, const byte *data)
{
}

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

class UAppClientMain * appClientMainUser;
ISocketProvider * clientSocketProviderUDP;
ISocketProvider * clientSocketProviderTCP;
ISocketProvider * clientSocketProviderTLS;
ISocketProvider * clientSocketProviderLocal;
class AppPluginMain * appPluginMain;
//class Browser * browser;

void ILocalStorage::SetItem(const char * id, const char * val)
{
    //browser->SetLocalStorageItem(id, val);
}

char * ILocalStorage::GetItem(const char * id)
{
    return nullptr; //(browser->GetLocalStorageItem(id));
}

void ILocalStorage::DeleteItem(const char * id)
{
    //browser->DeleteLocalStorageItem(id);
}

class AppPluginMain : public UDebugLogFlags, public ULauncherApi, public URemoteServices, public IInstanceLog
{
    IIoMux * iomux;

    ISocketProvider * socketProviderTLS;
    ISocketProvider * socketProviderLocal;
    char * encodedPassword;
    const char * domain;
    byte * cert;
    size_t certLen;
    class DebugLogFlags * logFlags;
    class AppClientMedia * appClientMedia;
    class AppClientRinger * appClientRinger;
    class RemoteServicesService * remoteServicesService;

    class Client * client;

    class MediaConfiguration * mediaConfiguration;
    const char *apirelayLocalPath;
    char *servicePassword;
    bool turnOnly;
    bool iceNoHost;

    void DebugLogFlagsChanged(class DebugLogFlags * debugLogFlags, const char * appName, const char * appDomain, ulong64 flags, bool noHexdump, UDebugLogFlags * src) override {
        appClientMainUser->DebugLogFlagsChanged(debugLogFlags, appName, appDomain, flags, noHexdump, src);
    }

    void LauncherApiClose() override {
        appClientMainUser->AppClientMainShutdown();
    };

    void ClientConnected() override {};
    void ClientDisconnected() override {};
    void ServerConnected() override {};
    void ServerDisconnected() override {};
    void Message(const char* type, class json_io& msg, word base) override;
    
    const char* GetAppName() const { return "AppPluginMain"; }
    const char* GetAppDomain() const { return ""; }

public:
    AppPluginMain(IIoMux * const iomux, UAppClientMain * const user);
    ~AppPluginMain();
    
    ClientUDPSocketProvider * socketProviderUDP;
    ClientTCPSocketProvider * socketProviderTCP;
};


AppPluginMain::AppPluginMain(IIoMux * const iomux, UAppClientMain * const user)
{
    int appSharingNumUpdates = 32;
    int appSharingCaptureTimer = 100;
    int appSharingBitrate = 500;
    int appSharingJpegQuality = 65;
    int appSharingUpdateSum = 5000;
    int appSharingWaitMsForAck = 200;
    int mediaDropPacketsTx = 0;
    int mediaDropPacketsRx = 0;
    int execInterval = 160;
    bool phoneLoad = false;
    bool hdVideo = false;
    bool recordRtpStream = false;

    this->iomux = iomux;

    appClientMainUser = user;
    
    clientSocketProviderUDP = socketProviderUDP = ClientCreateUDPSocketProvider();
    clientSocketProviderTCP = socketProviderTCP = ClientCreateTCPSocketProvider();
    clientSocketProviderTLS = socketProviderTLS = CreateTLSSocketProvider(socketProviderTCP);
    clientSocketProviderLocal = socketProviderLocal = ClientCreateLocalSocketProvider();
    IRandom::Init((dword)(ITime::TimeStampMilliseconds() / 1000));

    // start service
    turnOnly = false;
    iceNoHost = false;

    logFlags = new class DebugLogFlags();
    logFlags->Set(NULL, NULL, 0, false, this);
    debug->printf("start browser");
    //browser = new class Browser(iomux, user, logFlags);

    mediaConfiguration = new class MediaConfiguration(turnOnly, iceNoHost, appSharingNumUpdates, appSharingCaptureTimer, appSharingBitrate, appSharingJpegQuality, appSharingUpdateSum, appSharingWaitMsForAck, mediaDropPacketsTx, mediaDropPacketsRx,
        phoneLoad, hdVideo, execInterval, recordRtpStream);

    debug->printf("start client");
    //client = new Client(iomux, webserverPluginProvider, socketProviderLocal, args->webserverLocalPath, "/client", user, browser, servicePassword);
    //logFlags->Add("client", domain, client);
    //browser->SetClient(client);

    debug->printf("start medialib");
    AppInstanceArgs medialibInstanceArgs;
    medialibInstanceArgs.appName = "medialib";
    medialibInstanceArgs.appDomain = "";
    medialibInstanceArgs.appPassword = nullptr;
    medialibInstanceArgs.webserver = nullptr;
    medialibInstanceArgs.webserverPath = nullptr;
    medialibInstanceArgs.workingPath = nullptr;
    medialibInstanceArgs.logFlags = 0;
    appClientMedia = new class AppClientMedia(iomux, NULL, socketProviderLocal, socketProviderUDP, socketProviderTCP, socketProviderTLS, &medialibInstanceArgs, mediaConfiguration);
    logFlags->Add(medialibInstanceArgs.appName, medialibInstanceArgs.appDomain, appClientMedia);
    //browser->AddIAppClientMedia(appClientMedia);

    debug->printf("start ringer");
    AppInstanceArgs ringerInstanceArgs;
    ringerInstanceArgs.appName = "ringer";
    ringerInstanceArgs.appDomain = "";
    ringerInstanceArgs.appPassword = nullptr;
    ringerInstanceArgs.webserver = nullptr;
    ringerInstanceArgs.webserverPath = nullptr;
    ringerInstanceArgs.workingPath = nullptr;
    ringerInstanceArgs.logFlags = 0;
    appClientRinger = new class AppClientRinger(iomux, NULL, socketProviderLocal, &ringerInstanceArgs, mediaConfiguration);
    logFlags->Add(ringerInstanceArgs.appName, ringerInstanceArgs.appDomain, appClientRinger);

    debug->printf("start remote services");
    AppServiceArgs remoteServicesServiceArgs;
    AppInstanceArgs remoteServicesInstanceArgs;

    remoteServicesServiceArgs.serviceID = "remoteServices";
    remoteServicesServiceArgs.manager = NULL;
    remoteServicesServiceArgs.logFlags = 0;
    remoteServicesServiceArgs.segfaulted = false;

    char remoteServicesPath[PATH_MAX];
    _sprintf(remoteServicesPath, "%s%c%s", get_files_dir(), get_separator_char(), "windows");
    char remoteServicesCitrixPath[PATH_MAX];
    _sprintf(remoteServicesCitrixPath, "%s%c%s", get_files_dir(), get_separator_char(), "citrix");

    remoteServicesInstanceArgs.appName = "remoteServices";
    remoteServicesInstanceArgs.appDomain = "";
    remoteServicesInstanceArgs.appPassword = nullptr;
    remoteServicesInstanceArgs.webserver = nullptr;
    remoteServicesInstanceArgs.webserverPath = nullptr;
    remoteServicesInstanceArgs.workingPath = nullptr;
    remoteServicesInstanceArgs.logFlags = 0;
    remoteServicesService = new RemoteServicesService(iomux, this, socketProviderLocal, socketProviderTCP, socketProviderTLS, socketProviderUDP, appClientMedia->GetAudioIo(), appClientMedia->GetVideoIo(), appClientRinger->GetRingerIo(), appClientMedia->GetMediaProvider(), appClientMedia->GetSocketContext(), &remoteServicesServiceArgs, NULL, remoteServicesPath, remoteServicesCitrixPath, NULL, this);

    // Citrix instance
    remoteServicesService->AppStart(&remoteServicesInstanceArgs);
    logFlags->Add(remoteServicesInstanceArgs.appName, remoteServicesInstanceArgs.appDomain, remoteServicesService, remoteServicesService);
    remoteServicesService->GetWTSRemoteServices()->StartService();
    remoteServicesService->GetWTSRemoteServices()->SetRemoteServicesUser(this);

    // Citrix instance
    remoteServicesInstanceArgs.appName = "remoteCitrixServices";
    remoteServicesService->AppStart(&remoteServicesInstanceArgs);
    logFlags->Add(remoteServicesInstanceArgs.appName, remoteServicesInstanceArgs.appDomain, remoteServicesService, remoteServicesService);
    remoteServicesService->GetCitrixRemoteServices()->StartService();
    remoteServicesService->GetCitrixRemoteServices()->SetRemoteServicesUser(this);

    logFlags->AddUser(this);
}

AppPluginMain::~AppPluginMain()
{
    debug->printf("AppPluginMain shutdown complete, deleting services");

    delete appClientRinger;
    delete appClientMedia;

    logFlags->RemoveUser(this);
    delete logFlags;
    delete socketProviderLocal;
    delete socketProviderTLS;
    delete socketProviderTCP;
    delete socketProviderUDP;
    delete mediaConfiguration;

    debug->printf("deleting services complete");
}

void AppPluginMain::Message(const char* type, class json_io& msg, word base)
{
    bool pbx = msg.get_bool(base, "pbx");

    debug->printf("AppPluginMain(%p)::Message type=%s pbx=%d", this, type ? type : "", pbx);

    word baseStart = msg.get_object(base, "logFlags");
    if (baseStart != JSON_ID_ROOT) {
        ulong64 traceEnabled = std::strtoll(msg.get_string(baseStart, "value"), nullptr, 16);
        logFlags->Set(NULL, NULL, traceEnabled, logFlags->NoHexdump(), this);
        debug->printf("AppPluginMain(%p)::Message new trace flag 0x%llx", this, traceEnabled);
    }
}

void IAppPluginMain::CloseListen()
{
    appPluginMain->socketProviderUDP->CloseListen();
    appPluginMain->socketProviderTCP->CloseListen();
}

void IAppPluginMain::RestartListen()
{
    appPluginMain->socketProviderUDP->RestartListen();
    appPluginMain->socketProviderTCP->RestartListen();
}

int IAppPluginMain::main(IIoMux * const iomux, UAppClientMain * const user)
{
    appPluginMain = new AppPluginMain(iomux, user);

    debug->printf("run");
    iomux->Run();

    delete appPluginMain;
    // ----

    return 0;
}

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