#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <mbstring.h>

#include "kmp_pi.h"
#include "kbpmd_decoder.h"
#include "fmpmd/fmpmd.h"
#include "kbfmpmd_config.h"
#include "kbpmd_config.h"

static HINSTANCE g_hPmdWin = NULL;
static HINSTANCE g_hKpi = NULL;

typedef HRESULT (WINAPI *pfnPmd_CoCreateInstance)(REFCLSID,LPUNKNOWN,DWORD,REFIID,LPVOID);
typedef int (WINAPI *pfnPmd_GetVersion)(void);

pfnPmd_CoCreateInstance Pmd_CoCreateInstance = NULL;
pfnPmd_GetVersion       Pmd_GetVersion = NULL;

static IPMDWIN* CreatePmdInstance(IKpiConfig *pConfig, IKpiFolder *pFolder, IKpiFile *pFile, FMPMD_FileIO **ppFileIO)
{
    if(!Pmd_CoCreateInstance){
        return NULL;
    }
    IPMDWIN *pPmdWin = NULL;
    if(FAILED(Pmd_CoCreateInstance(CLSID_PMDWIN,
                                   NULL,
                                   CLSCTX_ALL,
                                   IID_IPMDWIN,
                                   (void**)&pPmdWin))){
        return NULL;
    }
    FMPMD_FileIO *pFileIO = NULL;
    ISETFILEIO* pSetFileIO = NULL;
    if(ppFileIO){
        pPmdWin->QueryInterface(IID_IFILESTREAM, (void**)&pSetFileIO);
    }
    if (pSetFileIO) {//FileIO インターフェースを使用可能
        pFileIO = new FMPMD_FileIO(pConfig, g_hKpi, pFolder, pFile);
        pSetFileIO->setfileio(pFileIO);
        pSetFileIO->Release();
        pPmdWin->init(pFileIO->GetRhythmPath());
        pPmdWin->setpcmdir(pFileIO->GetPcmDirs());
        *ppFileIO = pFileIO;
    }
    else {
        char rythm_path[MAX_PATH*2];
        char *pcmdirs[MAX_PCMDIR+1];
        //リズム音源のパス（プラグインが置かれているパスと同じ）
        GetRythmPathA(pConfig, g_hKpi, rythm_path, sizeof(rythm_path));
        //PCM ディレクトリ
        GetPcmDirsA(pConfig, pcmdirs);
        pPmdWin->init(rythm_path);
        pPmdWin->setpcmdir(pcmdirs);
        FreePcmDirsA(pcmdirs);
    }
    return pPmdWin;
}
///////////////////////////////////////////////////////////////////////////
KbPmdDecoder::KbPmdDecoder(IKpiConfig *pConfig)
{
    m_pPmdWin = NULL;
    m_pFileIO = NULL;
    m_qwCurrentSample = 0;
    m_qwLastSample = -1;
    kpi_InitMediaInfo(&m_MediaInfo);
    m_pFile = NULL;
    m_ppPcmFiles = NULL;
    m_nPcmFiles = 0;
    m_pConfig = pConfig;
    pConfig->AddRef();
    m_bUpdateConfig = true;
}
///////////////////////////////////////////////////////////////////////////
KbPmdDecoder::~KbPmdDecoder(void)
{
    if(m_pPmdWin){
        m_pPmdWin->music_stop();
        m_pPmdWin->Release();
    }
    if(m_pFileIO){
        ULONG ref = m_pFileIO->Release();//オリジナルの PMDWin.dll v0.51 だと参照カウント残り 4 になる
        if(ref > 0){//PMDWin.dll のバグ対策(同梱の PMDWin.dll では修正済み)
            delete m_pFileIO;
        }
    }
    if(m_pFile){
        m_pFile->Release();
    }
    if(m_ppPcmFiles){
        for(int i = m_nPcmFiles - 1; i >= 0; i--){
            m_ppPcmFiles[i]->Release();
        }
        free(m_ppPcmFiles);
    }
    m_pConfig->Release();
}
///////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbPmdDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                   IKpiFile   *pFile,
                                   IKpiFolder *pFolder)
{
    KbFMPMD_Settings settings;
    kbfmpmd_GetSettings(m_pConfig, &settings);
    int nSampleRate = settings.nSampleRate;
    if(nSampleRate >= 0 && (cpRequest->dwSampleRate != 0)){
        nSampleRate = cpRequest->dwSampleRate;
    }
    if(nSampleRate < -192000){
        nSampleRate = -192000;
    }
    else if(nSampleRate < 0){
        if(nSampleRate > -22050){
            nSampleRate = -22050;
        }
    }
    else if(nSampleRate < 22050){
        nSampleRate = 22050;
    }
    else if(nSampleRate > 192000){
        nSampleRate = 192000;
    }
    if(nSampleRate < 0){
        nSampleRate = -nSampleRate;
    }
    DWORD dwSampleRate = (DWORD)nSampleRate;
    char szFileNameA[MAX_PATH];

    FMPMD_FileIO *pFileIO = NULL;
    IPMDWIN* pPmdWin = CreatePmdInstance(m_pConfig, pFolder, pFile, settings.bEnableFileIO ? &pFileIO : NULL);
    if(!pPmdWin){
        return 0;
    }
    m_pPmdWin = pPmdWin;
    m_pFile = pFile;
    pFile->AddRef();
    if (pFileIO) {
        strncpy_s(szFileNameA, pFileIO->GetMusFileName(), _TRUNCATE);
        m_pFileIO = pFileIO;
    }
    else{
        const char *cszFileNameA;
        if(!pFile->GetRealFileA(&cszFileNameA)){
            return 0;
        }
        strncpy_s(szFileNameA, cszFileNameA, _TRUNCATE);
        GetPcmFiles(pFile, pFolder);
    }
    pPmdWin->setpcmrate(dwSampleRate);
    if(settings.nPPZRate != 0){
        pPmdWin->setppzrate(settings.nPPZRate);
    }
    else{
        pPmdWin->setppzrate(dwSampleRate);
    }
    pPmdWin->setppsinterpolation(settings.bPPSInterpolation);
    pPmdWin->setp86interpolation(settings.bP86Interpolation);
    pPmdWin->setppzinterpolation(settings.bPPZInterpolation);
    pPmdWin->setppsuse(settings.bUsePPS);
    pPmdWin->setrhythmwithssgeffect(settings.bRhythmUseWithSSGDrum);
    pPmdWin->setpmd86pcmmode(settings.bPMD86PCMMode);
    pPmdWin->setfmcalc55k(settings.bFmCalc55k);
    // PMDWin.dll のソースを見る限り getlength の内部で music_load() を呼び出す
    // gentlength を呼ぶなら music_load は要らない気がするが
    // PCM がないかときに失敗させるには music_load を呼ぶしかない気もする
    int ret = pPmdWin->music_load (szFileNameA);
    if (settings.bNoPCMForcePlay) {//PCM がなくても強制的に再生
        if (ret == ERR_OPEN_PPC_FILE ||
            ret == ERR_WRONG_PPC_FILE ||
            ret == ERR_OPEN_PPS_FILE ||
            ret == ERR_OPEN_PPZ1_FILE ||
            ret == ERR_OPEN_PPZ2_FILE ||
            ret == ERR_WRONG_PPZ1_FILE ||
            ret == ERR_WRONG_PPZ2_FILE ||
            0) {
            ret = PMDWIN_OK;
        }
    }
    if(ret != PMDWIN_OK &&
       ret != WARNING_PPC_ALREADY_LOAD &&
       ret != WARNING_PPS_ALREADY_LOAD &&
       ret != WARNING_PPZ1_ALREADY_LOAD &&
       ret != WARNING_PPZ2_ALREADY_LOAD){
        return 0;
    }
    int length = 0, loop = 0;
    pPmdWin->getlength(szFileNameA, &length, &loop);
    pPmdWin->music_start();

    m_MediaInfo.dwSampleRate = dwSampleRate;
    m_MediaInfo.dwChannels = 2;
    m_MediaInfo.nBitsPerSample = 16;
    m_MediaInfo.qwLength = length * 10000ui64;//(length+loop)*10000ui64;
    m_MediaInfo.dwSeekableFlags = KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE;
    m_MediaInfo.dwUnitSample = 0;
    m_MediaInfo.qwLoop = loop * 10000ui64;
    if(loop != 0){
        m_MediaInfo.qwFadeOut = -1;
    }
    else{
        m_MediaInfo.qwFadeOut = 0;
    }
    m_MediaInfo.dwCount = 1;
    m_qwCurrentSample = 0;
    if(loop == 0){
        m_qwLastSample = ((UINT64)length) * dwSampleRate / 1000;
    }
    else{
        m_qwLastSample = -1;
    }
    return 1;
}
///////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbPmdDecoder::Render(BYTE *pBuffer, DWORD dwSizeSample)
{
    if(m_pPmdWin->getloopcount() < 0){//演奏終了
        return 0;
    }
    if(m_qwCurrentSample >= m_qwLastSample){//演奏終了
        return 0;
    }
    ApplySettings();
    DWORD dwRetSample = dwSizeSample;
    m_pPmdWin->getpcmdata ((short *)pBuffer, dwSizeSample);
    m_qwCurrentSample += dwSizeSample;
    if(m_pPmdWin->getloopcount() < 0){//演奏終了
        if(m_qwCurrentSample > m_qwLastSample){
            UINT64 qwDiffSample = m_qwCurrentSample - m_qwLastSample;
            if(qwDiffSample > dwSizeSample){
                qwDiffSample = dwSizeSample;
            }
            dwRetSample -= qwDiffSample;
            m_qwCurrentSample -= qwDiffSample;
        }
    }
    return dwRetSample;
}
///////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbPmdDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{//要求した位置よりも後ろにシークされてしまうので 100ms だけ手前にシーク
    UINT64 qwPos100ns = kpi_SampleTo100ns(qwPosSample, m_MediaInfo.dwSampleRate);
    DWORD dwPosMs = (DWORD)(qwPos100ns / 10000);
    if(dwPosMs < 100){
        dwPosMs = 0;
    }
    else{
        dwPosMs -= 100;
    }
    m_pPmdWin->setpos(dwPosMs);
    DWORD dwRetMs = m_pPmdWin->getpos();
    m_qwCurrentSample = kpi_100nsToSample(dwRetMs*10000ui64,  m_MediaInfo.dwSampleRate);
    return m_qwCurrentSample;
}
///////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbPmdDecoder::Select(DWORD dwNumber,
                                  const KPI_MEDIAINFO **ppMediaInfo,
                                  IKpiTagInfo *pTagInfo,
                                  DWORD dwTagGetFlags)
{
    if(ppMediaInfo){
        *ppMediaInfo = NULL;
    }
    if(dwNumber > m_MediaInfo.dwCount) {
        return 0;
    }
    m_MediaInfo.dwNumber = dwNumber;
    GetTagInfo(pTagInfo, dwTagGetFlags);
    if (!ppMediaInfo) {//タグ取得のみ(タイトル/アーティストのみ)
        return dwNumber;
    }
    *ppMediaInfo = &m_MediaInfo;
    return dwNumber;
}
///////////////////////////////////////////////////////////////////////////
void __fastcall KbPmdDecoder::GetTagInfo(IKpiTagInfo *pTagInfo, DWORD dwTagGetFlags)
{
    if(!pTagInfo){
        return;
    }
    if(!(dwTagGetFlags & KPI_TAGGET_FLAG_GENERAL)){
        return;
    }
    static const wchar_t *cszFields[] = {
        L"PcmFileName", 
        L"PpcFileName",
        L"PpsFileName",
        L"P86FileName",
        L"PpzFileName1",
        L"PpzFileName2"
    };
    wchar_t szPcmW[MAX_PATH];
    char szPcmA[MAX_PATH*2];
    const char *cszFileNameA;
    for(int i = 0; i < 6; i++){
        szPcmA[0] = 0;
	    switch(i){
        case 0:
            m_pPmdWin->getpcmfilename(szPcmA);
            break;
        case 1:
            m_pPmdWin->getppcfilename(szPcmA);
            break;
        case 2:
            m_pPmdWin->getppsfilename(szPcmA);
            break;
        case 3:
            m_pPmdWin->getp86filename(szPcmA);
            break;
        case 4:
            m_pPmdWin->getppzfilename(szPcmA, 0);
            break;
        case 5:
            m_pPmdWin->getppzfilename(szPcmA, 1);
            break;
        }
        if(!szPcmA[0]){
            continue;
        }
        MultiByteToWideChar(CP_ACP, 0, szPcmA, -1, szPcmW, _countof(szPcmW));
        szPcmW[_countof(szPcmW)-1] = 0;
        wchar_t *pszPathDelimiter = wcsrchr(szPcmW, L'\\');
        if(pszPathDelimiter){
            pTagInfo->wSetValueW(cszFields[i], -1, pszPathDelimiter+1, -1);
        }
        else if(szPcmW[0]){
            pTagInfo->wSetValueW(cszFields[i], -1, szPcmW + 1, -1);
        }
    }
}
/////////////////////////////////////////////////////////////////////////
void  __fastcall KbPmdDecoder::GetPcmFiles(IKpiFile *pFile, IKpiFolder *pFolder)
{
    const char* cszPmdPathA = NULL;
    if (!pFile->GetRealFileA(&cszPmdPathA)) {
        return;
    }
    for (int i = -2; i <= 0; i++) {
        char szPcmA[1024];
        szPcmA[0] = 0;
        m_pPmdWin->fgetmemo(szPcmA, (char*)cszPmdPathA, i);
        if (!szPcmA[0]) {
            continue;
        }
        wchar_t szPcmW[1024];
        MultiByteToWideChar(CP_ACP, 0, szPcmA, -1, szPcmW, _countof(szPcmW));
        szPcmW[_countof(szPcmW) - 1] = 0;
        wchar_t* pszPcm = szPcmW;
        while (1) {
            wchar_t* next = wcschr(pszPcm, L',');
            if (next) {
                *next = 0;
            }
            IKpiFile* pFile = NULL;
            if (pFolder->OpenFile(pszPcm, &pFile)) {
                const wchar_t* cszPcmFullPath;
                if (pFile->GetRealFileW(&cszPcmFullPath)) {
                    m_ppPcmFiles = (IKpiFile**)realloc(m_ppPcmFiles, sizeof(IKpiFile*) * (m_nPcmFiles + 1));
                    m_ppPcmFiles[m_nPcmFiles++] = pFile;
                }
                else {
                    pFile->Release();
                }
            }
            if (!next) {
                break;
            }
            pszPcm = next + 1;
        }
    }

/*
    //力技で関係ありそうなファイルを検索
    wchar_t szPath[MAX_PATH];
    DWORD dwIndex = 0;
    while(1){
        if(!pFolder->EnumFiles(dwIndex++, szPath, sizeof(szPath), 0)){
            break;
        }
        if(!szPath[0]){
            continue;
        }
        wchar_t *pszExt = wcsrchr(szPath, L'.');
        if(!pszExt){
            continue;
        }
        if(_wcsicmp(pszExt, L".pvi") == 0 ||
           _wcsicmp(pszExt, L".ppc") == 0 ||
           _wcsicmp(pszExt, L".pps") == 0 ||
           _wcsicmp(pszExt, L".p86") == 0 ||
           _wcsicmp(pszExt, L".pzi") == 0){
            IKpiFile *pFile = NULL;
            if(!pFolder->OpenFile(szPath, &pFile)){
                continue;
        }
            const char *cszFileName;
            if(pFile->GetRealFileA(&cszFileName)){
                m_ppPcmFiles = (IKpiFile**)realloc(m_ppPcmFiles, sizeof(IKpiFile*)*(m_nPcmFiles + 1));
                m_ppPcmFiles[m_nPcmFiles++] = pFile;
            }
            else{
                pFile->Release();
            }
        }
    }
*/
}
/////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbPmdDecoder::Initialize(HINSTANCE hDll, 
                                         IKpiConfig *pConfig, 
                                         int *pnPmdWinVersion)
{
    *pnPmdWinVersion = 0;
    g_hKpi = hDll;
    if(LoadPmdWin(pConfig)){
        *pnPmdWinVersion = Pmd_GetVersion();
        return TRUE;
    }
    return FALSE;
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbPmdDecoder::Uninitialize(void)
{
    if(g_hPmdWin){
        FreeLibrary(g_hPmdWin);
        g_hPmdWin = NULL;
        Pmd_CoCreateInstance = NULL;
        Pmd_GetVersion = NULL;
    }
}
/////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbPmdDecoder::LoadPmdWin(IKpiConfig *pConfig)
{
    if(g_hPmdWin)return TRUE;
    wchar_t szPmdWin[MAX_PATH];
    kbfmpmd_GetPmdWinDllPath(pConfig, szPmdWin, _countof(szPmdWin));
    
    if(!szPmdWin[0]){//未指定の場合はプラグインと同じフォルダ
        GetModuleFileName(g_hKpi, szPmdWin, _countof(szPmdWin));
        wchar_t *path_delimiter = wcsrchr(szPmdWin, L'\\');
        if(path_delimiter){
            path_delimiter[1] = 0;
        }
        wcsncat_s(szPmdWin, L"PMDWin.dll", _TRUNCATE);
    }
    g_hPmdWin = LoadLibrary(szPmdWin);
    if(!g_hPmdWin){
        return FALSE;
    }
    Pmd_CoCreateInstance = (pfnPmd_CoCreateInstance)
                            GetProcAddress(g_hPmdWin, "pmd_CoCreateInstance");
    Pmd_GetVersion = (pfnPmd_GetVersion)
                            GetProcAddress(g_hPmdWin, "getversion");
    if(Pmd_CoCreateInstance && Pmd_GetVersion){
        return TRUE;
    }
    else{
        FreeLibrary(g_hPmdWin);
        g_hPmdWin = NULL;
        Pmd_CoCreateInstance = NULL;
        Pmd_GetVersion = NULL;
        return FALSE;
    }
}
/////////////////////////////////////////////////////////////////////////
DWORD  WINAPI KbPmdDecoder::UpdateConfig(void* pvReserved)
{
    m_bUpdateConfig = true;
    return 0;
}
/////////////////////////////////////////////////////////////////////////
/*
struct KbFMP_Settings
{
    int nVolFM;
    int nVolSSG;
    int nVolRhythm;
    int nVolADPCM;
    int nVolPPZ;
    int nPartMask[22];
    int nPartMaskRhythm;
};
*/
void __fastcall KbPmdDecoder::ApplySettings(void)
{
    if (!m_bUpdateConfig)return;//設定が変わってないので何もしない
    m_bUpdateConfig = false;

    KbFMPMD_Settings fmpmd_settings;
    kbfmpmd_GetSettings(m_pConfig, &fmpmd_settings);
    m_pPmdWin->setppsinterpolation(fmpmd_settings.bPPSInterpolation);
    m_pPmdWin->setp86interpolation(fmpmd_settings.bP86Interpolation);
    m_pPmdWin->setppzinterpolation(fmpmd_settings.bPPZInterpolation);
    m_pPmdWin->setppsuse(fmpmd_settings.bUsePPS);
    m_pPmdWin->setrhythmwithssgeffect(fmpmd_settings.bRhythmUseWithSSGDrum);
    m_pPmdWin->setpmd86pcmmode(fmpmd_settings.bPMD86PCMMode);
    //m_pPmdWin->setadpcmppz8emulate(fmpmd_settings.bADPCMPPZ8Emulate);

    KbPMD_Settings settings;
    kbpmd_GetSettings(m_pConfig, &settings);
    //volume
    m_pPmdWin->setfmvoldown(settings.nVolFM);
    m_pPmdWin->setssgvoldown(settings.nVolSSG);
    m_pPmdWin->setrhythmvoldown(settings.nVolRhythm);
    m_pPmdWin->setadpcmvoldown(settings.nVolADPCM);
    m_pPmdWin->setppzvoldown(settings.nVolPPZ);
 /*
    int maskon(int ch);
    function maskon(ch : Integer) : Integer;
-------------------------------------------------------------------------------
    input
        ch          マスクをかけるチャンネル(０〜２３)
    output          下記の戻り値を参照
-------------------------------------------------------------------------------

    パートマスクをかけます。
    チャンネル番号とパートの対応は以下のとおりです。

        0〜5    FM1 (A)〜FM6(F)
        6〜8    SSG1(G)〜SSG3(I)
        9       ADPCM(J)
        10      リズムパート(K)
        11〜13  FMEx1〜FMEx3
        14      Rhythm音源全体
        15      なし
        16〜23  PPZ1〜PPZ8
*/
//PartMask
    int i;
    for (i = 0; i < 24; i++) {
        if (i == 15)continue;//無効なチャンネルは処理しない
        if (settings.bPartMask[i]) {
            m_pPmdWin->maskon(i);
        }
        else {
            m_pPmdWin->maskoff(i);
        }
    }
}
