/*
 * xSF - NCSF configuration
 * By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
 * Last modification on 2014-10-15
 *
 * Partially based on the vio*sf framework
 */

#include "XSFConfig_NCSF.h"
#include "convert.h"
#ifdef _DEBUG
# include "resource.h"
# include <CommCtrl.h>
#endif

enum
{
	idInterpolation = 1000,
	idMutes
};

unsigned XSFConfig::initSampleRate = 44100;
std::string XSFConfig::commonName = "NCSF Decoder";
std::string XSFConfig::versionNumber = "1.10.2";
unsigned XSFConfig_NCSF::initInterpolation = 4;
std::string XSFConfig_NCSF::initMutes = "0000000000000000";

XSFConfig *XSFConfig::Create()
{
	return new XSFConfig_NCSF();
}

XSFConfig_NCSF::XSFConfig_NCSF() : XSFConfig(), interpolation(0), mutes()
#ifdef _DEBUG
	, soundViewData()
#endif
{
	this->supportedSampleRates.push_back(8000);
	this->supportedSampleRates.push_back(11025);
	this->supportedSampleRates.push_back(16000);
	this->supportedSampleRates.push_back(22050);
	this->supportedSampleRates.push_back(32000);
	this->supportedSampleRates.push_back(44100);
	this->supportedSampleRates.push_back(48000);
	this->supportedSampleRates.push_back(88200);
	this->supportedSampleRates.push_back(96000);
	this->supportedSampleRates.push_back(176400);
	this->supportedSampleRates.push_back(192000);
}

void XSFConfig_NCSF::LoadSpecificConfig()
{
	this->interpolation = this->configIO->GetValue("Interpolation", XSFConfig_NCSF::initInterpolation);
	std::stringstream mutesSS(this->configIO->GetValue("Mutes", XSFConfig_NCSF::initMutes));
	mutesSS >> this->mutes;
}

void XSFConfig_NCSF::SaveSpecificConfig()
{
	this->configIO->SetValue("Interpolation", this->interpolation);
	this->configIO->SetValue("Mutes", this->mutes.to_string<char>());
}

void XSFConfig_NCSF::GenerateSpecificDialogs()
{
	this->configDialog.AddLabelControl(DialogLabelBuilder(L"Interpolation").WithSize(50, 8).InGroup(L"Output").WithRelativePositionToSibling(RelativePosition::FROM_BOTTOMLEFT, Point<short>(0, 10), 2).IsLeftJustified());
	this->configDialog.AddComboBoxControl(DialogComboBoxBuilder().WithSize(110, 14).InGroup(L"Output").WithRelativePositionToSibling(RelativePosition::FROM_TOPRIGHT, Point<short>(5, -3)).WithID(idInterpolation).
		IsDropDownList().WithTabStop());
	this->configDialog.AddLabelControl(DialogLabelBuilder(L"Mute").WithSize(50, 8).InGroup(L"Output").WithRelativePositionToSibling(RelativePosition::FROM_BOTTOMLEFT, Point<short>(0, 10), 2).IsLeftJustified());
	this->configDialog.AddListBoxControl(DialogListBoxBuilder().WithSize(78, 45).WithExactHeight().InGroup(L"Output").WithRelativePositionToSibling(RelativePosition::FROM_TOPRIGHT, Point<short>(5, -3)).WithID(idMutes).
		WithBorder().WithVerticalScrollbar().WithMultipleSelect().WithTabStop());
}

INT_PTR CALLBACK XSFConfig_NCSF::ConfigDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
		case WM_INITDIALOG:
			// Interpolation
			SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"None"));
			SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Linear"));
			SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"4-point, 3rd-order Legrange"));
			SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"6-point, 5th-order Legrange"));
			SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"16-point Sinc (Lanczos Window)"));
			SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_SETCURSEL, this->interpolation, 0);
			// Mutes
			for (int x = 0, numMutes = this->mutes.size(); x < numMutes; ++x)
			{
				SendMessageW(GetDlgItem(hwndDlg, idMutes), LB_ADDSTRING, 0, reinterpret_cast<LPARAM>((L"SPU " + wstringify(x + 1)).c_str()));
				SendMessageW(GetDlgItem(hwndDlg, idMutes), LB_SETSEL, this->mutes[x], x);
			}
			break;
		case WM_COMMAND:
			break;
	}

	return XSFConfig::ConfigDialogProc(hwndDlg, uMsg, wParam, lParam);
}

void XSFConfig_NCSF::ResetSpecificConfigDefaults(HWND hwndDlg)
{
	SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_SETCURSEL, XSFConfig_NCSF::initInterpolation, 0);
	auto tmpMutes = std::bitset<16>(XSFConfig_NCSF::initMutes);
	for (int x = 0, numMutes = tmpMutes.size(); x < numMutes; ++x)
		SendMessageW(GetDlgItem(hwndDlg, idMutes), LB_SETSEL, tmpMutes[x], x);
}

void XSFConfig_NCSF::SaveSpecificConfigDialog(HWND hwndDlg)
{
	this->interpolation = static_cast<unsigned>(SendMessageW(GetDlgItem(hwndDlg, idInterpolation), CB_GETCURSEL, 0, 0));
	for (int x = 0, numMutes = this->mutes.size(); x < numMutes; ++x)
		this->mutes[x] = !!SendMessageW(GetDlgItem(hwndDlg, idMutes), LB_GETSEL, x, 0);
}

void XSFConfig_NCSF::CopySpecificConfigToMemory(XSFPlayer *xSFPlayer, bool)
{
	auto NCSFPlayer = static_cast<XSFPlayer_NCSF *>(xSFPlayer);
	NCSFPlayer->SetInterpolation(this->interpolation);
	NCSFPlayer->SetMutes(this->mutes);
}

void XSFConfig_NCSF::About(HWND parent)
{
	MessageBoxW(parent, ConvertFuncs::StringToWString(XSFConfig::commonName + " v" + XSFConfig::versionNumber + ", using xSF Winamp plugin framework (based on the vio*sf plugins) by Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]\n\n"
		"Utilizes code adapted from the FeOS Sound System library by fincs, git revision 5204c55 on GitHub, for audio playback.").c_str(), ConvertFuncs::StringToWString(XSFConfig::commonName + " v" + XSFConfig::versionNumber).c_str(), MB_OK);
}

#ifdef _DEBUG
INT_PTR CALLBACK XSFConfig_NCSF::SoundViewDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	auto data = reinterpret_cast<SoundViewData *>(GetWindowLongW(hwndDlg, DWLP_USER));
	if (!data && uMsg != WM_INITDIALOG)
		return false;

	switch (uMsg)
	{
		case WM_INITDIALOG:
			if (!data)
			{
				data = reinterpret_cast<SoundViewData *>(lParam);
				SetWindowLongW(hwndDlg, DWLP_USER, reinterpret_cast<LONG>(data));
			}
			data->hDlg = hwndDlg;
			for (int chanId = 0; chanId < 16; ++chanId)
			{
				SendDlgItemMessageW(hwndDlg, IDC_SOUND0VOLBAR + chanId, PBM_SETRANGE, 0, MAKELPARAM(0, 128));
				SendDlgItemMessageW(hwndDlg, IDC_SOUND0PANBAR + chanId, PBM_SETRANGE, 0, MAKELPARAM(0, 128));
				SendDlgItemMessageW(hwndDlg, IDC_SOUND0MUTE + chanId, BM_SETCHECK, data->config->mutes[chanId], 0);
			}
			break;
		case WM_CLOSE:
		case WM_DESTROY:
			data->config->CloseSoundView();
			break;
		case WM_COMMAND:
			{
				auto id = GET_WM_COMMAND_ID(wParam, lParam);
				switch (id)
				{
					case IDC_BUTTON_VOLMODE:
						data->volModeAlternative = !!IsDlgButtonChecked(hwndDlg, IDC_BUTTON_VOLMODE);
						break;
					case IDC_SOUND0MUTE:
					case IDC_SOUND1MUTE:
					case IDC_SOUND2MUTE:
					case IDC_SOUND3MUTE:
					case IDC_SOUND4MUTE:
					case IDC_SOUND5MUTE:
					case IDC_SOUND6MUTE:
					case IDC_SOUND7MUTE:
					case IDC_SOUND8MUTE:
					case IDC_SOUND9MUTE:
					case IDC_SOUND10MUTE:
					case IDC_SOUND11MUTE:
					case IDC_SOUND12MUTE:
					case IDC_SOUND13MUTE:
					case IDC_SOUND14MUTE:
					case IDC_SOUND15MUTE:
						data->config->mutes[id - IDC_SOUND0MUTE] = !!IsDlgButtonChecked(hwndDlg, id);
						data->config->SaveConfig();
						data->player->SetMutes(data->config->mutes);
						break;
					case IDC_SOUND_UNMUTE_ALL:
						for (int chanId = 0; chanId < 16; ++chanId)
							SendDlgItemMessageW(hwndDlg, IDC_SOUND0MUTE + chanId, BM_SETCHECK, false, 0);
						data->config->mutes.reset();
						data->config->SaveConfig();
						data->player->SetMutes(data->config->mutes);
						break;
					default:
						return false;
				}
			}
			break;
		default:
			return false;
	}

	return true;
}

void XSFConfig_NCSF::CallSoundView(XSFPlayer *xSFPlayer, HINSTANCE hInstance, HWND hwndParent)
{
	this->soundViewData.reset(new SoundViewData);
	this->soundViewData->config = this;
	this->soundViewData->player = static_cast<XSFPlayer_NCSF *>(xSFPlayer);

	auto hDlg = CreateDialogParamW(hInstance, MAKEINTRESOURCEW(IDD_SOUND_VIEW), hwndParent, XSFConfig_NCSF::SoundViewDialogProc, reinterpret_cast<LPARAM>(this->soundViewData.get()));
	if (!hDlg)
	{
		this->soundViewData.reset();
		return;
	}

	ShowWindow(hDlg, SW_SHOW);
	UpdateWindow(hDlg);
}

static inline void ProgressSetPosImmediate(HWND hDlg, int nIDDlgItem, int nPos)
{
	int nMin = SendDlgItemMessageW(hDlg, nIDDlgItem, PBM_GETRANGE, true, reinterpret_cast<LPARAM>(nullptr));
	int nMax = SendDlgItemMessageW(hDlg, nIDDlgItem, PBM_GETRANGE, false, reinterpret_cast<LPARAM>(nullptr));

	// get rid of fancy progress animation since Windows Vista
	// http://stackoverflow.com/questions/1061715/how-do-i-make-tprogressbar-stop-lagging
	if (nPos < nMax)
	{
		SendDlgItemMessageW(hDlg, nIDDlgItem, PBM_SETPOS, nPos + 1, 0);
		SendDlgItemMessageW(hDlg, nIDDlgItem, PBM_SETPOS, nPos, 0); // This will set Progress backwards and give an instant update
	}
	else
	{
		SendDlgItemMessageW(hDlg, nIDDlgItem, PBM_SETRANGE32, nMin, nPos + 1);
		SendDlgItemMessageW(hDlg, nIDDlgItem, PBM_SETPOS, nPos + 1, 0);
		SendDlgItemMessageW(hDlg, nIDDlgItem, PBM_SETRANGE32, nMin, nPos); // This will also set Progress backwards also so instant update
	}
}

static inline int32_t muldiv7(int32_t val, uint8_t mul)
{
	return mul == 127 ? val : ((val * mul) >> 7);
}

void XSFConfig_NCSF::RefreshSoundView()
{
	auto player = this->soundViewData->player;
	auto hDlg = this->soundViewData->hDlg;
	std::wstring buf;
	for (size_t chanId = 0; chanId < 16; ++chanId)
	{
		auto chn = player->GetChannel(chanId);

		if (chn.state > CS_START)
		{
			ProgressSetPosImmediate(hDlg, IDC_SOUND0PANBAR + chanId, muldiv7(128, chn.reg.panning));
			uint8_t datashift = chn.reg.volumeDiv;
			if (datashift == 3)
				datashift = 4;
			int32_t vol = muldiv7(128, chn.reg.volumeMul) >> datashift;
			ProgressSetPosImmediate(hDlg, IDC_SOUND0VOLBAR + chanId, vol);

			if (this->soundViewData->volModeAlternative)
				buf = wstringify(chn.reg.volumeMul) + L"/" + wstringify(1 << datashift);
			else
				buf = wstringify(vol);
			SetDlgItemTextW(hDlg, IDC_SOUND0VOL + chanId, buf.c_str());

			if (!chn.reg.panning)
				buf = L"L";
			else if (chn.reg.panning == 64)
				buf = L"C";
			else if (chn.reg.panning == 127)
				buf = L"R";
			else if (chn.reg.panning < 64)
				buf = L"L" + wstringify(64 - chn.reg.panning);
			else //if (chn.reg.panning > 64)
				buf = L"R" + wstringify(chn.reg.panning - 64);
			SetDlgItemTextW(hDlg, IDC_SOUND0PAN + chanId, buf.c_str());

			SetDlgItemTextW(hDlg, IDC_SOUND0HOLD + chanId, L"---");
			SetDlgItemTextW(hDlg, IDC_SOUND0BUSY + chanId, L"---");

			static const std::wstring modes[] = { L"Manual", L"Loop Infinite", L"One-Shot", L"Prohibited" };
			SetDlgItemTextW(hDlg, IDC_SOUND0REPEATMODE + chanId, (wstringify(chn.reg.repeatMode) + L" (" + modes[chn.reg.repeatMode] + L")").c_str());

			if (chn.reg.format != 3)
			{
				static const std::wstring formats[] = { L"PCM8", L"PCM16", L"IMA-ADPCM" };
				SetDlgItemTextW(hDlg, IDC_SOUND0FORMAT + chanId, (wstringify(chn.reg.format) + L" (" + formats[chn.reg.format] + L")").c_str());
			}
			else
			{
				buf = L"3 (";
				if (chanId < 8)
					buf += L"PSG/Noise?";
				else if (chanId < 14)
					buf += wstringify(chn.reg.waveDuty == 7 ? 0 : 12.5 * (chn.reg.waveDuty + 1)) + L"% Square";
				else
					buf += L"Noise";
				buf += L")";
				SetDlgItemTextW(hDlg, IDC_SOUND0FORMAT + chanId, buf.c_str());
			}

			SetDlgItemTextW(hDlg, IDC_SOUND0SAD + chanId, L"---");

			SetDlgItemTextW(hDlg, IDC_SOUND0PNT + chanId, (L"samp #" + wstringify(chn.reg.loopStart)).c_str());

			std::wstring tmpBuf = ConvertFuncs::StringToWString(NumToHexString(chn.reg.timer)).substr(2);
			buf = L"$" + tmpBuf + L" (";
			tmpBuf = wstringify((ARM7_CLOCK / 2) / static_cast<double>(0x10000 - chn.reg.timer));
			if (tmpBuf.find('.') != std::wstring::npos)
				tmpBuf = tmpBuf.substr(0, tmpBuf.find('.') + 2);
			buf += tmpBuf + L" Hz)";
			SetDlgItemTextW(hDlg, IDC_SOUND0TMR + chanId, buf.c_str());

			SetDlgItemTextW(hDlg, IDC_SOUND0POSLEN + chanId, (L"samp #" + wstringify(static_cast<uint32_t>(chn.reg.samplePosition)) + L" / " + wstringify(chn.reg.totalLength)).c_str());
		}
		else if (this->soundViewData->channelLastStates[chanId] != CS_NONE)
		{
			ProgressSetPosImmediate(hDlg, IDC_SOUND0PANBAR + chanId, 0);
			ProgressSetPosImmediate(hDlg, IDC_SOUND0VOLBAR + chanId, 0);
			SetDlgItemTextW(hDlg, IDC_SOUND0VOL + chanId, L"---");
			SetDlgItemTextW(hDlg, IDC_SOUND0PAN + chanId, L"---");
			SetDlgItemTextW(hDlg, IDC_SOUND0HOLD + chanId, L"---");
			SetDlgItemTextW(hDlg, IDC_SOUND0BUSY + chanId, L"---");
			SetDlgItemTextW(hDlg, IDC_SOUND0REPEATMODE + chanId, L"---");
			SetDlgItemTextW(hDlg, IDC_SOUND0FORMAT + chanId, L"---");
			SetDlgItemTextW(hDlg, IDC_SOUND0SAD + chanId, L"---");
			SetDlgItemTextW(hDlg, IDC_SOUND0PNT + chanId, L"---");
			SetDlgItemTextW(hDlg, IDC_SOUND0TMR + chanId, L"---");
			SetDlgItemTextW(hDlg, IDC_SOUND0POSLEN + chanId, L"---");
		}

		this->soundViewData->channelLastStates[chanId] = chn.state;
	}
}

void XSFConfig_NCSF::CloseSoundView()
{
	if (this->soundViewData)
	{
		DestroyWindow(this->soundViewData->hDlg);
		this->soundViewData.reset();
	}
}
#endif