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

#ifdef _DEBUG
#define LEAK_CHECK //メモリリーク検出（しない場合はコメントアウト）
#endif
#ifdef LEAK_CHECK
#include <crtdbg.h>
#endif

#include "kbsnesapu_module.h"
#include "kbsnesapu_decoder.h"
#include "kbsnesapu_config.h"

#ifdef _DEBUG
static const DWORD kbsnesapu_Version = 0x7FFFFFFF; //デバッグ時は優先度最大
#define KBSNESAPU_VERSION_STR L"0x7FFFFFFF(Debug)"
#else
static const DWORD kbsnesapu_Version = 424; //プラグインのバージョン
#define KBSNESAPU_VERSION_STR L"4.24"
#endif

static const wchar_t kbsnesapu_Description[] = L"SNESAPU SPC Decoder v" KBSNESAPU_VERSION_STR " based on SNESAPU.DLL";
static const wchar_t kbsnesapu_Copyright[] = L"kbsnesapu.kpi Copyright (C) 2004-2024 Kobarin\n"
                                             L"SNESAPU.DLL Copyright (C) 1999-2023 Alpha-II Productions, degrade-factory\n" 
                                             L"SNES is a registerd trademark of Nintendo"; 

// {10DED910-8EE4-4BD8-AA5C-0E8325D01389}
static const GUID kbsnesapu_GUID =
{ 0x10ded910, 0x8ee4, 0x4bd8, { 0xaa, 0x5c, 0xe, 0x83, 0x25, 0xd0, 0x13, 0x89 } };

static const DWORD kbsnesapu_MultipleInstance = KPI_MULTINST_INFINITE;//同時に何個でも作成可能
                                                //KPI_MULTINST_ZERO;  //常に別プロセス起動
                                                //KPI_MULTINST_ONE;   //2個目以降は別プロセス起動
                                                //KPI_MULTINST_UNIQUE;//プロセスをまたいでも１つまで
                                                //KPI_MULTINST_ZERO + N;//(N+1)個目以降は別プロセス起動
                                                //↑どれか１つを選択
static const wchar_t kbsnesapu_SupportExts[] = L".spc";
static const wchar_t kbsnesapu_MultiSongExts[] = L"";
static const DWORD kbsnesapu_SupportTagInfo = KPI_DECODER_MODULEINFO::TAG_EXTRA | 
                                              KPI_DECODER_MODULEINFO::TAG_TITLE;
static const DWORD kbsnesapu_SupportConfig = 1;
static void* kbsnesapu_Reserved1 = NULL;
static void* kbsnesapu_Reserved2 = NULL;

HINSTANCE g_hKpi = NULL;

//////////////////////////////////////////////////////////////////////////////
KbSnesApuDecoderModule::KbSnesApuDecoderModule(IKpiConfig *pConfig)
    :m_ModuleInfo{
        sizeof(KPI_DECODER_MODULEINFO), //cb(=sizeof(KPI_DECODER_MODULEINFO))
        KPI_DECODER_MODULE_VERSION,     //dwModuleVersion(=KPI_DECODER_MODULE_VERSION)
        kbsnesapu_Version,
        kbsnesapu_MultipleInstance,
        kbsnesapu_GUID,
        kbsnesapu_Description,
        kbsnesapu_Copyright,
        kbsnesapu_SupportExts,
        kbsnesapu_MultiSongExts,
        kbsnesapu_Reserved1,
        kbsnesapu_Reserved2,
        kbsnesapu_SupportTagInfo,
        kbsnesapu_SupportConfig,
        0, 0, 0, 0                      //reserved
     }
{
    wchar_t szDllPath[MAX_PATH];
    wchar_t szDescription[1024];
    wchar_t szVersion[64];
    //SNESAPU.DLL のパスを取得
    //未指定の場合はプラグインと同じフォルダの SNESAPU.DLL
    kbsnesapu_GetDllPath(pConfig, szDllPath, _countof(szDllPath));
    if(!szDllPath[0]){
        GetModuleFileName(g_hKpi, szDllPath, _countof(szDllPath));
        wchar_t* pszPathDelimiter = wcsrchr(szDllPath, L'\\');
        if (pszPathDelimiter) {
            pszPathDelimiter[1] = 0;
        }
        wcsncat_s(szDllPath, L"snesapu.dll", _TRUNCATE);
    }
    if(KbSnesApuDecoder::Initialize(szDllPath)){
        u32 ver,min,opt;
        KbSnesApuDecoder::s_Apu.SNESAPUInfo(&ver, &min, &opt);
        int major = ver >> 16;
        int minor = (ver & 0x0000FF00) >> 8;
        int rev = ver & 0x000000FF;
        if('a' <= rev && rev <= 'z'){
            _snwprintf_s(szVersion, _TRUNCATE,
                       L" v%d.%d%d.%d",
                       major, (minor>>4), (minor&0x0F), rev - 'a' + 1);
        }
        else{
            _snwprintf_s(szVersion, _TRUNCATE,
                       L" v%d.%d%d",
                       major, (minor>>4), (minor&0x0F));
        }
    }
    else{
        wcsncpy_s(szVersion, L" (failed to load dll)", _TRUNCATE);
    }
    wcsncpy_s(szDescription, kbsnesapu_Description, _TRUNCATE);
    wcsncat_s(szDescription, szVersion, _TRUNCATE);
    m_ModuleInfo.cszDescription = _wcsdup(szDescription);
    m_pConfig = pConfig;
    pConfig->AddRef();
}
//////////////////////////////////////////////////////////////////////////////
KbSnesApuDecoderModule::~KbSnesApuDecoderModule(void)
{
    free((void*)m_ModuleInfo.cszDescription);
    KbSnesApuDecoder::Uninitialize();
    m_pConfig->Release();
}
//////////////////////////////////////////////////////////////////////////////
void  WINAPI KbSnesApuDecoderModule::GetModuleInfo(const KPI_DECODER_MODULEINFO **ppInfo)
{
    *ppInfo = &m_ModuleInfo;
}
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbSnesApuDecoderModule::Open(const KPI_MEDIAINFO *cpRequest,
                                          IKpiFile     *pFile,   //音楽データ
                                          IKpiFolder   *pFolder, //音楽データがあるフォルダ
                                          IKpiDecoder **ppDecoder)
{
    KbSnesApuDecoder *pDecoder = new KbSnesApuDecoder(m_pConfig);
    DWORD dwCount = pDecoder->Open(cpRequest, pFile, pFolder);
    if(dwCount == 0){
        *ppDecoder = NULL;
        delete pDecoder;
        return 0;
    }
    *ppDecoder = pDecoder;
    return dwCount;
}
//////////////////////////////////////////////////////////////////////////////
BOOL  WINAPI KbSnesApuDecoderModule::EnumConfig(IKpiConfigEnumerator *pEnumerator)
{
    return kbsnesapu_EnumConfig(m_pConfig, pEnumerator);
}
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbSnesApuDecoderModule::ApplyConfig(const wchar_t *cszSection,
                                                 const wchar_t *cszKey,
                                                 INT64 nValue,
                                                 double dValue,
                                                 const wchar_t *cszValue)
{
    return kbsnesapu_ApplyConfig(m_pConfig, cszSection, cszKey, nValue, dValue, cszValue);
}
/////////////////////////////////////////////////////////////////////////////
HRESULT WINAPI kpi_CreateInstance(REFIID riid, void **ppvObject, IKpiUnknown *pUnknown)
{
    *ppvObject = NULL;
    if(!IsEqualIID(riid, IID_IKpiDecoderModule)){
        return E_NOINTERFACE;
    }
    IKpiConfig *pConfig;
    kpi_CreateConfig(pUnknown, &kbsnesapu_GUID, NULL, &pConfig);
    IKpiDecoderModule *pDecoderModule = new KbSnesApuDecoderModule(pConfig);
    *ppvObject = pDecoderModule;
    pConfig->Release();//KbSnesApuDecoderModule 内で AddRef しているので必ず必要
    return S_OK;
}
/////////////////////////////////////////////////////////////////////////////
//DllMain
/////////////////////////////////////////////////////////////////////////////
BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
    {
#ifdef LEAK_CHECK
        //メモリリーク検出
        int tmpDbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
        //tmpDbgFlag |= _CRTDBG_DELAY_FREE_MEM_DF;
        tmpDbgFlag |= _CRTDBG_LEAK_CHECK_DF;
        //tmpDbgFlag |= _CRTDBG_CHECK_ALWAYS_DF;
        _CrtSetDbgFlag(tmpDbgFlag);
#endif
        g_hKpi = hModule;
        DisableThreadLibraryCalls(hModule);
        break;
    }
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
