#include <windows.h>
#include <stdio.h>
#include "kbpsf_config.h"
#include "kbpsf_decoder.h"

/////////////////////////////////////////////////////////////////////////////
//spuPeopsSound.ini と spuPeopsSound_expert.ini を IKpiConfig::SetBin/GetBin を
//使用して管理
//spuPeopsSound.dll の設定ダイアログを閉じた直後の INI ファイルの内容を SetBin で保存
//spuPeopsSound.dll のロード時に GetBin で INI の内容を取得
//本体をバージョンアップして異なるフォルダにインストールしてもバージョンアップ前の 
//spuPeopsSound.ini/spuPeopsSound_expert.ini が適用されるようになる
//x86 版と x64 版で設定が共有されるようにもなる
/////////////////////////////////////////////////////////////////////////////
static const wchar_t SEC_GENERAL[] = L"General";
//SPUPlugin は x64 と x86 でキー名を区別する
//(spuEternal.dll が x86 版しかないため)
#ifdef _WIN64
static const wchar_t KEY_SPUPlugin[] = L"SPUPlugin_x64";
#else
static const wchar_t KEY_SPUPlugin[] = L"SPUPlugin_x86";
#endif
static const wchar_t KEY_SpuEternalPath[] = L"spuEternalPath";
static const wchar_t KEY_UseSpuEternal[] = L"UseSpuEternal";
static const wchar_t KEY_SPUPluginConfig[] = L"SPUPluginConfig";
static const wchar_t KEY_EnableInterpreterCPU[] = L"EnableInterpreterCPU";
static const wchar_t KEY_PeopsCfg[] = L"PeopsCfg";
static const wchar_t KEY_PeopsExpertCfg[] = L"PeopsExpertCfg";
static const wchar_t KEY_ResetPeops[] = L"ResetPeops";
static const wchar_t KEY_IgnoreVolumeTag[] = L"IgnoreVolumeTag";
static const wchar_t KEY_Volume[] = L"Volume";

static const BOOL  UseSpuEternal_default = FALSE;
static const wchar_t szUseSpuEternal_default[] = L"0";
static const BOOL EnableInterpreterCPU_default = FALSE;
static const wchar_t szEnableInterpreterCPU_default[] = L"0";
static const BOOL IgnoreVolumeTag_default = FALSE;
static const wchar_t szIgnoreVolumeTag_default[] = L"0";
static const double Volume_default = 100.0;
static const wchar_t szVolume_default[] = L"100.0";

static const wchar_t g_cszPeopsDll[] = L"spuPeopsSound.dll";

extern HINSTANCE g_hKpi;

static long (CALLBACK *SPU_configure)(void);
static void* (CALLBACK *SPU_getCfgData)(void);

static void __fastcall GetSPUPluginSearchPath(wchar_t *pszSearchPath, int nSize);//nSize は文字数単位
//SPUプラグインの検索パス(kbpsf.kpi と同じフォルダを取得)

static void __fastcall GetSPUPluginPath(IKpiConfig *pConfig, wchar_t *pszPath, int nSize);//nSize は文字数単位
//使用するSPUプラグインのファイル名(フルパス)を取得
//32bit版と64bit版で処理が異なる

static wchar_t* __fastcall CreateSPUPluginList(void);
static void __fastcall ReadPeopsCfg(IKpiConfig *pConfig, 
                                    const wchar_t *cszSection,
                                    const wchar_t *cszKeyCfg,
                                    const wchar_t *cszKeyCfgExpert);
static void __fastcall WritePeopsCfg(IKpiConfig *pConfig,
                                     const wchar_t *cszSection,
                                     const wchar_t *cszKeyCfg,
                                     const wchar_t *cszKeyCfgExpert);
static void __fastcall ResetPeopsCfg(IKpiConfig *pConfig);

static const DWORD MAX_PEOPS_CFG = 4096;//4KB あれば十分
struct PEOPS_CFG
{
    char szCfg[MAX_PEOPS_CFG];       
    char szCfgExpert[MAX_PEOPS_CFG]; 
};
/////////////////////////////////////////////////////////////////////////////
void __fastcall GetSPUPluginSearchPath(wchar_t *pszSearchPath, int nSize)
{//SPUプラグインの検索パス(kbpsf.kpi と同じフォルダを取得)
    GetModuleFileName(g_hKpi, pszSearchPath, nSize);
    wchar_t *pszPathDelimiter = wcsrchr(pszSearchPath, L'\\');
    if(pszPathDelimiter){
        pszPathDelimiter[1] = 0;
    }
}
/////////////////////////////////////////////////////////////////////////////
void __fastcall GetSPUPluginPath(IKpiConfig *pConfig, wchar_t *pszPath, int nSize)
{//使用するSPUプラグインのファイル名(フルパス)を取得
 //32bit版と64bit版で処理が異なる
#ifndef _WIN64 //32bit版
    BOOL bUseSpuEternal = pConfig->GetInt(SEC_GENERAL, KEY_UseSpuEternal, UseSpuEternal_default) != 0;
    if(bUseSpuEternal){
        pConfig->GetStr(SEC_GENERAL, KEY_SpuEternalPath, pszPath, nSize*sizeof(wchar_t), L"");
        if(pszPath[0]){
            return;
        }
    }
#endif
    GetSPUPluginSearchPath(pszPath, nSize);
    wchar_t szSPUPluginName[MAX_PATH];
    pConfig->GetStr(SEC_GENERAL, KEY_SPUPlugin, szSPUPluginName, sizeof(szSPUPluginName), g_cszPeopsDll);
    if (!szSPUPluginName[0]) {
        wcsncpy_s(szSPUPluginName, nSize, g_cszPeopsDll, _TRUNCATE);
    }
    wcsncat_s(pszPath, nSize, szSPUPluginName, _TRUNCATE);
}
/////////////////////////////////////////////////////////////////////////////
wchar_t* __fastcall CreateSPUPluginList(void)
{
    wchar_t szFind[MAX_PATH];
    GetSPUPluginSearchPath(szFind, _countof(szFind));
    wcsncat_s(szFind, L"spu*.dll", _TRUNCATE);
    WIN32_FIND_DATA fd;
    HANDLE hFind = FindFirstFile(szFind, &fd);
    if(hFind == INVALID_HANDLE_VALUE){
        return NULL;
    }
    wchar_t *pszRet = NULL;
    size_t total_len = 0;
    do{
        if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)){
            total_len += wcslen(fd.cFileName) + 1;
            if(pszRet){
                total_len++;//\t 
                pszRet = (wchar_t*)realloc(pszRet, total_len*sizeof(wchar_t));
                wcsncat_s(pszRet, total_len, L"\t", _TRUNCATE);
                wcsncat_s(pszRet, total_len, fd.cFileName, _TRUNCATE);
            }
            else{
                pszRet = _wcsdup(fd.cFileName);
            }
        }
	} while (FindNextFile(hFind, &fd));
    FindClose(hFind);
    return pszRet;
}
/////////////////////////////////////////////////////////////////////////////
void __fastcall ReadPeopsCfg(IKpiConfig *pConfig, 
                            const wchar_t *cszSection,
                            const wchar_t *cszKeyCfg,
                            const wchar_t *cszKeyCfgExpert)
{
    PEOPS_CFG *pCfgData = SPU_getCfgData ? (PEOPS_CFG*)SPU_getCfgData() : NULL;
    if(!pCfgData)return;
    DWORD dwSize = pConfig->GetBin(cszSection, cszKeyCfg, (BYTE*)pCfgData->szCfg, MAX_PEOPS_CFG);
    if(dwSize >= MAX_PEOPS_CFG){
        dwSize = 0;
    }
    ZeroMemory(pCfgData->szCfg + dwSize, MAX_PEOPS_CFG - dwSize);
    dwSize = pConfig->GetBin(cszSection, cszKeyCfgExpert, (BYTE*)pCfgData->szCfgExpert, MAX_PEOPS_CFG);
    if(dwSize >= MAX_PEOPS_CFG){
        dwSize = 0;
    }
    ZeroMemory(pCfgData->szCfgExpert + dwSize, MAX_PEOPS_CFG - dwSize);
}
/////////////////////////////////////////////////////////////////////////////
void __fastcall WritePeopsCfg(IKpiConfig *pConfig,
                              const wchar_t *cszSection,
                              const wchar_t *cszKeyCfg,
                              const wchar_t *cszKeyCfgExpert)
{
    PEOPS_CFG *pCfgData = SPU_getCfgData ? (PEOPS_CFG*)SPU_getCfgData() : NULL;
    if(!pCfgData)return;
    DWORD dwSizeCfg = strlen(pCfgData->szCfg);
    DWORD dwSizeCfgExpert = strlen(pCfgData->szCfgExpert);
    pConfig->SetBin(cszSection, cszKeyCfg, (BYTE*)pCfgData->szCfg, dwSizeCfg);
    pConfig->SetBin(cszSection, cszKeyCfgExpert, (BYTE*)pCfgData->szCfgExpert, dwSizeCfgExpert);
}
/////////////////////////////////////////////////////////////////////////////
void __fastcall ResetPeopsCfg(IKpiConfig *pConfig)
{
    pConfig->SetBin(SEC_GENERAL, KEY_PeopsCfg, NULL, 0);
    pConfig->SetBin(SEC_GENERAL, KEY_PeopsExpertCfg, NULL, 0);
    ReadPeopsCfg(pConfig, SEC_GENERAL, KEY_PeopsCfg, KEY_PeopsExpertCfg);
}
/////////////////////////////////////////////////////////////////////////////
BOOL __fastcall kbpsf_EnumConfig(IKpiConfigEnumerator *pEnumerator)
{
    static const KPI_CFG_SECTION sec[] = {
        {SEC_GENERAL, NULL, L"kbpsf.kpi に関する一般的な設定です。"},
        {NULL, NULL, NULL}
    };
    wchar_t *pszSPUPluginList = CreateSPUPluginList();
    KPI_CFG_KEY key[] = {//static const にしてはいけない(pszSPUPluginList があるので)
    //   type, section, key, keydesc, default, min, max, list, listdesc, help
        {KPI_CFG_TYPE_FILE, SEC_GENERAL, KEY_SpuEternalPath, L"spuEternal.dll のパス",
            L"", L"spuEternal.dll(*.dll)\t*.dll", NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"spuEternal.dll のパスを指定します。\r\n"
            L"spuEternal.dll は 32bit 版の kbpsf.kpi でのみ使用可能です。\r\n"
            L"64bit 版の kbpsf.kpi ではこの設定は無視されます。\r\n"
            L""
            },//help
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_UseSpuEternal, L"spuEternal.dll を使う",
            szUseSpuEternal_default, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"true にすると spuEternal.dll を使用します。\r\n"
            L"spuEternal.dll は 32bit 版の kbpsf.kpi でのみ使用可能です。\r\n"
            L"64bit 版の kbpsf.kpi ではこの設定は無視されます。\r\n"
            L"64bit 版の本体で 32bit 版の kbpsf.kpi を使用するには、\r\n"
            L"\r\n"
            L"・同名の x86 プラグイン(非表示&無効化)のチェックを外す\r\n"
            L"・64bit版の kbpsf.kpi を無効化する\r\n"
            L"・32bit版の kbpsf.kpi を有効化する\r\n"
            L"\r\n"
            L""
            },//help
        {KPI_CFG_TYPE_STR, SEC_GENERAL, KEY_SPUPlugin, NULL,
            L"spuPeopsSound.dll", NULL, NULL, //default/min/max
            pszSPUPluginList, //list
            NULL, //listdesc
            L"再生に使用する SPU プラグインを選択します。\r\n"
            L"「spuEternal.dll を使う」が true の場合、この設定は無視されます。\r\n"
            L"(32bit版の kbpsf.kpi のみ)\r\n"
            L"\r\n"
            L"spuPeopsSound.dll/spumame.dll/spumednafen.dll から選択することが出来ます。\r\n"
            L"32bit 版の kbpsf.kpi と同じフォルダに置けば、spuEternal.dll を使用することが出来ます。\r\n"
            L"同じフォルダに置くのが煩わしい場合は「spuEternal.dll のパス」で設定することも出来ます。\r\n"
            L"64bit 版の kbpsf.kpi で spuEternal.dll を使用することは出来ません。\r\n"
            L"spuEternal.dll がない場合のお勧めは spuPeopsSound.dll です。\r\n"
            L""
            },//help
        {KPI_CFG_TYPE_BIN, SEC_GENERAL, KEY_SPUPluginConfig, NULL, 
            NULL, NULL, NULL, //default/min/max
            NULL, //list
            NULL, //listdesc
            L"使用中の SPU プラグインの設定画面を開きます。\r\n"
            L"spuPeopsSound.dll: 設定画面が開きます。\r\n"
            L"spumame.dll/spumadnafen.dll: 設定画面は開きません。\r\n"
            L"spuEternal.dll: 設定画面が開きます。設定の保存場所は spuEternal.dll が管理します。(レジストリに保存されるようです)"
            },//help
        {KPI_CFG_TYPE_BIN, SEC_GENERAL, KEY_ResetPeops, NULL, 
            NULL, NULL, NULL, //default/min/max
            NULL, //list
            NULL, //listdesc
            L"spuPeopsSound.dll の設定を初期値に戻します。"
            },//help
       {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_EnableInterpreterCPU, NULL, 
            szEnableInterpreterCPU_default, NULL, NULL, //default/min/max
            NULL, //list
            NULL, //listdesc
            L"true にすると CPU のコアに Interpreter を使用します。\r\n"
            L"false にすると CPU のコアに Recompiler を使用します。\r\n"
            L"再生時の負荷は Interpreter の方が高いようです。\r\n"
            },//help
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_IgnoreVolumeTag, NULL,
            szIgnoreVolumeTag_default, NULL, NULL, //default/min/max
            NULL, //list
            NULL, //listdesc
            L"true にすると volume タグを無視します。\r\n"
            L"true にすると「音量(%)」の設定だけが適用されます。\r\n"
            L"false にすると volume タグと「音量(%)」の設定が重ねて適用されます。\r\n"
            L"リプレイゲインとは区別されます。\r\n"
            L"リプレイゲインは本体によって処理されます。\r\n"
            L""
            },//help
        {KPI_CFG_TYPE_FLOAT, SEC_GENERAL, KEY_Volume, L"音量(%)",
            szVolume_default, L"10.0", L"500.0", //default/min/max
            L"200\t150\t125\t100\t90\t80\t70\t60\t50\t40\t30\t20\t10", //list
            L"200%\t150%\t125%\t100%\t90%\t80%\t70%\t60%\t50%\t40%\t30%\t20%\t10%", //listdesc
            L"音量(%)を指定します。\r\n"
            L"連続再生時に他のファイル形式と比べて大きいと感じる場合は 100 より小さく、\r\n"
            L"音量が小さいと感じる場合は 100 より大きくして下さい。\r\n"
            L"100 より大きくすると音割れする可能性があります。\r\n"
            L"PSF タグに volume タグを含む場合、重ねて適用されます。\r\n"
            L"リプレイゲインは本体によって処理されます。\r\n"
            L"(この設定と重ねて適用されます。)\r\n"
            L""
            },//help
        {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
    };
    for(int i = 0; sec[i].cszSection; i++){
        pEnumerator->EnumSection(&sec[i]);
    }
    for(int i = 0; key[i].cszSection; i++){
        pEnumerator->EnumKey(&key[i]);
    }
    free(pszSPUPluginList);
    return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
DWORD __fastcall kbpsf_ApplyConfig(IKpiConfig *pConfig,
                               const wchar_t *cszSection,//セクション名
                               const wchar_t *cszKey,    //キー名
                               INT64  nValue, //変更後の値(BOOL/INT の場合)
                               double dValue, //変更後の値(FLOAT の場合)
                               const wchar_t *cszValue)////変更後の値(文字列)
{
    if(_wcsicmp(cszKey, KEY_SPUPluginConfig) == 0){
        if(!SPU_configure){
            return KPI_CFGRET_OK;
        }
        PEOPS_CFG *pCfgData = SPU_getCfgData ? (PEOPS_CFG*)SPU_getCfgData() : NULL;
        if(!pCfgData){
            SPU_configure();
            return KPI_CFGRET_RELOAD_KPI;
        }
        PEOPS_CFG CfgDataSaved = *pCfgData;
        SPU_configure();
        WritePeopsCfg(pConfig, SEC_GENERAL, KEY_PeopsCfg, KEY_PeopsExpertCfg);
        if(strcmp(pCfgData->szCfg, CfgDataSaved.szCfg) == 0 &&
           strcmp(pCfgData->szCfgExpert, CfgDataSaved.szCfgExpert) == 0){//設定が変わっていない
            return KPI_CFGRET_OK;
        }
        return KPI_CFGRET_RELOAD_KPI;
    }
    if(_wcsicmp(cszKey, KEY_ResetPeops) == 0){
        int nRet = MessageBox(NULL, 
                    L"spuPeopsSound.dll の設定を初期値に戻しても良いですか？", L"確認", MB_YESNO);
        if(nRet == IDYES){
            ResetPeopsCfg(pConfig);
            return KPI_CFGRET_RELOAD_KPI;
        }
        return KPI_CFGRET_OK;
    }
    pConfig->SetStr(cszSection, cszKey, cszValue);
    return KPI_CFGRET_RELOAD_KPI;
}
/////////////////////////////////////////////////////////////////////////////
void __fastcall kbpsf_GetSettings(IKpiConfig *pConfig, kbpsf_Settings *pSettings)
{
//   GetSPUPluginName(pConfig, pSettings->szSPUPluginName_, _countof(pSettings->szSPUPluginName_));
//    pSettings->bUseSpuEternal = 
//        pConfig->GetInt(SEC_GENERAL, KEY_UseSpuEternal, UseSpuEternal_default) != 0;
    pSettings->bEnableInterpreterCPU = 
        pConfig->GetInt(SEC_GENERAL, KEY_EnableInterpreterCPU, EnableInterpreterCPU_default) != 0;
    pSettings->bIgnoreVolumeTag = 
        pConfig->GetInt(SEC_GENERAL, KEY_IgnoreVolumeTag, IgnoreVolumeTag_default) != 0;
    pSettings->dVolume =
        pConfig->GetFloat(SEC_GENERAL, KEY_Volume, Volume_default) / 100.0;
    if(pSettings->dVolume < 0.1){
        pSettings->dVolume = 0.1;
    }
    else if(pSettings->dVolume > 5.0){
        pSettings->dVolume = 5.0;
    }
}
/////////////////////////////////////////////////////////////////////////////
HINSTANCE __fastcall kbpsf_LoadSPUPlugin(IKpiConfig *pConfig)
{
    wchar_t szPath[MAX_PATH];
    GetSPUPluginPath(pConfig, szPath, _countof(szPath));

    HINSTANCE hDll = LoadLibrary(szPath);
    if(!hDll){
        return NULL;
    }
    SPU_configure = (long (CALLBACK*)(void))GetProcAddress(hDll, "SPUconfigure");
    SPU_getCfgData = (void* (CALLBACK*)(void))GetProcAddress(hDll, "SPUgetCfgData");
    ReadPeopsCfg(pConfig, SEC_GENERAL, KEY_PeopsCfg, KEY_PeopsExpertCfg);
    return hDll;
}
/////////////////////////////////////////////////////////////////////////////
void __fastcall kbpsf_UnloadSPUPlugin(HINSTANCE hDll)
{
    if(hDll){
        FreeLibrary(hDll);
        SPU_configure = NULL;
        SPU_getCfgData = NULL;
    }
}
/////////////////////////////////////////////////////////////////////////////
