#include "stdafx.h"

HINSTANCE hAppInstance = 0;
#pragma warning(disable:4459)
// ----
const TCHAR* ttitle = _T("MP3 Player");
#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"

unsigned long GetNextMP3Frame(unsigned char* dat, unsigned int ds, int& st)
{
	// Find a 0xFF with one with 3 
	for (unsigned int i = st; i < (ds - 1); i++)
	{
		unsigned char d0 = dat[i];
		unsigned char d1 = dat[i + 1];
		//		unsigned char d2 = dat[i + 2];
		//		unsigned char d3 = dat[i + 2];
		if (d0 != 0xFF)
			continue;
		if ((d1 & 0xE0) != 0xE0)
			continue;
		int Ly = ((d1 >> 1) & 3);

		if (Ly != 1)
			continue;

		st = i;

		unsigned int j = 0;
		memcpy(&j, dat + i, 4);
		return j;
	}
	return 0;
}


void ReadMP3Header(FILE* fp, int& sr, int& nCh)
{
	int CP = ftell(fp);
	vector<unsigned char> dat(100000);
	size_t ds = fread(dat.data(), 1, 100000, fp);
	fseek(fp, CP, SEEK_SET);

	if (ds == 0)
		return;


	int st = 0;

	for (int jF = 0; jF < 1; jF++)
	{
		size_t hdr = GetNextMP3Frame(dat.data(), (unsigned int)ds, st);
		if (hdr == 0)
			break; // duh


		// This is it
//		DWORD a = dat[st];
//		DWORD b = dat[st + 1];
		DWORD c = dat[st + 2];
		DWORD d = dat[st + 3];

		st++; // To go to next


		unsigned int SRT = (c & 0xC) >> 2;
		switch (SRT)
		{
		case 0:
			sr = 44100;
			break;
		case 1:
			sr = 48000;
			break;
		case 2:
			sr = 32000;
			break;
		}

		unsigned int CHT = (d & 0xC0) >> 6;
		switch (CHT)
		{
		case 3:
			nCh = 1;
			break;
		default:
			nCh = 2;
			break;
		}
	}

}


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;


#define MP3_BLOCK_SIZE 522
void StartMP3(wstring fi)
{
	FILE* fp = 0;
	_wfopen_s(&fp, fi.c_str(), L"rb");
	if (!fp)
		return;
	int nCh = 0;
	ReadMP3Header(fp, SR, nCh);
	BS = SR / 10;
	BR = 32;

	wout = 0;
	wout = make_shared<WOUT>();
	wout->Open(WAVE_MAPPER, SR,16);
	v.writelock([&](vector<VV>& v) {
		for (auto& e : v)
		{
			e.v->Reconfigure(SR, BS, BR, 0);
			e.v->CreateBuffers3(&e.a1, &e.a2);
			e.v->Resume(1);
		}	
		});

	vector<char> ww(1000);
	WAVEFORMATEX* w2 = (WAVEFORMATEX*)ww.data();
	WAVEFORMATEX& w = *w2;
	w.wFormatTag = WAVE_FORMAT_MPEGLAYER3;
	//		SR = 44100;


	w.cbSize = MPEGLAYER3_WFX_EXTRA_BYTES;
	w.wFormatTag = WAVE_FORMAT_MPEGLAYER3;
	w.nChannels = (WORD)nCh;
	w.nAvgBytesPerSec = 128 * (1024 / 8);  // not really used but must be one of 64, 96, 112, 128, 160kbps
	w.wBitsPerSample = 0;                  // MUST BE ZERO
	w.nBlockAlign = 1;                     // MUST BE ONE
	w.nSamplesPerSec = SR;              // 44.1kHz
	MPEGLAYER3WAVEFORMAT* mp3format = (MPEGLAYER3WAVEFORMAT*)&w;
	mp3format->fdwFlags = MPEGLAYER3_FLAG_PADDING_OFF;
	mp3format->nBlockSize = MP3_BLOCK_SIZE;             // voodoo value #1
	mp3format->nFramesPerBlock = 1;                     // MUST BE ONE
	mp3format->nCodecDelay = 1393;                      // voodoo value #2
	mp3format->wID = MPEGLAYER3_ID_MPEG;

	// This is a raw stream, based on ACM, so start decompression
	vector<char> e(1000);
	WAVEFORMATEX * wDest = (WAVEFORMATEX*)e.data();
	wDest->wFormatTag = WAVE_FORMAT_PCM;
	wDest->nSamplesPerSec = w.nSamplesPerSec;
	wDest->nChannels = 1;
	wDest->wBitsPerSample = 16;
	auto f = acmFormatSuggest(0, &w, wDest, 1000, ACM_FORMATSUGGESTF_NSAMPLESPERSEC | ACM_FORMATSUGGESTF_WFORMATTAG | ACM_FORMATSUGGESTF_NCHANNELS | ACM_FORMATSUGGESTF_WBITSPERSAMPLE);
	if (f)
		return;
	SR = wDest->nSamplesPerSec;
	BS = SR / 10;

	HACMSTREAM has = 0;
	f = acmStreamOpen(&has, 0, &w, wDest, 0, 0, 0, 0);
	if (f)
		return;

	DWORD InputSize = 3145728;
	InputSize = MP3_BLOCK_SIZE;//*2000;
	InputSize *= 100;

	DWORD OutputSize = 0;
	acmStreamSize(has, InputSize, &OutputSize, ACM_STREAMSIZEF_SOURCE);

	vector<char> in(InputSize + 1);
	vector<char> out(OutputSize + 1);
	ACMSTREAMHEADER ash = { 0 };
	ash.cbStruct = sizeof(ash);
	ash.pbSrc = (LPBYTE)in.data();
	ash.cbSrcLength = InputSize;
	ash.pbDst = (LPBYTE)out.data();
	ash.cbDstLength = OutputSize;
	f = acmStreamPrepareHeader(has, &ash, 0);

	vector<char> dx;
	size_t BytesRead = 0;
	for (;;)
	{
		size_t FBR = fread(in.data(), 1, InputSize, fp);
		if (FBR == 0)
			break;
		BytesRead += FBR;
		ash.cbSrcLength = (DWORD)FBR;
		f = acmStreamConvert(has, &ash, 0);

		if (ash.cbSrcLengthUsed != ash.cbSrcLength)
			fseek(fp, (signed)(ash.cbSrcLengthUsed - ash.cbSrcLength), SEEK_CUR);

		auto ds = dx.size();
		dx.resize(ds + ash.cbDstLengthUsed);
		memcpy(dx.data() + ds, out.data(), ash.cbDstLengthUsed);

		
		for (;;)
		{
			if (dx.size() < (size_t)(BS*2))
				break;

			// Convert
			int fsz = BS;
			vector<float> d2(fsz);
			c.f16t32((const short*)dx.data(), fsz, d2.data());

			bool Has = false;

			float** flastout = 0;
			v.readlock([&](const vector<VV>& v) {
				for (auto e : v)
				{
					// Write to channel 1
					float** fin = (float**)e.a1;
					memcpy(fin[0], d2.data(), BS*sizeof(float));
					e.v->Process();
					Has = true;
					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);

			dx.erase(dx.begin(), dx.begin() + BS*2);
		}

	}

	acmStreamUnprepareHeader(has, &ash, 0);
	acmStreamClose(has, 0);
	fclose(fp);

}

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;

	INT wi = 0, wo = 0, mi = 0, mo = 0;
	vv->get_Ports(&wi, &wo, &mi, &mo);
	if (wi == 0 || wo == 0)
	{
		MessageBox(0, L"This VST cannot accept audio 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;
}


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


	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"MP3 player with VST.\r\n";
	tc.dwCommonButtons = 0;
	//	tc.pszFooter = foot().c_str();
	TASKDIALOG_BUTTON b2[7] = { 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"Select MP3";
	b2[6].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 = IDCANCEL;

	tc.pButtons = b2;
	tc.cButtons = 7;




	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_BUTTON_CLICKED)
		{
			if (wParam == 106)
			{
				auto fi = OpenSingleFile(hwnd, L"*.mp3\0*.mp3\0\0", 0, 0, 0, 0);
				if (fi.empty())
					return S_FALSE;

				std::thread t(StartMP3,fi);
				t.detach();
				return S_FALSE;


				//				wout = make_shared<WOUT>(WAVE_MAPPER);
			}
			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((ULONG_PTR)hwnd, 1,1);
					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;
}