#include "stdafx.h"
#include <mmintrin.h>
#include <xmmintrin.h>

HWND MainWindow = 0;
HINSTANCE hAppInstance = 0;
HICON hIcon1 = 0;

// ----
const TCHAR* ttitle = _T("VST Test");

// ----

#ifdef _WIN64
#import "..\\vstx64.dll" raw_interfaces_only
#else
#import "..\\vstx32.dll" raw_interfaces_only
#endif
#include "..\\vstx.h"

void StartWout(int wd);
#pragma warning(disable:4100)

bool Ending = false;

#ifdef USING_DLL
HINSTANCE hVSTX = 0;
#endif

// Wave Classes
#include <queue>
using namespace std;

struct VSMAP
{

};

struct VS
{
	CComPtr<vstxlib::VSTZ> vst;
	CComPtr<vstxlib::VSTZ> vst2;
	mutable CComPtr<IStream> str;
	GUID g;
	void* din = 0;
	void* dout = 0;
	wstring sm;
	int ComType = 0; 
	int nwin, nwout, nmin, nmout;

};
#include ".\\mt\\rw.hpp"

tlock<std::vector<VS>> v;
#include "wave.h"

class MyVSTCallback : public vstxlib::VSTNOTIFICATION
{
	int refNum = 1;
	vstxlib::VSTZ* v;

public:

	void Set(vstxlib::VSTZ* vv)
	{
		v = vv;
	}

	// FUnknown
	DWORD WINAPI AddRef()
	{
		return ++refNum;
	}
	DWORD WINAPI Release()
	{
		if (refNum)
			--refNum;
		if (refNum == 0)
			delete this;
		return refNum;
	}
	HRESULT WINAPI QueryInterface(const IID& riid, void ** object)
	{
		if (!object)
			return E_POINTER;

		*object = 0;
		bool F = false;

		if (riid == __uuidof(IUnknown) || riid == __uuidof(vstxlib::VSTNOTIFICATION))
		{
			*object = this;
			F = true;
		}

		if (F)
		{
			AddRef();
			return S_OK;
		}

		return E_NOINTERFACE;
	}

	virtual /* [id] */ HRESULT __stdcall ParameterChanged(
		INT pidx,
		FLOAT)
	{
		return S_OK;
	}

	virtual /* [id] */ HRESULT __stdcall SetDirty(INT)
	{
		return S_OK;
	}

	virtual /* [id] */ HRESULT __stdcall BeforeDelete(
		IUnknown*)
	{
		return S_OK;
	}

	virtual /* [id] */ HRESULT __stdcall ReceiveMidi(
		SAFEARRAY* m)
	{
		return E_NOTIMPL;
	}

	virtual HRESULT __stdcall EditEnded(IUnknown*)
	{
		return S_OK;
	}
};


shared_ptr<WIN> win;
shared_ptr<WOUT> wout;
shared_ptr<CONV> co;
typedef  float SS;
SS** a1 = 0;
SS** a2 = 0;
int BR = 32;
int BS = 480;
int SR = 48000;
HMIDIIN mIN = 0;
int wid = -1;
int wod = -1;
int mid = -1;

auto ff2 = [](const char* d, int sz)
{
	// Convert it
	int fsz = sz / 2;
	vector<float> d2(fsz);
	co->f16t32((const short*)d, fsz, d2.data());

	// Write to channel 1
	memcpy(a1[0], d2.data(), fsz * sizeof(float));

	// Process
	v.readlock([&](const vector<VS>& vv) {

		bool Has = false;
		for (auto& vvv : vv)
		{
			if (vvv.ComType == 0 || vvv.ComType == 1)
			{
				// Direct
				auto e = vvv.vst->InputDirect((char**)a1);
				e = vvv.vst->Process();
				memset(a2[0], 0, fsz * sizeof(float));
				e = vvv.vst->OutputDirect((char**)a2);
				Has = true;
				memcpy(a1[0], a2[0], fsz * sizeof(float));
			}
			else
			{
				if (vvv.sm.length())
				{

				}
				else
				{
					if (vvv.vst2 == 0)
					{
						auto* s = vvv.str.Detach();
						CoGetInterfaceAndReleaseStream(s, vvv.g, (LPVOID*)&vvv.vst2);
					}

					if (vvv.vst2)
					{
						if (vvv.nwin > 0)
						{
							int bs = BS;
							if (BR == 64)
								bs *= sizeof(double);
							else
								bs *= sizeof(float);
							auto rs = vstxlib::CreateDoubleArrayInContinuousMemory(vvv.nwin, bs, 0, 0);
							auto s = SafeArrayCreateVector(VT_I1, 0, rs);
							void* dx1 = 0;
							SafeArrayAccessData(s, &dx1);
							float** f = 0;
							vstxlib::CreateDoubleArrayInContinuousMemory(vvv.nwin, bs, (char*)dx1, (char***)&f);
							memcpy(f[0], a1[0], fsz * sizeof(float));
							SafeArrayUnaccessData(s);
							vvv.vst2->InputArray(s);
							SafeArrayDestroy(s);
						}
						vvv.vst2->Process();
						if (vvv.nwout > 0)
						{
							SAFEARRAY*s = 0;
							vvv.vst2->OutputArray(&s);
							if (s)
							{
								int bs = BS;
								if (BR == 64)
									bs *= sizeof(double);
								else
									bs *= sizeof(float);

								void* dx1 = 0;
								SafeArrayAccessData(s, &dx1);
								float** f = 0;
								vstxlib::CreateDoubleArrayInContinuousMemory(vvv.nwout, bs, (char*)dx1, (char***)&f);
								Has = true;
								memcpy(a2[0], f[0], fsz * sizeof(float));
								SafeArrayUnaccessData(s);
								SafeArrayDestroy(s);
							}
						}
					}
				}

			}
		}
		if (!Has)
			memcpy(a2[0], a1[0], fsz * sizeof(float));

		});


	memcpy(d2.data(), a2[0], fsz * sizeof(float));

	// Output
	vector<short> d4(fsz);
	co->f32t16(d2.data(), fsz, d4.data());
	if (wout)
		wout->Write((const char*)d4.data(), fsz * 2);
};


int ib = 50;
void StartRun()
{
	if (co)
		return;

	co = make_shared<CONV>();
	a1 = new SS*[ib];
	for (int i = 0; i < ib; i++)
	{
		a1[i] = new SS[BS];
		memset(a1[i], 0, BS * sizeof(SS));
	}
	a2 = new SS*[ib];
	for (int i = 0; i < ib; i++)
	{
		a2[i] = new SS[BS];
		memset(a2[i], 0, BS * sizeof(SS));
	}

	v.readlock([](const vector<VS>& vl) {
		for (auto& vv : vl)
		{
			vv.vst->put_SamplingRate(SR);
			vv.vst->put_BufferSize(BS);
		}
		});
}

void EndRun()
{
	if (!co)
		return;
	co = 0;
	for (int i = 0; i < ib; i++)
	{
		delete[] a1[i];
	}
	delete[] a1;
	for (int i = 0; i < ib; i++)
	{
		delete[] a2[i];
	}
	delete[] a2;
	a1 = 0;
	a2 = 0;

}
void StartWin(int wd)
{
	StartRun();
	Ending = false;
	win = make_shared<WIN>(ff2, SR / BS);
	win->Open(wd, SR, 16);
	win->Start();
}

void StartWout(int wd)
{
	wout = make_shared<WOUT>();
	wout->Open(wd, SR, 16);
}

void StopWin()
{
	Ending = true;
	if (win)
		win->Stop();
	win = 0;
}
void StopWout()
{
	wout = 0;
}


wstring OpenSingleFile(HWND hh, const wchar_t* filter, int fidx, const wchar_t* initf, const wchar_t* dir, const wchar_t* title)
{
	OPENFILENAME of = { 0 };
	of.lStructSize = sizeof(of);
	of.hwndOwner = hh;
	of.lpstrFilter = filter;
	of.lpstrInitialDir = dir;
	of.nFilterIndex = fidx;
	vector<wchar_t> fnx(10000);
	of.lpstrFile = fnx.data();
	if (initf)
		wcscpy_s(fnx.data(), 10000, initf);
	of.nMaxFile = 10000;
	of.lpstrTitle = title;
	of.Flags = OFN_FILEMUSTEXIST | OFN_EXPLORER;
	if (!GetOpenFileName(&of))
		return wstring(L"");
	return wstring(fnx.data());
}

void LoadF(CLSID g,HWND hh,wstring& fi,vstxlib::VSTZ* vv,int ComType = 0)
{
	VS vs;
	auto cb = new MyVSTCallback;
	cb->Set(vv);
	auto e = vv->InstallHandler(cb);
	CComBSTR b1, b2;
	e = vv->put_Bitrate(BR);
	e = vv->put_BufferSize(BS);
	e = vv->put_SamplingRate(SR);
	e = vv->Load(_bstr_t(fi.c_str()),0,0);
	e = vv->GetNameAndVendor(&b1, &b2);
	e = vv->Show(0,1,0);
	vv->get_Ports(&vs.nwin, &vs.nwout, &vs.nmin, &vs.nmout);
	auto c32 = vv->CanBitrate(32);
	auto c64 = vv->CanBitrate(64);
	CComBSTR b = 0;
//	char* din = 0;
//	char* dout = 0;
//	vv->CreateBuffers(&b, &din, &dout);
	if (ComType == 2)
		e = vv->CreateBuffers2(&b);
	else
		e = vv->CreateBuffers1();
	e = vv->Resume(0);

/*	vector<vstxlib::VSTPARAMETER> pars;
	vv->ListOfParametersDirect((void*)&pars);
	SAFEARRAY* s = 0;
	vv->ListOfParametersArray(&s);
	SafeArrayDestroy(s);
	*/

	auto m1 = GetSubMenu(GetMenu(hh), 1);
	AppendMenu(m1, MF_STRING, 1001 + v->size(), b1);
	auto m2 = GetSubMenu(GetMenu(hh), 2);
	AppendMenu(m2, MF_STRING, 2001 + v->size(), b1);

	vs.ComType = ComType;
	if (b)
		vs.sm = b;
//	vs.din = din;
//	vs.dout = dout;
	vs.vst = vv;
	vs.g = g;


	// Create also a marshal interface if comtype > 1
	if (ComType >= 2)		CoMarshalInterThreadInterfaceInStream(g, vv, &vs.str);

	v->push_back(vs);

}

#ifdef _WIN64
#define CLSID_VSTXLIBOUTPROC vstxlib::CLSID_VSTXLIBOUTPROC64
#define CLSID_VSTXLIBINPROC vstxlib::CLSID_VSTXLIBINPROC64
#else
#define CLSID_VSTXLIBOUTPROC vstxlib::CLSID_VSTXLIBOUTPROC32
#define CLSID_VSTXLIBINPROC vstxlib::CLSID_VSTXLIBINPROC32
#endif



LRESULT CALLBACK Main_DP(HWND hh, UINT mm, WPARAM ww, LPARAM ll)
	{
	switch (mm)
		{
		case WM_TIMER:
			{
			return 0;
			}
		case WM_CREATE:
			{
			SetTimer(hh, 1, 3000, 0);


			if (true)
			{
				auto m1 = GetSubMenu(GetMenu(hh), 3);
				int nd = waveInGetNumDevs();
				for (int i = 0; i < nd; i++)
				{
					WAVEINCAPS w;
					waveInGetDevCaps(i, &w, sizeof(w));
					AppendMenu(m1, MF_STRING, 10000 + i, w.szPname);
				}
			}
			if (true)
			{
				auto m1 = GetSubMenu(GetMenu(hh), 4);
				int nd = waveOutGetNumDevs();
				for (int i = 0; i < nd; i++)
				{
					WAVEOUTCAPS w;
					waveOutGetDevCaps(i, &w, sizeof(w));
					AppendMenu(m1, MF_STRING, 11000 + i, w.szPname);
				}
			}
			if (true)
			{
				auto m1 = GetSubMenu(GetMenu(hh), 5);
				int nd = midiInGetNumDevs();
				for (int i = 0; i < nd; i++)
				{
					MIDIINCAPS w;
					midiInGetDevCaps(i, &w, sizeof(w));
					AppendMenu(m1, MF_STRING, 12001 + i, w.szPname);
				}
			}
			break;
			}


		case MM_MIM_DATA:
		{
			if (ll == 248)
				return 0;

			vstxlib::MIDI m[1];
			m[0].dwDeltaTime = 0;
			m[0].dwEvent = (DWORD)ll;
			v.readlock([&](const vector<VS>& vv) {

				for (auto& vvv : vv)
				{
					if (vvv.ComType == 0)
						vvv.vst->SendMidiDirect((char*)m, 1,0);
					if (vvv.ComType == 1)
						vvv.vst->SendMidiDirect((char*)m, 1,0);
					if (vvv.ComType == 2)
					{
						auto e = SafeArrayCreateVector(VT_I1, 0, sizeof(vstxlib::MIDI));
						void* d;
						SafeArrayAccessData(e, &d);
						memcpy(d, (char*)m, sizeof(vstxlib::MIDI));
						SafeArrayUnaccessData(e);
						vvv.vst->SendMidiArray(e, 1,0);
						SafeArrayDestroy(e);
					}
				}
				});
			return 0;
		}

		case WM_COMMAND:
			{
			int LW = LOWORD(ww);
			UNREFERENCED_PARAMETER(LW);

			if (LW >= 10000 && LW <= 10999)
			{
				// Wave In
				StopWin();
				if (wid != -1)
					CheckMenuItem(GetMenu(hh), 10000 + wid, MF_UNCHECKED);
				wid = LW - 10000;
				StartWin(wid);
				CheckMenuItem(GetMenu(hh), 10000 + wid, MF_CHECKED);
				return 0;
			}

			if (LW >= 11000 && LW <= 11999)
			{
				// Wave Out
				StopWout();
				if (wod != -1)
					CheckMenuItem(GetMenu(hh), 11000 + wod, MF_UNCHECKED);
				wod = LW - 11000;
				StartWout(wod);
				CheckMenuItem(GetMenu(hh), 11000 + wod, MF_CHECKED);
				return 0;
			}
			if (LW >= 12000 && LW <= 12999)
			{
				if (mIN)
					midiInClose(mIN);
				mIN = 0;
				midiInOpen(&mIN, 0, (DWORD_PTR)hh, 0, CALLBACK_WINDOW);
				if (mIN)
					midiInStart(mIN);
				return 0;
			}

			if (LW >= 1001 && LW <= 2000)
			{
				v.readlock([&](const vector<VS>& d) {
					d[LW - 1001].vst->Show(0,0,0);
					});
				return 0;
			}
			if (LW >= 2001 && LW <= 3000)
			{
				v.writelock([&](vector<VS>& d) {
					d[LW - 2001].vst2.Release();
					d[LW - 2001].vst.Release();
					d.erase(d.begin() + (LW - 2001));
					});
				auto m1 = GetMenu(hh);
				DeleteMenu(m1, LW, MF_BYCOMMAND);
				DeleteMenu(m1, LW - 2001 + 1001, MF_BYCOMMAND);
				return 0;
			}

			static int n8 = 40;
			if (LW == 104 || LW == 105)
			{
				vstxlib::MIDI m[1];
				m[0].dwDeltaTime = 0;
				m[0].dwEvent = 0x7F0090;
				if (LW == 105)
					m[0].dwEvent = 0x000090;
				m[0].dwEvent |= (n8 << 8);
				n8++;
				SendMessage(hh,MM_MIM_DATA, 0,(DWORD_PTR)m[0].dwEvent);
				return 0;
			}

			if (LW == 91)
			{
				ShellExecute(0, L"open", L"vstx32.exe", 0, L".", SW_SHOWNORMAL);
				return 0;
			}
			if (LW == 92)
			{
				ShellExecute(0, L"open", L"vstx64.exe", 0, L".", SW_SHOWNORMAL);
				return 0;
			}
			if (LW == 101 || LW == 102)
			{
				auto fi = OpenSingleFile(hh, L"*.dll,*.vst3\0*.dll;*.vst3\0\0", 0, 0, 0, 0);
				if (fi.empty())
					return 0;

				CComPtr<vstxlib::VSTZ> vv;
#ifdef USING_DLL
				if (LW == 101)
					CreateVST(2, (IUnknown**)&vv);
				else
					CreateVST(3, (IUnknown**)&vv);
#else
				if (LW == 101)
					vstxlib::Create2(GetModuleHandle(0),(IUnknown**)&vv);
				else
					vstxlib::Create3(GetModuleHandle(0),(IUnknown**)&vv);
#endif

				if (vv)
					LoadF(IID_NULL,hh,fi,vv);
				return 0;
			}

			if (LW == 111 || LW == 112)
			{
				auto fi = OpenSingleFile(hh, L"*.dll,*.vst3\0*.dll;*.vst3\0\0", 0, 0, 0, 0);
				if (fi.empty())
					return 0;


				CComPtr<vstxlib::VSTZ> vv;
				CComPtr<IClassFactory> cf;
				auto hr = CoCreateInstance(CLSID_VSTXLIBINPROC, 0, CLSCTX_INPROC_SERVER, __uuidof(IClassFactory), (LPVOID*)&cf);
				if (!cf)
					return 0;
				if (LW == 111)
					hr = cf->CreateInstance(0, __uuidof(vstxlib::VSTZ2), (LPVOID*)&vv);
				else
					hr = cf->CreateInstance(0, __uuidof(vstxlib::VSTZ3), (LPVOID*)&vv);
				if (!vv)
					return 0;
				LoadF(LW == 112 ? __uuidof(vstxlib::VSTZ3) : __uuidof(vstxlib::VSTZ2),hh, fi, vv,1);
				return 0;
			}

			if (LW == 121 || LW == 122 || LW == 123 || LW == 124)
			{
				auto fi = OpenSingleFile(hh, L"*.dll,*.vst3\0*.dll;*.vst3\0\0", 0, 0, 0, 0);
				if (fi.empty())
					return 0;

				CComPtr<vstxlib::VSTZ> vv;
				CComPtr<IClassFactory> cf;
				HRESULT hr = 0;
				if (LW == 121 || LW == 122)
					hr = CoCreateInstance(vstxlib::CLSID_VSTXLIBOUTPROC32, 0, CLSCTX_LOCAL_SERVER, __uuidof(IClassFactory), (LPVOID*)&cf);
				else
					hr = CoCreateInstance(vstxlib::CLSID_VSTXLIBOUTPROC64, 0, CLSCTX_LOCAL_SERVER, __uuidof(IClassFactory), (LPVOID*)&cf);
				
				if (!cf)
					return 0;
				if (LW == 121 || LW == 123)
					hr = cf->CreateInstance(0, __uuidof(vstxlib::VSTZ2), (LPVOID*)&vv);
				else
					hr = cf->CreateInstance(0, __uuidof(vstxlib::VSTZ3), (LPVOID*)&vv);
				if (!vv)
					return 0;
				LoadF((LW == 122 || LW == 124) ? __uuidof(vstxlib::VSTZ3) : __uuidof(vstxlib::VSTZ2),hh, fi, vv, 2);
				return 0;
			}




			return 0;
			}

		case WM_CLOSE:
			{
			StopWin();
			StopWout();
			EndRun();
			DestroyWindow(hh);
			return 0;
			}

		case WM_DESTROY:
			{
			PostQuitMessage(0);
			return 0;
			}
		}
	return DefWindowProc(hh, mm, ww, ll);
	}



int __stdcall WinMain(HINSTANCE h, HINSTANCE, LPSTR, int)
	{
//	double cAngle = 0.1;
	//double sAngle = 0.2;

	/*
	CComPtr<ITypeLib> tlb;
	wstring f;
	auto zr = vstxlib::LoadTheTLB(h, f);
*/

	WSADATA wData;
	WSAStartup(MAKEWORD(2, 2), &wData);
	CoInitializeEx(0, COINIT_APARTMENTTHREADED);
	INITCOMMONCONTROLSEX icex = { 0 };
	icex.dwICC = ICC_LISTVIEW_CLASSES | ICC_DATE_CLASSES | ICC_WIN95_CLASSES;
	icex.dwSize = sizeof(icex);
	InitCommonControlsEx(&icex);
	InitCommonControls();
	hIcon1 = LoadIcon(h, _T("ICON_1"));

	hAppInstance = h;

//	TestRemote();

	WNDCLASSEX wClass = { 0 };
	wClass.cbSize = sizeof(wClass);

	wClass.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_PARENTDC;
	wClass.lpfnWndProc = (WNDPROC)Main_DP;
	wClass.hInstance = h;
	wClass.hIcon = hIcon1;
	wClass.hCursor = LoadCursor(0, IDC_ARROW);
	wClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	wClass.lpszClassName = _T("CLASS");
	wClass.hIconSm = hIcon1;
	RegisterClassEx(&wClass);

	MainWindow = CreateWindowEx(0,
		_T("CLASS"),
		ttitle,
		WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS |
		WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		0,LoadMenu( 0,L"MENU_1"), h, 0);

	ShowWindow(MainWindow, SW_SHOW);


	MSG msg;

	while (GetMessage(&msg, 0, 0, 0))
		{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
		}

	return 0;
}

