#include "stdafx.h"
#include <windows.h>
#include <wchar.h>
#include "NSFDialogs.h"

#include "kbnsfplug_decoder.h"
#include "kbnsfplug_thread.h"
#include "kbnsfplug_wa2.h"

extern HINSTANCE g_hKpi;//KMP_STATUS を得る為に必要

KbNsfplugUIThread* KbNsfplugDecoder::s_pThread = NULL;//必要になるまで作らない
NSFDialogManager* KbNsfplugDecoder::s_pManager = NULL;//必要になるまで作らない
NSFPlayerConfigMem* KbNsfplugDecoder::s_pNsfConfig = NULL;//再生時に必ず必要
NSFplug_Model KbNsfplugDecoder::s_Model = {0};//s_Model.cf を s_pNsfConfig にする
bool KbNsfplugDecoder::s_bResetConfig = false;
bool KbNsfplugDecoder::s_bResetPos = false;
//////////////////////////////////////////////////////////////////////////
KbNsfplugDecoder::KbNsfplugDecoder(IKpiConfig *pConfig)
{
    KbNsfplugDecoder::Initialize(pConfig);
    kpi_InitMediaInfo(&m_MediaInfo);
    m_pConfig = pConfig;
    pConfig->AddRef();
    m_pNsfPlayer = new xgm::NSFPlayer();
    m_pNsf = new xgm::NSF();
    m_pNsfPlayer->SetConfig(s_pNsfConfig);
    m_pManager = NULL;
    ZeroMemory(&m_Model, sizeof(m_Model));
    m_pWa2 = NULL;
    m_qwCurPos = 0;
    m_bTrack = false;
    m_bMemory = false;
    m_bInfo = false;
    m_bUpdated = false;
    m_hFileMap = NULL;
    m_pStatus = NULL;
}
//////////////////////////////////////////////////////////////////////////
KbNsfplugDecoder::~KbNsfplugDecoder(void)
{
    if(m_pManager){
        s_pThread->DeleteManager(m_pManager);
    }
    if(m_pStatus){
        UnmapViewOfFile(m_pStatus);
    }
    if(m_hFileMap){
        CloseHandle(m_hFileMap);
    }
    delete m_pNsfPlayer;
    delete m_pNsf;
    delete m_pWa2;
    m_pConfig->Release();
    #if 0
    if(!s_pManager)return;

    CWnd *pWnds[] = {
        s_pManager->config, s_pManager->easy, s_pManager->mixer, s_pManager->panner, s_pManager->mask, s_pManager->preset};
    bool bVisibleFound = false;
    for(int i = 0; i < _countof(pWnds); i++){
        if(!pWnds[i])continue;
        if(pWnds[i]->IsWindowVisible()){
            bVisibleFound = true;
            break;
        }
    }
    if(!bVisibleFound){//全て閉じられている
        s_pThread->DeleteManager(s_pManager);
        s_pManager = NULL;
    }
    #endif
}
//////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbNsfplugDecoder::Open(const KPI_MEDIAINFO *cpRequest, 
                                        IKpiFile   *pFile, 
                                        IKpiFolder *pFolder,
                                        IKpiConfig *pConfig)
{
    DWORD dwSampleRate = cpRequest ? cpRequest->dwSampleRate : 0;
    if(dwSampleRate == 0){//プラグインのデフォルト
        dwSampleRate = (*s_pNsfConfig)["RATE"];
    }
    else if(dwSampleRate < 8000){
        dwSampleRate = 8000;
    }
    else if(dwSampleRate > 192000){
        dwSampleRate = 192000;
    }
    const BYTE *buf = NULL;
    size_t buf_size = 0;
    if(!pFile->GetBuffer(&buf, &buf_size)){
        return 0;
    }
    if(!m_pNsf->Load((xgm::UINT8*)buf, buf_size)){//Load/LoadNSFe の実装を見る限り buf の内容を書き換えることはない
        return 0;
    }
    //以後、buf はもう参照されることがないので pFile を AddRef しておく必要はない
    if(!m_pNsfPlayer->Load(m_pNsf)){
        return 0;
    }
    //プロパティ表示の為に m_pNsf::filename をセットしておく
    wchar_t szFileName[MAX_PATH];
    pFile->GetFileName(szFileName, sizeof(szFileName));
    //NSF::filename は UTF8
    WideCharToMultiByte(CP_UTF8, 0, szFileName, -1, m_pNsf->filename, _countof(m_pNsf->filename), NULL, NULL);
    m_pNsf->filename[_countof(m_pNsf->filename)-1] = 0;
    
    bool bFastSeek = (*s_pNsfConfig)["FAST_SEEK"] != 0;
    m_MediaInfo.dwSeekableFlags = bFastSeek ? KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE : 0;
    m_MediaInfo.dwUnitSample = 0;
    m_MediaInfo.dwSampleRate = dwSampleRate;
    m_MediaInfo.dwChannels = 2;
    m_MediaInfo.nBitsPerSample = -64;//16;
    m_MediaInfo.dwCount = m_pNsf->GetSongNum();
    //m_MediaInfo.qwLength = 0; //Length/Loop/FadeOut は Select で設定
    //m_MediaInfo.qwLoop = 0;   //
    //m_MediaInfo.qwFadeOut = 0;//
    CreateStatusFileMap();//ここで呼んでおかないといけない
    return m_MediaInfo.dwCount;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbNsfplugDecoder::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;
    m_pNsfPlayer->SetSong(dwNumber-1);//dwNumber は 1ベース、SetSong は 0 ベース
    GetTagInfo(pTagInfo, dwTagGetFlags);
    if (!ppMediaInfo) {
    //タイトル/アーティスト取得のみ
        return dwNumber;
    }
    m_pNsfPlayer->SetPlayFreq(m_MediaInfo.dwSampleRate);
    m_pNsfPlayer->SetChannels(m_MediaInfo.dwChannels);
    
    UINT8 *plist = m_pNsf->nsfe_plst;
    int number;
    if(m_pNsf->nsfe_plst_size == m_MediaInfo.dwCount){
        number = m_pNsf->nsfe_plst[m_MediaInfo.dwNumber-1];
    }
    else{
        number = m_MediaInfo.dwNumber - 1;
    }
    NSFE_Entry *entry = &m_pNsf->nsfe_entry[number];
    if(entry->time < 0){
        m_MediaInfo.qwLength = -1;
        m_MediaInfo.qwFadeOut = -1;
    }
    else{
        m_MediaInfo.qwLength = (UINT64)entry->time * 10000;
        if(entry->fade >= 0){
            m_MediaInfo.qwFadeOut = (UINT64)entry->fade * 10000;
        }
        else{
            m_MediaInfo.qwFadeOut = -1;
        }
    }
    m_MediaInfo.qwLoop = -1;//曲に依らずループ時間不明(演奏の打ち切りは本体に任せる)
    *ppMediaInfo = &m_MediaInfo;
    m_qwCurPos = -1;//初回のシーク時に必ず Reset が呼ばれるようにする(どんな位置が指定されても位置を戻すと見なされる)
    return m_MediaInfo.dwNumber;
}
//////////////////////////////////////////////////////////////////////////
static UINT64 _Skip(xgm::NSFPlayer *pNsfPlayer, UINT64 qwSkip, DWORD dwSampleRate)
{//シーク先が遠いと時間がかかるので一定時間経過したら諦める
 //dwWaitMax(ms)以内にシーク出来るところまでとする
    const DWORD dwWaitMax = 5000;//最大5秒まで
    const DWORD dwStartTime = GetTickCount();
    const DWORD dwUnit = dwSampleRate * 60;//1分ごとにスキップする
    UINT64 qwRet = 0;
    do{
        DWORD skip;
        if(qwRet + dwUnit < qwSkip){
            skip = dwUnit;
        }
        else{
            skip = qwSkip - qwRet;
        }
        DWORD ret = pNsfPlayer->Skip(skip);
        qwRet += ret;
        if(ret < skip){
            break;
        }
        if(GetTickCount() - dwStartTime > dwWaitMax)break;//dwWaitMax(ms)以内に終わらなかったら諦める
    }while(qwRet < qwSkip);
    return qwRet;
}
//////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbNsfplugDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{//シーク（戻り値はシーク後の再生位置）
    (*s_pNsfConfig)["PLAY_ADVANCE"] = 1;////常に infinite Loop(設定が変わっても元に戻す)
    UpdateConfig(NULL);//m_bTrack/m_bMemory/m_bInfo が更新されていれば m_bUpdated が true にセットされる
    if(m_qwCurPos <= qwPosSample){//現在位置より先へのシーク
        //m_qwCurPos += m_pNsfPlayer->Skip(qwPosSample - m_qwCurPos);
        m_qwCurPos += _Skip(m_pNsfPlayer, qwPosSample - m_qwCurPos, m_MediaInfo.dwSampleRate);
        return m_qwCurPos;
    }
    else{//位置を戻す
        m_pNsfPlayer->Reset();
        //m_qwCurPos = m_pNsfPlayer->Skip(qwPosSample);
        m_qwCurPos = _Skip(m_pNsfPlayer, qwPosSample, m_MediaInfo.dwSampleRate);
    }
    return m_qwCurPos;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbNsfplugDecoder::Render(BYTE* pBuffer, DWORD dwSizeSample)
{
    CheckUpdated();
    //DWORD dwRet = m_pNsfPlayer->Render((xgm::INT16*)pBuffer, dwSizeSample);
    DWORD dwRet = m_pNsfPlayer->Render((double*)pBuffer, dwSizeSample);
    m_qwCurPos += dwRet;
    return dwRet;
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbNsfplugDecoder::GetTagInfo(IKpiTagInfo *pTagInfo, DWORD dwTagGetFlags)
{
    pTagInfo->GetTagInfo(NULL, NULL, KPI_TAGTYPE_NONE, 0);//本体のタグ取得ルーチンを呼ばない
    //return;//↑を呼んで何もしないで戻るとタイトルが取得されないが、呼ばないと取得するのを確認出来る
    //https://vgmrips.net/wiki/NSF_File_Format には ASCII Format としか書かれていない
    //https://www.nesdev.org/wiki/NSF  を見る限り NSF は Shift-JIS で良さそう
    //https://www.nesdev.org/wiki/NSFe には UTF-8 を推奨と書いてある
    //
    //NSF::title 等は UTF-8 で格納される模様
    int number;
    if(m_pNsf->nsfe_plst_size == m_MediaInfo.dwCount){
        number = m_pNsf->nsfe_plst[m_MediaInfo.dwNumber-1];
    }
    else{
        number = m_MediaInfo.dwNumber - 1;
    }
    NSFE_Entry *entry = &m_pNsf->nsfe_entry[number];
    //曲番号ごとにタイトルやアーティスト名があった場合は曲番号に依らないタイトルとアーティスト
    //についてはそれぞれゲーム名、アルバムアーティストとして通知する
    if(entry->tlbl[0]){
        pTagInfo->wSetValueU8(L"title", -1, entry->tlbl, -1);
        pTagInfo->wSetValueU8(L"game", -1, m_pNsf->title, -1);//ゲーム名として通知
    }
    else{
        pTagInfo->wSetValueU8(L"title", -1, m_pNsf->title, -1);
    }
    if(entry->taut[0]){//見たことがない
        pTagInfo->wSetValueU8(L"artist", -1, entry->taut, -1);
        pTagInfo->wSetValueU8(L"albumartist", -1, m_pNsf->artist, -1);//アルバムアーティストとして通知
    }
    else{
        pTagInfo->wSetValueU8(L"artist", -1, m_pNsf->artist, -1);
    }
    if(m_pNsf->copyright&&m_pNsf->copyright[0])pTagInfo->wSetValueU8(L"copyright", -1, m_pNsf->copyright, -1);
    if(m_pNsf->ripper&&m_pNsf->ripper[0])pTagInfo->wSetValueU8(L"ripper", -1, m_pNsf->ripper, -1);//nsfe only
    if(m_pNsf->text&&m_pNsf->text[0])pTagInfo->wSetValueU8(L"comment", -1, m_pNsf->text, -1);     //nsfe only
}
/////////////////////////////////////////////////////////////////////////////
int KbNsfplugDecoder::GetOutputTime()
{
    if(m_pStatus && m_pStatus->dwAccepted){
        return m_pStatus->dwPosition;
    }
    return m_qwCurPos * 1000 / m_MediaInfo.dwSampleRate;
}
/////////////////////////////////////////////////////////////////////////////
DWORD  WINAPI KbNsfplugDecoder::UpdateConfig(void *pvReserved)
{//m_bTrack/m_bMemory/m_bInfo の設定が更新されていれば m_bUpdated = true にセット
 //シーク時にも呼び出す
    kbnsfplug_Settings settings;
    kbnsfplug_GetSettings(m_pConfig, &settings);
    if(m_bTrack  != settings.bTrack ||
       m_bMemory != settings.bMemory ||
       m_bInfo   != settings.bInfo){
        m_bUpdated = true;
        m_bTrack = settings.bTrack;
        m_bMemory = settings.bMemory;
        m_bInfo = settings.bInfo;
    }
    else if(m_pManager){//ダイアログが閉じられていた場合に再表示されるようにする
        if((m_bTrack  && !m_pManager->track->IsWindowVisible()) ||
           (m_bMemory && !m_pManager->memory->IsWindowVisible()) ||
           (m_bInfo   && !m_pManager->info->IsWindowVisible())){
            m_bUpdated = true;
        }
    }
    return 0;
}
/////////////////////////////////////////////////////////////////////////////
void __fastcall KbNsfplugDecoder::CheckUpdated(void)
{
    if(!m_bUpdated)return;
    m_bUpdated = false;
    if(!m_pManager && (m_bTrack || m_bMemory || m_bInfo)){
        CreateUIThread(m_pConfig);
        m_Model.cf = s_pNsfConfig;
        m_Model.sdat = m_pNsf;
        m_Model.pl = m_pNsfPlayer;
        m_Model.no_save_config = false;
        m_pManager = s_pThread->CreateManager(&m_Model, 0);
        m_pWa2 = new WA2InputModule2(this, s_pNsfConfig, m_pNsf);
        m_pManager->SetWA2InputModule(m_pWa2);
        //CreateStatusFileMap();//IKpiDecoderModule::Open から戻る前に呼ぶ必要がある
    }
    if(!m_pManager)return;
    if(m_bTrack){
        s_pThread->OpenDialog(m_pManager, NSFplug_UI::DLG_TRACK);
        s_pThread->StartTrack(m_pManager);
    }
    else{
        s_pThread->HideDialog(m_pManager, NSFplug_UI::DLG_TRACK);
    }
    if(m_bMemory){
        s_pThread->OpenDialog(m_pManager, NSFplug_UI::DLG_MEMORY);
        s_pThread->StartMemory(m_pManager);
    }
    else{
        s_pThread->HideDialog(m_pManager, NSFplug_UI::DLG_MEMORY);
    }
    if(m_bInfo){
        s_pThread->OpenDialog(m_pManager, NSFplug_UI::DLG_INFO);
        s_pThread->SetInfoData(m_pManager, m_pNsf);
    }
    else{
        s_pThread->HideDialog(m_pManager, NSFplug_UI::DLG_INFO);
    }
}
/////////////////////////////////////////////////////////////////////////////
void __fastcall KbNsfplugDecoder::CreateStatusFileMap(void)
{//本体の再生位置を取得する非公式の機能
 //元々は kbwinamp + in_bm.dll 用
 //再生位置取得用のファイルマップオブジェクトを 「IKpiDecoderModule::Open から戻る前に」作成しておく
 //本体は IKpiDecoderModule::Open が返した IKpiDecoder* のインスタンスポインタを元に
 //ファイルマップオブジェクトを開く
 //ファイルマップ名 = kmp_status_filemapping_(process id)_(dll instance)_(addres of IKpiDecoder* or HKMP)
 //dll instance=プラグインDLL の HINSTANCE
 //address of IKpiDecoder* : 旧仕様のプラグインでは KMPMDULE::Open が返す HKMP
 //
 //Open から戻ってから(Select/Seek/Render で)作成するのでは遅いことに注意
    if(m_hFileMap)return;
    wchar_t szFileMapName[128];
    IKpiDecoder *pThis = this;
#ifdef _WIN64 //64bit 版
    _snwprintf_s(szFileMapName, _TRUNCATE,
                 L"kmp_status_filemapping_%lu_%llu_%llu",
                 GetCurrentProcessId(),
                 (UINT64)g_hKpi,
                 (UINT64)pThis);
#else //32bit 版
    _snwprintf_s(szFileMapName, _TRUNCATE,
                 L"kmp_status_filemapping_%lu_%lu_%lu",
                 GetCurrentProcessId(),
                 (DWORD)g_hKpi,
                 (DWORD)pThis);
#endif
    HANDLE hFileMap = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL,
                        PAGE_READWRITE, 0, sizeof(KMP_STATUS), szFileMapName);
    if(!hFileMap){
        return;
    }
    void *pData = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(KMP_STATUS));
    if(!pData){
        CloseHandle(hFileMap);
        return;
    }
    m_hFileMap = hFileMap;
    m_pStatus = (KMP_STATUS*)pData;
    m_pStatus->dwStructSize = sizeof(KMP_STATUS);//プラグイン側でセットしておかなければならない
    m_pStatus->dwAccepted = 0;//本体によって1がセットされる(されなければ m_pStatus は使用不可)
    m_pStatus->dwPosition = 0;//本体によって再生位置(ms)がセットされる
    m_pStatus->dwStatus = 0;  //本体によって再生状態(KPI_STATUS_STOP/PLAYING/PAUSE)がセットされる
}
//////////////////////////////////////////////////////////////////////////
void KbNsfplugDecoder::Initialize(IKpiConfig *pConfig)
{
    if(s_pNsfConfig)return;
    s_pNsfConfig = new NSFPlayerConfigMem;
    s_Model.cf = s_pNsfConfig;
    
    char *pszIniData;
    kbnsfplug_GetIniData(pConfig, &pszIniData);
    if(pszIniData){
        s_pNsfConfig->LoadFromMem(pszIniData);
        free(pszIniData);
    }
    s_pNsfConfig->CreateValue("WRITE_TAGINFO", 0);
    s_pNsfConfig->CreateValue("READ_TAGINFO", 0);
    s_pNsfConfig->CreateValue("UPDATE_PLAYLIST", 0);
    s_pNsfConfig->CreateValue("MASK_INIT", 1);
    s_pNsfConfig->CreateValue("INFO_DELAY", 50);
    s_pNsfConfig->CreateValue("INFO_FREQ", 30);
    s_pNsfConfig->CreateValue("GRAPHIC_MODE", 1);
    s_pNsfConfig->CreateValue("FREQ_MODE", 1);
    s_pNsfConfig->CreateValue("LAST_PRESET", "Default");
}
//////////////////////////////////////////////////////////////////////////
void KbNsfplugDecoder::UnInitialize(IKpiConfig *pConfig)
{
    if(s_pNsfConfig){
        if(!s_bResetConfig){
            char *pszIniData;
            s_pNsfConfig->SaveToMem(&pszIniData);
            kbnsfplug_SaveIniData(pConfig, pszIniData);
            free(pszIniData);
        }
        else{
            kbnsfplug_SaveIniData(pConfig, "");
        }
    }
    if(s_bResetPos){
        kbnsfplug_ResetDialogPos(pConfig);
    }
    if(s_pThread){
        if(s_pManager){
            s_pThread->DeleteManager(s_pManager);
            s_pManager = NULL;
        }
        delete s_pThread;
        s_pThread = NULL;
    }
    delete s_pNsfConfig; s_pNsfConfig = NULL;
}
/////////////////////////////////////////////////////////////////////////////
void KbNsfplugDecoder::CreateUIThread(IKpiConfig *pConfig)
{
    if(s_pThread)return;
    s_pThread = new KbNsfplugUIThread(pConfig);
}
//////////////////////////////////////////////////////////////////////////
void KbNsfplugDecoder::OpenDialog(IKpiConfig *pConfig, int id)
{
    Initialize(pConfig);
    CreateUIThread(pConfig);
    if(!s_pManager){
        s_pManager = s_pThread->CreateManager(&s_Model, 1);
    }
    s_pThread->OpenDialog(s_pManager, id);
}
//////////////////////////////////////////////////////////////////////////
void KbNsfplugDecoder::ResetConfig(void)
{
    s_bResetConfig = true;
}
//////////////////////////////////////////////////////////////////////////
void KbNsfplugDecoder::ResetPos(void)
{
    s_bResetPos = true;
    KbNsfplugUIThread::ResetPos();
}
/////////////////////////////////////////////////////////////////////////////
