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

#define _CRT_SECURE_NO_WARNINGS
#include "vsti_host.h"
#include "vsti_loader.h"

///////////////////////////////////////////////////////////////////////////////
VstiHost::VstiHost(AEffect* pEffect, VstiLoader* pLoader)
{
    effect_ = pEffect;
    loader_ = pLoader;
    effect_->user = this;
    rate_ = 0;
    timer_ = 0;
    dlg_ = NULL;
    dlg_pos_x_ = 0;
    dlg_pos_y_ = 0;
    port_ = 0;
    block_size_ = 0;
    outputs_ = NULL;
    inputs_ = NULL;
    ZeroMemory(phosts_, sizeof(phosts_));
    //ZeroMemory(outputs_, sizeof(outputs_));
    //ZeroMemory(inputs_, sizeof(inputs_));
    ZeroMemory(&timeinfo_, sizeof(timeinfo_));
    //timeinfo_.samplePos = 0;
    //timeinfo_.sampleRate = 0;
    //timeinfo_.nanoSeconds = 0;
    //timeinfo_.ppqPos = 0;
    timeinfo_.tempo = 120;
    //timeinfo_.barStartPos = 0;
    //timeinfo_.cycleStartPos = 0;
    //timeinfo_.cycleEndPos = 0;
    timeinfo_.timeSigNumerator = 4;
    timeinfo_.timeSigDenominator = 4;
    //timeinfo_.smpteOffset = 0;
    timeinfo_.smpteFrameRate = kVstSmpte24fps;
    //timeinfo_.samplesToNextClock = 0;
    timeinfo_.flags = (/*kVstNanosValid |*/ kVstPpqPosValid | kVstTempoValid | kVstTimeSigValid);
}
///////////////////////////////////////////////////////////////////////////////
VstiHost::~VstiHost()
{
    Term();
}
//-----------------------------------------------------------------------------
// シンセ終了
//-----------------------------------------------------------------------------
void VstiHost::Term()
{
    if (loader_) {
        SendMessage(loader_->m_hWnd, VstiLoader::WM_EFFCLOSE, (WPARAM)effect_, 0);
        effect_ = NULL;
        loader_ = NULL;
    }
    if (outputs_) {//outputs_32/ouputs_64 と共有
        free(outputs_[0]);
        free(outputs_);
        //free(inputs_[0]);//outputs_ と一緒に確保するので不要
        //free(inputs_);   //outputs_ と一緒に確保するので不要
        outputs_ = inputs_ = NULL;
    }
}
//-----------------------------------------------------------------------------
// 演奏パラメーター設定
//-----------------------------------------------------------------------------
void VstiHost::Setup(int sample_rate, int block_size)
{
    //VST SynthFont だと block_size の値が大きすぎると落ちる(4140だとノイズが発生、4141だと落ちる)(4096まで?)
    //MAmiVSTi.dll だと block_size の値が 1000/30 ms 未満(44100Hz なら 1470未満, 192000Hz なら 6400未満) だと音が途切れる
    //最適な block_size の値を自動的に求める方法があるのだろうか。
    const wchar_t *cszDllPath = wcsrchr(loader_->m_szPath, L'\\');
    if(cszDllPath && _wcsicmp(cszDllPath+1, L"MAmiVSTi.dll") == 0){
        block_size = (sample_rate / 50)*2;
    }
    if(block_size_ != block_size){
        if (outputs_) {//バッファをリセット
            free(outputs_[0]);
            free(outputs_);
            outputs_ = inputs_ = NULL;
        }
    }
    rate_ = sample_rate;
    block_size_ = block_size;
    if (outputs_){
        return;
    }
    const int numOutputs = effect_->numOutputs;
    const int numInputs = effect_->numInputs;
    int i;
    outputs_ = (void**)calloc(numOutputs + numInputs, sizeof(void*));
    if(numInputs > 0){
        inputs_ = outputs_ + numOutputs;
    }
    if (IsSupport64()) {
        double* buf = (double*)calloc((numOutputs + numInputs) * (block_size), sizeof(double));
        for (i = 0; i < numOutputs; i++) {
            outputs_64[i] = buf + block_size * i;
        }
        for (i = 0; i < numInputs; i++) {
            inputs_64[i] = buf + block_size * (numOutputs + i);
        }
    }
    else {
        float* buf = (float*)calloc((numOutputs + numInputs) * block_size, sizeof(float));
        for (i = 0; i < numOutputs; i++) {
            outputs_32[i] = buf + block_size * i;
        }
        for (i = 0; i < numInputs; i++) {
            inputs_32[i] = buf + block_size * (numOutputs + i);
        }
    }
}
//-----------------------------------------------------------------------------
// 演奏開始
//-----------------------------------------------------------------------------
void VstiHost::Start()
{
    float rate = static_cast<float>(rate_);

    Dispatcher(effSetProgram, 0, 0, NULL, 0);
    Dispatcher(effSetSampleRate, 0, 0, NULL, rate);
    Dispatcher(effSetBlockSize, 0, block_size_, NULL, 0);
    if(IsSupport64()){
        Dispatcher(effSetProcessPrecision, 0, kVstProcessPrecision64, 0, 0);
    }
    else{
        Dispatcher(effSetProcessPrecision, 0, kVstProcessPrecision32, 0, 0);
    }
    Dispatcher(effMainsChanged, 0, 1, NULL, 0);
    Dispatcher(effStartProcess, 0, 0, NULL, 0);

}
//-----------------------------------------------------------------------------
// 演奏停止
//-----------------------------------------------------------------------------
void VstiHost::Stop()
{
    Dispatcher(effStopProcess, 0, 0, NULL, 0);
    Dispatcher(effMainsChanged, 0, 0, NULL, 0);
}
//-----------------------------------------------------------------------------
// シンセ実行（buffer==NULLなら出力なしで処理）
//-----------------------------------------------------------------------------
bool VstiHost::Render(const std::vector<SmfLoader::MidiMessage>& message_data, int samples, UINT64 pos_sample)
{
    const size_t message_num = message_data.size();

    const size_t SEND_EVENT_SIZE = sizeof(VstEvents) + sizeof(VstEvent*) * message_num;
    const size_t MIDI_EVENT_SIZE = /*sizeof(VstEvent)*/sizeof(VstMidiSysexEvent) * message_num;
    //↑sizeof(VstEvent) だと 64bit 版のときに VstMidiSysexEvent でサイズが足りない
    //(sizeof(VstEvent)/sizeof(VstMidiEvent)/sizeof(MidiSysexEvent) のうち最も大きい値を使う)
    VstEvents* send_event = static_cast<VstEvents*>(calloc(1, SEND_EVENT_SIZE));
    VstMidiSysexEvent* midi_event = static_cast<VstMidiSysexEvent*>(calloc(1, MIDI_EVENT_SIZE));
    //(sizeof(VstEvent)/sizeof(VstMidiEvent)/sizeof(MidiSysexEvent) のうち最も大きい型を使う)

    send_event->numEvents = message_num;
    send_event->reserved = 0;
    timeinfo_.samplePos = pos_sample;
    UINT64 start_time_sample = pos_sample;
    if (start_time_sample < 0) {
        start_time_sample = 0;
    }
    int numEvents = 0;
    for (int i = 0; i < message_num; ++i)
    {
        UINT64 cur_time_sample = message_data[i].time_smp;
        INT64 diff_sample = cur_time_sample - start_time_sample;
        if (diff_sample < 0) {
            diff_sample = 0;
        }
        //if (message_data[i].data == SmfLoader::SYS_EX_ALL_PORT) {//SysEx
            //continue;
        //}
        if ((message_data[i].data & SmfLoader::SYS_EX) == SmfLoader::SYS_EX) {//SysEx
            VstMidiSysexEvent* sysex = reinterpret_cast<VstMidiSysexEvent*>(&midi_event[i]);
            send_event->events[numEvents++] = reinterpret_cast<VstEvent*>(sysex);
            sysex->deltaFrames = diff_sample;
            sysex->type = kVstSysExType;
            sysex->byteSize = sizeof(VstMidiSysexEvent);
            sysex->dumpBytes = message_data[i].sysex_dump.size();
            sysex->sysexDump = reinterpret_cast<char*>(const_cast<unsigned char*>(message_data[i].sysex_dump.data()));
        }
        else if (message_data[i].data != SmfLoader::END_OF_TRACK) {//MIDI Event
            VstMidiEvent* e = reinterpret_cast<VstMidiEvent*>(&midi_event[i]);
            int mmsg = message_data[i].data;
            int type = (mmsg & 0xF0);
            send_event->events[numEvents++] = reinterpret_cast<VstEvent*>(e);
            e->deltaFrames = diff_sample;
            e->type = kVstMidiType;
            e->byteSize = sizeof(VstMidiEvent);
            e->noteLength = ((type == 0xC0) || (type == 0xD0)) ? 2 : 3;
            //memcpy(e->midiData, &mmsg, mmsg);
            *((DWORD*)e->midiData) = mmsg;
        }
    }
    send_event->numEvents = numEvents;
    Dispatcher(effProcessEvents, 0, 0, send_event, 0);

    if (0 < samples)
    {
        if (IsSupport64()) {
            ProcessDoubleReplacing(outputs_64, samples);
        }
        else {
            ProcessReplacing(outputs_32, samples);
        }
    }
    free(midi_event);
    free(send_event);
    timeinfo_.samplePos += samples;
    return true;
}
//-----------------------------------------------------------------------------
// VSTi設定エディタ
//-----------------------------------------------------------------------------
void VstiHost::ShowEditor(int nPosX, int nPosY, int nPort, VstiHost **ppHosts, int nHosts)
{// プラグインをロードしたスレッド内で作成する
    if (dlg_)return;//既に作成済
    if (!HasEditor())return;
    SetHosts(ppHosts, nHosts);
    port_ = nPort;
    //Port-A を基準としたダイアログの位置を取得
    //Port-A(nPort==0) の位置はダイアログ作成前でも取得可能
    phosts_[0]->GetEditorPos(nPosX, nPosY, nPort, &nPosX, &nPosY);

    VstiLoader::SHOW_EDITOR_PARAM param;
    param.pos_x = nPosX;
    param.pos_y = nPosY;
    param.WndProc = EditorDlgProc;
    param.pvInstance = this;
    SendMessage(loader_->m_hWnd, VstiLoader::WM_SHOW_EDITOR, (WPARAM)effect_, (LPARAM)&param);
}
void VstiHost::CloseEditor(void)
{// プラグインをロードしたスレッド内で閉じる
    if (dlg_) {
        SendMessage(loader_->m_hWnd, VstiLoader::WM_CLOSE_EDITOR, (WPARAM)effect_, (LPARAM)dlg_);
    }
}
//-----------------------------------------------------------------------------
// dispatcherの呼び出しラッパー
//-----------------------------------------------------------------------------
VstIntPtr VstiHost::Dispatcher(VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt)
{
    if (GetCurrentThreadId() == loader_->m_dwThreadId) {
        return effect_->dispatcher(effect_, opcode, index, value, ptr, opt);
    }
    VstiLoader::DISPATCHER_PARAM param;
    param.opcode = opcode;
    param.index = index;
    param.value = value;
    param.ptr = ptr;
    param.opt = opt;
    param.retval = 0;
    SendMessage(loader_->m_hWnd, VstiLoader::WM_DISPATCHER, (WPARAM)effect_, (LPARAM)&param);
    return param.retval;

}
void VstiHost::ProcessReplacing(float** outputs, VstInt32 sampleFrames)
{
//    effect_->processReplacing(effect_, inputs_, outputs, sampleFrames);
    VstiLoader::PROCESS_REPLACING_PARAM param;
    param.inputs = inputs_;
    param.outputs = (void**)outputs;
    param.sampleFrames = sampleFrames;
    param.is_double = false;
    SendMessage(loader_->m_hWnd, VstiLoader::WM_PROCESS_REPLACING, (WPARAM)effect_, (LPARAM)&param);
}
void VstiHost::ProcessDoubleReplacing(double** outputs, VstInt32 sampleFrames)
{
//    effect_->processDoubleReplacing(effect_, inputs_, outputs, sampleFrames);
    VstiLoader::PROCESS_REPLACING_PARAM param;
    param.inputs = inputs_;
    param.outputs = (void**)outputs;
    param.sampleFrames = sampleFrames;
    param.is_double = true;
    SendMessage(loader_->m_hWnd, VstiLoader::WM_PROCESS_REPLACING, (WPARAM)effect_, (LPARAM)&param);
}
//-----------------------------------------------------------------------------
// ダイアログコールバック
//-----------------------------------------------------------------------------
LRESULT CALLBACK VstiHost::EditorDlgProc(HWND dlg, UINT msg, WPARAM wp, LPARAM lp)
{
    VstiHost* host;
    if (msg == WM_CREATE) {
        CREATESTRUCT* pcs = reinterpret_cast<CREATESTRUCT*>(lp);
        host = reinterpret_cast<VstiHost*>(pcs->lpCreateParams);
        host->dlg_ = dlg;
        SetWindowLongPtr(dlg, GWLP_USERDATA, (LONG_PTR)host);
    }
    else {
        host = reinterpret_cast<VstiHost*>(GetWindowLongPtr(dlg, GWLP_USERDATA));
    }
    if (host && host->dlg_ == dlg) {
        return host->EditorDlgProcInternal(dlg, msg, wp, lp);
    }
    return DefWindowProc(dlg, msg, wp, lp);
}
LRESULT VstiHost::EditorDlgProcInternal(HWND dlg, UINT msg, WPARAM wp, LPARAM lp)
{
    if (msg == WM_SIZE) {
        ERect* rt = NULL;
        Dispatcher(effEditGetRect, 0, 0, &rt, 0);
        if (rt) {
            RECT wrc;
            RECT crc;
            GetWindowRect(dlg, &wrc);
            GetClientRect(dlg, &crc);
            int nWidth = (rt->right - rt->left) + (wrc.right - wrc.left) - (crc.right - crc.left);
            int nHeight = (rt->bottom - rt->top) + (wrc.bottom - wrc.top) - (crc.bottom - crc.top);
            SetWindowPos(dlg, HWND_NOTOPMOST, 0, 0, nWidth, nHeight, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
        }
        return 0;
    }
    else if (msg == WM_WINDOWPOSCHANGED) {
        //ウィンドウの位置を保存
        WINDOWPOS* wp = (WINDOWPOS*)lp;
        dlg_pos_x_ = wp->x;
        dlg_pos_y_ = wp->y;

        ERect* rt = NULL;
        Dispatcher(effEditGetRect, 0, 0, &rt, 0);
        if(!rt)return 0;
        RECT wrc;
        RECT crc;
        GetWindowRect(dlg, &wrc);
        GetClientRect(dlg, &crc);
        int nWidth = (rt->right - rt->left) + (wrc.right - wrc.left) - (crc.right - crc.left);
        int nHeight = (rt->bottom - rt->top) + (wrc.bottom - wrc.top) - (crc.bottom - crc.top);
        //SoundCanvas VA で「Part」や「FX」をクリック時に WM_SIZE が呼ばれないのでサイズもここで設定しておく
        SetWindowPos(dlg_, HWND_NOTOPMOST, dlg_pos_x_, dlg_pos_y_, nWidth, nHeight, /*SWP_NOSIZE |*/ SWP_NOZORDER | SWP_NOACTIVATE);
        //関連する他のポートのウィンドウを連動して移動
        for (int i = 0; i < 4; i++) {
            if (i == port_)continue;//自分自身
            if (!phosts_[i])break;
            if (!phosts_[i]->dlg_)continue;
            int xPos, yPos;
            GetEditorPos(dlg_pos_x_, dlg_pos_y_, i, &xPos, &yPos);
            SetWindowPos(phosts_[i]->dlg_, HWND_NOTOPMOST, xPos, yPos, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
        }
        return 0;
    }
    else if (msg == WM_CREATE)
    {
        Dispatcher(effEditOpen, 0, 0, dlg, 0);

        timer_ = SetTimer(dlg, 0, 10, NULL);

        char szVendorA[kVstMaxVendorStrLen + 1];
        char szProductA[kVstMaxProductStrLen + 1];

        szVendorA[0] = szProductA[0] = 0;
        Dispatcher(effGetVendorString, 0, 0, szVendorA, 0);
        Dispatcher(effGetProductString, 0, 0, szProductA, 0);

        wchar_t szVendor[kVstMaxVendorStrLen + 1];
        wchar_t szProduct[kVstMaxProductStrLen + 1];
        MultiByteToWideChar(CP_ACP, 0, szVendorA, -1, szVendor, _countof(szVendor));
        MultiByteToWideChar(CP_ACP, 0, szProductA, -1, szProduct, _countof(szVendor));
        szVendor[_countof(szVendor) - 1] = 0;
        szProduct[_countof(szProduct) - 1] = 0;

        wchar_t szCaption[512];
        _snwprintf_s(szCaption, _TRUNCATE, L"%s: %s (Port-%c)", szVendor, szProduct, L'A' + port_);
        SetWindowText(dlg, szCaption);
        //return DefWindowProc(dlg, msg, wp, lp);
    }
    else if (msg == WM_DESTROY) {
        Dispatcher(effEditClose, 0, 0, 0, 0);
        SetWindowLongPtr(dlg, GWLP_USERDATA, 0);
        if (timer_) {
            KillTimer(dlg, timer_);
            timer_ = NULL;
        }
        dlg_ = NULL;
        //return DefWindowProc(dlg, msg, wp, lp);
    }
    else if (msg == WM_CLOSE) {
        if (timer_) {
            KillTimer(dlg, timer_);
            timer_ = NULL;
        }
        //ShowWindow(dlg, SW_HIDE);
        //return DefWindowProc(dlg, msg, wp, lp);
    }
    else if (msg == WM_TIMER) {
        Dispatcher(effEditIdle, 0, 0, 0, 0);
        return 0;
    }
    return DefWindowProc(dlg, msg, wp, lp);
}
void VstiHost::GetVSTiInfoStr(wchar_t* pszInfo, int nSize)
{
    static const struct {
        int flag;
        wchar_t* str;
    }flags[] = {
        { effFlagsHasEditor, L"HasEditor"},
        { effFlagsCanReplacing, L"CanReplacing" },
        { effFlagsProgramChunks, L"ProgramChunks" },
        { effFlagsIsSynth, L"IsSynth" },
        { effFlagsNoSoundInStop, L"NoSoundInStop" },
        { effFlagsCanDoubleReplacing, L"CanDoubleReplacing" },
        { 0, NULL },
    };
    wchar_t szPath[MAX_PATH];
    wchar_t szVendor[kVstMaxVendorStrLen + 1];
    wchar_t szProduct[kVstMaxProductStrLen + 1];
    wchar_t szFlags[128];
    szVendor[0] = szProduct[0] = szFlags[0] = 0;

    wcsncpy_s(szPath, loader_->m_szPath, _TRUNCATE);
    if (effect_) {
        for (int i = 0; flags[i].str; i++) {
            if ((effect_->flags & flags[i].flag)) {
                if (szFlags[0]) {
                    wcsncat_s(szFlags, L" | ", _TRUNCATE);
                }
                wcsncat_s(szFlags, flags[i].str, _TRUNCATE);
            }
        }
        char szVendorA[kVstMaxVendorStrLen + 1];
        char szProductA[kVstMaxProductStrLen + 1];
        szVendorA[0] = szProductA[0] = 0;
        Dispatcher(effGetVendorString, 0, 0, szVendorA, 0);
        Dispatcher(effGetProductString, 0, 0, szProductA, 0);
        MultiByteToWideChar(CP_ACP, 0, szVendorA, -1, szVendor, _countof(szVendor));
        MultiByteToWideChar(CP_ACP, 0, szProductA, -1, szProduct, _countof(szVendor));
        szVendor[_countof(szVendor) - 1] = 0;
        szProduct[_countof(szProduct) - 1] = 0;
    }
    _snwprintf_s(pszInfo, nSize, _TRUNCATE,
        L"path: %s\r\n"
        L"vendor: %s\r\n"
        L"product: %s\r\n"
        L"numPrograms: %d\r\n"
        L"numParams: %d\r\n"
        L"numInputs: %d\r\n"
        L"numOutputs: %d\r\n"
        L"flags: %d(%s)\r\n"
        L"uniqueID: %d\r\n"
        L"version: %d"
        ,
        szPath,
        szVendor,
        szProduct,
        effect_->numPrograms,
        effect_->numParams,
        effect_->numInputs,
        effect_->numOutputs,
        effect_->flags, szFlags,
        effect_->uniqueID,
        effect_->version
    );
}
void VstiHost::GetEditorPos(int xBasePos, int yBasePos, int nPort, int* pXPos, int* pYPos)
{//Port-X(X=port_) が xBasePos, yBasePos の位置にある場合の Port-Y(Y=nPort) の位置を返す
 //port_/nPort ともに 0～3  であることが前提
 //
    if (nPort == port_) {//自身と同じポート番号
        *pXPos = xBasePos;
        *pYPos = yBasePos;
        return;
    }
    *pXPos = *pYPos = 0;
    int xpos = xBasePos;
    int ypos = yBasePos;
    int width[4] = { 0 };
    int height[4] = { 0 };
    for (int i = 0; i < 3/*4*/; i++) {//Port-D の幅と高さは不要
        if (!phosts_[i])break;
        ERect* rt = NULL;
        phosts_[i]->Dispatcher(effEditGetRect, 0, 0, &rt, 0);
        if (!rt) {
            continue;
        }
        RECT wrc;
        RECT crc;
        GetWindowRect(dlg_, &wrc);
        GetClientRect(dlg_, &crc);
        width[i] = (rt->right - rt->left) + (wrc.right - wrc.left) - (crc.right - crc.left);
        height[i] = (rt->bottom - rt->top) + (wrc.bottom - wrc.top) - (crc.bottom - crc.top);;
    }
    //Port-A/B の ypos, Port-C/D の ypos は同じ
    //Port-A/C の xpos, Port-B/D の xpos は同じ
    //Port-B/D の xpos は Port-A/C のうち幅が広い方に合わせる
    //Port-C/D の ypos は Port-A/B のうち高さが大きい方に合わせる
    if (port_ == 0) {//Port-A から見た位置
        if (nPort == 1) {//B
            xpos += max(width[0], width[2]);  // A/C のうち幅が広い方
        }
        else if (nPort == 2) {//C
            ypos += max(height[0], height[1]);// A/B のうち高さが大きい方
        }
        else {// if (nPort == 3) {//D
            xpos += max(width[0], width[2]);  // A/C のうち幅が大きい方
            ypos += max(height[0], height[1]);// A/B のうち高さが大きい方
        }
    }
    else if (port_ == 1) {//Port-B から見た位置
        if (nPort == 0) {
            xpos -= max(width[0], width[2]);
        }
        else if (nPort == 2) {
            xpos -= max(width[0], width[2]);
            ypos += max(height[0], height[1]);
        }
        else {// if (nPort == 3) {
            ypos += max(height[0], height[1]);
        }
    }
    else if (port_ == 2) {//Port-C から見た位置
        if (nPort == 0) {
            ypos -= max(height[0], height[1]);
        }
        else if (nPort == 1) {
            xpos += max(width[0], width[2]);
            ypos -= max(height[0], height[1]);
        }
        else {// if (nPort == 3) {
            xpos += max(width[0], width[2]);
        }
    }
    else if (port_ == 3) {//Port-D から見た位置
        if (nPort == 0) {
            xpos -= max(width[0], width[2]);
            ypos -= max(height[0], height[1]);
        }
        else if (nPort == 1) {
            ypos -= max(height[0], height[1]);
        }
        else {// if (nPort == 2) {
            xpos -= max(width[0], width[2]);
        }
    }
    *pXPos = xpos;
    *pYPos = ypos;
}
void VstiHost::SetHosts(VstiHost** ppHosts, int nHosts)
{//設定画面を連動して移動する用
    int i;
    for (i = 0; i < _countof(phosts_) && i < nHosts; i++) {
        phosts_[i] = ppHosts[i];
    }
    for (; i < _countof(phosts_); i++) {
        phosts_[i] = NULL;
    }
}
