#include <windows.h>
#include <process.h>
#include <wchar.h>
#define _CRT_SECURE_NO_WARNINGS
#include "vsti_loader.h"
#include "vsti_host.h"

#ifndef _WIN64
#ifdef _DEBUG
//SGP2.DLL 対策
//SGP2.DLL はデバッグ中だと動作しない
//IsDebuggerPresent が FALSE を返すようにすれば動作する
namespace{

#include "../../../kpi_common/apihijack.cpp"
BOOL WINAPI hook_IsDebuggerPresent(void)
{
    return FALSE;
}

SDLLHook Kernel32Hook =
{
    "kernel32.dll",
    {
        {"IsDebuggerPresent", hook_IsDebuggerPresent},
        {NULL, NULL}
    }
};

};
#endif
#endif

////////////////////////////////////////////////////////////////
//VSTi プラグインの全てのメソッドを同じスレッドから呼び出す
//プラグインをロードするスレッドも同じとする
////////////////////////////////////////////////////////////////
static HWND __fastcall kbAllocateHWnd(HWND hWndParent, WNDPROC WndProc, int x, int y, LPVOID pParam)
{
    wchar_t szClassName[256];
    WNDCLASSEX wc = { 0 };
    wc.cbSize = sizeof(wc);
    wc.lpfnWndProc = WndProc;
    wc.hInstance = GetModuleHandle(NULL);
    LARGE_INTEGER counter = { 0 };
    QueryPerformanceCounter(&counter);
    _snwprintf_s(szClassName, _TRUNCATE,
        L"kbvsti.kpi:%08x:%016llx",
        GetCurrentThreadId(), counter.QuadPart);
    wc.lpszClassName = szClassName;
    RegisterClassEx(&wc);
    return CreateWindowEx(WS_EX_DLGMODALFRAME,
        szClassName,
        L"",
        WS_SYSMENU | WS_CAPTION | WS_BORDER,
        x,
        y,
        0,
        0,
        hWndParent,
        NULL,
        wc.hInstance,
        pParam);
}
///////////////////////////////////////////////////////////////////////////////
static void __fastcall kbDeallocateHWnd(HWND hWnd)
{
    wchar_t szClassName[256];
    GetClassName(hWnd, szClassName, _countof(szClassName));
    DestroyWindow(hWnd);
    UnregisterClass(szClassName, GetModuleHandle(NULL));
}
//////////////////////////////////////////////////////////////////////////////
VstiLoader::VstiLoader(void)
{
    m_hDll = NULL;
    m_szPath[0] = 0;
    m_dwThreadId = 0;
    m_VSTPluginMain = NULL;
    m_hWnd = NULL;   //↓のスレッドで作成するウィンドウのハンドル
    m_hThread = NULL; //プラグインを使用するスレッド
    m_hEventInit = NULL;//初期化完了時にシグナル
    m_hEventExit = NULL;//終了時にシグナル
    for(int i = 0; i < MAX_CACHE; i++){
        m_pCache[i] = NULL;
    }
}
//////////////////////////////////////////////////////////////////////////////
VstiLoader::~VstiLoader(void)
{
    CacheVsti(NULL);//全てクリア
    if(m_hThread){
        PostThreadMessage(m_dwThreadId, WM_QUIT, 0, 0);
        SetEvent(m_hEventExit);
        WaitForSingleObject(m_hThread, INFINITE);
        CloseHandle(m_hThread);
    }
    if(m_hEventInit)CloseHandle(m_hEventInit);
    if(m_hEventExit)CloseHandle(m_hEventExit);
}
//////////////////////////////////////////////////////////////////////////////
bool VstiLoader::Init(const wchar_t* cszPath)
{
    wcsncpy_s(m_szPath, cszPath, _TRUNCATE);
    m_hEventInit = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_hEventExit = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, this, 0, (unsigned*)&m_dwThreadId);
    HANDLE hHandles[] = { m_hEventInit, m_hThread};
    //初期化が終わるまで待つ
    if (WaitForMultipleObjects(2, hHandles, FALSE, INFINITE) != WAIT_OBJECT_0) {
        return false;
    }
    CloseHandle(m_hEventInit); m_hEventInit = NULL;//もう必要ない
    return true;
}
//////////////////////////////////////////////////////////////////////////////
unsigned VstiLoader::ThreadProc(void)
{//VSTi プラグインをこのスレッドでロードし、全てのメソッドはこのスレッドから呼び出す
#ifdef _DEBUG
#if 0 //kbrunkpi.exe 経由時のデバッグ用
    while (!IsDebuggerPresent()) {
        Sleep(1);
    }
#endif
#endif
    // クリティカルエラーなどのメッセージを非表示化
    UINT errMode = SetErrorMode(SEM_FAILCRITICALERRORS);
    m_hDll = LoadLibraryExW(m_szPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
    // エラーモードを戻す
    SetErrorMode(errMode);
    if (!m_hDll) {
        return 0;
    }
#ifndef _WIN64
#ifdef _DEBUG
    {//SGP2.DLL の場合のみ、
     //IsDebuggerPresent が常に FALSE を返すようにする
        const wchar_t* filename = wcsrchr(m_szPath, L'\\');
        if (filename && _wcsicmp(filename + 1, L"sgp2.dll") == 0) {
            HookAPICalls(&Kernel32Hook, m_hDll);
        }
    }
#endif
#endif
    // "VSTPluginMain"の場合もあるが、SGP(2).DLLはmainなのでそちらだけ
    m_VSTPluginMain = reinterpret_cast<pfnVSTPluginMain>(GetProcAddress(m_hDll, "VSTPluginMain"));
    if (!m_VSTPluginMain) {
        m_VSTPluginMain = reinterpret_cast<pfnVSTPluginMain>(GetProcAddress(m_hDll, "main"));
    }
    if (!m_VSTPluginMain){
        FreeLibrary(m_hDll); m_hDll = NULL;
        return 0;
    }
    HWND hWnd = kbAllocateHWnd(NULL, VstiWndProc, 0, 0, this);
    //m_hWnd_ = hWnd;//自動的に設定される
    SetEvent(m_hEventInit);//初期化完了

    HANDLE hHandles[] = {m_hEventExit};
    MSG msg;
    while (1) {
        DWORD dwRet = MsgWaitForMultipleObjects(1, hHandles, FALSE, INFINITE, QS_ALLINPUT);
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        if (dwRet == WAIT_OBJECT_0) {
            break;
        }
    }
    kbDeallocateHWnd(hWnd);
    FreeLibrary(m_hDll);
    m_hDll = NULL;
    return 0;
}
//////////////////////////////////////////////////////////////////////////////
VstiHost* VstiLoader::GetVsti(void)
{
    if(!m_hDll)return NULL;
    VstiHost *ret = NULL;
    for(int i = 0; i < MAX_CACHE; i++){
        if(m_pCache[i]){
            ret = m_pCache[i];
            m_pCache[i] = NULL;
            return ret;
        }
    }
    AEffect *effect = NULL;
    SendMessage(m_hWnd, WM_EFFOPEN, (WPARAM)&effect, 0);
    if(effect){
        return new VstiHost(effect, this);
    }
    return NULL;
}
//////////////////////////////////////////////////////////////////////////////
void VstiLoader::CacheVsti(VstiHost* pVsti)
{
    int i;
    if(!pVsti){//キャッシュをクリア
        for (i = 0; i < MAX_CACHE; i++) {
            if (m_pCache[i]) {
                delete m_pCache[i];
                m_pCache[i] = NULL;
            }
        }
        return;
    }
    for (i = 0; i < MAX_CACHE; i++) {
        if (!m_pCache[i]) {
            m_pCache[i] = pVsti;
            return;
        }
    }
    delete pVsti;
}
//////////////////////////////////////////////////////////////////////////////
void VstiLoader::FreeVsti(VstiHost* pVsti, bool bCache)
{
    if(bCache){
        CacheVsti(pVsti);
    }
    else{
        delete pVsti;
    }
}
//////////////////////////////////////////////////////////////////////////////
VstIntPtr VSTCALLBACK VstiLoader::HostCallback(AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt)
{
    static const char VENDOR[] = "YAMAHA";
    static const char PRODUCT[] = "Cubase VST";

    VstIntPtr result = 0;

    switch (opcode)
    {
    case audioMasterGetTime:{
        VstiHost *host = (VstiHost*)effect->user;
        if(!host)return 0;
        host->timeinfo_.nanoSeconds = GetTickCount() * 1000.0 * 1000.0;
        //host->timeinfo_.samplePos = 0;
        return (VstIntPtr)&host->timeinfo_;
    }
    case audioMasterVersion:
        result = kVstVersion;
        break;
    case audioMasterGetBlockSize:{
        VstiHost* host = (VstiHost*)effect->user;
        if (!host)return 0;
        return host->block_size_;
    }
    case audioMasterGetSampleRate: {
        VstiHost* host = (VstiHost*)effect->user;
        if (!host)return 0;
        return host->rate_;
    }
//    case audioMasterIdle:
//        result = 1;
//        break;
    case audioMasterGetVendorString:
        // MidRadioのSGP(2).DLLを動かすためには、YAMAHAでないといけない。
        memcpy(ptr, VENDOR, sizeof(VENDOR));
        break;
    case audioMasterGetProductString:
        memcpy(ptr, PRODUCT, sizeof(PRODUCT));
        break;
    case audioMasterGetLanguage:
        return kVstLangJapanese;

    }

    return result;    
}
//////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK VstiLoader::VstiWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
    VstiLoader* pLoader;
    if (msg == WM_CREATE) {
        CREATESTRUCT* pcs = (CREATESTRUCT*)(lp);
        pLoader = (VstiLoader*)(pcs->lpCreateParams);
        pLoader->m_hWnd = hwnd;
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pLoader);
    }
    else {
        pLoader = (VstiLoader*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
    }
    if (pLoader && pLoader->m_hWnd == hwnd) {
        return pLoader->VstiWndProcInternal(hwnd, msg, wp, lp);
    }
    return DefWindowProc(hwnd, msg, wp, lp);
}
LRESULT VstiLoader::VstiWndProcInternal(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
    if (msg == WM_DISPATCHER) {
        AEffect* effect = (AEffect*)wp;
        DISPATCHER_PARAM* param = (DISPATCHER_PARAM*)lp;
        param->retval = effect->dispatcher(effect, param->opcode, param->index, param->value, param->ptr, param->opt);
        return 0;
    }
    else if(msg == WM_PROCESS_REPLACING){
        AEffect* effect = (AEffect*)wp;
        PROCESS_REPLACING_PARAM* param = (PROCESS_REPLACING_PARAM*)lp;
        if(param->is_double){
            effect->processDoubleReplacing(effect, (double**)param->inputs, (double**)param->outputs, param->sampleFrames);
        }
        else{
            effect->processReplacing(effect, (float**)param->inputs, (float**)param->outputs, param->sampleFrames);
        }
        return 0;
    }
    else if (msg == WM_SHOW_EDITOR) {
        //AEffect *effect = (AEffect*)wp;
        SHOW_EDITOR_PARAM *param = (SHOW_EDITOR_PARAM*)lp;
        HWND hDlg = kbAllocateHWnd(m_hWnd, param->WndProc, param->pos_x, param->pos_y, param->pvInstance);
        if(hDlg)ShowWindow(hDlg, SW_SHOWNOACTIVATE);
        return 0;
    }
    else if (msg == WM_CLOSE_EDITOR) {
        //AEffect *effect = (AEffect*)wp;
        HWND hWnd = (HWND)lp;
        kbDeallocateHWnd(hWnd);
        return 0;
    }
    else if(msg == WM_EFFOPEN){
        AEffect *effect = m_VSTPluginMain(HostCallback);
        if(!effect){
            return 0;
        }
        do{
            if(effect->numOutputs < 2) {
                break;
            }
            if (!(effect->flags & effFlagsCanReplacing) &&
                !(effect->flags & effFlagsCanDoubleReplacing)) {
                break;
            }
            if(!(effect->flags & effFlagsIsSynth)){
                break;
            }
            effect->dispatcher(effect, effOpen, 0, 0, 0, 0);
            AEffect **ppEffect = (AEffect**)wp;
            *ppEffect = effect;
            return 0;
        }while(0);
        effect->dispatcher(effect, effClose, 0, 0, 0, 0);
        return 0;
    }
    else if (msg == WM_EFFCLOSE) {
        AEffect* effect = (AEffect*)wp;
        effect->dispatcher(effect, effClose, 0, 0, 0, 0);
        return 0;
    }
    else if (msg == WM_DESTROY) {
        m_VSTPluginMain = NULL;
        m_hWnd = NULL;
        PostQuitMessage(0);
    }
    return DefWindowProc(hwnd, msg, wp, lp);
}
