#include "stdafx.h"

HINSTANCE hAppInstance = 0;
#pragma warning(disable:4459)
// ----
const TCHAR* ttitle = _T("Synthesizer");
#ifdef _WIN64
#import "..\\vstx64.dll" raw_interfaces_only
#pragma comment(lib,"..\\vstx64.lib")
#else
#import "..\\vstx32.dll" raw_interfaces_only
#pragma comment(lib,"..\\vstx32.lib")
#endif
#include "..\\vstx.h"

#include "..\\mt\\rw.hpp"
#include "..\\xml\\xml3all.h"
// ----
bool Ending = false;
int SR = 44100;
int BS = 4410;
int BR = 32;
#include "..\\test\\wave.h"



struct VV
{
	CComPtr<vstxlib::VSTZ> v;
	HINSTANCE hDLL = 0;
	char* a1 = 0;
	char* a2 = 0;
};

tlock<vector<VV>> v;
CONV c;
shared_ptr<WOUT> wout;

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 BeforeDelete(
		IUnknown*)
	{
		return S_OK;
	}

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

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

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



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());
}
wstring SaveFile(HWND hh, const wchar_t* filter, int fidx, const wchar_t* initf, const wchar_t* dir, const wchar_t* title, const wchar_t* defext)
{
	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.lpstrDefExt = defext;
	of.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT | OFN_EXPLORER;
	if (!GetSaveFileName(&of))
		return wstring(L"");
	return wstring(fnx.data());
}


HRESULT Add(wstring fi,string c = "")
{
	CComPtr<vstxlib::VSTZ> vv;
	int rv = 0;
	auto hm = vstxlib::LoadTry(fi.c_str(), rv);
	if (rv == 0)
		return S_FALSE;
	if (rv == 1)
	{
		FreeLibrary(hm);
		return S_FALSE;
	}
	if (rv == 2)
		CreateVST(2, (IUnknown**)&vv);
	else
		CreateVST(3, (IUnknown**)&vv);

	if (!vv)
		return S_FALSE;


	auto hr = vv->Load(_bstr_t(fi.c_str()), (ULONG_PTR)hm,0);
	if (FAILED(hr))
		return S_FALSE;

	vv->InstallHandler(new MyVSTCallback());

	INT wi = 0, wo = 0, mi = 0, mo = 0;
	vv->get_Ports(&wi, &wo, &mi, &mo);
	if (mi == 0 || wo == 0)
	{
		MessageBox(0, L"This VST cannot accept midi input!", ttitle, MB_OK);
		return S_FALSE;
	}
	if (c.length())
	{
		XML3::BXML out;
		XML3::Base64ToChar(c.c_str(), c.length(), out);
		std::vector<char> oout(out.size());
		memcpy(oout.data(), out.p(), out.size());
		auto b = vstxlib::VectorToSafeArray<char>(oout);
		vv->put_State(0, b);
	}

	VV a;
	vv->Reconfigure(SR, BS, BR, 0);
	vv->CreateBuffers3(&a.a1,&a.a2);
	vv->Resume(0);
	vv->Show(0, 1,0);

	a.v = vv;
	a.hDLL = hm;
	v->push_back(a);
	return S_FALSE;
}

void Start()
{
	wout = 0;
	wout = make_shared<WOUT>();
	wout->Open(WAVE_MAPPER, SR, 16);
	for (;;)
	{
		float** flastout = 0;
		// Convert
		int fsz = BS;
		vector<float> d2(fsz);
		v.readlock([&](const vector<VV>& v) {
			for (auto e : v)
			{
				// Write to channel 1
				float** fin = (float**)e.a1;
				e.v->Process();
				float** fout = (float**)e.a2;
				memcpy(fin[0], fout[0], fsz * sizeof(float));
				flastout = fout;
			}
			});

		vector<short> d4(fsz * 2);
		if (flastout)
			memcpy(d2.data(), flastout[0], fsz * sizeof(float));
		c.f32t16(d2.data(), fsz, d4.data());
		if (wout)
			wout->Write((const char*)d4.data(), fsz * 2);
	}
}


int Oct = 4;
/*int p()
{

}*/

HWND hS = 0;
LRESULT CALLBACK SYNTH_WP(HWND hh, UINT mm, WPARAM ww, LPARAM ll)
{

	if (mm == 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<VV>& vv) {
			for (auto& vvv : vv)
			{
				vvv.v->SendMidiDirect((char*)m, 1,0);
			}
			});
		return 0;
	}


	if (mm == WM_KEYDOWN || mm == WM_KEYUP)
	{
		int n = -1;
		if (ww == '1') Oct = 1;
		if (ww == '2') Oct = 2;
		if (ww == '3') Oct = 3;
		if (ww == '4') Oct = 4;
		if (ww == '5') Oct = 5;
		if (ww == '6') Oct = 6;
		if (ww == '7') Oct = 7;
		if (ww == '8') Oct = 8;
		if (ww == '9') Oct = 9;
		if (ww == '0') Oct = 0;


		if (ww == 'A') n = 0;
		if (ww == 'W') n = 1;
		if (ww == 'S') n = 2;
		if (ww == 'E') n = 3;
		if (ww == 'D') n = 4;
		if (ww == 'F') n = 5;
		if (ww == 'T') n = 6;
		if (ww == 'G') n = 7;
		if (ww == 'Y') n = 8;
		if (ww == 'H') n = 9;
		if (ww == 'U') n = 10;
		if (ww == 'J') n = 11;
		if (ww == 'K') n = 12;
		int lb = ll & 0xF;
		if (mm == WM_KEYDOWN && lb != 1)
			return 0;
		if (n < 0)
			return 0;
		int Note = Oct * 12 + n;
		v.writelock([&](vector<VV>& v) {
			for (auto e : v)
			{
				vstxlib::MIDI m;
				m.dwEvent = 0x7F0090;
				if (mm == WM_KEYUP)
					m.dwEvent = 0x000090;
				m.dwEvent |= (Note << 8);
				//ActiveNotes[Note] = !ActiveNotes[Note];
				e.v->SendMidiDirect((char*)&m, 1,0);
			}
			});
		return 0;
	}

	switch (mm)
	{
		case WM_CREATE:
		{
			MessageBox(hh, L"Notes ASDFGHJKL, Octave 123456789",0,0);
			break;
		}
		case WM_CLOSE:
		{
			hS = 0;
			DestroyWindow(hh);
			return 0;
		}
	}
	return DefWindowProc(hh,mm,ww,ll);
}

int mid = -1;
HMIDIIN hmid = 0;

int __stdcall WinMain(HINSTANCE h, HINSTANCE, LPSTR, int)
	{
	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();


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

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



	TASKDIALOGCONFIG tc = { 0 };
	tc.cbSize = sizeof(tc);
	tc.hwndParent = 0;
	tc.pszWindowTitle = ttitle;
	//PCWSTR ic2 = TD_INFORMATION_ICON;
	tc.hMainIcon = 0;
	tc.dwFlags = TDF_ENABLE_HYPERLINKS | TDF_CAN_BE_MINIMIZED | TDF_USE_COMMAND_LINKS;

	tc.pszMainInstruction = ttitle;
	tc.pszContent = L"Synthesizer with VST.\r\n";
	tc.dwCommonButtons = 0;
	//	tc.pszFooter = foot().c_str();
	TASKDIALOG_BUTTON b2[8] = { 0 };
	b2[0].pszButtonText = L"Open Setup";
	b2[1].pszButtonText = L"Save Setup";
	b2[2].pszButtonText = L"Add VST";
	b2[3].pszButtonText = L"Edit VST";
	b2[4].pszButtonText = L"Remove VST";
	b2[5].pszButtonText = L"Play (Keyboard)";
	b2[6].pszButtonText = L"Play (MIDI)";
	b2[7].pszButtonText = L"Exit";
	b2[0].nButtonID = 101;
	b2[1].nButtonID = 102;
	b2[2].nButtonID = 103;
	b2[3].nButtonID = 104;
	b2[4].nButtonID = 105;
	b2[5].nButtonID = 106;
	b2[6].nButtonID = 107;
	b2[7].nButtonID = IDCANCEL;

	tc.pButtons = b2;
	tc.cButtons = 8;


	tc.pfCallback = [](_In_ HWND hwnd, _In_ UINT msg, _In_ WPARAM wParam, _In_ LPARAM , _In_ LONG_PTR ) ->HRESULT
	{
		if (msg == TDN_HYPERLINK_CLICKED)
		{
			return S_FALSE;
		}
		if (msg == TDN_CREATED)
		{
			std::thread t(Start);
			t.detach();
		}
		if (msg == TDN_DESTROYED)
		{
		}
		if (msg == TDN_BUTTON_CLICKED)
		{
			if (wParam == 107)
			{
				HMENU m = CreatePopupMenu();
				int l =  midiInGetNumDevs();
				for (int i = 0; i < l; i++)
				{
					MIDIINCAPS c;
					midiInGetDevCaps(i, &c, sizeof(c));
					if (i == mid)
						AppendMenu(m, MF_CHECKED | MF_STRING, i + 1, c.szPname);
					else
						AppendMenu(m, MF_STRING, i + 1, c.szPname);
				}
				POINT p = { 0 };
				GetCursorPos(&p);
				int tcmd = TrackPopupMenu(m, TPM_RETURNCMD | TPM_RIGHTALIGN, p.x, p.y, 0, hwnd, 0);
				if (tcmd == 0)
					return S_FALSE;
				tcmd--;
				if (hmid)
					midiInClose(hmid);
				hmid = 0;
				mid = tcmd;
				if (hS)
					ShowWindow(hS, SW_SHOW);
				else
					hS = CreateWindowEx(0, L"SYNTHCLASS", L"Synthesizer", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwnd, 0, 0, 0);
				midiInOpen(&hmid, 0, (DWORD_PTR)hS, 0, CALLBACK_WINDOW);
				if (hmid)
					midiInStart(hmid);

				return S_FALSE;
			}
			if (wParam == 106)
			{
				if (hS)
					ShowWindow(hS, SW_SHOW);
				else
					hS = CreateWindowEx(0, L"SYNTHCLASS", L"Synthesizer", WS_OVERLAPPEDWINDOW | WS_VISIBLE,  CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwnd, 0, 0, 0);
				return S_FALSE;
			}
			if (wParam == 103)
			{
				auto fi = OpenSingleFile(hwnd, L"*.dll,*.vst3\0*.dll;*.vst3\0\0", 0, 0, 0, 0);
				if (fi.empty())
					return S_FALSE;
				return Add(fi);

//				wout = make_shared<WOUT>(WAVE_MAPPER);
			}
			if (wParam == 101)
			{
				auto fi = OpenSingleFile(hwnd, L"*.xml\0*.xml\0\0", 0, 0, 0, 0);
				if (fi.empty())
					return S_FALSE;
				XML3::XML x(fi.c_str());
				for (auto& a : x.GetRootElement())
				{
					vstxlib::ystring fi2 = a.vv("f");
					Add(fi2,a.vv("c"));
				}

				return S_FALSE;
			}
			if (wParam == 102)
			{
				auto fi = SaveFile(hwnd, L"*.xml\0*.xml\0\0", 0, 0, 0, 0, L"xml");
				if (fi.empty())
					return S_FALSE;
	
				DeleteFile(fi.c_str());
				XML3::XML x(fi.c_str());
				v.readlock([&](const vector<VV>& v) {
					for (auto e : v)
					{
						wchar_t mf[1000] = { 0 };
						GetModuleFileName(e.hDLL, mf, 1000);

						auto& el = x.GetRootElement().AddElement("v");
						el.vv("f").SetValue(vstxlib::ystring(mf));
						SAFEARRAY* b;
						e.v->get_State(0, &b);
						if (b)
						{
							auto v = vstxlib::SafeArrayToVector<>(b);
							SafeArrayDestroy(b);
							auto s = XML3::Char2Base64((const char*)v.data(),v.size());
							el.vv("c").SetValue(s.c_str());
//							el.AddContent(s.c_str(),s.length());
						}

					}
					});
				
				x.Save();
				return S_FALSE;
			}

			if (wParam == 104 || wParam == 105)
			{
				HMENU m = CreatePopupMenu();
				int i = 1;
				v.readlock([&](const vector<VV>& v) {
					for (auto e : v)
					{
						CComBSTR b1, b2;
						e.v->GetNameAndVendor(&b1, &b2);
						AppendMenu(m, MF_STRING, i++, b1);

					}
					});

				POINT p = { 0 };
				GetCursorPos(&p);
				int tcmd = TrackPopupMenu(m, TPM_RETURNCMD | TPM_RIGHTALIGN, p.x, p.y, 0, hwnd,0);
				if (tcmd == 0)
					return S_FALSE;
				tcmd--;
				v.writelock([&](vector<VV>& v) {

					if (wParam == 104)
						v[tcmd].v->Show(0, 1,0);
					if (wParam == 105)
					{
						v.erase(v.begin() + tcmd);
					}

					});

				return S_FALSE;
			}

		}
		return S_OK;
	};


	int rv = 0;
	BOOL ch = 0;
	TaskDialogIndirect(&tc, &rv, 0, &ch);

	return rv;
}