#include <windows.h>
#include <stdlib.h>
#include <wchar.h>
#include <process.h>
#include <mbstring.h>
#include "kbfpd_decoder.h"
#include "kbfpd_config.h"
#include "../../../kpi_common/ringbuf.h"
#include "../../../kpi_common/apihijack.h"
#include "fpv.h"
#include "resource.h"

#ifndef _DEBUG
#define DEBUG_OUT(X)
#else
#define DEBUG_OUT(X) OutputDebugString(X)
#endif

#define DETECT_HUNGUP_FPD98DLL
#ifdef DETECT_HUNGUP_FPD98DLL
static BOOL g_bAbnormalTerminate = FALSE;
#endif

static KbFpdDecoder *g_pActive = NULL;
extern HINSTANCE g_hKpi;
static HANDLE    g_hHeap;
static CRITICAL_SECTION g_cs;

static KbFpvSearcher *g_pFpvSearcher;

static CRITICAL_SECTION g_csFpdHook;

//スレッドの終了待ちにバグがあるのを回避
static const int MAX_FPD_THREADS = 8;        //実際には4個で十分
static HANDLE  g_hFpdThreads[MAX_FPD_THREADS];
static DWORD   g_dwFpdThreadIds[MAX_FPD_THREADS];
static int     g_nFpdThreads;
//イベントオブジェクトの閉じ忘れを回避
static const int MAX_FPD_EVENTS = 6;         //実際には3個で十分
static HANDLE  g_hFpdEvents[MAX_FPD_EVENTS];
static int     g_nFpdEvents;
//DeleteCriticalSection し忘れを回避
static CRITICAL_SECTION *g_pcsFpd;
static BOOL      g_bcsFpdDelete = TRUE;
//
static char      g_szWndClassName[256];//fpd98.dll からフックしたクラス名
static HINSTANCE g_hInstance;
static HANDLE    g_hEventDone;     //書き込みが完了したらシグナル
static HANDLE    g_hEventRequest;  //書き込みを要求されたらシグナル
static HANDLE    g_hEventTerminate;//waveOutReset/waveOutClose が呼ばれたらシグナル

static int g_nWaveOutWrite;//waveOutWrite が呼ばれた回数

struct MSGBOX_PARAM{
    const char *cszText;
    const char *cszCaption;
};
static BOOL CALLBACK MsgBoxProc(HWND hwndDlg,
                                UINT uMsg,
                                WPARAM wParam,
                                LPARAM lParam);
static BOOL CALLBACK MsgBoxProc(HWND hwndDlg,
                                UINT uMsg,
                                WPARAM wParam,
                                LPARAM lParam)
{
    switch(uMsg){
        case WM_INITDIALOG:{
            MSGBOX_PARAM *pParam = (MSGBOX_PARAM*)lParam;
            SetWindowText(hwndDlg, pParam->cszCaption);
            SetDlgItemText(hwndDlg, IDC_STATIC_MSG, pParam->cszText);
            SetWindowPos(hwndDlg, HWND_TOPMOST, 0, 0, 0, 0,
                        SWP_NOMOVE|SWP_NOSIZE);
            SetTimer(hwndDlg, 1, 5000, NULL);
            return TRUE;
        }
        case WM_COMMAND:{
            if(HIWORD(wParam)==BN_CLICKED){
                PostMessage(hwndDlg, WM_CLOSE, 0, 0);
            }
            return TRUE;
        }
        case WM_CLOSE:{
            KillTimer(hwndDlg, 1);
            //DestroyWindow(hwndDlg);
            EndDialog(hwndDlg, 0);
            return TRUE;
        }
        case WM_TIMER:{
            PostMessage(hwndDlg, WM_CLOSE, 0, 0);
            return TRUE;
        }
        default:
            break;
    }
    return FALSE ;
}
static inline void fpd_Lock(void)
{
    EnterCriticalSection(&g_cs);
}
static inline void fpd_Unlock(void)
{
    LeaveCriticalSection(&g_cs);
}

class KbWaveOut
{
private:
    KbRingBuffer m_rb;
    enum{
        MAX_WAVEHDR_COUNT = 4 //実際には 2 個で十分
    };
    LPWAVEHDR    m_pWaveHdrs[MAX_WAVEHDR_COUNT];
    int          m_nWaveHdrsCount;
public:
    HWND         m_hWnd;
    void      __fastcall AddWavHdr(LPWAVEHDR pWaveHdr);
    LPWAVEHDR __fastcall RemoveWavHdr(void);
    int   __fastcall GetWaveHdrs(void){return m_nWaveHdrsCount;}
    void  __fastcall Close(void){SetEvent(g_hEventDone);}
    void  __fastcall Write(const BYTE* buf, DWORD dwSize){m_rb.Write(buf, dwSize);}
    DWORD __fastcall Read(BYTE* buf, DWORD dwSize){return m_rb.Read(buf, dwSize);}
    DWORD __fastcall GetWritten(void){return m_rb.GetWritten();}
    KbWaveOut(void);
    ~KbWaveOut(void);
};

static KbWaveOut *g_wo = NULL;

KbWaveOut::KbWaveOut(void)
{
    m_hWnd = NULL;
    m_nWaveHdrsCount = 0;
    ZeroMemory(m_pWaveHdrs, sizeof(m_pWaveHdrs));
}
KbWaveOut::~KbWaveOut(void)
{
}
void __fastcall KbWaveOut::AddWavHdr(LPWAVEHDR pWaveHdr)
{
    fpd_Lock();
    if(++m_nWaveHdrsCount <= MAX_WAVEHDR_COUNT){
        m_pWaveHdrs[m_nWaveHdrsCount-1] = pWaveHdr;
    }
    fpd_Unlock();
}
LPWAVEHDR __fastcall KbWaveOut::RemoveWavHdr(void)
{
    LPWAVEHDR ret = NULL;
    fpd_Lock();
    if(m_nWaveHdrsCount > 0){
        ret = m_pWaveHdrs[0];
        m_nWaveHdrsCount--;
        int i;
        for(i = 0; i < m_nWaveHdrsCount; i++){
            m_pWaveHdrs[i] = m_pWaveHdrs[i+1];
        }
    }
    fpd_Unlock();
    return ret;
}
//winmm.dll
static MMRESULT WINAPI hook_waveOutOpen(LPHWAVEOUT phwo,
                                        UINT uDeviceID,
                                        LPCWAVEFORMATEX pwfx,
                                        DWORD dwCallback,
                                        DWORD dwCallbackInstance,
                                        DWORD fdwOpen);
static MMRESULT WINAPI hook_waveOutClose(HWAVEOUT hwo);
static MMRESULT WINAPI hook_waveOutWrite(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh);
static MMRESULT WINAPI hook_waveOutReset(HWAVEOUT hwo);
static MMRESULT WINAPI hook_waveOutPrepareHeader(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh);
static MMRESULT WINAPI hook_waveOutUnprepareHeader(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh);
static MMRESULT WINAPI hook_waveOutPause(HWAVEOUT hwo);
static MMRESULT WINAPI hook_waveOutRestart(HWAVEOUT hwo);
//user32.dll
static int WINAPI hook_MessageBox(HWND hWnd,
                                  LPCSTR lpText,
                                  LPCSTR lpCaption,
                                  UINT uType);
static ATOM WINAPI hook_RegisterClass(CONST WNDCLASS *lpwcx);
static BOOL WINAPI hook_UnregisterClass(LPCSTR lpClassName, HINSTANCE hInstance);
//kernel32.dll
static HANDLE WINAPI hook_CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
                                       DWORD dwStackSize,
                                       LPTHREAD_START_ROUTINE lpStartAddress,
                                       LPVOID lpParameter,
                                       DWORD dwCreationFlags,
                                       LPDWORD lpThreadId);
static BOOL   WINAPI hook_CloseHandle(HANDLE hObject);
static HANDLE WINAPI hook_CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,
                                      BOOL bManualReset,
                                      BOOL bInitialState,
                                      LPCSTR lpName);
static VOID WINAPI hook_InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
static VOID WINAPI hook_DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
static HANDLE WINAPI hook_CreateFileA(
                        LPCSTR lpFileName,
                        DWORD dwDesiredAccess,
                        DWORD dwShareMode,
                        LPSECURITY_ATTRIBUTES lpSecurityAttributes,
                        DWORD dwCreationDisposition,
                        DWORD dwFlagsAndAttributes,
                        HANDLE hTemplateFile);

static HANDLE WINAPI hook_CreateFileA(
                        LPCSTR lpFileName,
                        DWORD dwDesiredAccess,
                        DWORD dwShareMode,
                        LPSECURITY_ATTRIBUTES lpSecurityAttributes,
                        DWORD dwCreationDisposition,
                        DWORD dwFlagsAndAttributes,
                        HANDLE hTemplateFile)
{
    HANDLE hFile = CreateFile(lpFileName,
                              dwDesiredAccess,
                              FILE_SHARE_READ|FILE_SHARE_WRITE,//dwShareMode,
                              lpSecurityAttributes,
                              dwCreationDisposition,
                              dwFlagsAndAttributes,
                              hTemplateFile);
    if(hFile == INVALID_HANDLE_VALUE &&
       ((dwDesiredAccess & GENERIC_WRITE) == 0)){
        hFile = g_pActive->OpenFpvFile(lpFileName);
    }
    return hFile;
}

static HANDLE WINAPI hook_GetProcessHeap(VOID)
{
    return g_hHeap;
}
static HGLOBAL WINAPI hook_GlobalAlloc(UINT uFlags, DWORD dwBytes)
{
    return ::HeapAlloc(g_hHeap, 0, dwBytes);
}
static HGLOBAL WINAPI hook_GlobalFree(HGLOBAL hMem)
{
    if(hMem){
        ::HeapFree(g_hHeap, 0, hMem);
    }
    return NULL;
}
static LPVOID WINAPI hook_GlobalLock(HGLOBAL hMem)
{
    return (LPVOID)hMem;
}
static BOOL WINAPI hook_GlobalUnlock(HGLOBAL hMem)
{
    return TRUE;
}

static HWND WINAPI hook_CreateWindowEx(
    DWORD dwExStyle, // extended window style
    LPCSTR lpClassName, // pointer to registered class name
    LPCSTR lpWindowName, // pointer to window name
    DWORD dwStyle, // window style
    int x, // horizontal position of window
    int y, // vertical position of window
    int nWidth, // window width
    int nHeight, // window height
    HWND hWndParent, // handle to parent or owner window
    HMENU hMenu, // handle to menu, or child-window identifier
    HINSTANCE hInstance, // handle to application instance
    LPVOID lpParam // pointer to window-creation data
);
static BOOL WINAPI hook_DestroyWindow(HWND hWnd);

static SDLLHook WINMMHook =
{
    "WINMM.DLL",
    {
        {"waveOutOpen",            hook_waveOutOpen},
        {"waveOutClose",           hook_waveOutClose},
        {"waveOutWrite",           hook_waveOutWrite},
        {"waveOutReset",           hook_waveOutReset},
        {"waveOutPrepareHeader",   hook_waveOutPrepareHeader},
        {"waveOutUnprepareHeader", hook_waveOutUnprepareHeader},
        {"waveOutPause",           hook_waveOutPause},
        {"waveOutRestart",         hook_waveOutRestart},
        {NULL, NULL}
    }
};
static SDLLHook USER32Hook =
{
    "USER32.DLL",
    {
        {"MessageBoxA", hook_MessageBox},
        {"RegisterClassA", hook_RegisterClass},
        {"UnregisterClassA", hook_UnregisterClass},
        {"CreateWindowExA", hook_CreateWindowEx},
        {NULL, NULL}
    }
};
static SDLLHook Kernel32Hook =
{
    "kernel32.dll",
    {
        {"CreateThread",              hook_CreateThread},
        {"CreateEventA",              hook_CreateEvent},
        {"CloseHandle",               hook_CloseHandle},
        {"InitializeCriticalSection", hook_InitializeCriticalSection},
        {"DeleteCriticalSection",     hook_DeleteCriticalSection},
        {"CreateFileA",               hook_CreateFileA},
        {NULL, NULL}
    }
};

static HWND WINAPI hook_CreateWindowEx(
    DWORD dwExStyle,      // extended window style
    LPCSTR lpClassName,  // pointer to registered class name
    LPCSTR lpWindowName, // pointer to window name
    DWORD dwStyle,        // window style
    int x,                // horizontal position of window
    int y,                // vertical position of window
    int nWidth,           // window width
    int nHeight,          // window height
    HWND hWndParent,      // handle to parent or owner window
    HMENU hMenu,          // handle to menu, or child-window identifier
    HINSTANCE hInstance,  // handle to application instance
    LPVOID lpParam        // pointer to window-creation data
    )
{
    HWND hWnd = CreateWindowEx(dwExStyle,
                               lpClassName,
                               lpWindowName,
                               dwStyle,
                               x,y,nWidth,nHeight,hWndParent,hMenu,hInstance,lpParam);
    if(g_hEventDone){
        SetEvent(g_hEventDone);
    }
    return hWnd;
}
static BOOL WINAPI hook_DestroyWindow(HWND hWnd)
{
    return ::DestroyWindow(hWnd);
}

static HANDLE WINAPI hook_CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
                                       DWORD dwStackSize,
                                       LPTHREAD_START_ROUTINE lpStartAddress,
                                       LPVOID lpParameter,
                                       DWORD dwCreationFlags,
                                       LPDWORD lpThreadId)
{
    EnterCriticalSection(&g_csFpdHook);
    DWORD dwThreadId = 0;

    HANDLE hRet = ::CreateThread(lpThreadAttributes,
                                 dwStackSize,
                                 lpStartAddress,
                                 lpParameter,
                                 CREATE_SUSPENDED, //dwCreationFlags,
                                 &dwThreadId);
    if(lpThreadId){
        *lpThreadId = dwThreadId;
    }
    if(g_nFpdThreads < MAX_FPD_THREADS){
        g_hFpdThreads[g_nFpdThreads] = hRet;
        g_dwFpdThreadIds[g_nFpdThreads] = dwThreadId;
        g_nFpdThreads++;
    }
    LeaveCriticalSection(&g_csFpdHook);
    if((dwCreationFlags & CREATE_SUSPENDED) == 0){
        ::ResumeThread(hRet);
    }
    return hRet;
}
static BOOL WINAPI hook_CloseHandle(HANDLE hObject)
{
    EnterCriticalSection(&g_csFpdHook);
    int i;
    for(i = 0; i < g_nFpdThreads; i++){
        if(hObject == g_hFpdThreads[i]){
            DEBUG_OUT("hook_CloseHandle\n");
            DWORD dwThreadId = g_dwFpdThreadIds[i];
            g_nFpdThreads--;
            while(i < g_nFpdThreads){
                g_hFpdThreads[i] = g_hFpdThreads[i+1];
                g_dwFpdThreadIds[i] = g_dwFpdThreadIds[i+1];
                i++;
            }
            LeaveCriticalSection(&g_csFpdHook);
            PostThreadMessage(dwThreadId, WM_QUIT, 0, 0);
            WaitForSingleObject(hObject, INFINITE);
            DEBUG_OUT("hook_CloseHandle WaitForSingleObject returned\n");
            return ::CloseHandle(hObject);
        }
    }
    for(i = 0; i < g_nFpdEvents; i++){
        if(hObject == g_hFpdEvents[i]){
        //fpd98.dll のバグにより、実際にはここが呼ばれることはない
            g_nFpdEvents--;
            while(i < g_nFpdEvents){
                g_hFpdEvents[i] = g_hFpdEvents[i+1];
                i++;
            }
            break;
        }
    }
    LeaveCriticalSection(&g_csFpdHook);
    return ::CloseHandle(hObject);
}
static HANDLE WINAPI hook_CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,
                                      BOOL bManualReset,
                                      BOOL bInitialState,
                                      LPCSTR lpName)
{
    EnterCriticalSection(&g_csFpdHook);
    HANDLE hRet = ::CreateEvent(lpEventAttributes, bManualReset, bInitialState, lpName);
    if(g_nFpdEvents < MAX_FPD_EVENTS){
        g_hFpdEvents[g_nFpdEvents++] = hRet;
    }
    LeaveCriticalSection(&g_csFpdHook);
    return hRet;
}

static VOID WINAPI hook_InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
{
    InitializeCriticalSection(lpCriticalSection);
    if(!g_pcsFpd){
        g_pcsFpd = lpCriticalSection;
        g_bcsFpdDelete = FALSE;
    }
}
static VOID WINAPI hook_DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
{
    if(g_pcsFpd == lpCriticalSection){
        if(g_bcsFpdDelete){
            return;
        }
        else{
            g_bcsFpdDelete = TRUE;
        }
    }
    DeleteCriticalSection(lpCriticalSection);
}

//winmm.dll
MMRESULT WINAPI hook_waveOutOpen(LPHWAVEOUT phwo,
                                 UINT uDeviceID,
                                 LPCWAVEFORMATEX pwfx,
                                 DWORD dwCallback,
                                 DWORD dwCallbackInstance,
                                 DWORD fdwOpen)
{
    if(phwo){
        *phwo = NULL;
        if(g_wo){
            return MMSYSERR_ERROR;
        }
        KbWaveOut *wo = NULL;
        switch(fdwOpen){
            case CALLBACK_EVENT:{
                break;
            }
            case CALLBACK_FUNCTION:{
                break;
            }
            case CALLBACK_THREAD:{
                break;
            }
            case CALLBACK_WINDOW:{
                wo = new KbWaveOut;
                wo->m_hWnd = (HWND)dwCallback;
                *phwo = (HWAVEOUT)wo;
                g_wo = wo;
                break;
            }
        }
        if(wo){
            g_nWaveOutWrite = 0;
            ::SendMessage(wo->m_hWnd, WOM_OPEN, (WPARAM)wo, 0);
            return MMSYSERR_NOERROR;
        }
        else{
            return MMSYSERR_ERROR;
        }
    }
    return MMSYSERR_NOERROR;
}
MMRESULT WINAPI hook_waveOutClose(HWAVEOUT hwo)
{
    KbWaveOut *wo = (KbWaveOut*)hwo;
    if(wo){
        DEBUG_OUT("hook_waveOutClose PostMessage WOM_DONE\n");
        PostMessage(wo->m_hWnd, WOM_CLOSE, (WPARAM)wo, 0);
        //SendMessage だとデッドロックする
        DEBUG_OUT("hook_waveOutClose PostMessage WOM_DONE returned.\n");
        wo->Close();
        ::SetEvent(g_hEventTerminate);
    }
    return MMSYSERR_NOERROR;
}
MMRESULT WINAPI hook_waveOutWrite(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh)
{
    KbWaveOut *wo = (KbWaveOut*)hwo;
    if(wo){
        if(++g_nWaveOutWrite > 2){
            HANDLE hHandles[] = {g_hEventTerminate, g_hEventRequest};
            DWORD dwWaitRet = WaitForMultipleObjects(2, hHandles, FALSE, INFINITE);
            if(dwWaitRet == WAIT_OBJECT_0){
                SetEvent(g_hEventDone);
                return MMSYSERR_NOERROR;
            }
        }
        fpd_Lock();
        const BYTE* data = (const BYTE*)(pwh->lpData);
        DWORD dwSize = pwh->dwBufferLength;
        wo->Write(data, dwSize);
        wo->AddWavHdr(pwh);
        if(g_nWaveOutWrite > 2){
            ::SetEvent(g_hEventDone);
        }
        fpd_Unlock();
    }
    return MMSYSERR_NOERROR;
}
MMRESULT WINAPI hook_waveOutReset(HWAVEOUT hwo)
{
    KbWaveOut *wo = (KbWaveOut*)hwo;
    if(wo){
        DEBUG_OUT("hook_waveOutReset\n");
        HWND hWnd = wo->m_hWnd;
        LPWAVEHDR wh = wo->RemoveWavHdr();
        while(hWnd && wh){
            PostMessage(hWnd, WOM_DONE, (WPARAM)wo, (LPARAM)wh);//SendMessage だとデッドロックする
            DEBUG_OUT("hook_waveOutReset PostMessage WOM_DONE returned.\n");
            wh = wo->RemoveWavHdr();
        }
        SetEvent(g_hEventDone);
    }
    return MMSYSERR_NOERROR;
}
MMRESULT WINAPI hook_waveOutPrepareHeader(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh)
{
    if(pwh){
        pwh->dwFlags = WHDR_PREPARED;
    }
    return MMSYSERR_NOERROR;
}
MMRESULT WINAPI hook_waveOutUnprepareHeader(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh)
{
    if(pwh){
        pwh->dwFlags |= WHDR_DONE;
    }
    return MMSYSERR_NOERROR;
}
MMRESULT WINAPI hook_waveOutPause(HWAVEOUT hwo)
{
    return MMSYSERR_NOERROR;
}
MMRESULT WINAPI hook_waveOutRestart(HWAVEOUT hwo)
{
    return MMSYSERR_NOERROR;
}
//user32.dll

int WINAPI hook_MessageBox(HWND hWnd,         // handle of owner window
                           LPCSTR lpText,     // address of text in message box
                           LPCSTR lpCaption,  // address of title of message box
                           UINT uType)        // style of message box
{
#if 0
    DEBUG_OUT( lpText );
    return IDOK;
#else
    /*return ::MessageBox(NULL, lpText, lpCaption,
                    uType | MB_ICONERROR | MB_APPLMODAL|MB_TOPMOST | MB_SETFOREGROUND );
    */
    MessageBeep(MB_ICONERROR);
    MSGBOX_PARAM param;
    param.cszCaption = lpCaption;
    param.cszText = lpText;
    ::DialogBoxParam((HINSTANCE)g_hKpi,
                     (MAKEINTRESOURCE(IDD_MSGBOX)),
                     NULL,//hWnd,
                     MsgBoxProc,
                     (LPARAM)&param);
    return IDOK;
#endif
}
ATOM WINAPI hook_RegisterClass(CONST WNDCLASS *lpwcx)
{
    if(!g_szWndClassName[0]){
        _mbsncpy_s((unsigned char*)g_szWndClassName, _countof(g_szWndClassName),
                   (unsigned char*)lpwcx->lpszClassName, _TRUNCATE);
        g_hInstance = lpwcx->hInstance;
    }
    return ::RegisterClass(lpwcx);
}
BOOL WINAPI hook_UnregisterClass(LPCSTR lpClassName,
                                 HINSTANCE hInstance)
{
    BOOL ret = UnregisterClass(lpClassName, hInstance);
    if(g_szWndClassName[0] && _mbscmp((unsigned char*)g_szWndClassName, (unsigned char*)lpClassName) == 0){
        g_szWndClassName[0] = 0;
        g_hInstance = NULL;
    }
    return ret;
}
unsigned WINAPI KbFpdDecoder::KbFpdThreadProc(LPVOID pParam)
{
    MSG msg;
    PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
    KbFpdDecoder *pFpd = (KbFpdDecoder*)pParam;
    ::ResetEvent(g_hEventDone);
    ::ResetEvent(g_hEventRequest);
    ::ResetEvent(g_hEventTerminate);
    char *pszFileName = pFpd->m_szTempFileName;
    if(!pszFileName[0]){
        pszFileName = pFpd->m_szFileName;
    }
    if(FPDPlay(pszFileName, &pFpd->m_FpdPlay, 0, NULL) != 0){
        FPDPlayCancel();
        return 1;
    }
    SetEvent(pFpd->m_hEvent);
    while(GetMessage(&msg, NULL, 0, 0) > 0){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    DEBUG_OUT("KbFpdThreadProc FPDPlayCancel\n");
    FPDPlayCancel();//ここでフリーズすることがある(回避方法分からず)
    DEBUG_OUT("KbFpdThreadProc FPDPlayCancel returned\n");
    return 0;
}
//////////////////////////////////////////////////////////////////////////
KbFpdDecoder::KbFpdDecoder(IKpiConfig *pConfig)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_szFileName[0] = 0;
    m_szTempFileName[0] = 0;
    m_uThreadId = 0;
    m_hThread = NULL;
    ZeroMemory(&m_FpdPlay, sizeof(m_FpdPlay));
    m_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    m_pFile = NULL;
    m_pFolder = NULL;
    m_ppFpvFiles = NULL;
    m_nFpvFiles = 0;
    m_pConfig = pConfig;
    pConfig->AddRef();
}
//////////////////////////////////////////////////////////////////////////
KbFpdDecoder::~KbFpdDecoder(void)
{
    Close();
    if(m_szTempFileName[0]){
        DeleteFile(m_szTempFileName);
    }
    int i;
    for(i = m_nFpvFiles - 1; i >= 0; i--){
        m_ppFpvFiles[i]->Release();
    }
    if(m_ppFpvFiles){
        free(m_ppFpvFiles);
    }
    if(m_pFile){
        m_pFile->Release();
    }
    if(m_pFolder){
        m_pFolder->Release();
    }
    CloseHandle(m_hEvent);
    m_pConfig->Release();
#ifdef DETECT_HUNGUP_FPD98DLL
    if(!g_bAbnormalTerminate)
#endif
        InterlockedCompareExchangePointer((void**)&g_pActive, NULL, this);
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbFpdDecoder::Terminate(void)
{
    if(m_hThread){
        SetEvent(g_hEventTerminate);
        PostThreadMessage(m_uThreadId, WM_QUIT, 0, 0);
#ifndef DETECT_HUNGUP_FPD98DLL
        WaitForSingleObject(m_hThread, INFINITE);
#else
        //FPDPlayCancel でのハングアップ検出
        //ここでスレッドを強制終了しても、その後本体を巻き込んでハングアップするため
        //プラグイン自体を本体とは別プロセスで動作させる以外に手だてがなさそう
        //その場合、本体が kbrunkpi のハングアップを検出するので以下の処理は不要となる
        const DWORD dwWaitTime = 5000;
        while(1){
            if(WaitForSingleObject(m_hThread, dwWaitTime) == WAIT_TIMEOUT){
                extern DWORD g_dwPlatform;
                int nRet = MessageBoxW(NULL, L"fpd98.dll から応答がありません。強制終了しますか？", L"kbfpd.kpi", MB_YESNO);
                if(nRet == IDYES){
                    TerminateThread(m_hThread, 0);
                    if(g_dwPlatform){//kbrunkpi.exe 経由の場合は自身のプロセスを終了
                        TerminateProcess(GetCurrentProcess(), 0);
                        ExitProcess(0);
                    }
                    //kbrunkpi.exe 経由でない場合は m_hThread を強制終了
                    //(この後本体を巻き込んでハングアップしてしまう)
                    g_bAbnormalTerminate = TRUE;
                    MessageBoxW(NULL, L"強制終了しました。以後、プレイヤーを終了するまで fpd は開けなくなります。\r\n"
                                      L"このまま使用し続けるのは安全ではないため、なるべくプレイヤーを終了して起動し直して下さい。\r\n"
                                      L"正常終了出来ない可能性もありますので、その場合はタスクマネージャで強制終了して下さい。\r\n"
                                      L"頻繁に発生する場合は、設定画面で「常に別プロセスで使用」を有効にして下さい。", L"", MB_OK);
                }
            }
            else{
                break;
            }
        }
#endif
        CloseHandle(m_hThread);
        m_hThread = NULL;
        m_uThreadId = 0;
    }
    if(g_pActive == this){
        delete g_wo;
        g_wo = NULL;
    }
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbFpdDecoder::Close(void)
{
    Terminate();
    m_szFileName[0] = 0;
    ZeroMemory(&m_FpdPlay, sizeof(m_FpdPlay));
}
//////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbFpdDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                    IKpiFile *pFile,
                                    IKpiFolder *pFolder)
{
    if(!cpRequest){
        //タイトルの取得のみ(実際の取得は Select で)
        const char *cszFileName;
        if(!pFile->GetRealFileA(&cszFileName)){
            return 0;
        }
        m_pFile = pFile;
        pFile->AddRef();
        _mbsncpy_s((unsigned char*)m_szFileName, _countof(m_szFileName), (unsigned char*)cszFileName, _TRUNCATE);
        m_MediaInfo.dwCount = 1;
        return 1;
    }
    if(InterlockedCompareExchangePointer((void**)&g_pActive, this, NULL) != NULL){
    //複数のインスタンスを同時に持つことは出来ない
        return 0;
    }
    ZeroMemory(&m_FpdPlay, sizeof(m_FpdPlay));
    const char *cszFileName;
    if(!pFile->GetRealFileA(&cszFileName)){
        return false;
    }
    _mbsncpy_s((unsigned char*)m_szFileName, _countof(m_szFileName), (unsigned char*)cszFileName, _TRUNCATE);
    KbFpd_Settings settings;
    kbfpd_GetSettings(m_pConfig, &settings);

    m_FpdPlay.op_s = settings.op_s;  //再生周波数0:44100Hz 1:33075Hz 2:22050Hz 3:16538Hz 4:11025Hz
    m_FpdPlay.op_v = settings.op_v;  //パン制御1:モノラル2:ステレオ
    m_FpdPlay.op_e = settings.op_e;  //エフェクト 0:なし1:リバーブ 2:リバーブ・コーラス
    m_FpdPlay.op_f = settings.op_f;  //フェードアウト速度    1(小)〜40(大)
    m_FpdPlay.op_b = settings.op_b;  //ビット数   8:8ビット  16:16ビット
    m_FpdPlay.op_l = settings.op_l;  //ループ回数 0〜2147483647 (ただし0は無限回)
    m_FpdPlay.op_se = settings.op_se;//システムエフェクト 0:なし 1:リバーブ 2:リバーブ・コーラス
    m_FpdPlay.reserved = 0;

    UINT64 qwLength = -1;
    UINT64 qwLoop = 0;
    if(FPD98SubGetFpdHeader){//曲長計算
        //FPD98SubManufactMusicMain は対象ファイルの中身を書き換えてしまうので
        //コピーを作成してコピーのファイル名を渡す
        char szTempPath[MAX_PATH*2];
        char szTempFileName[MAX_PATH*2+32];
        szTempPath[0] = 0;
        szTempFileName[0] = 0;
        GetTempPath(_countof(szTempPath), szTempPath);
        GetTempFileName(szTempPath, "fpd", 0, szTempFileName);
        if(szTempFileName[0]){
            FPDHEADER header = {0};
            FPDSUB_MANUFACT_PARAM mp = {0};
            mp.bLoop = TRUE;
            //ループ回数分の曲長を取得
            mp.iLoop = cpRequest->dwLoopCount;//本体からの要求に応じる
            if(mp.iLoop == 0){
                mp.iLoop = settings.op_l;
            }
            mp.iBpm = settings.bpm;
            mp.iTp = settings.tp;
            __try{//bpm/tp の値によっては 0 による除算エラーが発生する
                FPD98SubGetFpdHeader((char*)cszFileName, &header);
                if(settings.bpm || settings.tp){
                    FPD98SUB_AddTranspose((char*)cszFileName, szTempFileName, &header);
                    FPD98SubGetFpdHeader(szTempFileName, &header);
                }
                else{
                    CopyFile(cszFileName, szTempFileName, FALSE);
                    SetFileAttributes(szTempFileName, FILE_ATTRIBUTE_NORMAL);
                }
                FPD98SubManufactMusicMain(szTempFileName, &mp, &header);
                qwLength = (UINT64)mp.iPlayTime*1000*10000;
                ZeroMemory(&mp, sizeof(mp));
                mp.bLoop = TRUE;
                mp.iLoop = cpRequest->dwLoopCount;//本体からの要求に応じる
                if(mp.iLoop == 0){
                    mp.iLoop = settings.op_l;
                }
                mp.iLoop++;//要求+1
                mp.iBpm = settings.bpm;
                mp.iTp = settings.tp;

                FPD98SubGetFpdHeader((char*)cszFileName, &header);
                if(settings.bpm || settings.tp){
                    FPD98SUB_AddTranspose((char*)cszFileName, szTempFileName, &header);
                    FPD98SubGetFpdHeader(szTempFileName, &header);
                }
                else{
                    CopyFile(cszFileName, szTempFileName, TRUE);
                    SetFileAttributes(szTempFileName, FILE_ATTRIBUTE_NORMAL);
                }
                FPD98SubManufactMusicMain(szTempFileName, &mp, &header);
            }
            __except(EXCEPTION_EXECUTE_HANDLER)
            {
                DeleteFile(szTempFileName);
                return 0;
            }
            UINT64 qwLength2 = (UINT64)mp.iPlayTime*1000*10000;
            if(qwLength2 != qwLength){
                qwLoop = -1;
            }
            else{
                qwLoop = 0;//差が 0 ならループしない曲
            }
            m_FpdPlay.op_l = 0;//ループ回数 0〜2147483647 (ただし0は無限回)
            _mbsncpy_s((unsigned char*)m_szTempFileName, _countof(m_szTempFileName),
                       (unsigned char*)szTempFileName, _TRUNCATE);
        }
    }
    const DWORD dwSapmleRateTable[5] = {44100, 33075, 22050, 16538, 11025};
    m_MediaInfo.dwSampleRate = dwSapmleRateTable[settings.op_s];
    m_MediaInfo.dwChannels = settings.op_v;
    m_MediaInfo.nBitsPerSample = settings.op_b;
    m_MediaInfo.qwLength = qwLength;
    m_MediaInfo.dwSeekableFlags = 0;
    m_MediaInfo.dwUnitSample = 0;
    m_MediaInfo.qwLoop = qwLoop != 0 ? (UINT64)-1 : 0;//無限ループ
    m_MediaInfo.qwFadeOut = qwLoop == 0 ? 0 : (UINT64)-1;
    m_MediaInfo.dwCount = 1;//1ファイル複数曲の場合は曲の数
    m_MediaInfo.dwNumber = 1;
    m_pFile = pFile;
    pFile->AddRef();
    m_pFolder = pFolder;
    pFolder->AddRef();
    return m_MediaInfo.dwCount;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbFpdDecoder::Select(DWORD dwNumber,
                                  const KPI_MEDIAINFO **ppMediaInfo,
                                  IKpiTagInfo *pTagInfo,
                                  DWORD dwTagGetFlags)
{
    if(ppMediaInfo){
        *ppMediaInfo = NULL;
    }
    if(dwNumber > m_MediaInfo.dwCount){
        return 0;
    }
    if(FPDGetTitle && pTagInfo && (dwTagGetFlags& KPI_TAGGET_FLAG_TITLE)){
        char szTitle[256];
        szTitle[0] = 0;
        int t = FPDGetTitle((char*)m_szFileName, szTitle);
        if(t){
            szTitle[0] = 0;
        }
        szTitle[255] = 0;
        pTagInfo->wSetValueA(L"title", -1, szTitle, -1);
    }
    if(!ppMediaInfo){
        return dwNumber;
    }
    m_MediaInfo.dwNumber = dwNumber;
    *ppMediaInfo = &m_MediaInfo;
    return m_MediaInfo.dwNumber;
}
//////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbFpdDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    if(!m_szFileName[0]){
        return 0;
    }
    Terminate();
    ResetEvent(m_hEvent);
    wchar_t szFpvPath[MAX_PATH];
    kbfpd_GetFpvPath(m_pConfig, szFpvPath, _countof(szFpvPath));
    g_pFpvSearcher->SearchFpv(szFpvPath);
    m_hThread = (HANDLE)_beginthreadex(NULL, 0, KbFpdThreadProc, this, 0, &m_uThreadId);
    HANDLE hEvents[] = {m_hThread, m_hEvent};
    if(WaitForMultipleObjects(2, hEvents, FALSE, INFINITE) == WAIT_OBJECT_0){
        Close();
        return 0;
    }
    return 0;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbFpdDecoder::Render(BYTE* pBuffer, DWORD dwSizeSample)
{
    const DWORD cdwBlockAlign = (m_MediaInfo.nBitsPerSample/8) * m_MediaInfo.dwChannels;
    fpd_Lock();
    KbWaveOut *pwo = (KbWaveOut*)g_wo;
    HWND hWnd = pwo ? pwo->m_hWnd : NULL;
    if(!hWnd){
        fpd_Unlock();
        return 0;
    }
    DWORD dwRetSample = pwo->Read(pBuffer, dwSizeSample * cdwBlockAlign) / cdwBlockAlign;
    while(dwRetSample < dwSizeSample){
        WAVEHDR *pwh = pwo->RemoveWavHdr();
        fpd_Unlock();
        SetEvent(g_hEventRequest);
        if(pwh){
            SendMessage(hWnd, MM_WOM_DONE, (WPARAM)pwo, (LPARAM)pwh);
        }
        HANDLE hHandles[] = {g_hEventTerminate, g_hEventDone};
        DWORD dwWaitRet = WaitForMultipleObjects(2, hHandles, FALSE, INFINITE);
        fpd_Lock();
        dwRetSample += pwo->Read(pBuffer + dwRetSample * cdwBlockAlign,
                                (dwSizeSample - dwRetSample) * cdwBlockAlign) / cdwBlockAlign;
        if(dwWaitRet == WAIT_OBJECT_0){
            break;
        }
    }
    fpd_Unlock();
    return dwRetSample;
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbFpdDecoder::Initialize(void)
{
    InitializeCriticalSection(&g_cs);
    InitializeCriticalSection(&g_csFpdHook);
    HINSTANCE hFpd = LoadFpdDll(g_hKpi);
    if(hFpd){
        g_hHeap = HeapCreate(0, 32768, 0);
        g_pFpvSearcher = new KbFpvSearcher;
        g_hEventDone = ::CreateEvent(NULL, FALSE, FALSE, NULL);
        g_hEventRequest= ::CreateEvent(NULL, FALSE, FALSE, NULL);
        g_hEventTerminate = ::CreateEvent(NULL, TRUE, FALSE, NULL);
        HookAPICalls(&WINMMHook, hFpd);
        HookAPICalls(&USER32Hook, hFpd);
        HookAPICalls(&Kernel32Hook, hFpd);
        FPDPlayInit();
        WaitForSingleObject(g_hEventDone, INFINITE);
        FPDQuietFlag(0);
    }
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbFpdDecoder::Uninitialize(void)
{
    if(FPDPlayEnd){
        FPDPlayEnd();
    }
    if(g_pcsFpd && !g_bcsFpdDelete){
        DeleteCriticalSection(g_pcsFpd);
        g_pcsFpd = NULL;
        g_bcsFpdDelete = TRUE;
    }
    UnloadFpdDll();
    delete g_pFpvSearcher;
    g_pFpvSearcher = NULL;
    if(g_szWndClassName[0]){
        ::UnregisterClass(g_szWndClassName, g_hInstance);
        g_szWndClassName[0] = 0;
        g_hInstance = NULL;
    }
    int i;
    for(i = 0; i < g_nFpdEvents; i++){
        CloseHandle(g_hFpdEvents[i]);
    }
    g_nFpdEvents = 0;
    if(g_hEventDone){
        CloseHandle(g_hEventDone);
        g_hEventDone = NULL;
    }
    if(g_hEventRequest){
        CloseHandle(g_hEventRequest);
        g_hEventRequest = NULL;
    }
    g_nFpdThreads = 0;
    if(g_hHeap){
        HeapDestroy(g_hHeap);
        g_hHeap = NULL;
    }
    DeleteCriticalSection(&g_csFpdHook);
    DeleteCriticalSection(&g_cs);
}
//////////////////////////////////////////////////////////////////////////
HANDLE __fastcall KbFpdDecoder::OpenFpvFile(const char *cszFpvFullPathA)
{
    wchar_t szFileNameFullPath[MAX_PATH];
    MultiByteToWideChar(CP_ACP, 0, cszFpvFullPathA, -1, szFileNameFullPath, _countof(szFileNameFullPath));
    szFileNameFullPath[_countof(szFileNameFullPath)-1] = 0;
    const wchar_t *cszFileName = wcsrchr(szFileNameFullPath, L'\\');
    if(!cszFileName){
        return INVALID_HANDLE_VALUE;
    }
    cszFileName++;
    if(!m_pFolder){
        return OpenFpvFromSearcher(cszFileName);
    }
    int i;
    IKpiFile *pFileFpv = NULL;
    wchar_t szFpv[MAX_PATH];
    for(i = m_nFpvFiles - 1; i >= 0; i--){
        m_ppFpvFiles[i]->GetFileName(szFpv, sizeof(szFpv));
        if(_wcsicmp(szFpv, cszFileName) == 0){
            pFileFpv = m_ppFpvFiles[i];
            break;
        }
    }
    if(!pFileFpv){
        if(!m_pFolder->OpenFile(cszFileName, &pFileFpv)){
            return OpenFpvFromSearcher(cszFileName);
        }
        m_ppFpvFiles = (IKpiFile**)realloc(m_ppFpvFiles, sizeof(IKpiFile*) * (m_nFpvFiles+1));
        m_ppFpvFiles[m_nFpvFiles++] = pFileFpv;
    }
    if(pFileFpv){
        const wchar_t *cszFileNameFullPathW;
        if(pFileFpv->GetRealFileW(&cszFileNameFullPathW)){
            return CreateFileW(cszFileNameFullPathW,
                               GENERIC_READ,
                               FILE_SHARE_READ|FILE_SHARE_WRITE,
                               NULL,
                               OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL,
                               NULL);
        }
    }
    return OpenFpvFromSearcher(cszFileName);
}
//////////////////////////////////////////////////////////////////////////
HANDLE __fastcall KbFpdDecoder::OpenFpvFromSearcher(const wchar_t *cszFpv)
{
    wchar_t szFpvFullPath[MAX_PATH];
    g_pFpvSearcher->Find(szFpvFullPath, cszFpv, _countof(szFpvFullPath));
    if(szFpvFullPath[0]){
        return CreateFileW(szFpvFullPath,
                           GENERIC_READ,
                           FILE_SHARE_READ|FILE_SHARE_WRITE,//dwShareMode,
                           NULL,
                           OPEN_EXISTING,//dwCreationDisposition,
                           FILE_ATTRIBUTE_NORMAL,
                           NULL);
    }
    return INVALID_HANDLE_VALUE;
}
//////////////////////////////////////////////////////////////////////////////

