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

#define MAX_EXEC_INTERVAL_8KHZ          160
#define MAX_SAMPLE_RATE                 16000

class UHookDeviceHelper : public istd::listElement<UHookDeviceHelper> {
public:
    UHookDeviceHelper(UHookDevice * user) {
        this->user = user;
    }
    ~UHookDeviceHelper() {};

    UHookDevice* user;
};

class AudioDeviceItem : public istd::listElement<AudioDeviceItem> {
public:
    AudioDeviceItem(const char * deviceId, enum AudioDeviceType type, unsigned int capabilities, const char * name, bool isRemote = false) {
        this->deviceId = _strdup(deviceId);
        this->name = _strdup(name);
        this->wasRemoved = false;
        this->isSelected = false;
        this->type = type;
        this->capabilities = capabilities;
        this->activeHookDevice = false;
        this->isRemote = isRemote;
    }
    ~AudioDeviceItem() {
        if(this->deviceId) free(this->deviceId);
        if(this->name) free(this->name);
    }

    char * deviceId;
    char* name;
    enum AudioDeviceType type;
    unsigned int capabilities;
    bool wasRemoved;
    bool isSelected;
    bool activeHookDevice;
    bool isRemote;
};

class AudioIoUpdateDsp {};
class AudioIoClose {};

class AudioIo : public IAudioIo, public UAudioPhone,
    public UIoExecSubclass<AudioIoUpdateDsp>,
    public UIoExecSubclass<AudioIoClose>
{
    void SetExec(IIoMux * iomux, UIoExecSubclass<AudioIoUpdateDsp> * ioContext, AudioIoUpdateDsp * execContext) { iomux->SetExec(ioContext, execContext); };
    void IoExec(AudioIoUpdateDsp * context);
    void SetExec(IIoMux * iomux, UIoExecSubclass<AudioIoClose> * ioContext, AudioIoClose * execContext) { iomux->SetExec(ioContext, execContext); };
    void IoExec(AudioIoClose * context);

    class IIoMux * iomux;
    class IAudioPhone * audioPhone;
    unsigned execInterval8khz;
    class UAudioIo * user;
    class UHookDevice * hookUser;
    class UDeviceIo * deviceIoUser;
    char *deviceId;
    enum AudioDeviceMode deviceMode;
    bool deviceStarted;
    class USelectDeviceIo * selectDeviceUser;
    class MediaConfiguration * mediaConfiguration;
    class IInstanceLog * log;

    bool channel_attached;
    unsigned record_sample_rate_id;
    unsigned play_sample_rate_id;
    unsigned used_channels;
    unsigned allocated_channels;

    struct audioIoCh {
        class IAudioIoChannel *p;
        bool channel_initialized;
        bool channel_opened;
        bool channel_attached;
        enum AudioCoder coder;
        bool sc;
        unsigned mediaPacketizationMs;
        unsigned payload_sample_rate_id;
    } *channels;

    bool microphoneMuted;
    bool speakerMuted;

    bool tone_active;
    dword toneFlags;
    unsigned toneCount;
    unsigned allocatedTones;
    struct AudioIoDualTone *toneQueue;
    unsigned toneIndex;
    bool toneOn;
    short toneLastSample;
    dword toneTime;
    dword tonePhase0;
    dword toneIncrement0;
    dword tonePhase1;
    dword toneIncrement1;
    word toneAmplitude0;
    word toneAmplitude1;

    bool loop_test;
    bool loop_test_on;
    short loopTestDelayLine[MAX_SAMPLE_RATE];
    unsigned loopTestDelayPos;

    int execCnt;
    bool shutDown;

    void update_dsp(struct audioIoCh *calling_channel);

    void AudioExec(class IAudioExec *audioExec);
    void AudioDeviceList(void * context) override;
    class AudioDeviceItem * FindDevice(const char * deviceId);
    istd::list<class AudioDeviceItem> devicesList;
    void AudioRouteChanged(const char *deviceId) override;
    void HookKeyReceived(const char *deviceId, byte key) override;
    void HookDeviceMsg(const char* deviceId, const char* msg) override;
    void CloseAudioPhoneComplete() override;
    void AudioDeviceDelayed(const char* deviceIn, const char* deviceOut) override;
    void AudioDeviceOpened(const char* deviceIn, const char* deviceOut) override;

    istd::list<class SelectedDeviceIo> selectedDevices;
    istd::list<class UHookDeviceHelper> hookDeviceUsers;
    istd::list<class UDeviceIoHelper> remoteDeviceUsers;

    void ReadSelectedDevices();
    void WriteSelectedDevices();
    char * softphoneApp;
    bool selectedDeviceRemoved;

public:
    AudioIo(class IIoMux * const iomux, class IAudioPhone * const audioPhone, class IInstanceLog * log, unsigned execInterval8khz, class MediaConfiguration * mediaConfiguration);
    ~AudioIo();

    void QueryDevices(void * context) override; // Calls DeviceAdded() for all present devices.
    void CleanRemoteDevices() override;
    const char * StartDevice(void * context, void * src, const char *deviceId, int deviceMode) override;
    void StopDevice(void * context, const char *deviceId) override;
    void SelectDevice(const char *app, const char *deviceId) override;
    void ChangeDevice(const char *app, const char *deviceId) override;
    const char * GetDeviceIo(const char * app) override;
    int GetDeviceIoType(const char* deviceId) override;
    const char * GetDeviceId(unsigned index) override;
    const char * GetDeviceByType(int type) override;

    class IAudioPhone *Initialize(class UAudioIo * const user, class UDeviceIo * const deviceIoUser) override;

    void StartMonitoring(const char *deviceId, int deviceMode) override;
    void StopMonitoring(const char *deviceId) override;

    void AcquireHookDevice(const char* deviceId) override;
    void StartHookDevice(const char *deviceId) override;
    void StopHookDevice(const char *deviceId) override;
    void SendHookKey(const char *deviceId, byte key, byte callId, const char * number, const char * name) override;
    unsigned DeviceCapabilities(const char* deviceId) override;

    void Mute(bool microphone, bool speaker) override;
    void RecordWaves() override;

    void StartDualTones(dword flags, unsigned count, const struct AudioIoDualTone *tones) override;
    void StopDualTones() override;

    void LoopTest(bool on) override;

    class IAudioIoChannel *CreateChannel(class UMediaIoChannel * const user) override;
    void InitializeChannel(class IAudioIoChannel *audioIoChannel, enum AudioCoder coder, bool sc, unsigned mediaPacketizationMs) override;

    void ChannelOpened(class IAudioIoChannel *audioIoChannel) override;
    void ChannelClosed(class IAudioIoChannel *audioIoChannel) override;
    void ChannelDestroyed(class IAudioIoChannel *audioIoChannel) override;

    void Close(class UAudioIo * user) override;
    void SetUSelectDeviceIoUser(USelectDeviceIo* user) override { this->selectDeviceUser = user; };
    void AddUHookDeviceUser(UHookDevice * user) override { 
        if (!hookDeviceUsers.empty()) {
            class UHookDeviceHelper* h = hookDeviceUsers.front();
            while (h) {
                if (h->user == user) return;
                h = h->goNext();
            }
        }
        hookDeviceUsers.push_back(new class UHookDeviceHelper(user));
    };
    void RemoveUHookDeviceUser(UHookDevice* user) override {
        if (!hookDeviceUsers.empty()) {
            class UHookDeviceHelper* h = hookDeviceUsers.front();
            while (h) {
                if (h->user == user) {
                    h->remove();
                    delete h;
                    return;
                }
                h = h->goNext();
            }
        }
    };
    void RemoteHookKeyReceived(const char* deviceId, byte key);
    void RemoteHookDeviceMsg(const char* deviceId, const char* msg);

    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 RemoteMediaIoDeviceAdded(const char* deviceId, int deviceType, unsigned deviceCapabilities, const char* deviceName, bool selected = false) override;
    void RemoteMediaIoDeviceRemoved(const char* deviceId) override;
    void RemoteMediaIoQueryDevicesResult() override;
};

