#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "kpi.h"
#include "fmpmd.h"
#include "../kbfmpmd_config.h"

#define SZ_MUSIC_FILE "MUSIC_FILE.xxx"
#define SZ_MUSIC_DIR  "C:\\MUSIC_DIR\\"

const char FMPMD_FileIO::s_musfullpath_alias[] = SZ_MUSIC_DIR SZ_MUSIC_FILE; //再生曲データのフルパス
const char FMPMD_FileIO::s_musdir_alias[] =      SZ_MUSIC_DIR;  //再生曲データのフォルダ
const char FMPMD_FileIO::s_rhythm_alias[] =      "C:\\RHYTHM_DIR\\";//リズム音源のパス
const char FMPMD_FileIO::s_pcmdir_alias[] =      "C:\\PCM_DIR";     //PCM 検索パス

void __fastcall GetRythmPathW(IKpiConfig *pConfig, HINSTANCE hKpi, wchar_t *pszRythmPath, int nSize)
{
    //リズム音源のパス(設定がない場合はプラグインが置かれているパスと同じ)
    wchar_t szRythmPath[MAX_PATH];
    kbfmpmd_GetRythmPath(pConfig, szRythmPath, _countof(szRythmPath));
    if(!szRythmPath[0]){
        GetModuleFileName(hKpi, szRythmPath, _countof(szRythmPath));
        wchar_t *delimiter = wcsrchr(szRythmPath, L'\\');
        if(delimiter){
            delimiter[1] = 0;
        }
    }
    else{//終端に \ 付加
        int len = wcslen(szRythmPath);
        if(szRythmPath[len-1] != L'\\'){
            wcsncat_s(szRythmPath, L"\\", _TRUNCATE);
        }
    }
    wcsncpy_s(pszRythmPath, nSize, szRythmPath, _TRUNCATE);
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall GetRythmPathA(IKpiConfig* pConfig, HINSTANCE hKpi, char* pszRythmPathA, int nSize)
{
    //リズム音源のパス(設定がない場合はプラグインが置かれているパスと同じ)
    wchar_t szRythmPath[MAX_PATH];
    GetRythmPathW(pConfig, hKpi, szRythmPath, _countof(szRythmPath));
    WideCharToMultiByte(CP_ACP, 0, szRythmPath, -1, pszRythmPathA, nSize, NULL, NULL);
    pszRythmPathA[nSize - 1] = 0;
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall GetPcmDirsW(IKpiConfig* pConfig, wchar_t* pszDirsW[FMPMD_MAX_PCMDIR + 1])
{
    kbfmpmd_GetPcmDirs(pConfig, pszDirsW, FMPMD_MAX_PCMDIR + 1);
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall GetPcmDirsA(IKpiConfig* pConfig, char* pszDirsA[FMPMD_MAX_PCMDIR + 1])
{
    wchar_t *pszDirsW[FMPMD_MAX_PCMDIR + 1];
    GetPcmDirsW(pConfig, pszDirsW);
    int i;
    for (i = 0; i < FMPMD_MAX_PCMDIR; i++) {
        if(!pszDirsW[i]){
            break;
        }
        char szDirA[MAX_PATH * 2];
        WideCharToMultiByte(CP_ACP, 0, pszDirsW[i], -1, szDirA, _countof(szDirA), NULL, NULL);
        szDirA[_countof(szDirA) - 1] = 0;
        pszDirsA[i] = _strdup(szDirA);
    }
    for(; i <= FMPMD_MAX_PCMDIR; i++){
        pszDirsA[i] = NULL;
    }
    FreePcmDirsW(pszDirsW);
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall FreePcmDirsW(wchar_t* pszDirsW[FMPMD_MAX_PCMDIR+1])
{
    for (int i = 0; pszDirsW[i]; i++) {
        free(pszDirsW[i]);
    }
    pszDirsW[0] = NULL;
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall FreePcmDirsA(char *pszDirsA[FMPMD_MAX_PCMDIR+1])
{
    for(int i = 0; pszDirsA[i]; i++){
        free(pszDirsA[i]);
    }
    pszDirsA[0] = NULL;
}
//////////////////////////////////////////////////////////////////////////////
int64_t WINAPI FMPMD_FileIO::GetFileSize(const char* filename)
{
    int64_t ret;
    CACHE* cache = GetCache(filename);
    if (cache) {//既に開いている
        ret = cache->pFile->GetSize();
    }
    else{
        IKpiFile* pFile = NULL;
        wchar_t szFileName[MAX_PATH];
        GetFileName(szFileName, _countof(szFileName), filename);
        if(m_pFolder->OpenFile(szFileName, &pFile)){
            ret = pFile->GetSize();
            //サイズを確認した場合の多くは後にファイルを開き直すので
            //閉じずにキャッシュしておく       
            AddCache(pFile, filename);
            pFile->Release();
        }
        else{
            ret = -1;
        }
    }
    if(ret <= 0)return -1;
    if(ret > 0x7FFFFFFF)return -1;
    return ret;
}
//////////////////////////////////////////////////////////////////////////////
bool WINAPI FMPMD_FileIO::Open(const char* filename, uint flg)
{
    Close();
    CACHE *cache = GetCache(filename);
    if(cache){
        m_pFile = cache->pFile;
        m_pFile->Seek(0, FILE_BEGIN);//先頭に戻す
        strncpy_s(m_szCurFileNameA_alias, filename, _TRUNCATE);
        m_pFile->AddRef();
        return true;
    }
    IKpiFile* pFile = NULL;
    wchar_t szFileName[MAX_PATH];
    GetFileName(szFileName, _countof(szFileName), filename);
    if (!m_pFolder->OpenFile(szFileName, &pFile)) {
        return false;
    }
    m_pFile = pFile;
    strncpy_s(m_szCurFileNameA_alias, filename, _TRUNCATE);
    AddCache(pFile, filename);
    return true;
}
//////////////////////////////////////////////////////////////////////////////
void WINAPI FMPMD_FileIO::Close()
{
    if(m_pFile){
        IKpiFile *pFile = m_pFile;
        m_pFile = NULL;
        AddCache(pFile, m_szCurFileNameA_alias);
        pFile->Release();
    }
    m_szCurFileNameA_alias[0] = 0;
}
//////////////////////////////////////////////////////////////////////////////
int32_t WINAPI FMPMD_FileIO::Read(void* dest, int32_t len)
{
    int32_t ret;
    if(m_pFile){
        ret = m_pFile->Read(dest, len);
    }
    else{
        ret = 0;
    }
    //PMDWin.DLL が戻り値をチェックしないようなので念の為足りない分をゼロクリアする
    if (ret < len) {
        ZeroMemory(((BYTE*)dest) + ret, len - ret);
    }
    return ret;
}
//////////////////////////////////////////////////////////////////////////////
bool WINAPI FMPMD_FileIO::Seek(int32_t fpos, SeekMethod method)
{//呼ばれない？
    if(m_pFile){
        return m_pFile->Seek(fpos, method) != KPI_FILE_EOF;
    }
    return false;
}
//////////////////////////////////////////////////////////////////////////////
_int32 WINAPI FMPMD_FileIO::Tellp()
{//呼ばれない？
    if(m_pFile){
        return m_pFile->Seek(0, FILE_CURRENT);
    }
    return 0;
}
//////////////////////////////////////////////////////////////////////////////
HRESULT WINAPI FMPMD_FileIO::QueryInterface(REFIID riid, void** ppvObject)
{
    if (!ppvObject) {
        return E_INVALIDARG;
    }
    else if (IsEqualGUID(riid, __uuidof(IUnknown))) {
        *ppvObject = (IUnknown*)this;
    }
    else if (IsEqualGUID(riid, __uuidof(IFILEIO))) {
        *ppvObject = (IFILEIO*)this;
    }
    else {
        *ppvObject = NULL;
        return E_NOINTERFACE;
    }
    AddRef();
    return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
ULONG   WINAPI FMPMD_FileIO::AddRef(void)
{ 
    return InterlockedIncrement(&m_cRef);
}
//////////////////////////////////////////////////////////////////////////////
ULONG   WINAPI FMPMD_FileIO::Release(void)
{ 
    ULONG ret = InterlockedDecrement(&m_cRef);
    if (ret == 0) {
        delete this;
    }
    return ret;
}
//////////////////////////////////////////////////////////////////////////////
FMPMD_FileIO::FMPMD_FileIO(IKpiConfig* pConfig, HINSTANCE hKpi, IKpiFolder* pFolder, IKpiFile* pFile)
{
    m_cRef = 1;
    m_szMusFileName[0] = 0;
    m_szRhythmPath[0] = 0;
    m_pszPcmDirs[0] = NULL;
    m_szCurFileNameA_alias[0] = 0;
    m_pCache = NULL;
    m_nCache = 0;
    ZeroMemory(m_pszPcmDirs, sizeof(m_pszPcmDirs));
    ZeroMemory(m_pszPcmDirsA_alias, sizeof(m_pszPcmDirsA_alias));
    m_pFolder = pFolder;
    m_pFile = pFile;
    pFolder->AddRef();
    pFile->AddRef();
    pFile->GetFileName(m_szMusFileName, sizeof(m_szMusFileName));
    GetRythmPathW(pConfig, hKpi, m_szRhythmPath, _countof(m_szRhythmPath));
    GetPcmDirsW(pConfig, m_pszPcmDirs);

    int i;
    for (i = 0; m_pszPcmDirs[i]; i++) {//C:\PCM_DIRxx\\ のようにする
        char filename_alias[MAX_PATH];
        _snprintf_s(filename_alias, _TRUNCATE, "%s%02d\\", s_pcmdir_alias, i);
        m_pszPcmDirsA_alias[i] = _strdup(filename_alias);
    }
    m_pszPcmDirsA_alias[i] = NULL;

    strncpy_s(m_szCurFileNameA_alias, s_musfullpath_alias, _TRUNCATE);
}
//////////////////////////////////////////////////////////////////////////////
FMPMD_FileIO::~FMPMD_FileIO(void)
{
    if(m_pCache){
        int i;
        for(i = 0; i < m_nCache; i++){
            m_pCache[i].pFile->Release();
        }
        free(m_pCache);
    }
    if (m_pFile) {
        m_pFile->Release();
    }
    m_pFolder->Release();
    FreePcmDirsW(m_pszPcmDirs);
    FreePcmDirsA(m_pszPcmDirsA_alias);

}
//////////////////////////////////////////////////////////////////////////////
void __fastcall FMPMD_FileIO::GetFileName(wchar_t* pszFileName, int nSize, const char* cszFileName_alias)
{//DLL から呼ばれる IFILEIO::Open/GetFileSize で渡される filename から実際に開くべきファイル名を得る
    pszFileName[0] = 0;
    if (strcmp(cszFileName_alias, s_musfullpath_alias) == 0){
    //再生曲データ(C:\MUSIC_DIR\MUSIC_FILE.xxx)
        wcsncpy_s(pszFileName, nSize, m_szMusFileName, _TRUNCATE);//これが実行されることはない?(AddCache で何もしなければ実行される)
    }
    else if (strstr(cszFileName_alias, s_musdir_alias) == cszFileName_alias) {
    //再生曲データと同じフォルダ(C:\MUSIC_DIR\filename)
        const char* filename = cszFileName_alias + _countof(s_musdir_alias) - 1;
        MultiByteToWideChar(CP_ACP, 0, filename, -1, pszFileName, nSize);
        pszFileName[nSize - 1] = 0;
    }
    else if (strstr(cszFileName_alias, s_rhythm_alias) == cszFileName_alias) {
    //リズム音源のパス(C:\RHYTHM_DIR\filename)
        const char* filename = cszFileName_alias + _countof(s_rhythm_alias) - 1;
        wchar_t szFileName[MAX_PATH];
        MultiByteToWideChar(CP_ACP, 0, filename, -1, szFileName, _countof(szFileName));
        szFileName[_countof(szFileName) - 1] = 0;
        wcsncpy_s(pszFileName, nSize, m_szRhythmPath, _TRUNCATE);//本来のリズム音源のパス
        wcsncat_s(pszFileName, nSize, szFileName, _TRUNCATE);
    }
    else if (strstr(cszFileName_alias, s_pcmdir_alias) == cszFileName_alias) {
    //PCM 検索パス (C:\PCM_DIRxx\filename)(xx が m_pszPcmDirs の index)
        const char *filename = cszFileName_alias + _countof(s_pcmdir_alias) - 1;
        int index = atoi(filename);//xx\filename
        if(0 <= index && index < FMPMD_MAX_PCMDIR && m_pszPcmDirs[index]){
            filename += 3;//xx\ == 3文字
            wchar_t szFileName[MAX_PATH];
            MultiByteToWideChar(CP_ACP, 0, filename, -1, szFileName, nSize);
            szFileName[_countof(szFileName) - 1] = 0;
            wcsncpy_s(pszFileName, nSize, m_pszPcmDirs[index], _TRUNCATE);
            wcsncat_s(pszFileName, nSize, szFileName, _TRUNCATE);
        }
    }
}
//////////////////////////////////////////////////////////////////////////////
FMPMD_FileIO::CACHE* __fastcall FMPMD_FileIO::GetCache(const char* filename_alias)
{
    int i;
    for(i = 0; i < m_nCache; i++){
        if(strcmp(filename_alias, m_pCache[i].szFileNameA_alias) == 0){
            return &m_pCache[i];
        }
    }
    return NULL;
}
//////////////////////////////////////////////////////////////////////////////
void   __fastcall FMPMD_FileIO::AddCache(IKpiFile *pFile, const char* filename_aliasA)
{
    //return;
    int i;
    for(i = 0; i < m_nCache; i++){
        if(m_pCache[i].pFile == pFile){
            return;
        }
    }
    m_pCache = (CACHE*)realloc(m_pCache, sizeof(CACHE)*(m_nCache+1));
    m_pCache[m_nCache].pFile = pFile;
    strncpy_s(m_pCache[m_nCache].szFileNameA_alias, filename_aliasA, _TRUNCATE);
    m_nCache++;
    pFile->AddRef();
}
//////////////////////////////////////////////////////////////////////////////
