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

#include "kbFmp7_player.h"
#include "kbFmp7_config.h"

#define FMAP_NAME_SUFFIX "{E3EF8724 - 6BCC - 4F46 - 9BAD - C8DA5EDA321D}" //FILEMAP_DATA の仕様が変わったら名前を変える
const wchar_t *KbFmp7Player::s_kbfmp7_filemap_name = L"kbfmp7_filemap_" FMAP_NAME_SUFFIX;
KbFmp7Player::FILEMAP_DATA* KbFmp7Player::s_pFileMapData = NULL;
HANDLE KbFmp7Player::s_hFileMap = NULL;

KbFmp7Player::KbFmp7Player(IKpiConfig* pConfig)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_pConfig = pConfig;
    pConfig->AddRef();
    m_cszFileName = NULL;
    m_pFile = NULL;
    m_pFolder = NULL;
    m_pFunc = GetFMP7Instance();
    m_pWork = NULL;
    m_nGlobalWorkSize = 0;
    m_hFileMap = NULL;
    m_hMutex = NULL;
    m_ppPwiFiles = NULL;
    m_nPwiFiles = 0;
    m_bRepeat = false;
    m_bFading = false;
    m_dwTime = 0;
    Initialize();
    if(s_pFileMapData){
        s_pFileMapData->dwInstanceCount++;
    }
}
///////////////////////////////////////////////////////////////////////////////
KbFmp7Player::~KbFmp7Player(void)
{
    if(m_pWork){
        UnmapViewOfFile(m_pWork);
    }
    if(m_hFileMap){
        CloseHandle(m_hFileMap);
    }
    if(m_hMutex){
        CloseHandle(m_hMutex);
    }
    kbfmp7_Settings settings;
    kbfmp7_GetSettings(m_pConfig, &settings);
    m_pConfig->Release();

    if(m_ppPwiFiles){
        for(int i = m_nPwiFiles - 1; i >= 0; i--){
            m_ppPwiFiles[i]->Release();
        }
        free(m_ppPwiFiles);
    }
    if (m_pFile)m_pFile->Release();
    if (m_pFolder)m_pFolder->Release();
    if (s_pFileMapData) {
        s_pFileMapData->dwInstanceCount--;
    }
    switch(settings.nCloseFMP7){
        //case 0 break: 
        case 1: TerminateFMP7(); break;//閉じる度に終了
        //case 2: break;
        default: break;
    }
}
///////////////////////////////////////////////////////////////////////////////
DWORD KbFmp7Player::Open(const KPI_MEDIAINFO* cpRequest, IKpiFile* pFile, IKpiFolder* pFolder)
{
    if(!ExecFmp7()){
        return 0;
    }
    const wchar_t *cszFileName;
    if(!pFile->GetRealFileW(&cszFileName)){
        return 0;
    }
    m_cszFileName = cszFileName;
    m_pFile = pFile;
    pFile->AddRef();
    m_pFolder = pFolder;
    if (pFolder)pFolder->AddRef();//pFolder は NULL の可能性がある
    GetPwiFiles();//pwi ファイル抽出(アーカイブ対応)
    if(s_pFileMapData && s_pFileMapData->dwInstanceCount > 1){
        s_pFileMapData->dwReopenTime = GetTickCount();
        if(s_pFileMapData->dwCountNow == 0){
            FMP32_GLOBALWORK* pWork = GetWork();
            s_pFileMapData->dwCountNow = pWork->CountNow;
            ReleaseWork();
        }
    }
    wchar_t szFileName[MAX_PATH];//MusicLoad の引数にそのまま渡すことが出来ないのでコピーする
    wcsncpy_s(szFileName, m_cszFileName, _TRUNCATE);
    if(!m_pFunc->MusicLoad(szFileName)){
        return 0;
    }
    if(cpRequest){//cpRequest==NULL の場合はタグ取得用に開く(非NULLの場合は曲長の取得が必要)
        m_pFunc->MusicPlay();//一度再生しないと曲長を取得出来ない(グローバルワークの内容が前回再生時のものになる)
        Stop();
    }
    FMP32_GLOBALWORK* pWork = GetWork();
    m_MediaInfo.qwLength = (UINT64)pWork->Count *100000;
    m_MediaInfo.dwCount = 1;
    ReleaseWork();
    return m_MediaInfo.dwCount;
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbFmp7Player::Select(DWORD dwNumber,
    const KPI_MEDIAINFO** ppMediaInfo,
    IKpiTagInfo* pTagInfo,
    DWORD dwTagGetFlags)
{
    if (ppMediaInfo) {
        *ppMediaInfo = NULL;
    }
    if (dwNumber > m_MediaInfo.dwCount) {
        return 0;
    }
    GetTagInfo(pTagInfo);
    if (!ppMediaInfo) {
        return 1;
    }

    *ppMediaInfo = &m_MediaInfo;

    return 1;
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbFmp7Player::SetRepeat(BOOL bRepeat)
{
    m_bRepeat = bRepeat != 0;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbFmp7Player::Play(DWORD dwStartPos, BOOL bVisible)
{
    wchar_t szFileName[MAX_PATH];
    szFileName[0] = 0;
    m_pFunc->GetPlayFilename(szFileName, _countof(szFileName));
    if(_wcsicmp(szFileName, m_cszFileName) != 0){
        wcsncpy_s(szFileName, m_cszFileName, _TRUNCATE);//MusicLoad に m_cszFileName をそのまま渡すことが出来ない
        m_pFunc->MusicLoad(szFileName);
    }
    if(m_dwTime){
        while(GetTickCount() - m_dwTime < 200) {//少し待ってから再生開始する
            Sleep(1);
        }
        m_dwTime = 0;
    }
    if(GetStatus() == KPI_STATUS_PLAYING){
        m_pFunc->SetSeek(dwStartPos / 10);
    }
    else{
        m_pFunc->MusicPlay();
        if(dwStartPos > 0){
            Sleep(10); // ある程度待ってからでないとSetSeekが機能しないが待ち過ぎると冒頭の音が鳴ってしまう
            m_pFunc->SetSeek(dwStartPos / 10);
        }
    }
    if(s_pFileMapData){
        s_pFileMapData->dwReopenTime = 0;
    }
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbFmp7Player::Pause(void)
{
    m_pFunc->MusicPause();
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbFmp7Player::Restart(void)
{
    m_pFunc->MusicPause();
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbFmp7Player::Stop(void)
{//演奏停止
 //別のインスタンスが曲データを再生していたら停止しない
 //本体がクロスフェードしようとしているので先に曲データを開いていた本インスタンス
 //で停止してしまうと後に開いた方が停止されてしまう
    wchar_t szFileName[MAX_PATH];
    szFileName[0] = 0;
    m_pFunc->GetPlayFilename(szFileName, _countof(szFileName));
    if(_wcsicmp(szFileName, m_cszFileName) != 0){//別のインスタンスが曲データを再生中
        return;//何もしない(クロスフェードで自分は先に再生していた方)
    }
    //m_pFunc->MusicFadeOut(1);
    m_pFunc->MusicStop();
    m_dwTime = GetTickCount();//Stop したらある程度経過してからでないと MusicPlay に失敗する
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbFmp7Player::GetPosition(void)
{
    DWORD ret = 0;
    FMP32_GLOBALWORK* pWork = GetWork();
    bool bFade = false;
    ret = pWork->PlayTime * 10;
    if (!m_bRepeat &&
        (pWork->Status & FMP32_STAT_LOOP)/* &&
        (pWork->Status & FMP32_STAT_FADE) == 0*/) {//FMP32_STAT_FADE は機能していない?
        bFade = true;
    }
    ReleaseWork();
    if (bFade && !m_bFading) {
        m_pFunc->MusicFadeOut(1);
        m_bFading = true;
    }
    return ret;
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbFmp7Player::GetStatus(void)
{
    DWORD ret = KPI_STATUS_STOP;
    bool bFade = false;
    FMP32_GLOBALWORK *pWork = GetWork();
    if(pWork->Status & FMP32_STAT_PLAY){
        ret = KPI_STATUS_PLAYING;
    }
    else if(pWork->Status & FMP32_STAT_PAUSE){
        ret = KPI_STATUS_PAUSE;
    }
    if (!m_bRepeat &&
        (pWork->Status & FMP32_STAT_LOOP)/* &&
        (pWork->Status & FMP32_STAT_FADE) == 0*/) {//FMP32_STAT_FADE は機能していない?
        bFade = true;
    }
    ReleaseWork();
    if (bFade && !m_bFading) {
        m_pFunc->MusicFadeOut(1);
        m_bFading = true;
    }
    if(s_pFileMapData){
        if(s_pFileMapData->dwInstanceCount > 1){//再生中に別の曲データの情報が取得された
            ret = KPI_STATUS_PAUSE;
        }
        else if (s_pFileMapData->dwReopenTime > 0) {
            ret = KPI_STATUS_PAUSE;
            const DWORD dwWaitTime = 500;//最後に別データを開いてからdwWaitTime経過したら再開
            if(GetTickCount() - s_pFileMapData->dwReopenTime > dwWaitTime){
                s_pFileMapData->dwReopenTime = 0;
                Play(s_pFileMapData->dwCountNow*10, FALSE);
                s_pFileMapData->dwCountNow = 0;

            }
        }
    }
    return ret;
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbFmp7Player::SetTempo(int Tempo)
{
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbFmp7Player::SetVolume(int Volume)
{
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbFmp7Player::UpdateConfig(void* pvReserved)
{
    return 0;
}
///////////////////////////////////////////////////////////////////////////////
bool __fastcall KbFmp7Player::ExecFmp7(void)
{//FMP7.exe を起動
    if (s_pFileMapData) {
    //FMP7 を終了中だったら終了するまで待つ(最大5秒)
        DWORD time = GetTickCount();
        while (s_pFileMapData->dwTerminating) {
            Sleep(1);
            if(GetTickCount() - time > 5000)break;
        }
    }
    HWND hWnd = FindWindow(L"FMP7", NULL);
    if(!hWnd){//まだ起動していない
        kbfmp7_Settings settings;
        kbfmp7_GetSettings(m_pConfig, &settings);
        if(!settings.szFMP7Path[0]){
            return false;
        }
        PROCESS_INFORMATION pi = {0};
        STARTUPINFO s = { 0 };
        s.cb = sizeof(s);
        s.dwFlags = STARTF_USESHOWWINDOW;
        s.wShowWindow = SW_SHOWDEFAULT;
        //s.wShowWindow = SW_HIDE;
        //FMP7.exe の起動
        CreateProcess(settings.szFMP7Path,
            NULL,//szCmdLine,
            NULL,
            NULL,
            FALSE,
            0,//CREATE_SUSPENDED,//dwCreationFlags
            NULL,
            NULL,
            &s,
            &pi);
        if (!pi.hProcess) {//起動に失敗
            return false;
        }
        WaitForInputIdle(pi.hProcess, 1000);
    }
    if (m_pWork) {
        UnmapViewOfFile(m_pWork);m_pWork = NULL;
    }
    if (m_hFileMap) {
        CloseHandle(m_hFileMap);m_hFileMap = NULL;
    }
    if (m_hMutex) {
        CloseHandle(m_hMutex);m_hMutex = NULL;
    }
    //FMP7.exe 起動後、しばらく待たないと OpenFileMapping に失敗する
    //本来は RegisterWindowMessage(FMP32::MESSAGE::REGISTER_STRING) して
    //FMP32::MESSAGE::START_FMP が来るのを待つべきかも
    HANDLE hFileMap = NULL;
    DWORD time = GetTickCount();
    do{
        hFileMap = OpenFileMapping(FILE_MAP_READ, FALSE, FMP32_KEY_MAP);
        if(hFileMap)break;
        Sleep(1);
    }while(GetTickCount() - time < 5000);//5秒待って駄目だったら諦める
    if(!hFileMap){
        return false;
    }
    // 共有メモリmutex取得
    HANDLE hMutex = OpenMutex(SYNCHRONIZE, FALSE, FMP32_KEY_MUTEX);
    if (hMutex == NULL){
        CloseHandle(hFileMap);
        return false;
    }
    if (!m_pFunc->GetWorkSize(&m_nGlobalWorkSize, NULL)) {
        CloseHandle(hFileMap);
        CloseHandle(hMutex);
        return false;
    }
    if(m_nGlobalWorkSize < sizeof(FMP32_GLOBALWORK)){//小さすぎる
        CloseHandle(hFileMap);
        CloseHandle(hMutex);
        return false;
    }
    m_pWork = (FMP32_GLOBALWORK*)MapViewOfFile(hFileMap,
        FILE_MAP_READ,
        0,
        0,
        m_nGlobalWorkSize/* + (SizePart + SizeEx) * FMP32_MAX_PART*/);
    if(!m_pWork){
        CloseHandle(hFileMap);
        CloseHandle(hMutex);
        return false;
    }
    m_hFileMap = hFileMap;
    m_hMutex = hMutex;
    return true;
}
///////////////////////////////////////////////////////////////////////////////
FMP32_GLOBALWORK* __fastcall KbFmp7Player::GetWork(void)
{//グローバルワークを取得、不要になったら ReleaseWork を呼ぶ
    WaitForSingleObject(m_hMutex, INFINITE);
    return m_pWork;
}
///////////////////////////////////////////////////////////////////////////////
void __fastcall KbFmp7Player::ReleaseWork(void)
{
    ReleaseMutex(m_hMutex);
}
///////////////////////////////////////////////////////////////////////////////
void __fastcall KbFmp7Player::GetTagInfo(IKpiTagInfo* pTagInfo)
{
    if(!pTagInfo)return;
    wchar_t sz[256];
    sz[0] = 0;
    if(m_pFunc->GetMusicTitle(sz, _countof(sz)) > 0){
        pTagInfo->wSetValueW(L"Title", -1, sz, -1);
    }
    sz[0] = 0;
    if(m_pFunc->GetMusicComposer(sz, _countof(sz)) > 0){
        pTagInfo->wSetValueW(L"Artist", -1, sz, -1);
    }
    sz[0] = 0;
    if (m_pFunc->GetMusicCreator(sz, _countof(sz)) > 0) {
        pTagInfo->wSetValueW(L"Creator", -1, sz, -1);
    }
    sz[0] = 0;
    if (m_pFunc->GetMusicComment(sz, _countof(sz)) > 0) {
        pTagInfo->wSetValueW(L"Comment", -1, sz, -1);
    }
}
///////////////////////////////////////////////////////////////////////////////
void __fastcall KbFmp7Player::GetPwiFiles(void)
{// 曲データと同じフォルダにある pwi ファイルを取得する(アーカイブ対応に必要)
 // 曲データから必要な pwi ファイル名を取得する方法が分からない為、列挙して見つかったもの
 // 全て実ファイルを作る(最大10個までとしておく)
    if(!m_pFolder)return;
    const int MAX_PWI = 10;
    DWORD i = 0;
    for(i = 0; m_nPwiFiles < MAX_PWI; i++){
        wchar_t szName[MAX_PATH];
        if(!m_pFolder->EnumFiles(i, szName, sizeof(szName), 0)){
            break;
        }
        const wchar_t *ext = wcsrchr(szName, L'.');
        if(!ext || _wcsicmp(ext, L".pwi") != 0){
            continue;
        }
        IKpiFile *pPwiFile = NULL;
        if(!m_pFolder->OpenFile(szName, &pPwiFile)){
            continue;
        }
        //実ファイルを作る
        const wchar_t *cszPwiFileName = NULL;
        if(!pPwiFile->GetRealFileW(&cszPwiFileName)){
            pPwiFile->Release();//失敗
            continue;
        }
        m_ppPwiFiles = (IKpiFile**)realloc(m_ppPwiFiles, sizeof(IKpiFile*)*(m_nPwiFiles+1));
        m_ppPwiFiles[m_nPwiFiles++] = pPwiFile;
    }
}
///////////////////////////////////////////////////////////////////////////////
void __fastcall KbFmp7Player::TerminateFMP7(void)
{//FMP7 を終了する(強制終了はしない)
    if (!s_pFileMapData)return;
    if (s_pFileMapData->dwInstanceCount == 0) {
        HWND hWnd = FindWindow(L"FMP7", NULL);
        if (hWnd) {
            s_pFileMapData->dwTerminating++;
            DWORD dwProcessId = 0;
            GetWindowThreadProcessId(hWnd, &dwProcessId);
            HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, dwProcessId);
            SendMessage(hWnd, WM_CLOSE, 0, 0);
            if (hProcess) {
                WaitForSingleObject(hProcess, 5000);
                CloseHandle(hProcess);
            }
            s_pFileMapData->dwTerminating--;
        }
    }
}
///////////////////////////////////////////////////////////////////////////////
void KbFmp7Player::Initialize(void)
{
    if(s_pFileMapData)return;
    HANDLE hFileMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, s_kbfmp7_filemap_name);
    bool bOpen = false;
    if (!hFileMap) {
        hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, sizeof(FILEMAP_DATA), s_kbfmp7_filemap_name);
    
    }
    else{
        bOpen = true;
    }
    if (hFileMap) {
        s_pFileMapData = (FILEMAP_DATA*)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(FILEMAP_DATA));
        if (!s_pFileMapData) {
            CloseHandle(hFileMap);
            return;
        }
        s_hFileMap = hFileMap;
    }
}
///////////////////////////////////////////////////////////////////////////////
void KbFmp7Player::Uinitialize(IKpiConfig *pConfig)
{
    kbfmp7_Settings settings;
    kbfmp7_GetSettings(pConfig, &settings);
    if(settings.nCloseFMP7 != 2){
        TerminateFMP7();
    }
    if(!s_pFileMapData)return;
    UnmapViewOfFile(s_pFileMapData);
    s_pFileMapData = NULL;
    CloseHandle(s_hFileMap);
    s_hFileMap = NULL;
}
///////////////////////////////////////////////////////////////////////////////
