
#pragma once
#include <dmusici.h>
#include <dmusicc.h>

#include <vector>
#include <queue>

#include <Core/Constants.h>

namespace GuruGuruSmf { namespace Dxmus
{

    // DLS1つ分を扱うクラス
    class Dls
    {
    private:
        IDirectMusicLoader8* loader;
        IDirectMusicCollection8* collection;

    public:
        Dls(IDirectMusicLoader8* ldr);
        ~Dls();

        GGSERROR LoadFromFile(wchar_t* fileName);
        int GetInstrument(int voice, IDirectMusicInstrument8** instrument);
    };

    enum CommandType
    {
        CommandType_Open, CommandType_Close,
        CommandType_GetRefTime, CommandType_SendShortMsg, CommandType_SendLongMsg, //追加 by Kobarin
        CommandType_SetVoiceList, CommandType_AddDls, //追加 by Kobarin
    };

    struct PortCommand
    {
        CommandType type;
        HANDLE event;
        union{//基本的に戻り値が必要なものは同期、不要なものは非同期で処理(Close だけ例外)
            struct{//CommandType_Open //同期
                HWND handle;
                GGSERROR* presult;
            }Open;
            struct{//CommandType_Close //同期
                //特に何もなし
            }Close;
            struct{//CommandType_GetRefTime //同期
                long long *time;
                GGSERROR* presult;
            }GetRefTime;
            struct {//CommandType_SendShortMsg //非同期
                int port;
                long long time;
                int midiMessage;
                //GGSERROR* presult;
            }ShortMsg;
            struct {//CommandType_SendLongMsg //非同期
                int port;
                long long time;
                LPBYTE midiMessage;
                int length;
                //GGSERROR* presult;
            }LongMsg;
            struct {//CommandType_SetVoiceList //非同期
                DWORD* list;
                int count;
            }SetVoiceList;
            struct {//CommandType_AddDls //同期
                wchar_t* fileName;
                GGSERROR* presult;
            }AddDls;
        };
    };


    // DirectMusicPortを扱うクラス
    class Port
    {
    private:
        IDirectMusic8* music;
        IDirectMusicPort8* port;
        IDirectMusicBuffer8* buffer;
        IDirectMusicLoader8 *loader;
        IDirectMusicCollection8 *defaultDls;
        IReferenceClock *clock;

        std::vector<Dls*> dlsList;
        bool isOpen;
        IDirectMusicDownloadedInstrument8** downloadedList;
        int downloadedCount;

        void ResetVoiceList();
        // スレッド関連
        HANDLE handle_thread;
        //volatile bool isWorkerOn;//廃止
        CRITICAL_SECTION lockObject;
        std::queue<PortCommand> commands;
        //by Kobarin
        enum{ MAX_PORT = GuruGuruSmf::Core::Constant::MaxPort};
        int port_max;
        enum{ command_max = 500/*20*/};//非同期で Post 出来るコマンドの最大数(30だと開始直後以外ほぼ上限に達することがない)
        //↑ほどほどの値にしておいたが事実上無制限と言えるような大きな値にしてもデメリットはない筈
        HANDLE sem_exec;        //コマンドを処理する側の待ち用。カウンタが 0 以外になったら処理可能、初期値0、最大値command_max
        HANDLE sem_post;        //コマンドをポストする側の待ち用。カウンタが 0 だとポスト出来ない、初期値command_max、最大値command_max
        HANDLE event_wait;      //同期が必要なコマンド(Open/Close等)の処理待ち用
        HWND hwnd;              //Open の HWND が NULL だったときに作成するウィンドウのハンドル
        //GGSERROR resultOpen;  //返り値(Open専用)(廃止 by Kobarin)(PortCommand.Open.presult に値を入れる)
        static unsigned int WINAPI threadPort(void* p_userdata);//クラスの静的メンバ関数に変更
        void _CreateWindow(); //Open の HWND が NULL だったときにウィンドウ作成
        void _DestroyWindow();//↑のウィンドウを破棄
        void PostCommand(PortCommand cmd);//コマンド発行(上限に達してたら空きが出るまで待ってから発行)
        //ここまで
        GGSERROR _Open(HWND handle);
        void _Close();
        void StartWorker();
        void FinalizeWorker();
    public:
        Port();
        ~Port();
        //同期関係メソッド(DirectMusic関連オブジェクトをワーカースレッド内で操作)
        //同期：処理が終わってから戻る
        //非同期：処理が終わるのを待たずに直ちに戻る
        GGSERROR Open(HWND handle);//同期(本当は非同期対応したいところだが戻り値必須なので)
        void Close();//同期

        GGSERROR GetReferenceTime(long long* time);//同期(戻り値必須なので)
        //GGSERROR SendShortMessage(long long time, int midiMessage);//非同期(戻り値は GgsError::NotReady or NoError のみ)
        //GGSERROR SendLongMessage(long long time, LPBYTE midiMessage, int length);//非同期(戻り値は GgsError::NotReady or NoError のみ)(midiMessage はメモリをコピーした方が安全？)
        //第１引数のポート番号を追加
        GGSERROR SendShortMessage(int port_num, long long time, int midiMessage);//非同期(戻り値は GgsError::NotReady or NoError のみ)
        GGSERROR SendLongMessage(int port_num, long long time, LPBYTE midiMessage, int length);//非同期(戻り値は GgsError::NotReady or NoError のみ)(midiMessage はメモリをコピーした方が安全？)
        void SetVoiceList(DWORD* list, int count);//非同期(list はメモリをコピーした方が安全？)
        GGSERROR AddDls(wchar_t* fileName);//同期
        void SetNumPort(int port_num){port_max = port_num < MAX_PORT ? port_num : MAX_PORT;}
    private://by Kobarin
        GGSERROR _GetReferenceTime(long long* time);//ワーカースレッド上で実行版
        GGSERROR _SendShortMessage(int port_num, long long time, int midiMessage);//ワーカースレッド上で実行版(送信先ポート指定あり)
        GGSERROR _SendLongMessage(int port_num, long long time, LPBYTE midiMessage, int length);//ワーカースレッド上で実行版(送信先ポート指定あり)
        void _SetVoiceList(DWORD* list, int count);//ワーカースレッド上で実行版
        GGSERROR _AddDls(wchar_t* fileName);//ワーカースレッド上で実行版
        //同期関係ここまで
        int GetInstrument(int voice, IDirectMusicInstrument8** instrument);//Portクラス内部からのみ呼ばれる
        void OnWorker();//Portクラス内部からのみ呼ばれる
    };



}}

