#include <windows.h>
#include <initguid.h>
#include "kbdmusic_player.h"

///////////////////////////////////////////////////////////////////////////////
static bool __fastcall kbFileExists(const wchar_t *cszFileNameName)
{
    DWORD dwCode = GetFileAttributesW(cszFileNameName);
    if (dwCode != 0xffffffff) {
        if (!(FILE_ATTRIBUTE_DIRECTORY & dwCode)) {
            return true;
        }
    }
    return false;
}
///////////////////////////////////////////////////////////////////////////////
// DirectMusic
//
// IDirectMusicLoader::SetSearchDirectory は
// １つしか検索ディレクトリを指定出来ない
//
//・SGT ファイルから参照ファイルを抽出する
//・参照ファイルがすべて SGT と同じフォルダに存在するならば、
//  ・SetSearchDirectory で指定するフォルダは SGT と同じフォルダ
//・参照ファイルの一部が SGT と同じフォルダにあり、DLS フォルダには
//  参照ファイルが含まれない場合は
//  ・SetSearchDirectory で指定するフォルダは SGT と同じフォルダ
//・参照ファイルの一部が SGT と同じフォルダにあり、DLS フォルダにも
//  参照ファイルが含まれる場合は
//  ・SetSearchDirectory で指定するフォルダはテンポラリフォルダに設定し、
//    テンポラリフォルダにすべての参照ファイルをコピー
//・参照ファイルが SGT と同じフォルダにはなく、DLS フォルダにすべての
//  参照ファイルが含まれる場合は
//  ・SetSearchDirectory で指定するフォルダは DLS フォルダ
///////////////////////////////////////////////////////////////////////////////
KbDMusicPlayer::KbDMusicPlayer(IKpiConfig *pConfig)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_szFileName[0] = 0;
    m_rtStartTime = 0;
    m_pDML = NULL;
    m_pDMP = NULL;
    m_pSeg = NULL;
    m_pCollection = NULL;
    m_hEvent = NULL;
    m_bRepeat = false;
    m_dwStatus = KPI_STATUS_STOP;
    m_dwPausedPos = 0;
    m_pFile = NULL;
    m_pConfig = pConfig;
    pConfig->AddRef();
}
///////////////////////////////////////////////////////////////////////////////
KbDMusicPlayer::~KbDMusicPlayer(void)
{
    if (m_pDMP) {
        Stop();
        m_pDMP->SetNotificationHandle(0, 0);
        m_pDMP->CloseDown();
    }
    if (m_pCollection) {
        m_pCollection->Release();
        m_pCollection = NULL;
    }
    if (m_pSeg) {
        m_pSeg->Release();
        m_pSeg = NULL;
    }
    if (m_pDMP) {
        m_pDMP->Release();
        m_pDMP = NULL;
    }
    if (m_pDML) {
        m_pDML->CollectGarbage();
        m_pDML->ClearCache(GUID_DirectMusicAllTypes);

        m_pDML->Release();
        m_pDML = NULL;
    }
    if (m_hEvent) {
        CloseHandle(m_hEvent);
        m_hEvent = NULL;
    }
    if(m_pFile){
        m_pFile->Release();
    }
    for(int i = m_DlsFiles.size() - 1; i >= 0; i--){
        m_DlsFiles[i]->Release();
    }
    m_dwPausedPos = 0;
    m_pConfig->Release();
}
///////////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbDMusicPlayer::Open(IKpiFile *pFile, IKpiFolder *pFolder)
{
    //開く
    const wchar_t *cszFileName;
    if(!pFile->GetRealFileW(&cszFileName)){
        return FALSE;
    }
    if(FAILED(CoCreateInstance(CLSID_DirectMusicLoader,
                               NULL,CLSCTX_INPROC,
                               IID_IDirectMusicLoader8,
                               (void **)&m_pDML))){
        return FALSE;
    }
    if(FAILED(CoCreateInstance(CLSID_DirectMusicPerformance,
                               NULL,CLSCTX_INPROC,
                               IID_IDirectMusicPerformance8,
                               (void **)&m_pDMP))){
        m_pDML->Release();
        m_pDML = NULL;
        return FALSE;
    }
    // パフォーマンスを初期化
    //DWORD dwTime = timeGetTime();
    LARGE_INTEGER liStart, liEnd, liFreq;
    QueryPerformanceFrequency(&liFreq);
    QueryPerformanceCounter(&liStart);
    if(FAILED(m_pDMP->InitAudio(NULL,
                                NULL,
                                NULL,
                                DMUS_APATH_SHARED_STEREOPLUSREVERB,
                                128,//64,
                                DMUS_AUDIOF_ALL,
                                NULL))){
        m_pDMP->Release();
        m_pDMP = NULL;
        m_pDML->Release();
        m_pDML = NULL;
        return FALSE;
    }
    //Sleep(3000);
    REFERENCE_TIME rtStart;
    //rtStart - rtEnd==曲の長さ
    //InitAudio 呼び出しから GetTime 呼び出しまでの時間によって
    //長さが微妙に変化する
    //誤算の最大値は InitAudio 呼び出し前から GetTime 呼び出し後までの経過時間
    m_pDMP->GetTime(&rtStart, NULL);
    QueryPerformanceCounter(&liEnd);

    REFERENCE_TIME rtElapsed = (liEnd.QuadPart - liStart.QuadPart)*10000000ui64/liFreq.QuadPart;
    //DWORD dwElapsed = timeGetTime() - dwTime;
    rtStart -= rtElapsed;//dwElapsed * 10000ui64;
    if(rtStart < 0){
        rtStart = 0;
    }
    //DLS の検索ディレクトリを設定
    //参照ファイル(*.dls など)を抽出
    wchar_t szPath[MAX_PATH];
    wcsncpy_s(szPath, cszFileName, _TRUNCATE);
    wchar_t *path_delimiter = wcsrchr(szPath, L'\\');
    if(path_delimiter){
        path_delimiter[1] = 0;
    }
    std::vector<std::wstring> files;
    GetRefferenceFiles(pFile, &files);
    const int Dls_Max = 16;
    const wchar_t *pszDlsInFolders[Dls_Max];
    int dls_count = 0;
    int i;
    for(i = 0; i < Dls_Max; i++){
        pszDlsInFolders[i] = NULL;
    }
    wchar_t szDlsFolder[MAX_PATH];
    m_pConfig->GetStr(L"General", L"DlsPath", szDlsFolder, sizeof(szDlsFolder), L"");
    if(szDlsFolder[0]){
        int len = wcslen(szDlsFolder);
        if(len > 0 && szDlsFolder[len-1] != '\\'){
            wcsncat_s(szDlsFolder, L"\\", _TRUNCATE);
        }
    }
    BOOL bInSgtFolder = FALSE;
    const int count = files.size();
    for(int i = 0; i < count; i++){
        IKpiFile *pFileDls = NULL;
        if(pFolder->OpenFile(files[i].c_str(), &pFileDls)){
            const wchar_t *cszDlsFile;
            if(pFileDls->GetRealFileW(&cszDlsFile)){
                bInSgtFolder = TRUE;
                pszDlsInFolders[dls_count] = files[i].c_str();
                dls_count++;
                if(dls_count >= Dls_Max)break;
            }
            m_DlsFiles.push_back(pFileDls);
        }
        else if(szDlsFolder[0]){
            wchar_t szTmp[MAX_PATH];
            wcsncpy_s(szTmp, szDlsFolder, _TRUNCATE);
            wcsncat_s(szTmp, files[i].c_str(), _TRUNCATE);
            if(kbFileExists(szTmp)){
                pszDlsInFolders[dls_count] = files[i].c_str();
                dls_count++;
                if(dls_count >= Dls_Max)break;
            }
        }
    }
    m_pDML->SetSearchDirectory(GUID_DirectMusicAllTypes, szPath, TRUE);
    //if(bInSgtFolder){
    //    m_pDML->SetSearchDirectory(GUID_DirectMusicAllTypes, szPath, TRUE);
    //}
    if(FAILED(m_pDML->LoadObjectFromFile(CLSID_DirectMusicSegment,
                                         IID_IDirectMusicSegment8,
                                         (WCHAR*)cszFileName,
                                         (void **)&m_pSeg))){
        m_pDMP->Release();
        m_pDMP = NULL;
        m_pDML->Release();
        m_pDML = NULL;
        return FALSE;
    }
    //Sleep(100);
    do{//DLS を DLS フォルダから読み込む
        HRESULT hr;
        DMUS_OBJECTDESC desc;
        wchar_t *pDlsFile = NULL;
        for(i = -1; i < dls_count; i++){
            wchar_t szTmp[MAX_PATH];
            if(i >= 0){
                const wchar_t *p = pszDlsInFolders[i];
                if(!p)break;
                wcsncpy_s(szTmp, szDlsFolder, _TRUNCATE);
                wcsncat_s(szTmp, p, _TRUNCATE);
                if(!kbFileExists(szTmp)){
                    continue;
                }
                pDlsFile = szTmp;
            }
            else{
                pDlsFile = NULL;
            }
            IDirectMusicCollection *pDLSCollection = NULL;
            ZeroMemory(&desc, sizeof(DMUS_OBJECTDESC));
            desc.dwSize    = sizeof(DMUS_OBJECTDESC);
            desc.guidClass = CLSID_DirectMusicCollection;
            if(pDlsFile == NULL){
                desc.guidObject  = GUID_DefaultGMCollection;
                desc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_OBJECT;
            }
            else{
                wcsncpy_s(desc.wszFileName, pDlsFile, _TRUNCATE);
                desc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME | DMUS_OBJ_FULLPATH;
            }
            hr = m_pDML->GetObject(&desc , IID_IDirectMusicCollection , (void**)&pDLSCollection );
            if(SUCCEEDED(hr)){
                hr = m_pSeg->SetParam(GUID_ConnectToDLSCollection,
                                       0xFFFFFFFF, DMUS_SEG_ALLTRACKS, 0,
                                       (void*)pDLSCollection);
                //hr = m_lpSeg->SetParam(GUID_StandardMIDIFile,
                //                    0xFFFFFFFF, DMUS_SEG_ALLTRACKS, 0,0);
            }
            //hr = m_lpSeg->SetParam(GUID_StandardMIDIFile,
            //                    0xFFFFFFFF, DMUS_SEG_ALLTRACKS, 0,0);
            if(pDLSCollection){
                pDLSCollection->Release();
            }
            //m_lpCollection = pDLSCollection;
        }
    }while(0);
    //m_lpSeg->SetParam(GUID_StandardMIDIFile,
    //                    0xFFFFFFFF, DMUS_SEG_ALLTRACKS, 0,0);
    //if(pszzFiles){
    //    free(pszzFiles);
    //}
    MUSIC_TIME mt;
    REFERENCE_TIME rtEnd;
    //REFERENCE_TIME rtStart;
    // パフォーマンスにダウンロード
    m_pSeg->GetLength(&mt);
    //m_pDMP->GetTime(&rtStart, NULL);
    m_pDMP->MusicToReferenceTime(mt, &rtEnd);
    
    //m_pSeg->Download(m_pDMP);
    //m_MediaInfo.qwLength = (UINT64)mt  * 10000;//rtEnd - rtStart;
    m_MediaInfo.qwLength = rtEnd - rtStart;
    m_MediaInfo.dwCount = 1;
    m_MediaInfo.dwNumber = 1;
    m_MediaInfo.dwFormatType = KPI_MEDIAINFO::FORMAT_PLAYER;
    m_pFile = pFile;
    pFile->AddRef();
    return true;
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbDMusicPlayer::Select(DWORD dwNumber, 
                                    const KPI_MEDIAINFO **ppMediaInfo,
                                    IKpiTagInfo *pTagInfo,
                                    DWORD dwTagGetFlags)
{
    if(ppMediaInfo){
        *ppMediaInfo = NULL;
    }
    if(dwNumber > m_MediaInfo.dwCount){
        return 0;
    }
    if(!ppMediaInfo){
        return 1;
    }
    *ppMediaInfo = &m_MediaInfo;
    return 1;
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbDMusicPlayer::SetRepeat(BOOL bRepeat)
{
    m_bRepeat = bRepeat;
    if (m_pSeg) {
        if (bRepeat) {
            m_pSeg->SetRepeats(DMUS_SEG_REPEAT_INFINITE);
        }
        else {
            m_pSeg->SetRepeats(0);
        }
    }
}
///////////////////////////////////////////////////////////////////////////////
static const BYTE* __fastcall memfind(const void* seek, const BYTE* src,
                                      DWORD dwSeekSize, size_t stSrcSize)
{
    if (dwSeekSize > stSrcSize){
        return NULL;
    }
    const BYTE* found = src;
    while (memcmp(found, seek, dwSeekSize) != 0) {
        found++;
        if (found - src >= (stSrcSize - dwSeekSize)){
            return NULL;
        }
    }
    return found;
}
///////////////////////////////////////////////////////////////////////////////
void __fastcall KbDMusicPlayer::GetRefferenceFiles(IKpiFile *pFile,
                                              std::vector<std::wstring> *pFiles)
{
    const BYTE *data = NULL;
    size_t stBufSize = 0;
    if(!pFile->GetBuffer(&data, &stBufSize)){
        return;
    }
    std::vector<std::wstring> &files = *pFiles;
    const BYTE *p_dmrf = memfind("DMRFrefh", data, 8, stBufSize);
    if(!p_dmrf){
        return;
    }
    size_t pos = p_dmrf - data;
    if(pos < 8){//LISTxxxxDMRFrefhxxxxguid....
        return;
    }
    while(p_dmrf){
        pos = (p_dmrf-data);
        if(memcmp(p_dmrf-8, "LIST", 4) != 0){
            p_dmrf = memfind("DMRFrefh", p_dmrf, 8, stBufSize - pos);
            continue;
        }
        DWORD dwChunkSize = *(DWORD*)(p_dmrf-4);
        if((UINT64)pos + dwChunkSize >= stBufSize){
            break;
        }
        p_dmrf += 8;
        pos += 12;
        dwChunkSize -= 12;
        const BYTE *p_file = memfind("file", p_dmrf, 4, dwChunkSize);
        if(!p_file){
            p_dmrf = memfind("DMRFrefh", p_dmrf, 8, stBufSize - pos);
            continue;
        }
        p_file += 4;
        dwChunkSize = *(DWORD*)p_file;
        p_file += 4;
        pos = p_file - data;
        if((UINT64)pos + dwChunkSize > stBufSize){
            break;
        }
        wchar_t tmp[MAX_PATH];
        if(dwChunkSize >= sizeof(tmp)){
            break;
        }
        memcpy(tmp, p_file, dwChunkSize);
        tmp[dwChunkSize/2] = 0;
        const int count = files.size();
        for(int i = 0; i < count; i++){
           if(_wcsicmp(files[i].c_str(), tmp) == 0){
              tmp[0] = 0;
              break;
           }
        }
        if(tmp[0]){
            files.push_back(tmp);
        }
        p_file += dwChunkSize;
        pos += dwChunkSize;
        p_dmrf = memfind("DMRFrefh", p_file, 8, stBufSize - pos);
    }
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbDMusicPlayer::GetPosition(void)
{
    if(m_pDMP && m_hEvent){
        REFERENCE_TIME rt = 0;
        m_pDMP->GetTime(&rt, NULL);
        DWORD dwElapsed = (rt - m_rtStartTime) / 10000;
        return dwElapsed;
    }
    else{
        return m_dwPausedPos;
    }
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbDMusicPlayer::Play(DWORD dwStartPos, BOOL bVisible)
{
    if (dwStartPos != 0 && GetStatus() == KPI_STATUS_PLAYING) {
        return 0;
    }
    Stop();
    // 再生
    Sleep(100);
    m_pSeg->Download(m_pDMP);
    m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    GUID guid = GUID_NOTIFICATION_SEGMENT;
    m_pDMP->SetNotificationHandle(m_hEvent, 10000i64*0xFFFFFFFF/*2500*10000*/);
    m_pDMP->AddNotificationType(guid);
    m_pDMP->GetTime(&m_rtStartTime, NULL);
    //IDirectMusicSegmentState *pState;
    if (m_bRepeat) {
        m_pSeg->SetRepeats(DMUS_SEG_REPEAT_INFINITE);
    }
    else {
        m_pSeg->SetRepeats(0);
    }
    m_pDMP->PlaySegmentEx(m_pSeg, 
                          NULL, 
                          NULL, 
                          0, 
                          0, 
                          NULL,//&pState, 
                          NULL, NULL);
    //if (pState) {
    //    pState->GetStartPoint(&m_mtStartTime);
    //    pState->Release();
    //}
    while(WaitForSingleObject(m_hEvent, INFINITE) == WAIT_OBJECT_0){
        DMUS_NOTIFICATION_PMSG *pmsg = NULL;
        m_pDMP->GetNotificationPMsg(&pmsg);
        DWORD dwNotificationOption = pmsg->dwNotificationOption;
        m_pDMP->FreePMsg((DMUS_PMSG *)pmsg);
        if (dwNotificationOption == DMUS_NOTIFICATION_SEGSTART) {
            m_dwStatus = KPI_STATUS_PLAYING;
            break;
        }
        if (dwNotificationOption == DMUS_NOTIFICATION_SEGABORT ||
            dwNotificationOption == DMUS_NOTIFICATION_SEGEND) {
            m_dwStatus = KPI_STATUS_STOP;
            break;
        }
    }
    m_dwStatus = KPI_STATUS_PLAYING;
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbDMusicPlayer::Pause(void)
{
    if (m_pDMP) {
        DWORD dwPausedPos = GetPosition();
        Stop();
        m_dwStatus = KPI_STATUS_PAUSE;
        m_dwPausedPos = dwPausedPos;
    }
    return true;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbDMusicPlayer::Restart(void)
{
    if (GetStatus() == KPI_STATUS_PAUSE) {
        Play(0, 0);
   }
   return true;
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbDMusicPlayer::Stop(void)
{
    if (m_hEvent) {
        m_pDMP->Stop(NULL, NULL, 0, 0);
        WaitForSingleObject(m_hEvent, INFINITE);
        m_pDMP->SetNotificationHandle(0, 0);
        CloseHandle(m_hEvent);
        m_hEvent = NULL;
    }
    m_rtStartTime = 0;
    m_dwStatus = KPI_STATUS_STOP;
    m_dwPausedPos = 0;
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbDMusicPlayer::GetStatus(void)
{
    if(WaitForSingleObject(m_hEvent, 0) == WAIT_OBJECT_0){
        DMUS_NOTIFICATION_PMSG *pmsg = NULL;
        m_pDMP->GetNotificationPMsg(&pmsg);
        switch(pmsg->dwNotificationOption){
        case DMUS_NOTIFICATION_SEGEND:
        case DMUS_NOTIFICATION_SEGABORT:{
            m_dwStatus = KPI_STATUS_STOP;
            m_pDMP->SetNotificationHandle(0, 0);
            CloseHandle(m_hEvent);
            m_hEvent = NULL;
            break;
            }
        }
        m_pDMP->FreePMsg((DMUS_PMSG *)pmsg);
    }
    return m_dwStatus;
}
///////////////////////////////////////////////////////////////////////////////
