/*---------------------------------------------------------------------------*/
/* appclient_media.h                                                         */
/* copyright (c) innovaphone 2017                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

typedef struct {
    int id;
    const char *name;
} MediaIdNameItem;

class AppClientMedia: public UWebserverPlugin, public IShutdownHandler, public IInstanceLog, public UAudioIo, public UHookDevice, public UVideoIo, public UAppSharingIo, public UDeviceIo, public IAppClientMedia {
    IIoMux * iomux;
    class IAudioPhone * audioPhone;
    class IAudioIo * audioIo;
    bool audioIoInitialized;
    class IVideoIo * videoIo;
    bool videoIoInitialized;
    class IAppSharingIo * appSharingIo;
    bool appSharingIoInitialized;
    class IMediaProvider * mediaProvider;
    ISocketProvider * socketUdpProvider;
    ISocketProvider * socketTcpProvider;
    ISocketProvider * socketTlsProvider;
    class ISocketContext * context;
    class ISocketContext * contextSrtp;
    char *appName;
    char *appDomain;
    istd::list<class AppClientMediaSession> sessions;
    class MediaConfiguration * mediaConfiguration;

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

    void CloseAppSharingIoComplete() override;
    void CloseVideoIoComplete() override;
    void CloseAudioIoComplete() override;

    // overrides of IInstanceLogs
    const char * GetAppName() const { return appName; }
    const char * GetAppDomain() const { return appDomain; }

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

    void MediaIoAppSharingContainerSend(const void * buf, size_t len, dword timestamp, const void * context, bool local, enum MediaType type) override;

    void MediaIoDeviceAdded(IDeviceIo * deviceIo, void * context, const char * deviceId, int deviceType, unsigned deviceCapabilities, const char * deviceName, bool selected) override;
    void MediaIoDeviceFailed(IDeviceIo * deviceIo, void * context, const char * deviceId) override;
    void MediaIoDeviceRemoved(IDeviceIo * deviceIo, void * context, const char * deviceId) override;
    void MediaIoQueryDevicesResult(IDeviceIo * deviceIo, void * context) override;
    void MediaIoDeviceStarted(IDeviceIo * deviceIo, const char * deviceId) override;
    void MediaIoDeviceStopped(IDeviceIo * deviceIo, const char * deviceId) override;
    void HookKeyReceived(IAudioIo * audioIo, const char *deviceId, byte key) override;
    void HookDeviceMsg(IAudioIo* audioIo, const char* deviceId, const char* msg) override;

    void RequestAppSharingControl(unsigned int userId) override;
    void AddAppSharingApplication(unsigned int id, const char * name, const char * desc, const char * hwId, const char * icon,  const char * thumbnail, bool isDesktop) override;
    void UpdateAppSharingApplication(unsigned int id, const char * name, const char * desc, const char * hwId, const char * icon,  const char * thumbnail) override;
    void RemoveAppSharingApplication(unsigned int id) override;

    void SetExcludeInterfaces(const char * excludeInf) override;
    void SetNoVpnAddresses(bool noVpnAddresses) override;
    void SetCurrentUserSip(const char * sip) override;
    void SetRecordingUrl(const char * url, bool force) override;
    void SetLangId(const char* langId) override;
    bool IsCapturingApplications() { return mediaConfiguration ? mediaConfiguration->isCapturingApplications : false; };
    void QueryDevices(bool cleanDevices) override;
    void MyAppsRunningRemote(bool remote) { if(mediaConfiguration) mediaConfiguration->isRunningTS = remote; };
    void SetRingerVolume(unsigned volume) override { if (mediaConfiguration) mediaConfiguration->ringerVolume = volume; };
    void SetHandsetVolume(unsigned volume) override { if (mediaConfiguration) mediaConfiguration->handsetVolume = volume; };
    void SetHandsfreeVolume(unsigned volume) override { if (mediaConfiguration) mediaConfiguration->handsfreeVolume = volume; };
    void SetHeadsetVolume(unsigned volume) override { if (mediaConfiguration) mediaConfiguration->headsetVolume = volume; };

    class AppClientMediaSession * FindSessionByContext(const void * context, enum MediaType media, bool local);
    class AppClientMediaSession * FindSessionByChannelId(const char * channelId, enum MediaType media);

public:
    AppClientMedia(IIoMux * const iomux, IWebserverPluginProvider * webserverPluginProvider, ISocketProvider * localSocketProvider,
        ISocketProvider * socketUdpProvider, ISocketProvider * socketTcpProvider, ISocketProvider * socketTlsProvider, AppInstanceArgs * args, class MediaConfiguration * mediaConfiguration);
    ~AppClientMedia();

    static const char *IdName(MediaIdNameItem *map, int id);
    static int NameId(MediaIdNameItem *map, const char *name);

    void RemoveSession(class AppClientMediaSession * session);
    class IAudioIo * GetAudioIo() { return this->audioIo; };
    class IVideoIo * GetVideoIo() override { return this->videoIo; };
    class IMediaProvider * GetMediaProvider() { return this->mediaProvider; };
    class ISocketContext * GetSocketContext() { return this->contextSrtp; };

    bool stopping;
    bool stoppingAudio;
    bool stoppingVideo;
    bool stoppingAppSharing;
    char * pwd;
    IWebserverPlugin * webserverPlugin;
};

class MediaConnection: public UMedia, public UMediaIoChannel, public istd::listElement<MediaConnection> {
    class IInstanceLog * const log;
    class IIoMux * const iomux;

    class IMediaIoChannel * mediaIoChannel;
    class IMediaProvider * mediaProvider;
    class AppClientMediaSession * mediaSession;
    ISocketProvider * socketUdpProvider;
    ISocketProvider * socketTcpProvider;
    ISocketProvider * socketTlsProvider;
    class ISocketContext * socketContext;

    class IMedia * media;
    class IDeviceIo * deviceIo;
    MediaType mediaType;
    class MediaConfig * config;
    class MediaConfiguration * mediaConfiguration;
    istd::list<class CodecAssignments> assignmentsList;

    char * hostPbx;
    char * selectedInputDevice;  // To be removed when Thomas A. implements StartDevice/StopDevices JSON Messages 
    char * ch;
    char * dn;
    int mediaCodec;
    bool closing;
    bool closed;
    enum ConnectionType connType;

    int DefaultPayloadType(unsigned coder);

    class IMediaContainer * container;

public:
    MediaConnection(class AppClientMediaSession * mediaSession, IIoMux * const iomux, class IInstanceLog * log, class ISocketProvider * socketUdpProvider, class ISocketProvider * socketTcpProvider, class ISocketProvider * socketTlsProvider, class ISocketContext * context, class IMediaProvider * mediaProvider, class IDeviceIo * deviceIO, MediaType mediaType, const char * channelId, enum ConnectionType local, const char * dn, class MediaConfiguration * mediaConfiguration, char * hostPbx);
    ~MediaConnection();

    void WebsocketMessage(class json_io & msg, word base, const char * mt, const char * src);
    void RtpDtmf(char digit, byte pt) { media->RtpDtmf(digit, pt); };
    const char * GetChannelId() { return this->ch; };
    enum MediaType GetMediaType() { return this->mediaType; };
    enum ConnectionType GetConnectionType() { return this->connType; };
    void CloseMediaConnection();
    bool IsClosing() { return this->closing; };

    void MediaInitializeComplete(IMedia * const media, class MediaConfig *mediaConfig) override;
    void MediaConnectResult(IMedia * const media, const char * error) override;
    void MediaRtpSendResult(IMedia * const media) override;
    void MediaSctpSendResult(IMedia * const media) override;
    void MediaRtpRecvResult(IMedia * const media, void * buf, size_t len, dword timestamp, short sequenceNumberDiff, bool marker) override;
    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;
    enum VideoCoder AvailableVideoCoder(unsigned coderNumber) override;
    enum AppSharingCoder AvailableAppSharingCoder(unsigned coderNumber) override;
    unsigned AvailableAudioCoderCount() override;
    unsigned AvailableVideoCoderCount() override;
    unsigned AvailableAppSharingCoderCount() 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 MediaIoSendFIR() override;
};

class SelectedDevices : public istd::listElement<SelectedDevices> {
public:
    SelectedDevices(void * context, const char * device) {
        this->context = context;
        this->device = _strdup(device);
        this->counter = 0;
    }
    ~SelectedDevices() { 
        if(this->device) free(this->device); 
    };

    void IncCounter() { this->counter++; };
    void DecCounter() { this->counter--; };
    int GetCounter() { return this->counter; };

    void * context;
    char * device;
    int counter;
};    

#define APPCLIENT_MEDIA_MAX_CALLS   2

class AppClientMediaSession: public AppWebsocket, public UMediaContainer, public istd::listElement<AppClientMediaSession> {
    void AppWebsocketAccept(class UWebsocket * uwebsocket) { appclient->webserverPlugin->WebsocketAccept(uwebsocket); };
    char * AppWebsocketPassword() { return appclient->pwd; };
    void AppWebsocketMessage(class json_io & msg, word base, const char * mt, const char * src);
    void AppWebsocketBinaryMessage(void * buffer, int len);
    bool AppWebsocketConnectComplete(class json_io & msg, word info);
    void AppWebsocketSendResult();
    void AppWebsocketClosed();

    class IMediaProvider * mediaProvider;
    class IInstanceLog * log;
    class IIoMux * iomux;
    MediaType mediaType;
    class MediaConfiguration * mediaConfiguration;
    char * hostPbx;

    bool closed;

    ISocketProvider * socketUdpProvider;
    ISocketProvider * socketTcpProvider;
    ISocketProvider * socketTlsProvider;
    class ISocketContext * socketContext;

    istd::list<class SelectedDevices> devices;
    istd::list<class SelectedDevices> hookDevices;
    istd::list<class MediaConnection> connections;
    unsigned callCount;
    int callIds[APPCLIENT_MEDIA_MAX_CALLS];

    unsigned allocatedTones;
    struct AudioIoDualTone *tones;

    class MediaConnection * GetConnectionByChannelID(const char * ch, enum ConnectionType connType, MediaType mediaType);

    class IMediaContainer * container;

    char * id;
    bool local;

    class AppClientMediaSrc * mediaSessionContext;

public:
    AppClientMediaSession(class AppClientMedia * appclient, IIoMux * const iomux, class ISocketProvider * socketUdpProvider, class ISocketProvider * socketTcpProvider, class ISocketProvider * socketTlsProvider, class ISocketContext * context, class IMediaProvider * mediaProvider, class IDeviceIo * deviceIo, MediaType mediaType, char * id, bool local, class MediaConfiguration * mediaConfiguration);
    ~AppClientMediaSession();

    const char * GetSip() { return sip; };
    const char * GetId() { return id; };
    bool GetLocal() { return local; };
    class AppClientMediaSession * FindSessionByChannelId(const char * channelId, enum MediaType media);

    class IMediaContainer * GetMediaContainer() { return container; };
    MediaType GetMediaType() { return mediaType; };
    void RemoveConnection(class MediaConnection * connection);

    void ContainerSample(char * buf, int len) override;

    class IDeviceIo * deviceIo;
    class AppClientMedia * appclient;
};

class AppClientMediaSrc: public MediaProviderContext {
public:
    AppClientMediaSrc(class AppClientMediaSession * session, const char * src, const char * app);
    ~AppClientMediaSrc();
};
