
/*-----------------------------------------------------------------------------------------------*/
/* signaling.h                                                                                   */
/* copyright (c) innovaphone 2020                                                                */
/*                                                                                               */
/*-----------------------------------------------------------------------------------------------*/

#ifndef SIGNALING_H
#define SIGNALING_H

#define SIG_CALL_STATE_SETUP        1
#define SIG_CALL_STATE_SETUP_ACK    2
#define SIG_CALL_STATE_CALL_PROC    3
#define SIG_CALL_STATE_ALERT        4
#define SIG_CALL_STATE_CONN         5
#define SIG_CALL_STATE_DISC_SENT    6
#define SIG_CALL_STATE_DISC_RCVD    7
#define SIG_CALL_STATE_PARKED       8
#define SIG_CALL_STATE_OUTG         0x80
#define SIG_CALL_STATE_HOLD         0x100
#define SIG_CALL_STATE_HELD         0x200
#define SIG_CALL_STATE_CALL_WAITING 0x400

#define MEDIA_CMD_NONE          0
#define MEDIA_CMD_SELECT        1
#define MEDIA_CMD_PROPOSAL      2
#define MEDIA_CMD_REQUEST       3
#define MEDIA_CMD_CLOSE         4
#define MEDIA_CMD_INFO          5

#define PBX_INNOVAPHONE_DATA_ATTACH         12
#define PBX_INNOVAPHONE_DATA_DETACH         13

#define FTY_REMOTE_CONTROL_REC_ON          8
#define FTY_REMOTE_CONTROL_REC_OFF         9
#define FTY_REMOTE_CONTROL_3PTY_ON        10
#define FTY_REMOTE_CONTROL_MIC_ON         14
#define FTY_REMOTE_CONTROL_MIC_OFF        15
#define FTY_REMOTE_CONTROL_ADD_VIDEO      42
#define FTY_REMOTE_CONTROL_REM_VIDEO      43
#define FTY_REMOTE_CONTROL_START_CAMERA   44
#define FTY_REMOTE_CONTROL_STOP_CAMERA    45
#define FTY_REMOTE_CONTROL_ADD_APPSHARING 46
#define FTY_REMOTE_CONTROL_REM_APPSHARING 47
#define FTY_REMOTE_CONTROL_ALLOW_VIDEO    48

#define Q931_CAUSE_NormalCallClearing       16
#define Q931_CAUSE_UserBusy                 17
#define Q931_CAUSE_NoUserResponding         18
#define Q931_CAUSE_NonSelectedUserClearing  26
#define Q931_CAUSE_NetworkOutOfOrder        38
#define Q931_CAUSE_TemporaryFailure         41
#define Q931_CAUSE_ServiceOrOptionNotAvailable_Unspecified 63

#define AUTOHANGUP_OFF             1
#define AUTOHANGUP_ALLCALLS        2
#define AUTOHANGUP_CONNECTEDCALLS  3

#define HOOK_DEVICE_WAITING        1
#define HOOK_DEVICE_ACQUIRE        2
#define HOOK_DEVICE_DENIED         3
#define HOOK_DEVICE_NOT_FOUND      4

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

class JsonIo
{
    json_io * j;
public:
    JsonIo(char * buffer) { j = new json_io(buffer); }
    ~JsonIo() { delete j; }
    operator json_io &() { return *j; }

    word add(byte type, byte flags, word base, const char* name, const char* info, dword len = 0xffffffff) { return j->add(type, flags, base, name, info, len); }
    word add_object(word base, const char * name)                                                    { return j->add_object(base, name); }
    word add_array(word base, const char * name)                                                     { return j->add_array(base, name); }
    void add_string(word base, const char * name, const char * value, dword len=0xffffffff)          { j->add_string(base, name, value, len); }
    void add_string(word base, const char * name, const word * value, dword len, char * & tmp)       { j->add_string(base, name, value, len, tmp); }
    void add_string(word base, const char * name, const word * value, char * & tmp)                  { j->add_string(base, name, value, 0xffffffff, tmp); }
    void add_replace_string(word base, const char * name, const char * value, dword len=0xffffffff)  { j->add_replace_string(base, name, value, len); }
    void add_json(word base, const char * name, const char * value, dword len=0xffffffff)            { j->add_json(base, name, value, len); }
    void add_int(word base, const char * name, int c, char * & tmp)                                  { j->add_int(base, name, c, tmp); }
    void add_unsigned(word base, const char * name, dword c, char * & tmp)                           { j->add_unsigned(base, name, c, tmp); }
    void add_long64(word base, const char * name, long64 c, char * & tmp)                            { j->add_long64(base, name, c, tmp); }
    void add_ulong64(word base, const char * name, ulong64 c, char * & tmp)                          { j->add_ulong64(base, name, c, tmp); }
    void add_printf(word base, const char * name, char * & tmp, const char * format, ...)            { va_list ap; va_start(ap,format); j->add_printfv(base, name, tmp, format, ap); va_end(ap); }
    void add_printfv(word base, const char * name, char * & tmp, const char * format, va_list ap)    { j->add_printfv(base, name, tmp, format, ap); }
    void add_hexstring(word base, const char * name, const byte * hex, word hex_len, char * & tmp)   { j->add_hexstring(base, name, hex, hex_len, tmp); }
    void add_bool(word base, const char * name, bool value)                                          { j->add_bool(base, name, value); }
    void add_null(word base, const char * name)                                                      { j->add_null(base, name); }
    word get_array(word base, const char* name) { return j->get_array(base, name); }
};

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

class SignalingHookKey: public istd::listElement<SignalingHookKey>
{
    const char* deviceId;
    const char* key;
public:
    SignalingHookKey(const char* deviceId, const char* key) : deviceId(_strdup(deviceId)), key(_strdup(key)) {};
    ~SignalingHookKey() { free((void*)deviceId); free((void*)key); };
    const char* DeviceId() { return deviceId; };
    const char* Key() { return key; };
};

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

class SignalingService :  public AppService, public ISignalingService
{
    void AppServiceApps(istd::list<AppServiceApp> * appList) override;
    void IAppStart(AppInstanceArgs * args) override;
    class ISignaling * ICreateInstance(class USignaling * user, AppInstanceArgs * args) override;

public:
    SignalingService(class IIoMux * const iomux, class ISocketProvider * localSocketProvider, class ISocketProvider * tcpSocketProvider, class ISocketProvider * tlsSocketProvider, IWebserverPluginProvider * const webserverPluginProvider, class IAppWebsocketClientProvider * appWebsocketClientProvider, IMediaProvider * mediaProvider, SignalingMediaConfig * mediaConfig, IAudioIo * audioIo, IRingerIo * ringerIo, IVideoIo * videoIo, IAudioIo* remoteAudioIo, IRingerIo* remoteRingerIo, IVideoIo* remoteVideoIo, IMediaProvider* remoteMediaProvider, ISignalingRemote* signalingRemote, AppServiceArgs * args);
    ~SignalingService();

    class AppInstance * CreateInstance(AppInstanceArgs * args) override;
    class ISignaling* GetISignalingInstance() override { return this->signaling; };

    class IIoMux * iomux;
    class ISocketProvider * localSocketProvider;
    class ISocketProvider * tcpSocketProvider;
    class ISocketProvider * tlsSocketProvider;
    class IWebserverPluginProvider * webserverPluginProvider;
    IAppWebsocketClientProvider * appWebsocketClientProvider;
    IMediaProvider * mediaProvider;
    SignalingMediaConfig * mediaConfig;
    IAudioIo * audioIo;
    IRingerIo * ringerIo;
    IVideoIo * videoIo;
    IAudioIo * remoteAudioIo;
    IRingerIo * remoteRingerIo;
    IVideoIo * remoteVideoIo;
    IMediaProvider * remoteMediaProvider;
    ISignalingRemote * remoteSignaling;
    class ISignaling * signaling;
};

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

class SignalingRcc :public UJsonApiContext {
    class JsonApi * CreateJsonApi(class IJsonApiConnection * connection, class json_io & msg, word base) override;
    class JsonApi * JsonApiRequested(class IJsonApiConnection * connection) override;
    const char * Name() { return "RCC"; };

    class IInstanceLog * log;
public:
    SignalingRcc(class JsonApiContext * context, class IInstanceLog* log);
    virtual class SignalingRegistration * GetRegistration() = 0;
    virtual void AddRegistrationMonitor(class SignalingRegistrationMonitor * monitor) = 0;
    virtual void RemoveRegistrationMonitor(class SignalingRegistrationMonitor * monitor) = 0;
    virtual void MonitorSync(class SignalingRegistrationMonitor * monitor) = 0;
    virtual class IAudioIo * GetAudioIo() = 0;
    virtual class IRingerIo * GetRingerIo() = 0;
    virtual class IVideoIo * GetVideoIo() = 0;
    virtual class IMediaProvider * GetMediaProvider() = 0;
};

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

class SignalingDialingLocation {
public:
    SignalingDialingLocation() { zeroize(); }
    ~SignalingDialingLocation() { cleanup(); }
    void zeroize();
    void cleanup();
    void read(class json_io & msg, word base);
    void write(class SignalingRCCMessage * msg);

    char * prefixIntl, *prefixNtl, *prefixSubs, *country, *area, *subscriber;
    unsigned maxIntern;
    unsigned tones;
};

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

enum CallWaitingType { CallWaitingOff, CallWaitingOn, CallWaitingSilent };

class Signaling : public ISignaling, public USelectDeviceIo, public UWebserverPlugin, public AppUpdates, public JsonApiContext, public SignalingRcc, public UHookDevice
{
    class IRTPTP * SetRTPTP4(class URTPTP * user, const char * sip, unsigned timeout);
    class IRTPTP * SetRTPTP6(class URTPTP * user, const char * sip, unsigned timeout);

    void WebserverPluginHttpListenResult(class IWebserverPlugin * plugin, ws_request_type_t requestType, char * resourceName, const char * registeredPathForRequest, ulong64 dataSize) override;
    void WebserverPluginWebsocketListenResult(IWebserverPlugin * plugin, const char * path, const char * registeredPathForRequest, const char * host) override;
    void WebserverPluginClose(IWebserverPlugin * plugin, wsp_close_reason_t reason, bool lastUser);

    void Stop() override;
    void AppInstanceShutdownTimeout() override;

    bool stopping;

    class istd::list<class SignalingHookKey> hookKeyQueue;

public:
    Signaling(IIoMux * const iomux, class USignaling * user, SignalingService * service, AppInstanceArgs * args);
    ~Signaling();
    void AddClientMonitor(class SignalingClientMonitor * monitor);
    void RemoveClientMonitor(class SignalingClientMonitor * monitor);
    void ClientState(bool up, bool localInit);

    class SignalingRegistration * GetRegistration() override { return registration; };
    void AddRegistrationMonitor(class SignalingRegistrationMonitor * monitor);
    void RemoveRegistrationMonitor(class SignalingRegistrationMonitor * monitor);
    void MonitorSync(class SignalingRegistrationMonitor * monitor);
    class IAudioIo * GetAudioIo() override;
    class IRingerIo * GetRingerIo() override;
    class IVideoIo * GetVideoIo() override;
    class IMediaProvider * GetMediaProvider() override;
    class ISignalingRemote * GetSignalingRemote();
    const char * AppendSipDomain(const char * sip, char * buffer, unsigned length);
    const char * RemoveSipDomain(const char * sip, char * buffer, unsigned length);
    void UiSessionOpened();
    void UiSessionClosed();
    void RegisterToAltPbx();
    void RegisterToRedirectUrl(const char* url);
    void InnovaphoneData(class json_io & msg, word base);

    void RegistrationState(bool up);
    void CallAdded(class SignalingCall * call, class SignalingRegistrationMonitor * exclude);
    void UnknownCallMessage(const char * type, class json_io & msg, word base);
    void RTPTPState(class IRTPTP * rtptp, bool up);

    void SetClient(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, bool localInit) override;
    void CloseClient() override;
    void SetRegistration(const char* hw, const char* phys, unsigned timeout) override;
    void RegistrationCheck() override;
    int NumActiveCalls() override;
    void LookupInfo(const char * src, class json_io& msg, word base) override;
    void StartRinging(const char* uuid) override;

    void AddSignalingSession(class SignalingSession * session);
    void RemoveSignalingSession(class SignalingSession* session);

    void PhoneStartCallAction(const char * uuid, const char * number, const char * name, bool video, bool sendingComplete) override;
    void PhoneAnswerCallAction(const char * uuid) override;
    void PhoneEndCallAction(const char * uuid, int callError) override;
    void PhoneDialNumber(const char *number, const char *name, const char *dn, bool videoMode, bool sendingComplete, bool numOffset = true) override;
    void PhoneHoldCallAction(const char * uuid) override;
    void PhoneRetrieveCallAction(const char * uuid) override;
    void PhoneSetMutedCallAction(const char * uuid, bool muted) override;
    void PhoneAudioRouteChanged(const char * uuid, const char * deviceId) override;
    void PhonePlayDtmfCallAction(const char * uuid, const char * digits) override;
    void PhoneStartRinging(const char * uuid) override;
    void PhoneCheckIncomingCall(const char * uuid) override;
    class SignalingCall * FindCallByConfId(const char * uuid);
    class SignalingCall * FindCallByRemoteNum(const char * num);
    class SignalingCall * FindOtherCall(class SignalingCall * call);

    void SelectAudioDevice(class IAudioIo * audioIo, const char * app, const char * deviceId, int type) override;
    void SelectVideoDevice(const char* app, const char* deviceId) override;
    void SelectRingerDevice(class IRingerIo* ringerIo, const char* app, const char* deviceId) override;
    void SelectRingtone(class IRingerIo* ringerIo, const char* app, const char* ringtoneId) override;
    void AudioDeviceList() override;
    void VideoDeviceList() override;
    void RingerDeviceList() override;
    void RingtoneList() override;
    void ChangeVideoDevice(const char* app, const char* deviceId) override;
    void ChangeAudioDevice(class IAudioIo* audioIo, const char* app, const char* deviceId, int type) override;
    void StartHookDevice(const char* deviceId, byte key, byte callId, const char* number, const char* name) override;
    void AudioDeviceRemoved(const char* deviceId) override;
    void VideoDeviceRemoved(const char* deviceId) override;
    void RingerDeviceRemoved(const char* deviceId) override;
    void AudioRouteChanged(const char *deviceId) override;

    void SendHookKey(const char* deviceId, byte key, byte callId, const char* number, const char* name);

    void 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);
    void SaveRegistration(const char* hw, const char* phys, unsigned timeout);

    void RegistrationDeleted(class SignalingRegistration * registration) { if (registration == this->registration) this->registration = NULL; };
    void KeysSessionClosed(class KeysSession * KeysSession);

    class IIoMux * GetIoMux() { return AppUpdates::GetIoMux(); };
    class ISocketProvider * GetTcpSocketProvider() { return this->service->tcpSocketProvider; };
    class ISocketProvider * GetTlsSocketProvider() { return this->service->tlsSocketProvider; };
    const char * GetWorkingPath() { return this->workingPath; };
    void SetWorkingPath(const char* path) override { if (this->workingPath) free(workingPath); this->workingPath = _strdup(path); };
    const char * GetRingerDevice() { return this->ringerDeviceId; };
    const char * GetAudioDevice() { return this->changedAudioDeviceId ? this->changedAudioDeviceId : this->defaultAudioDeviceId; };

    void SetExcludeInterfaces(const char * excludeInf) override;
    void SetNoVpnAddresses(bool noVpnAddresses) override;
    void SetCurrentUserSip(const char * sip) override;
    void SetRecordingParameters(const char* url, bool pbx, bool forceUrl, bool isRecordingByDefaultOn, bool forceDefaultOn, bool recordExternalOnly, bool forceExternalOnly) override;
    void SetSignalingUser(class USignaling* user) override;

    // UHookDevice
    void HookDeviceMsg(IAudioIo* audioIo, const char* deviceId, const char* msg) override;
    void HookKeyReceived(IAudioIo* audioIo, const char* deviceId, byte key) override;
    void AudioDeviceDelayed(IAudioIo* audioIo, const char* deviceIn, const char* deviceOut)  override;
    void AudioDeviceOpened(IAudioIo* audioIo, const char* deviceIn, const char* deviceOut)  override;
    void SendHookKeyReceived(const char * deviceId, const char * key);

    void RkeyHandling(char key);
    void IncomingCall();

    void TestCloseRegistration();

    class IInstanceLog * log;
    class USignaling * user;

    char* url;
    char* sec;
    char* phys;
    char* app;
    char* usr;
    char* userPwd;
    char* domain;
    char* appdomain;
    unsigned timeout;
    bool localInit;

    char * pwd;
    class SignalingService * service;
    class IWebserverPlugin * webserverPlugin, * webserverPluginKeys;
    class AppUpdatesFilters filters;
    class SignalingClient * client;
    class SignalingRegistration * registration;
    class SignalingRTPTP * rtptp4;
    class SignalingRTPTP * rtptp6;
    class istd::list<class SignalingSessionHelper> sessions;
    class istd::list<SignalingClientMonitor> clientMonitors;
    class istd::list<SignalingRegistrationMonitor> registrationMonitors;
    char * defaultAudioDeviceId;
    char * changedAudioDeviceId;
    char * ringerDeviceId;
    char * ringtoneId;
    char * videoDeviceId;
    char * testAudioDeviceId;
    char * testRingerDeviceId;
    char * testVideoDeviceId;
    char * localIpAddr;
    time_t lastRkeyPress;
    int hookDeviceState;
    bool videoLicense;
    bool videoLicenseBackup; // holds value of videoLicense while Phone-App is attached
    bool recordingLicense;
    bool startVideo;
    bool doNotStartCamera;
    enum CallWaitingType callWaiting;
    bool onhookTransfer;
    bool clir;
    bool dndRejectCall;
    int uiRunning;
    int autoHangup;
    bool recordingUrlSet;
    class VoipEndpoint * lastStartCallDest;
    char * workingPath;
};

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

class KeysSession : public UWebsocket {
    class Signaling * signaling;
    class IWebsocket * websocket;
    bool closing;
    
    // UWebsocket interface
    void WebsocketAcceptComplete(class IWebsocket * websocket) override;
    void * WebsocketRecvBuffer(size_t len) override;
    void WebsocketRecvResult(class IWebsocket * websocket, void * buffer, size_t len, bool text, bool isFragmented) override;
    void WebsocketRecvCanceled(class IWebsocket * websocket, void * buffer) override;
    void WebsocketSendResult(class IWebsocket * websocket) override;
    void WebsocketCloseComplete(class IWebsocket * websocket, ws_close_reason_t reason) override;

public:
    KeysSession(class Signaling * signaling);
    virtual ~KeysSession();
    void Close();
};

class SignalingSession : public AppUpdatesSession, public UMedia {
    void ResponseSent();
    void AppWebsocketClosed();
    char * AppWebsocketPassword() { return signaling->pwd; };
    bool AppWebsocketConnectComplete(class json_io & msg, word info);
    void AppWebsocketMessage(class json_io & msg, word base, const char * mt, const char * src);

    void MediaInitializeComplete(IMedia* const media, class MediaConfig* localMediaConfig) override;
    void MediaCloseComplete(IMedia* const media) override;

    class IMedia * mediaTest;
    bool closing;

    class Signaling * signaling;
    char * currentSrc;

    class SignalingUpdatesFilter* clientInfo;
public:
    SignalingSession(class Signaling * signaling);
};

class SignalingSessionHelper :public istd::listElement<SignalingSessionHelper> {
public:
    SignalingSessionHelper(class SignalingSession* session) {
        this->session = session;
    }
    ~SignalingSessionHelper() {};

    class SignalingSession* session;
};

enum SignalingUpdateType {
    SignalingClientInfo,
    SignalingRegistrationInfo,
    SignalingRTPTP4Info,
    SignalingRTPTP6Info
};

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

class SignalingUpdate : public AppUpdate {
public:
    SignalingUpdate(enum SignalingUpdateType type, const char * mt, const char * info, class AppUpdatesFilters & filters, const char * sip = 0);
    ~SignalingUpdate();

    enum SignalingUpdateType type;
    const char * mt;
    char * info;
};

class SignalingUpdatesFilter : public AppUpdatesFilter<SignalingUpdate> {
    bool Test(SignalingUpdate * update) override;
    void Send(SignalingUpdate * update) override;
public:
    SignalingUpdatesFilter(enum SignalingUpdateType type, AppUpdatesFilters & filters, class AppUpdatesSession * session, const char * src, const char * sip);
    ~SignalingUpdatesFilter();

    enum SignalingUpdateType type;
    char * src;
};

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

class SignalingClient : public UAppWebsocketClient, public UTimer {
    void AppWebsocketClientConnectComplete(class IAppWebsocketClient * appWebsocketClient);
    void AppWebsocketClientMessage(class IAppWebsocketClient * appWebsocketClient, class json_io & msg, word base, const char * mt, const char * src);
    void AppWebsocketClientSendResult(class IAppWebsocketClient * appWebsocketClient);
    void AppWebsocketClientClosed(class IAppWebsocketClient * appWebsocketClient);

    void TimerOnTimeout(ITimer * timer) override;

    void DeleteCalls();

    class Signaling * signaling;
    bool usePrimaryUrl;
    bool updateCredentials;
    char * url;
    char * sec;
    char * redirectUrl;
    char * curPbx;
    char * phys;
    char * app;
    char * usr;
    char * pwd;
    char * appdomain;
    unsigned clientTimeout;
    unsigned timeout;
    bool localInit;
    bool up;
    class ITimer retry;
    class ITimer tryAltPbx;
    bool clearRegistration;
    bool fastConnect;
public:
    SignalingClient(class Signaling * signaling);
    virtual ~SignalingClient();
    void Update(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, bool localInit);
    void Sync(class json_io & msg, word base);
    void RegisterToAltPbx();
    void RegisterToRedirectUrl(const char* url);
    void RegistrationState(bool up);
    IAppWebsocketClient * websocket;
    char * domain;
    void Close();
    bool IsClientUp() { return this->up; };
    void ClearRegistration() { this->clearRegistration = true; };
    void RegistrationCheck();
    void TestCloseRegistration();
};


class SignalingClientMonitor : public istd::listElement<SignalingClientMonitor> {
public:
    SignalingClientMonitor(class Signaling * signaling, const char * src);
    virtual ~SignalingClientMonitor();
    void MessageSend(class json_io & msg, word base, char * buffer);
    virtual void ClientState(bool up) = 0;
    virtual void ClientMessage(class json_io & msg, word base, const char * mt, const char * src) = 0;

    class Signaling * signaling;
    char * src;
};

class SignalingMessage : public JsonIo {
public:
    SignalingMessage(const char * mt, const char * src, void * sb, void * tmp);
    void Send(class SignalingSession * session);
    char * sb;
    char * tmp;
    word base;
};

class SignalingInboundVideoCall : public istd::listElement<SignalingInboundVideoCall> {
public:
    SignalingInboundVideoCall(int id, const char * mode) {
        this->id = id;
        this->mode = _strdup(mode);
    }
    ~SignalingInboundVideoCall() {
        free((char *)this->mode);
    }
    int id;
    const char * mode;
};

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

class SignalingRegistration : public SignalingClientMonitor, public UTimer {
    void ClientState(bool up) override;
    void ClientMessage(class json_io & msg, word base, const char * mt, const char * src) override;
    void ControlCall(class json_io& msg, word base, const char* mt, const char* src);
    void Register();

    void TimerOnTimeout(ITimer * timer) override;

    char * hw;
    char* phys;
    unsigned hwTimeout;
    unsigned timeout;
    class ITimer retry;
    istd::list<class SignalingInboundVideoCall> inboundVideoCalls;

public:
    SignalingRegistration(class Signaling * signaling, const char * phys);
    ~SignalingRegistration();
    void Update(const char * hw, const char* phys, unsigned timeout);
    void Sync(class json_io & msg, word base);
    void UnRegister();
    const char * GetHwId() { return this->hw; };
    dword GetNextCallId() { return callId++; };

    class SignalingCall * FindCallByState(word state);
    void StateChanged(SignalingCall* call);

    const char * AddExternalLinePrefix(const char * number, char * buffer, unsigned length);
    int CheckCountryCode(const char* num);
    bool RemoveDeppenNull(const char * number, char * buffer, unsigned length);
    unsigned ExternalLinePrefixOffset(const char * number);
    bool IsExternalCall(const char * number);
    void toggleDnd();

    bool clientUp;
    bool registered;
    class SignalingDialingLocation dialingLocation;
    char* err;
    class btree * calls;
    class SignalingCall * activePhoneCall;
    class SignalingRccSession * diversionSession;
    class SignalingCall * reconnectingCallDelete;
    class SignalingCall * pendingReconnectCall;
    bool presenceCall;
    bool dnd;
    dword callId;
};

enum CallType {
    CallTypeVoice,
    CallTypeData
};

class SignalingRegistrationMonitor : public istd::listElement<SignalingRegistrationMonitor> {
public:
    SignalingRegistrationMonitor(enum CallType callType);

    virtual void RegistrationState(bool up) = 0;
    virtual class SignalingCallMonitor * CallAdded(class SignalingCall * call) { return NULL; };
    virtual void UnknownCallMessage(const char * type, class json_io & msg, word base) {};

    enum CallType callType;
};

class SignalingRegMessage : public JsonIo {
public:
    SignalingRegMessage(const char * mt, const char * src, void * sb);
    void Send(class SignalingRegistration * registration);
    char * sb;
    word base;
};

class PresenceInfo {
public:
    const char * contact, * status, * activity, * note;
    PresenceInfo(const char * contact, const char * status, const char * activity, const char * note) {
        this->contact = _strdup(contact);
        this->status = _strdup(status);
        this->activity = _strdup(activity);
        this->note = _strdup(note);
    }
    ~PresenceInfo() {
        if (contact) free((char *)contact);
        if (status) free((char *)status);
        if (activity) free((char *)activity);
        if (note) free((char *)note);
    }
};

class VoipEndpoint {
public:
    VoipEndpoint(const char * num, const char * sip, const char * dn);
    ~VoipEndpoint() { Clear(); }
    void SetNum(const char * num);
    void AddtoNum(const char * num);
    void SetSip(const char * sip);
    void SetDn(const char * dn);
    void SetRestricted(bool restricted) { this->restricted = restricted; }
    void Clear();

    char * num;
    char * sip;
    char * dn;
    bool restricted;
};

class ConferenceInfo {
    void zeroize();
public:
    ConferenceInfo() { zeroize(); }
    ~ConferenceInfo() { cleanup(); }
    void cleanup();
    void read(class json_io& msg, word base);
    void write(class json_io& msg, word base, char*& tmp);

    char* reason;
    struct {
        unsigned id;
        char* name, * inRoomState, * activeSpeaker, * muted, * mutedByMod, * mutedByUser, * videoOffByMod, * videoOffByUser, * appSharing, * transcribing, * hand, * handOnTs, * handOffTs;
        struct { char* flags; char* num; char* sip; } remote;
        struct { unsigned id; unsigned bitrate; } videoChannels[10];
    } participant;
};

class ChatMessage {
    void zeroize();
public:
    ChatMessage() { zeroize(); }
    ~ChatMessage() { cleanup(); }
    void cleanup();
    void read(class json_io& msg, word base);
    void write(class json_io& msg, word base, char*& tmp);
    char* type, * sender, * sender_dn, * data, * guid, * mime;
    unsigned time;
};

typedef struct {
    int loss;
    int jitter;
    int roundTrip;
} stats;

class SignalingCallMonitor;

class SignalingCall : public btree, public UMedia, public UMediaIoChannel, public UTimer {
    friend class SignalingRegistration;

    int btree_compare(void * key) { return (int)(intp)key - call; };
    int btree_compare(class btree * b) { return ((class SignalingCall *)b)->call - call; };
    
    void MediaInitializeComplete(IMedia * const media, class MediaConfig *localMediaConfig) override;
    void MediaConnectResult(IMedia * const media, const char * error) override;
    void MediaRtpSendResult(IMedia * const media) override {};
    void MediaSctpSendResult(IMedia * const media) override {};
    void MediaIoSendFIR() override;
    void MediaRtpRecvResult(IMedia * const media, void * buf, size_t len, dword timestamp, short sequenceNumberDiff, bool marker);
    void MediaSctpRecvResult(IMedia * const media, void * buf, size_t len) override {};
    void MediaSctpRecvAck(IMedia * const media, unsigned num) override {};
    void MediaCloseComplete(IMedia * const media) override;
    void MediaEventReceived(IMedia * const media, enum MediaEndpointEvent event, void * ctx) override;
    void MediaRtpRecv(IMedia * const media, const char * src_addr, word src_port, dword ssrc, word pt) override;
    void MediaRtpDtmfNearStart(char digit) override;
    void MediaRtpDtmfNear(char digit) override;
    enum AudioCoder AvailableAudioCoder(unsigned coderNumber) override;
    unsigned AvailableAudioCoderCount() override;
    enum VideoCoder AvailableVideoCoder(unsigned coderNumber) override;
    unsigned AvailableVideoCoderCount() override;
    void RecordingStopped(const char * error) override;
    void RecordingStarted() override;

    void MediaIoRtpSend(const void * buf, size_t len, dword timestamp, enum MediaType mediaType, bool isConference) override;
    void MediaIoRtpForward(const void * buf, size_t len, dword timestamp, short sequenceNumberDiff, bool marker, enum MediaType mediaType) override;
    void MediaIoRtcpSend(const void * buf, size_t len, enum MediaType mediaType) override;
    void MediaIoSctpSend(const void * buf, size_t len, unsigned num) override;
    void MediaIoCloseComplete(class IMediaIoChannel * const mediaIoChannel) override;

    void TimerOnTimeout(ITimer * timer) override;

    void SendSetup();
    void SendInfo(const char * num, const char * sip);
    void SendAlert();
    void SendConn();
    void SendChannels();
    void SendRel();

    int SelectCoder(class MediaConfig * remoteMediaConfig, class MediaConfig * localMediaConfig);
    void SendLocalMedia(class json_io & json, word base, char * & tmp);
    void RecvRemoteMedia(class json_io & json, word base);
    void GenerateIceCredentials(class IceCandidates* ice);
    char* GenerateRandomString(int len);
    void MediaInitialized();
    void MediaInitialize(enum MediaType mediaType);

    void DTMF(const char* dtmf, bool feedback = false);

    class SignalingRegistration * registration;
    class IMedia* media, * media2;
    class MediaConfig * localAudioConfig, * localVideoConfig;
    class MediaConfig * remoteAudioConfig, * remoteVideoConfig;
    unsigned remoteMediaCmd;
    byte localDTMF;
    byte remoteDTMF;
    bool callWaiting;
    byte initializing;
    bool isMediaInitialized;
    byte connecting;
    bool noAudioIceCandidates;
    bool noVideoIceCandidates;
    class IAudioIoChannel * audioIoChannel;
    class IVideoIoChannel * videoIoChannel;

    class IMedia * mediaClosing;
    class IMedia * media2Closing;
    class IVideoIoChannel * videoIoChannelClosing;

    class IAudioIo * audioIo;
    class IVideoIo * videoIo;
    class IRingerIo * ringerIo;
    class IMediaProvider * mediaProvider;
    class IMediaProvider * mediaProviderVideo;

    char * hookAudioDeviceId;
    char * audioDeviceId;
    char * videoDeviceId;
    char * ringerDeviceId;
    char * ringertoneIdent;
    class URTPTP * rtptp;
    class SignalingRegistrationMonitor * exclude;
    bool deleting;
    byte hookState;
    bool audioMediaConnected;
    bool ignoreAddVideoReconnecting;

    unsigned allocatedTones;
    struct AudioIoDualTone* tones;

    stats tx;
    stats rx;

    class SignalingCall * ctIdentify;
    class SignalingCall * ctInitiate;

    int remoteCallId;
    char* remoteHwId;
    char* remoteSipId;
    int localCallId;
    char* localHwId;
    int recCallId;
    class SignalingCall * reconnectingCall;
    class SignalingCall * reconnectedCall;
    bool reconnectInitRecv;
    bool reconnectSetupRecv;
    void CreateReconnectCall();
    void StartReconnectCall(class IAudioIo* audioIo, class IVideoIo* videoIo, class URTPTP* rtptp, class SignalingRegistrationMonitor* exclude);
    void StartReconnectTimer();
    void StartReconnectRequestTimer(int timeout, bool restart = false);

    const char * GetNameIdentification(class json_io & msg, word);

public:
    SignalingCall(class SignalingRegistration * registration, enum CallType callType, int call, const char * num, const char * sip, const char * dn, bool sendingComplete);
    ~SignalingCall();
    void StartCall(class IAudioIo * audioIo, class IVideoIo * videoIo, class URTPTP * rtptp, class SignalingRegistrationMonitor * exclude);
    void ClearCall(word cause = 0);
    void ClearReconnectedCall(word cause = 0);
    void DoStartCall();
    void ConnectCall(class IAudioIo * audioIo, class IVideoIo * videoIo);
    void AbortTone();
    void Rc(unsigned rc);
    void Info(const char * num, const char * sip);
    void SendDTMF(const char * dtmf, bool feedback = false);
    void UUI(unsigned pd, const char * uui);
    void StartTransfer(SignalingCall * to);
    void StartRedirect(const char * num, const char * sip);
    void StartReroute(const char * num, const char * sip);
    void Hold();
    void Retrieve();
    void Park(const char * parked_to_num, const char * parked_to_sip);
    void Mute();
    void AudioRouteChanged();
    void RccSessionOpened();
    void RccSessionClosed();
    void StartRinging();
    void StopRinging();
    void HookDeviceAcquired();
    void HookDeviceDenied();
    void HookDeviceNotFound();
    void DisplayName(class json_io& json, word base, const char* src);

    void Delete();

    void Message(const char * type, class json_io & msg, word base);
    void MonitorXmit(const char * type, class json_io & msg, word base);
    void MonitorRecv(const char * type, class json_io & msg, word base);
    void MonitorSync();

    void SetAudioIoChannel(IAudioIoChannel * channel){audioIoChannel = channel;}
    void ChangeAudioDevice(const char* deviceId);
    void ChangeVideoDevice(const char* deviceId);
    void ChangeState(word state);
    void RegistrationState(bool up);
    void RequestStartCall();

    void ResetRegistration(class SignalingRegistration * registration) { this->registration = registration; };

    bool IsCameraOn() { return this->videoDeviceId != NULL; };
    bool IsCloseState() { return this->closeState; };
    bool IsConnectingState() { return this->connectingState; };

    const char * getAudioDeviceId() { return audioDeviceId; }

    void LookupInfo(class json_io& msg, word base);
    const char* GetConfId();
    const char* GetReconnectRole();

    class IAudioIo* GetAudioIo() { return this->audioIo; };

    void ReconnectCall(bool sync = false);
    int GetReconnectCallId() { return this->recCallId; };
    bool GetReconnectInitRecv() { return this->reconnectInitRecv; };
    bool GetReconnectSetupRecv() { return this->reconnectSetupRecv; };
    bool IsReconnectedCall() { return this->reconnectedCall != nullptr ? true : false; }
    class SignalingCall * GetReconnectingCall() { return this->reconnectingCall; };

    int call;
    OS_GUID conf;
    char * confId;
    char * reconnectConfId;
    word state;
    word cause;
    bool videoAllowed;
    bool videoDisabled;
    bool sendVideoProposal;
    bool alertPostponedOnHookDevice;
    bool connPostponed;
    bool connPostponedOnHookDevice;
    bool connReceived;
    bool selectProposalByConn;
    bool updateHookDevice;
    enum CallType callType;
    class PresenceInfo * alertPresence;
    char* videoDeviceIdBeforeHold;
    int audioCoder, videoCoder, videoCoderBeforeHold;
    class VoipEndpoint remote, diverting, originalCalled, transfering;
    istd::list<class QueuedDTMF> dtmfs;
    char * conferenceGuid;
    const char * conferenceType; // "3pty" or "central"
    bool multiVideoSupport;
    bool remoteVideoPriority;
    bool announce;
    bool muted;
    class ConferenceInfo conferenceInfo;
    class ChatMessage chatMessage;
    istd::list<SignalingCallMonitor> monitors;
    ITimer toneEndTimer, autoConnectTimer, mediaInfoInterval, queuedDTMFTimer, watingCallToneTimer, hookDeviceTimer, reconnectCallTimer, audioRtpReceived, reconnectCallResponse, reconnectCallRequest, mediaLostToneTimer, signalingTestTimer;
    unsigned ct_setup_id;
    int numVideoPackets, numAudioPackets;
    int lastNumVideoPackets, lastNumAudioPackets, lastNumRtpAudioPackets;
    int numNoRtpPackets;
    bool videoDataOff;
    bool audioDataOff;
    time_t duration;
    bool sendingComplete;
    bool closeState;
    bool connectingState;
    bool isRccCall;
    bool callNotInBtree;
    bool sigRelSent;
    int reconnectTimeout;
    bool failedReconnect;

    class SignalingCall * replacedCall;
    class SignalingCall * replacementCall;

    struct {
        unsigned id; char* num; char* sip; char* dn;
        void cleanup() { free(num); free(sip); free(dn); zerioze(); }
        void zerioze() { id = 0; num = 0; sip = 0; dn = 0; }
    } fty_ct_initiate;

    struct {
        bool isExternal; bool isTrunkRecording; bool isRecordingByDefaultOn; char* recordingError;
        void cleanup() { free(recordingError); initialize(); }
        void initialize() { isExternal = false; isTrunkRecording = false; isRecordingByDefaultOn = false; recordingError = 0; }
    } recordingInfo;
};

class QueuedDTMF : public istd::listElement<QueuedDTMF> {
public:
    QueuedDTMF(const char * dtmf, bool feedback = false) {
        this->dtmf = _strdup(dtmf); 
        this->feedback = feedback;
    }
    ~QueuedDTMF() {
        if (this->dtmf) free(this->dtmf);
    }
    char * dtmf;
    bool feedback;
};

class SignalingCallMonitor : public istd::listElement<SignalingCallMonitor> {
public:
    SignalingCallMonitor(class SignalingCall * call);
    virtual ~SignalingCallMonitor();
    virtual void MonitorXmit(const char * type, class json_io & msg, word base) = 0;
    virtual void MonitorRecv(const char * type, class json_io & msg, word base) = 0;
    virtual void MonitorSync() = 0;
    class SignalingCall * call;
};

class SignalingSigMessage : public JsonIo {
public:
    SignalingSigMessage(int call, const char * type, void * sb, void * tmp);
    void Send(class SignalingRegistration * registration);
    char * sb;
    char * tmp;
    word base;
    word sig;
};

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

#define RCC_TYPE_USER       1
#define RCC_TYPE_CALL       2
#define RCC_TYPE_CALL_INFO  3

class SignalingRccVideoConfCall : public istd::listElement<SignalingRccVideoConfCall> {
public:
    SignalingRccVideoConfCall(const char* confId, int id) {
        this->confId = _strdup(confId);
        this->id = id;
    }
    ~SignalingRccVideoConfCall() {
        if (this->confId) free(this->confId);
    }

    char* confId;
    int id;
};

class SignalingRccSessionHttpClient : public UHTTPClient, public UTask, public STaskContext {
    class Signaling * signaling;
    class SignalingRccSession * rccSession;
    class IHTTPClient * httpClient;

    char * url;
    byte * recvBuffer;
    class TaskFileWrite * fWrite;
    bool closing;
    bool httpOk;
    bool fileOk;
    bool firstFProgressReceived;

    void HTTPClientConnectComplete(IHTTPClient * const httpClient) override;
    void HTTPClientShutdown(IHTTPClient * const httpClient, http_shutdown_reason_t reason) override;
    void HTTPClientSendResult(IHTTPClient * const httpClient) override;
    void HTTPClientRecvResult(IHTTPClient * const httpClient, byte * buffer, size_t len, bool transferComplete) override;
    void HTTPClientRecvCanceled(IHTTPClient * const httpClient, byte * buffer) override;
    void HTTPClientRequestComplete(IHTTPClient * const httpClient) override;

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

    void TryClose();

public:
    SignalingRccSessionHttpClient(class Signaling * signaling, class SignalingRccSession * rccSession, const char * url);
    ~SignalingRccSessionHttpClient();

    bool Success();
    void Close();
};

class SignalingRccSession : public JsonApi, public SignalingRegistrationMonitor {
    friend class SignalingRCCMessage;
    const char * Name() { return "RCC"; };
    void JsonApiStart();
    void Message(class json_io & msg, word base, const char * mt, const char * src);
    void JsonApiConnectionClosed();

    void Version(class json_io & json, word base, const char * src);
    void DoNotStartCamera(class json_io& json, word base, const char* src);
    void DiversionInterrogate(class json_io & json, word base, const char * src);
    void DiversionActivateDeactivate(class json_io & json, word base, const char * src);
    void UserInitialize(class json_io & json, word base, const char * src);
    void UserCall(class json_io & json, word base, const char * src);
    void UserClear(class json_io & json, word base, const char * src);
    void UserConnect(class json_io & json, word base, const char * src);
    void UserRc(class json_io & json, word base, const char * src);
    void UserInfo(class json_io & json, word base, const char * src);
    void UserDTMF(class json_io & json, word base, const char * src);
    void UserUUI(class json_io & json, word base, const char * src);
    void UserTransfer(class json_io & json, word base, const char * src);
    void UserRedirect(class json_io & json, word base, const char * src);
    void UserReroute(class json_io & json, word base, const char * src);
    void UserHold(class json_io & json, word base, const char * src);
    void UserRetrieve(class json_io & json, word base, const char * src);
    void UserPark(class json_io& json, word base, const char* src);
    void UserOptions(class json_io& json, word base, const char* src);
    void PbxSignal(class json_io& json, word base, const char* src);
    void PlaySound(class json_io& json, word base, const char* src);
    void DisplayName(class json_io& json, word base, const char* src);

    class IJsonApiConnection * connection;
    char * src;
    char * userInitializeSrc;
    bool userInitializeRx;
    class SignalingRccUser * user;
    void UserInitializeResult();
    void RegistrationState(bool up) override;

    istd::list<class SignalingRccVideoConfCall> videoConfCalls;

    class SignalingRccSessionHttpClient * httpClient;
    char webaccessTone[4096]; //PATH_MAX = 4096

    bool stopping;

public:
    SignalingRccSession(class SignalingRcc * rcc, class IJsonApiConnection * connection, class IInstanceLog * log);
    ~SignalingRccSession();
    class SignalingRccUser * FindUser(word handle);
    class SignalingRccCall * FindCall(word handle);
    class SignalingRcc * rcc;
    class IInstanceLog * log;
    word lastSessionHandle;
    class btree * handles;
    istd::list<SignalingRccUser> users;

    class IInstanceLog * GetLog() { return this->log; };

    void JsonApiMessage(class json_io& msg, char* buffer) { connection->JsonApiMessage(msg, buffer); }

    void SignalingRccSessionHttpClientClosed(class SignalingRccSessionHttpClient * httpClient);
};

class SignalingRccHandle : public btree {

    int btree_compare(void * key);
    int btree_compare(class btree * b);

public:
    SignalingRccHandle(class SignalingRccSession * session, word type);
    virtual ~SignalingRccHandle();

    class SignalingRccSession * session;
    word type;
    word handle;
};

class SignalingRCCMessage : public JsonIo {
public:
    SignalingRCCMessage(const char * mt, const char * src, class SignalingRccSession * session, void * sb, void * tmp);
    void Send();
    class SignalingRccSession * session;
    char * sb;
    char * tmp;
    word base;
    word sig;
};

class SignalingRccUser : public SignalingRccHandle, public SignalingRegistrationMonitor, public istd::listElement<SignalingRccUser> {
    void RegistrationState(bool up) override;
    class SignalingCallMonitor * CallAdded(class SignalingCall * call) override;
    void UnknownCallMessage(const char * type, class json_io & msg, word base) override;
public:
    SignalingRccUser(class SignalingRccSession * session, const char * src);
    ~SignalingRccUser();
    char * src;
    class IInstanceLog * log;
    istd::list<SignalingRccCall> calls;
};

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

class SignalingRccCall : public SignalingRccHandle, public SignalingCallMonitor, public istd::listElement<SignalingRccCall> {
    void MonitorXmit(const char * type, class json_io & msg, word base);
    void MonitorRecv(const char * type, class json_io & msg, word base);
    void MonitorSync();
    void SendCallInfo(class json_io & msg, word base, char * & tmp);

    class SignalingRccSession * session;
    class SignalingRccUser * user;
public:
    SignalingRccCall(SignalingRccSession * session, SignalingRccUser * user, class SignalingCall * call);
    ~SignalingRccCall();
};

/*-----------------------------------------------------------------------------------------------*/
/* SignalingRTPTP                                                                                */
/*-----------------------------------------------------------------------------------------------*/

class SignalingRTPTP : public SignalingRegistrationMonitor, public UTimer, public IRTPTP {
    void RegistrationState(bool up) override;
    class SignalingCallMonitor * CallAdded(class SignalingCall * call) override { return 0; };

    void TimerOnTimeout(ITimer * timer) override;

    void Send(void * buf, size_t len) override;

    char * sip;
    unsigned timeout;
    class ITimer retry;
public:
    SignalingRTPTP(class Signaling * signaling);
    ~SignalingRTPTP();
    class SignalingRTPTP * Update(class URTPTP * user, const char * sip, unsigned timeout);
    void Sync(class json_io & msg, word base);
    void State(bool up);

    class Signaling * signaling;
    class URTPTP * user;
    bool up;
    class SignalingRTPTPCall * call;
};

class SignalingRTPTPCall : public SignalingCallMonitor {
    class SignalingRTPTP * rtptp;
    void MonitorXmit(const char * type, class json_io & msg, word base) override;
    void MonitorRecv(const char * type, class json_io & msg, word base) override;
    void MonitorSync() override;
public:
    SignalingRTPTPCall(class SignalingRTPTP * rtptp, class SignalingCall * call);
    ~SignalingRTPTPCall();
};

#endif
