#include <windows.h>
#include <math.h>
#include <wchar.h>

#include "kbGuru2SMF.h"
#include "kbGuru2SMF_player.h"
#include "kbGuru2SMF_config.h"

#ifdef TEST_MULTIPLE
//ファイル名が空白のときは１つ目と同じデータを開く
static const wchar_t test_mid[] = {L""};
#endif

KbGuru2SMFPlayer::KbGuru2SMFPlayer(IKpiConfig *pConfig)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    ZeroMemory(&m_smfInfo, sizeof(m_smfInfo));
    m_dwStatus = KPI_STATUS_STOP;
    m_dwStopTime = 0;
    m_bRepeat = false;
    m_bDevOpen = false;
    m_pConfig = pConfig;
    pConfig->AddRef();
    m_pGGS = GuruGuruSmf::Ggs4::CreateInstance();
    m_pFile = NULL;
    m_pFolder = NULL;
    m_ppDlsFiles = NULL;
    m_nDlsFiles= 0;
    m_nMasterVolume = 127;
    m_nVolume = 100;
    m_nTempo = 100;
    m_nMasterPitch = 0;
    m_dwFadeOut = 0;
#ifdef TEST_MULTIPLE
    m_pGGS2 = GuruGuruSmf::Ggs4::CreateInstance();
#endif
}
///////////////////////////////////////////////////////////////////////////////
KbGuru2SMFPlayer::~KbGuru2SMFPlayer(void)
{
#ifdef TEST_MULTIPLE
    if(m_pGGS2){
        m_pGGS2->FreeInstance();
    }
#endif
    if(m_pGGS) {
        if (m_bDevOpen) {
            m_pGGS->CloseDevice();    
        }
        m_pGGS->DeleteInstance();
    }
    if(m_ppDlsFiles){
        for(int i = m_nDlsFiles - 1; i >= 0; i--){
            m_ppDlsFiles[i]->Release();//アーカイブ内だとここでテンポラリが削除される
            //%TEMP%Kbmplay\XXX\xxx.dls が削除されることを確認
        }
        free(m_ppDlsFiles);
    }
    m_pConfig->Release();
    if(m_pFile)m_pFile->Release();
    if(m_pFolder)m_pFolder->Release();
}
///////////////////////////////////////////////////////////////////////////////
DWORD KbGuru2SMFPlayer::Open(IKpiFile *pFile, IKpiFolder *pFolder)
{
    if(!m_pGGS)return 0;
    // 開く
    GuruGuruSmf::GGSERROR err = GuruGuruSmf::GgsError::NoError;

    LPCBYTE midiData;
    size_t midiDataLength;
    if(!pFile->GetBuffer(&midiData, &midiDataLength)) {
        return 0;
    }
    //曲を閉じるまで pFile を保持しておけば GetBuffer の結果も保持されるので LoadOption::Buffered は指定しなくても良いが
    //指定しないとシークの度に内部で Compilier.cpp::Compiler::Compile が呼ばれるのは仕様？
    err = m_pGGS->AddListFromMemory(midiData, midiDataLength, 0/*GuruGuruSmf::LoadOption::Buffered*/, 0);
    if (err != GuruGuruSmf::GgsError::NoError) {
        return 0;
    }
    //デバイスを開くのは初回Play()時
    //err = m_pGGS->OpenDevice(GuruGuruSmf::Device::DirectMusic, NULL);
    //if(err != GuruGuruSmf::GgsError::NoError) {
    //    return 0;
    //}

    err = m_pGGS->GetSmfInformation(&m_smfInfo, 0);
    if(err != GuruGuruSmf::GgsError::NoError) {
        return 0;
    }
    m_MediaInfo.dwSampleRate = 44100;
    m_MediaInfo.dwChannels = 2;
    m_MediaInfo.nBitsPerSample = 0;//不明
    m_MediaInfo.qwLength = (UINT64)m_smfInfo.TotalTime * 10000;
    m_MediaInfo.dwCount = 1;
    m_MediaInfo.dwNumber = 1;
    m_MediaInfo.dwFormatType = KPI_MEDIAINFO::FORMAT_PLAYER;
    //ループ曲なら対応してることを本体に知らせる(KPI_MEDIANFO::qwLoop にループ時間か -1 を設定する)
    //単曲リピート時にはフェードアウトしないデータを渡してくれる
    //単曲リピートが有効時にループ曲を開くと本体から 2回 Open が呼ばれるのは仕様
    //(1回目と2回目で実際は異なるデータ)
    m_MediaInfo.qwLoop = m_smfInfo.LoopTick >= 0 ? -1 : 0;
    m_pFile = pFile;
    m_pFolder = pFolder;
    pFile->AddRef();
    if(pFolder)pFolder->AddRef();//pFolder は NULL かもしれない
#ifdef TEST_MULTIPLE
    if(m_pGGS2){
        if(test_mid[0]){
            err = m_pGGS2->AddListFromFileW(test_mid, GuruGuruSmf::LoadOption::Buffered, 0);
        }
        else{
            err = m_pGGS2->AddListFromMemory(midiData, midiDataLength, GuruGuruSmf::LoadOption::Buffered, 0);
        }
    }
#endif
    return 1;
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbGuru2SMFPlayer::Select(DWORD dwNumber, 
                                const KPI_MEDIAINFO ** ppMediaInfo,
                                      IKpiTagInfo * pTagInfo,
                                      DWORD dwTagGetFlags)
{
    if(ppMediaInfo){
        *ppMediaInfo = NULL;
    }
    if(dwNumber > m_MediaInfo.dwCount){
        return 0;
    }
    if(!ppMediaInfo){
        return 1;
    }

    *ppMediaInfo = &m_MediaInfo;

    return 1;
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbGuru2SMFPlayer::SetRepeat(BOOL bRepeat)
{
    m_bRepeat = bRepeat != 0;

}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbGuru2SMFPlayer::GetPosition(void)
{
    if(m_dwStatus == KPI_STATUS_STOP)return 0;
    GuruGuruSmf::PlayerStatus stat;
    m_pGGS->GetPlayerStatus(&stat);
    #if 0 //再生位置のテスト
    wchar_t sz[128];
    _snwprintf_s(sz, _TRUNCATE, L"pos=%d\n", stat.Time);
    OutputDebugString(sz);
    #endif
    return stat.Time;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbGuru2SMFPlayer::Play(DWORD dwStartPos, BOOL bVisible)
{
    m_dwFadeOut = 0;//直ちに終了させる為、一旦 0 にする
    Stop();
    // 再生
    kbguru2smf_Settings settings;
    kbguru2smf_GetSettings(m_pConfig, &settings);
    m_dwFadeOut = settings.dwFadeOut;
    m_nMasterVolume = settings.nVolume;
    m_nMasterPitch = settings.nMasterPitch;
    GuruGuruSmf::GGSERROR err = GuruGuruSmf::GgsError::NoError;
    if(!m_bDevOpen){
        //AddDls(settings.szDlsPath);//デバイスを開いてからでないと機能しない(公式版v4.0.8 はデバイスを開く前に呼ぶと落ちてしまう)
        err = m_pGGS->OpenDevice(GuruGuruSmf::Device::DirectMusic, NULL);
        if (err != GuruGuruSmf::GgsError::NoError) {
            return FALSE;
        }
        m_bDevOpen = TRUE;
        AddDls(settings.szDlsPath);//デバイスを開いてからでないと機能しない
    }
    if(dwStartPos > m_smfInfo.TotalTime){
        dwStartPos = m_smfInfo.TotalTime;
    }
    int startPos = static_cast<int>(dwStartPos * (m_smfInfo.TotalTick / (float)m_smfInfo.TotalTime));
    
    int playOptions = 0;//
    if(m_bRepeat){ 
        //非ループ曲の単曲リピートがおかしくなる曲確認(edencity.mid)(仕様らしい)
        //リセットを含む曲の場合は先頭に戻ったらリセットを送信するようにGuruGuruSMFを修正
        if(m_smfInfo.LoopTick >= 0){//ループ曲のときは常に有効
            playOptions |= GuruGuruSmf::PlayOption::Loop;
        }
        else if(settings.bLoop){//非ループ曲のときは設定に従う
            playOptions |= GuruGuruSmf::PlayOption::Loop;
        }
    }
    if (settings.bSkipBeginning)playOptions |= GuruGuruSmf::PlayOption::SkipBeginning;
    if (settings.bSkipEnding)playOptions |= GuruGuruSmf::PlayOption::SkipEnding;
    if (settings.bMasterPitch)playOptions |= GuruGuruSmf::PlayOption::MasterPitch;
    if (settings.bNormalize)playOptions |= GuruGuruSmf::PlayOption::Normalize;
    if (settings.bExclusive)playOptions |= GuruGuruSmf::PlayOption::Exclusive;
    SetVolume(m_nVolume);
    SetTempo(m_nTempo);
    m_pGGS->SetMasterPitch(m_nMasterPitch);
    err = m_pGGS->Play(playOptions, 0, startPos, settings.dwFadeIn, settings.nKeyShift);
    if(err) { return FALSE; }
    m_dwStatus = KPI_STATUS_PLAYING;
    m_dwStopTime = 0;
#ifdef TEST_MULTIPLE
    if(m_pGGS2){
        err = m_pGGS2->OpenDevice(GuruGuruSmf::Device::DirectMusic, NULL);
        err = m_pGGS2->Play(playOptions, 0, 0, 0, 0);
    }
#endif

    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbGuru2SMFPlayer::Pause(void)
{
    m_pGGS->Pause();
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbGuru2SMFPlayer::Restart(void)
{
    if(this->GetStatus() == KPI_STATUS_PAUSE) {
        m_pGGS->Restart();
    }
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbGuru2SMFPlayer::Stop(void)
{
    m_pGGS->Stop(m_dwFadeOut);
    if(m_dwFadeOut == 0){
        m_dwStatus = KPI_STATUS_STOP;
    }
    else{
        m_dwStopTime = 1;//フェードアウトが終わったら直ちに演奏終了を返すようにする
    }
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbGuru2SMFPlayer::GetStatus(void)
{
    GuruGuruSmf::PlayerStatus stat;
    m_pGGS->GetPlayerStatus(&stat);
    switch(stat.State) {
    case GuruGuruSmf::PLAYERSTATE::Play:
        m_dwStatus = KPI_STATUS_PLAYING; break;
    case GuruGuruSmf::PLAYERSTATE::Pause:
        m_dwStatus = KPI_STATUS_PAUSE; break;
    default:{
        //m_dwStatus = KPI_STATUS_STOP; break;
        //停止をすぐに通知すると連続再生時に最後が途切れた感じになってしまうので
        //1000ms経過してから停止を通知する 
        if(m_dwStopTime == 0){
            m_dwStopTime = GetTickCount();//停止を検出した時間
        }
        else if(GetTickCount() - m_dwStopTime > 1000){
            m_dwStatus = KPI_STATUS_STOP; 
        }
        break;
    }
    }

    return m_dwStatus;
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbGuru2SMFPlayer::SetTempo(int Tempo)
{
    m_nTempo = Tempo;//Play()前に呼ばれたときの為に値を保持しておく
    // Tempは 50 〜 100 〜 200
    // SetMasterTempo() は 0.1 〜 1.0 〜 8.0
    float t = Tempo / 100.0f;

    m_pGGS->SetMasterTempo(t);
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbGuru2SMFPlayer::SetVolume(int Volume)
{
    m_nVolume = Volume;//Play()前に呼ばれたときの為に値を保持しておく
    // Volume は 0 〜 100
    // SetMasterVolume() は -127 〜 0
    //int vol = static_cast<int>((Volume - 100) * (127 / 100.0f));
    //m_pGGS->SetMasterVolume(vol);
    
    //GuruGuruSMF の SetVolume は内部的にはチャンネルボリューム
    //https://amei.or.jp/midistandardcommittee/
    //https://amei.or.jp/midistandardcommittee/Recommended_Practice/GM2_japanese.pdf
    //によると値の２乗が音量に比例するとある
    //gain[dB] = 40 * log(cc7/127) = 20 * log( (cc7/127)^2 )
    //vol(%) = (cc7/127)^2
    //cc7 = sqrt(vol)*127
    //double dvol = sqrt(vol)*127.0 - 127.0;
    //master volume も加味すると
    double dvol = sqrt(Volume/100.0)*m_nMasterVolume - 127.0;//Volumeが100%のときにmaster volumeの設定値が確実に音量に反映されるようにする場合
                                                             //(元々音量調整の為の設定でこちらの方が微調整が出来るので採用)
    //double dvol = sqrt((Volume/100.0) * (m_nMasterVolume/127.0)) * 127 - 127;//master volumeも最大値に対して値を半分にしたら音量が半分になる感じにする場合
                                                            //(最大値を127にする意味がないし微調整出来ないので不採用)
    m_pGGS->SetMasterVolume((int)dvol);
    //DirectMusic が GM2 の規格に準じてるかは分からないが
    //↓より↑の方が音量%に対する実際の音量がしっくり来るのでこちらを採用
    //↓だと例えば50%にすると感覚的に50%より音が小さくなる
    //int vol = Volume * m_nMasterVolume / 100 - 127;
    //m_pGGS->SetMasterVolume(vol);
}
///////////////////////////////////////////////////////////////////////////////
void __fastcall KbGuru2SMFPlayer::AddDls(const wchar_t *cszDlsPath)
{//公式版 v4.0.8 だとデバイスを開く前に AddDls すると落ちてしまう
 //改造版は落ちないがデバイスを開いた後でないと失敗する
 //RemoveDls や ClearDls がなくドキュメントに記載がないので、デバイスを開く前に 
 //AddDls を呼んだら失敗(公式版は落ちるが)、デバイスを閉じたら AddDls の結果が
 //リセットされる現在の動作を仕様と見なすことにする
 //デバイスを閉じた後も AddDls の結果が保持されると、追加した DLS を削除する手段
 //がなくなり、再生を繰り返す度に同じ DLS を何回も追加することになってしまう
 //DLS を削除するには公式版では FreeLibrary するしかなくなる
 //(改造版の複数インスタンス対応版APIを使用する分には FreeLibrary は必要ないが)
    if (!m_pFolder)return;
    //曲データと同じフォルダ内の *.dls を検索(DLSパスの設定よりそちらを優先して使う)
    //複数見つかったら見つかった順(曲データと同じ名前を最優先の方が良い？)
    wchar_t szFileName[MAX_PATH];
    int i = 0;
    while (m_pFolder->EnumFiles(i++, szFileName, sizeof(szFileName), 0)) {//サブフォルダは調べない
        const wchar_t* cszExt = wcsrchr(szFileName, L'.');
        if (!cszExt || _wcsicmp(cszExt, L".dls") != 0)continue;//拡張子が .dls でない

        IKpiFile* pDlsFile = NULL;
        if (!m_pFolder->OpenFile(szFileName, &pDlsFile)) {
            continue;
        }
        const wchar_t* cszDls;//DLS 実ファイル名
        if (!pDlsFile->GetRealFileW(&cszDls)) {//失敗
            pDlsFile->Release();
            continue;
        }
#ifdef _DEBUG
        OutputDebugString(L"DLS path=");
        OutputDebugString(cszDls);
        if (m_pGGS->AddDlsW(cszDls) == GuruGuruSmf::GgsError::NoError) {
            OutputDebugString(L"(succeeded)\n");
        }
        else {
            OutputDebugString(L"(failed)\n");
        }
#else
        m_pGGS->AddDlsW(cszDls);
#endif
    //デバイスを閉じるまでの間、DirectMusic が DLS データを開いたままになるのでアーカイブ内だとテンポラリが削除されない
    //その為デバイスを閉じるまで Release しないで保持しておく
    //pDlsFile->Release(); //曲データがアーカイブ内だと %TEMP%Kbmplay\XXX\xxx.dls が生成されるが、これが削除されないことを確認出来る
    //実際は曲を閉じたときに本体がテンポラリをフォルダごと削除するので、その時点で DirectMusic が DLS を閉じていれば問題なく削除される
    //(なので必ずしも保持しておく必要はないが参照カウントが0になっても削除されないのは意図された動作ではない)
        m_ppDlsFiles = (IKpiFile**)realloc(m_ppDlsFiles, sizeof(IKpiFile*) * (++m_nDlsFiles));
        m_ppDlsFiles[m_nDlsFiles - 1] = pDlsFile;
    }
    //プラグイン設定で設定した DLS(複数ファイル指定対応)
    //こちらはテンポラリを想定しないので m_ppDlsFiles に保持しておく必要はない
    if(!cszDlsPath[0])return;
    wchar_t szDlsPath[_countof(kbguru2smf_Settings::szDlsPath)];//wcstok_s で壊れるのでコピーが必要
    wchar_t szPath[MAX_PATH];
    wcsncpy_s(szDlsPath, cszDlsPath, _TRUNCATE);
    szPath[0] = 0;

    wchar_t* pszDlsPath = szDlsPath;
    wchar_t* context = NULL;
    while (1) {
        wchar_t* p = wcstok_s(pszDlsPath, L"|", &context);
        if (!p)break;
        pszDlsPath = NULL;
        while (*p == ' ')p++;//空白をスキップ
        wchar_t* t = p + wcslen(p) - 1;
        while (t >= p && *t == ' ') {//末尾の空白を除く
            *t = 0;
            t--;
        }
        if (wcschr(p, L'\\') == NULL) {//フルパスでない
            wchar_t* t = wcsrchr(szPath, L'\\');
            if (t) {
                t[1] = 0;//1つ前と同じフォルダ
            }
            else {
                szPath[0] = 0;//おそらくカレントディレクトリから検索(未確認)
            }
            wcsncat_s(szPath, p, _TRUNCATE);
        }
        else{
            wcsncpy_s(szPath, p, _TRUNCATE);
        }
#ifdef _DEBUG
        OutputDebugString(L"DLS path=");
        OutputDebugString(szPath);
        if (m_pGGS->AddDlsW(szPath) == GuruGuruSmf::GgsError::NoError) {
            OutputDebugString(L"(succeeded)\n");
        }
        else{
            OutputDebugString(L"(failed)\n");
        }
#else
        m_pGGS->AddDlsW(szPath);
#endif
    }
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbGuru2SMFPlayer::UpdateConfig(void* pvReserved)
{ 
    kbguru2smf_Settings settings;
    kbguru2smf_GetSettings(m_pConfig, &settings);
    m_nMasterVolume = settings.nVolume;
    SetVolume(m_nVolume);
    m_dwFadeOut = settings.dwFadeOut;
    m_nMasterPitch = settings.nMasterPitch;
    m_pGGS->SetMasterPitch(m_nMasterPitch);
    return 0;
}
