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

#define FIT_FRAME_RATE_60_1       0x00001000L
#define FIT_FRAME_TO_720_480      0x00002000L
#define FIT_RGB_FRAME_TO_720_480  0x00004000L

enum ConferenceMode {
    FORWARDING_MODE = 0,
    TRANSCODING_MODE,
    MIXING_MODE
};

class WebcamHelper : public istd::listElement<WebcamHelper> {
public:
    WebcamHelper(class IWebcam * const webcam, const char* deviceId = NULL, int capabilities = 0, const char* name = NULL) {
        this->webcam = webcam;
        this->outputFormat = NULL;
        this->failed = 0;
        this->isSelected = false;
        this->deviceId = _strdup(deviceId);
        this->name = _strdup(name);
        this->capabilities = capabilities;
        this->isRemote = false;
        this->isNewDevice = true;
        this->context = NULL;
        this->deviceMode = 0;
    }
    ~WebcamHelper() {
        if(this->outputFormat) free(this->outputFormat);
        if (this->deviceId) free(this->deviceId);
        if (this->name) free(this->name);
    };

    bool isNewDevice;
    bool isRemote;
    bool isSelected;
    int width;
    int height;
    int failed;
    class IWebcam * webcam;
    struct VideoFrameFormat * outputFormat;
    char* deviceId;
    char* name;
    int capabilities;
    void* context;
    int deviceMode;
};

class VideoSession {
public:
    VideoSession(const char * deviceId, enum VideoCoder coder, bool local) {
        this->deviceId = _strdup(deviceId);
        this->localChannelId = NULL;
        this->remoteChannelId = NULL;
        this->localVideoCoder = coder;
        this->remoteVideoCoder = coder;
        this->deletedLocalEncoder = NULL;
        this->deletedRemoteEncoder = NULL;
        this->localEncoder = NULL;
        this->encoderInit = false;
        this->remoteEncoder = NULL;
        this->local = local;
        this->conference = false;
        this->reconfigureLocalEncoder = false;
        this->remoteServices = false;
        this->deviceMode = 0;
    };
    ~VideoSession() {
        if(this->localChannelId) free(this->localChannelId);
        if(this->remoteChannelId) free(this->remoteChannelId);
        if(this->deviceId) free(this->deviceId);
        if(this->localEncoder) this->localEncoder->Close();
        if(this->remoteEncoder) this->remoteEncoder->Close();
    }

    class IMediaEncoder * localEncoder;
    class IMediaEncoder * deletedLocalEncoder;
    class IMediaEncoder * remoteEncoder;
    class IMediaEncoder * deletedRemoteEncoder;
    enum VideoCoder localVideoCoder;
    enum VideoCoder remoteVideoCoder;
    char * deviceId;
    char * localChannelId;
    char * remoteChannelId;
    bool local;
    bool conference;
    bool encoderInit;
    bool reconfigureLocalEncoder;
    bool remoteServices;
    int deviceMode;
};

class ConferenceSample : public istd::listElement<ConferenceSample> {
public:
    ConferenceSample(class VideoIoChannel * videoIoChannel, const void * buf, size_t len, dword timestamp, dword duration, struct VideoFrameFormat * format) {
        this->videoIoChannel = videoIoChannel;
        this->buf = malloc(len);
        memcpy(this->buf, buf, len); 
        this->len = len;
        this->timestamp = timestamp;
        this->duration = duration;
        this->format = (struct VideoFrameFormat *)malloc(sizeof(struct VideoFrameFormat));
        memcpy(this->format, format, sizeof(struct VideoFrameFormat));
    };
    ~ConferenceSample() {
        if(this->buf) free(this->buf);
        if(this->format) free(this->format);
    };

    class VideoIoChannel * videoIoChannel;
    void * buf;
    int len;
    dword timestamp;
    dword duration;
    struct VideoFrameFormat * format;
};

class AppSharingApp : public istd::listElement<class AppSharingApp> {
public:
    AppSharingApp(unsigned int id, byte sessionId, const char* name, const char* description, const char* hwId, const char* icon, const char* thumbnail, void* handle, void* context, bool isDesktop, bool isRemote = false) {
        this->id = id;
        this->handle = handle;
        this->icon = _strdup(icon);
        this->thumbnail = _strdup(thumbnail);
        this->captureTimer = NULL;
        this->sentData = 0;
        this->sessionId = sessionId;
        this->context = context;
        this->lastImageBufferSample = 0;
        this->isDesktop = isDesktop;
        this->isRemote = isRemote;

        this->wcName = (word*)malloc((strlen(name) + 1) << 1);
        memset(this->wcName, 0x0, (strlen(name) + 1) << 1);
        str::latin1_to_ucs2_n(name, (word*)this->wcName, strlen(name) + 1);
        this->name = _strdup(name);

        this->wcDescription = (word*)malloc((strlen(description) + 1) << 1);
        memset(this->wcDescription, 0x0, (strlen(description) + 1) << 1);
        str::latin1_to_ucs2_n(description, this->wcDescription, strlen(description) + 1);
        this->description = _strdup(description);

        this->hwId = _strdup(hwId);
    };
    ~AppSharingApp() {
        if (this->wcName) free(this->wcName);
        if (this->name) free(this->name);
        if (this->wcDescription) free(this->wcDescription);
        if (this->description) free(this->description);
        if (this->hwId) free(this->hwId);
        if (this->icon) free(this->icon);
        if (this->thumbnail) free(this->thumbnail);
        if (captureTimer) delete captureTimer;
    };

    void Update(unsigned int id, const char* name, const char* description, const char* hwId, const char* icon, const char* thumbnail) {
        if (this->wcName) free(this->wcName);
        this->wcName = (word*)malloc((strlen(name) + 1) << 1);
        memset(this->wcName, 0x0, (strlen(name) + 1) << 1);
        str::latin1_to_ucs2_n(name, this->wcName, strlen(name) + 1);
        if (this->name) free(this->name);
        this->name = _strdup(name);

        if (this->wcDescription) free(this->wcDescription);
        this->wcDescription = (word*)malloc((strlen(description) + 1) << 1);
        memset(this->wcDescription, 0x0, (strlen(description) + 1) << 1);
        str::latin1_to_ucs2_n(description, this->wcDescription, strlen(description) + 1);
        if (this->description) free(this->description);
        this->description = _strdup(description);

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

        if (this->icon) free(this->icon);
        this->icon = _strdup(icon);
        if (this->thumbnail) free(this->thumbnail);
        this->thumbnail = _strdup(thumbnail);
    }

    unsigned int id;
    byte sessionId;
    void* context;
    int sentData;
    void* handle;
    char* name;
    char* description;
    char* hwId;
    word* wcName;
    word* wcDescription;
    char* icon;
    char* thumbnail;
    class ITimer* captureTimer;
    ulong64 lastImageBufferSample;
    ulong64 tvStart;
    bool isDesktop;
    bool isRemote;
};

class VideoIo : public IVideoIo, public UImageBuffer, public UIoExec, public UWebcam, public UWebcamProvider, public UMediaEncoder, public URenderVideoStream, public URenderVideoProvider, public UTimer {
    class UDeviceIo * deviceIoUser;
    class UVideoIo * videoIoUser;
    class UAppSharingIo * appSharingIoUser;
    class USelectDeviceIo* selectDeviceUser;

    class IRenderVideoProvider* videoRenderProvider;
    class IRenderVideoStream* rendererWebcam;
    class IRenderVideoStream* deletedRendererWebcam;

    class IInstanceLog * const log;
    class IIoMux * const iomux;
    class IWebcamProvider * webcamProvider;

    class URemoteMediaAppSharing * remoteMediaAppSharingUser;

    void TimerOnTimeout(ITimer * timer);

    void * ctx;

    class VideoSession * videoSession;
    istd::list<class ConferenceSample> conferenceSamples;
    istd::list<class WebcamHelper> webcams;
    istd::list<class VideoIoChannel> ioChannels;

    bool shutDown;
    int execCnt;
    class ITimer * fullIntraFrameTimer;
    class ITimer * restartWebcamTimer;
    class WebcamHelper * restartWebcam;

    class MediaConfiguration* mediaConfiguration;

    void TryClose(void);
    bool HasConferenceSamples(class VideoIoChannel * ch);

    // AppSharing
    istd::list<class AppSharingApp> apps;
    istd::list<class AppSharingApp> shareApps;
    bool subscribe;
    class IImageBuffer* imageBuffer;
    class IMediaEncoder* appSharingEncoder;
    class IMediaEncoder* deletedAppSharingEncoder;
    struct VideoFrameFormat appSharingFormat;
    int isSharing;
    int remoteVideoPriority;

    enum VideoCoder remoteVideoCoder;
    enum VideoCoder appSharingVideoCoder;
    bool remoteVideoRunning;

    class WebcamHelper * FindDevice(const char * deviceId);
    istd::list<class SelectedDeviceIo> selectedDevices;
    char* softphoneApp;
    void ReadSelectedDevices();
    void WriteSelectedDevices();

    bool selectedDeviceRemoved;

    bool remoteServices;

    istd::list<class UDeviceIoHelper> remoteDeviceUsers;
    istd::list<class UAppSharingIoHelper> remoteAppSharingUsers;

public:
    VideoIo(class IIoMux * const iomux, class IInstanceLog * log, class MediaConfiguration* mediaConfiguration);
    ~VideoIo();

    // IDeviceIo
    void QueryDevices(void * context) override;
    void CleanRemoteDevices () override;
    const char * StartDevice(void* context, void* src, const char* device, int deviceMode) override { return NULL; };
    void StopDevice(void * context, const char * device) override;
    void SelectDevice(const char *app, const char *deviceId);
    void ChangeDevice(const char *app, const char *deviceId) override;
    const char * GetDeviceIo(const char * app);
    void SubscribeApplications() override;
    void UnsubscribeApplications() override;
    void ShareApplication(void* context, unsigned int id, bool admin) override;
    void UnshareApplication(void* context, unsigned int id) override;
    void RemoteShareApplication() { this->isSharing++; };
    void RemoteUnshareApplication() { if (this->isSharing) this->isSharing--; };
    void RemoteVideoPriority(void* context, const char * ch, bool on) override;
    void UnshareAllApplications(void* context) override;
    void AddRemoteUDeviceIoUser(UDeviceIo* user) override {
        if (!remoteDeviceUsers.empty()) {
            class UDeviceIoHelper* h = remoteDeviceUsers.front();
            while (h) {
                if (h->user == user) return;
                h = h->goNext();
            }
        }
        remoteDeviceUsers.push_back(new class UDeviceIoHelper(user));
    };
    void RemoveRemoteUDeviceIoUser(UDeviceIo* user) override {
        if (!remoteDeviceUsers.empty()) {
            class UDeviceIoHelper* h = remoteDeviceUsers.front();
            while (h) {
                if (h->user == user) {
                    h->remove();
                    delete h;
                    return;
                }
                h = h->goNext();
            }
        }
    };
    void AddRemoteUAppSharingIoUser(UAppSharingIo* user) override {
        if (!remoteAppSharingUsers.empty()) {
            class UAppSharingIoHelper* h = remoteAppSharingUsers.front();
            while (h) {
                if (h->user == user) return;
                h = h->goNext();
            }
        }
        remoteAppSharingUsers.push_back(new class UAppSharingIoHelper(user));
    };
    void RemoveRemoteUAppSharingIoUser(UAppSharingIo* user) override {
        if (!remoteAppSharingUsers.empty()) {
            class UAppSharingIoHelper* h = remoteAppSharingUsers.front();
            while (h) {
                if (h->user == user) {
                    h->remove();
                    delete h;
                    return;
                }
                h = h->goNext();
            }
        }
    };

    // IVideoIo
    class IVideoIoChannel *CreateChannel(class UMediaIoChannel * const user) override;
    void Initialize(class UVideoIo * const user, class UDeviceIo * const mediaIoUser, class UAppSharingIo* const appSharingIoUser) override;
    const char* StartVideoDevice(void* context, void* src, const char* device, int width, int height, int deviceMode) override;
    void StartLocalVideoEncoder(void * session, const char * channelId, int coder, bool remoteServices = false) override;
    void StartRemoteVideoEncoder(void * session, const char * channelId, int coder) override;
    void StopLocalVideoEncoder(void * session, const char * channelId, int coder) override;
    void Close() override;
    void SetUSelectDeviceIoUser(USelectDeviceIo* user) override { this->selectDeviceUser = user; };
    void SetRemoteMediaAppSharingUser(class URemoteMediaAppSharing* user)  override { this->remoteMediaAppSharingUser = user; };

    // UWebcam
    void WebcamVideoFormat(class IWebcam * const webcam, const struct VideoFrameFormat * format) override {};
    void WebcamSample(class IWebcam * const webcam, const void * buf, int len, dword timestamp, dword duration, const struct VideoFrameFormat * format) override;

    // UWebcamProvider
    void WebcamLost(class IWebcam * const webcam, void * context) override;
    void WebcamFailed(class IWebcam* const webcam, void* context) override;
    void WebcamAdded(class IWebcam * const webcam, void * context) override;
    void WebcamQueryResult(void * context) override;
    void WebcamClosed(class IWebcam * const webcam) override;
    void WebcamProviderCloseResult() override;

    // URenderVideoProvider
    void RenderVideoProviderCloseResult() override;

    void RenderVideoStreamCloseResult(class IRenderVideoStream* renderer) override;

    // UVideoEncoder
    void EncodeResult(class IMediaEncoder * encoder, const void * buf, int len, dword timestamp, dword duration) override;
    void MediaEncoderCloseResult(class IMediaEncoder * encoder) override;

    // UIoMux
    void IoExec(void * execContext) override;

    // UImageBuffer
    void Add(unsigned int id, const char* name, const char* description, const char * hwId, const char* icon, const char* thumbnail, void* handle, bool isDesktop) override;
    void Update(unsigned int id, const char* name, const char* description, const char * hwId, const char* icon, const char* thumbnail) override;
    void Remove(unsigned int id) override;
    void ImageBufferSample(unsigned int appId, const void* buf, int len, int width, int height, dword timestamp, dword duration) override;
    void CloseComplete() override;
    void ReloadApplicationList() override;

    void RtpNackMessage(class VideoIoChannel* videoIoChannel, void* ctx);
    void KeyFrameSequences(class VideoIoChannel* videoIoChannel, void* ctx);
    void FullIntraRequest(class VideoIoChannel * videoIoChannel);
    void PacketLostIndication(class VideoIoChannel* videoIoChannel);
    void Add(class VideoIoChannel * videoIoChannel);
    void Remove(class VideoIoChannel * videoIoChannel);

    void RtpRecv(class VideoIoChannel * videoIoChannel, void * buf, size_t len, dword timestamp, short sequenceNumberDiff, bool marker);
    void ConferenceSample(class VideoIoChannel * videoIoChannel, const void * buf, size_t len, dword timestamp, dword duration, struct VideoFrameFormat * f);
    void DecodedSample(class VideoIoChannel* videoIoChannel, const void* buf, size_t len, dword timestamp, dword duration, struct VideoFrameFormat* f);
    class IRenderVideoStream* OpenVideoRenderStream(class VideoIoChannel* videoIoChannel, bool preview, enum VideoCoder decoder, bool alwaysOnTop = false);

    void RemoteMediaIoDeviceAdded(const char* deviceId, int deviceType, unsigned deviceCapabilities, const char* deviceName, bool selected = false) override;
    void RemoteMediaIoDeviceRemoved(const char* deviceId) override;
    void RemoteMediaIoDeviceFailed(const char* deviceId) override;
    void RemoteMediaIoQueryDevicesResult() override;
    void RemoteAddAppSharingApplication(unsigned int id, const char* name, const char* desc, const char* icon, const char* thumbnail, bool isDesktop) override;
    void RemoteRemoveAppSharingApplication(unsigned int id) override;
};

class RtpPacket : public istd::listElement<RtpPacket> {
public:
    RtpPacket(void* buf, size_t len, unsigned int timestamp, bool marker, word rtpSequence) {
        this->buf = (char*)malloc(len);
        memcpy(this->buf, buf, len);
        this->len = len;
        this->timestamp = timestamp;
        this->marker = marker;
        this->rtpSequence = rtpSequence;
    }
    ~RtpPacket() {
        if (this->buf) free(this->buf);
    }

    void* buf;
    size_t len;
    unsigned int timestamp;
    word rtpSequence;
    bool marker;
};

class VideoIoChannel: public IVideoIoChannel, public istd::listElement<VideoIoChannel>, public UMediaEncoder, public UMediaDecoder, public URenderVideoStream, public UIoExec, public UTimer {
protected:
    friend class VideoIo;
    class VideoIo * const videoIo;
    class UMediaIoChannel * const user;
    class IInstanceLog * const log;
    class IIoMux * const iomux;

    class IRenderVideoStream * renderer;
    class IRenderVideoStream * deletedRenderer;

    class IMediaDecoder * decoder;
    class IMediaEncoder * encoder;
    class IMediaDecoder * deletedDecoder;
    class IMediaEncoder * deletedEncoder;
    enum VideoCoder coder;
    enum MediaType type;
    int width;
    int height;
    struct VideoFrameFormat conf;

    char * channelId;
    enum ConnectionType connType;
    bool open;
    bool conference;
    bool decodeStream;
    dword sameTimestamp;
    dword sameTimestampSequence;
    int lkeyframe;
    int numKfPackets;
    dword keyframeTs;
    int isPacketLost;
    dword oldForwardTs;
    bool lastRtpMarker;
    bool lastSentVideoSample;

    virtual void TryClose(void);

    ulong64 firstTxTimeStamp;
    ulong64 partialTxTimeStamp;
    dword procTxDataPar;
    dword procTxDataTot;
    int numTxProcSamples;

    ulong64 firstRxTimeStamp;
    ulong64 partialRxTimeStamp;
    dword procRxDataPar;
    dword procRxDataTot;
    int numRxProcSamples;

    bool remoteVideoPriority;
    char* forwardingChannel;

    bool remoteServices;

    void TimerOnTimeout(ITimer* timer) override;
    void JitterBuffer(void* buf, size_t len, unsigned int timestamp, short sequenceNumberDiff, bool marker);
    word rtpSequence;
    word lastRtpSequence;
    word lastProcSequence;
    bool initLastProcSequence;
    istd::list<class RtpPacket> rtpPackets;
    class ITimer* processRtpQueue;
    int markers;

public:
    VideoIoChannel(class IIoMux * const iomux, class IVideoIo * videoIo, class UMediaIoChannel * const user, class IInstanceLog * log);
    ~VideoIoChannel();

    // IMediaIoChannel
    void Open() override;
    void Close() override;
    void RtpRecv(void * buf, size_t len, dword timestamp, short sequenceNumberDiff, bool marker) override;
    virtual void SctpRecv(void * buf, size_t len) override {};
    void EventRecv(enum MediaEndpointEvent event, void* ctx) override;

    // IVideoIoChannel
    virtual void Initialize(void * context, const char * channelId, enum VideoCoder coder, ConnectionType connType, bool remoteServices = false) override;

    // UMediaEncoder
    void EncodeResult(class IMediaEncoder * encoder, const void * buf, int len, dword timestamp, dword duration);
    void MediaEncoderCloseResult(class IMediaEncoder * encoder);

    // UMediaDecoder
    void DecodeResult(class IMediaDecoder * decoder, const void * buf, int len, dword timestamp, dword duration, struct VideoFrameFormat * f);
    void MediaDecoderCloseResult(class IMediaDecoder * decoder);

    void RenderVideoStreamCloseResult(class IRenderVideoStream* renderer) override;

    virtual void VideoSample(const void * buf, int len, dword timestamp, dword duration, enum MediaType mediaType);
    virtual void ForwardRtp(const void * buf, int len, dword timestamp, dword duration, short sequenceNumberDiff, bool marker, enum VideoCoder coder, ConnectionType connOrigin);
    void RtcpSend(const void * buf, int len);

    void Mix(const void * wcBuf, size_t wcLen, const struct VideoFrameFormat * wcFormat, const void * buf, size_t len, const struct VideoFrameFormat * f, dword timestamp, dword duration);
    void Transcoding(const void * buf, size_t len, dword timestamp, dword duration, const struct VideoFrameFormat * f);

    // UIoMux
    void IoExec(void* execContext);

    enum VideoCoder GetVideoCoder(void) { return this->coder; };
    const char * GetChannelId(void) { return this->channelId; };
    enum MediaType GetMediaType(void) { return this->type; };
    enum ConnectionType GetConnectionType(void) { return this->connType; };
    bool GetRemoteVideoPriority() { return this->remoteVideoPriority; };
    void FullIntraRequest();
    void RemoteVideoPriority(bool on);
};
