#pragma once

#include "kpi_impl.h"
#include "kpi_device.h"
#include "../../../kpi_common/ringbuf.h"
#include "asiosdk/iasiodrv.h"

class KbAsioDevice : public KbKpiUnknownImpl<IKpiOutDevice>
{
private:
    static KbAsioDevice *s_pActive;
    HWND   m_hWnd;      //IASIO::Init に渡すウィンドウのハンドル
    HANDLE m_hThread;   //IASIO のメソッドはなるべくこのスレッド内から呼び出す(m_hWnd を作成するのと同じスレッド)
    HANDLE m_hEventCmd; //コマンド発行待ち用イベント
    HANDLE m_hEventDone;//コマンド処理待ち用イベント

    enum {
        CMD_ASIO_OPEN = 0,
        CMD_ASIO_OPENDRIVER,
        //CMD_ASIO_START,       //IASIO::start/outputReady は例外的に m_hThread 以外のスレッドから呼び出す
        //CMD_ASIO_OUTPUTREADY, //
        CMD_ASIO_CONTROLPANEL,
        CMD_ASIO_QUIT,
    };
    struct{
        int    nCommand;
        WPARAM wParam;
        LPARAM lParam;
        LPARAM lResult;
    }m_Command;
    struct OPEN_PARAMS{
        const wchar_t* cszDeviceName;
        const wchar_t* cszId;
        const KPI_DEVICE_FORMAT* cpFormat;
        int nBufferSizeSample;
        IKpiConfig* pConfig;
    };
    IASIO           *m_pAsio;
    ASIOChannelInfo  m_asioChannelInfo;
    ASIOBufferInfo   m_asioBufferInfo[2];//2チャンネル分
    ASIOCallbacks    m_asioCallbacks;
    KbRingBuffer     m_RingBuffer;//リングバッファ。最終出力段のビット数でデータが入る

    CRITICAL_SECTION m_cs;
    HANDLE           m_hEventWritten;//書き込みが終わったらシグナル
    wchar_t m_szDeviceName[256];
    wchar_t m_szDeviceId[128];
    int     m_nSampleRate;
    int     m_nChannels;
    int     m_nInputBps;
    int     m_nOutputBps;
    int     m_nBufferSizeSample;
    int     m_nOutputBlockAlign;
    int     m_nInputBlockAlign;
    int     m_nSamplePerBytes;//1バイトあたりのサンプル数(DSD 時は 8、それ以外は 1)
    DWORD   m_dwFormatType;   //FORMAT_PCM or FORMAT_DOP
    long    m_lLatency;       //レイテンシ
    bool    m_bPaused;        //一時停止中は true
    bool    m_bPostOutput;    //outputReady() をサポートすれば true
    bool    m_bAsioStart;     //start() をコール済みなら true
    bool    m_bDopToDsd;      //Dop=>Dsd に変換時は true
    bool    m_bControlPanel;  //controlPanel() 動作中は true
    bool    m_bFinished;      //再生終了時は true
    BYTE   *m_pOutputWork;    //最終出力段の PCM。作業用
    int     m_nAsioBufferSizeSample;//asio のバッファサイズ(サンプル単位)
    UINT64  m_qwTotalWritten; //再生開始からの output された合計(サンプル単位)
    UINT64  m_qwAsioPos;      //Asio デバイスの再生位置(サンプル単位)
    UINT64  m_qwAsioTimeNs;   //bufferSwitch 呼び出し時の位置(ナノ秒単位)
    UINT64  m_qwPrevPos;      //前回 GetPosition 呼び出し時の再生位置(時刻が進み過ぎないようにする)
    INT64   m_i64OffsetPos;   //一時停止時等、再生位置のずれの補正(サンプル単位)
    DWORD   m_dwSilenceMS;    //デバイスオープン直後の無音再生時間(ms)
    LRESULT __fastcall SendCommand(int nCommand, WPARAM wParam, LPARAM lParam);
    static unsigned __stdcall ThreadProc(void *pvParam){
        return ((KbAsioDevice*)pvParam)->ThreadProcInternal();
    }
    UINT __fastcall ThreadProcInternal(void);
    void __fastcall CreateAsioThread(void);
    void __fastcall Lock(void){EnterCriticalSection(&m_cs);}
    void __fastcall Unlock(void){LeaveCriticalSection(&m_cs);}

    void __fastcall Close(void);
    void __fastcall bufferSwitch(long doubleBufferIndex, ASIOBool directProcess);
    ASIOTime* __fastcall bufferSwitchTimeInfo(ASIOTime* params, long doubleBufferIndex, ASIOBool directProcess);
    long __fastcall asioMessage(long selector, long value, void* message, double* opt);
    static void asio_callback_bufferSwitch(long doubleBufferIndex, ASIOBool directProcess);
    static void asio_callback_sampleRateDidChange(ASIOSampleRate sRate);
    static long asio_callback_asioMessage(long selector, long value, void* message, double* opt);
    static ASIOTime* asio_callback_bufferSwitchTimeInfo(ASIOTime* params, long doubleBufferIndex, ASIOBool directProcess);
    BOOL __fastcall Open(OPEN_PARAMS *pParam);
    ASIOError __fastcall start(void){return m_pAsio->start();}
    ASIOError __fastcall outputReady(void){return m_pAsio->outputReady();}

    BOOL __fastcall CreateAsio(const wchar_t* cszDeviceName, const wchar_t* cszId);
    BOOL __fastcall OpenDriver(const wchar_t *cszDeviceName, const wchar_t *cszId);
public:
    static BOOL __fastcall ControlPanel(const wchar_t *cszDeviceName, const wchar_t *cszId);
    virtual BOOL   WINAPI Output(BYTE *pOutput, int nSizeSample); //デバイスにデータを出力
    virtual void   WINAPI Wait(void);            //次の Output が可能になるまで待つ
    virtual UINT64 WINAPI GetPosition(void);     //デバイスの再生位置をサンプル単位で返す
    virtual void   WINAPI Stop(void);            //再生を停止する
    virtual void   WINAPI Pause(void);           //一時停止
    virtual void   WINAPI Restart(void);         //再開
    virtual BOOL   WINAPI SetVolume(int nVolume);//音量制御
    virtual BOOL   WINAPI SetRate(int nRate);    //サンプリングレートを変換
    virtual DWORD  WINAPI UpdateConfig(void *pvReserved){return 0;}
    BOOL __fastcall Open(const wchar_t *cszDeviceName,
                         const wchar_t *cszId,
                         const KPI_DEVICE_FORMAT *cpFormat, //再生周波数等
                         int nBufferSizeSample,
                         IKpiConfig *pConfig);
    KbAsioDevice(void);
    virtual ~KbAsioDevice(void);
};
