/*
* SACD Decoder plugin
* Copyright (c) 2011-2023 Maxim V.Anisiutkin <maxim.anisiutkin@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <dsd_processor_service.h>
#include "sacd_preferences.h"
#include "std_service.h"
#include <atldlgs.h>
#include <windowsx.h>

constexpr LPCTSTR FIR_FILTER = _T("Installable FIR (*.txt)\0*.txt\0All Files (*.*)\0*.*\0\0");

constexpr GUID NULL_output_guid = { 0xEEEB07DE, 0xC2C8, 0x44C2, { 0x98, 0x5C, 0xC8, 0x58, 0x56, 0xD9, 0x6D, 0xA1 } };
constexpr GUID skip_output_guids[] = { NULL_output_guid };

constexpr float MIN_VOLUME_ADJ = -20.0f;
constexpr float MAX_VOLUME_ADJ = +20.0f;
constexpr float MIN_TRANSITION = 0.0f;
constexpr float MAX_TRANSITION = 10.0f;
constexpr float MIN_RAMP = 0.0f;
constexpr float MAX_RAMP = 10.0f;

static const GUID g_guid_cfg_outputs = { 0x96991d8b, 0x1c0f, 0x43c4, { 0x96, 0xfe, 0x67, 0x56, 0xb8, 0x1d, 0xd3, 0xb1 } };
static cfg_objList<cfg_output_entry_t> g_cfg_outputs(g_guid_cfg_outputs);

static const GUID g_guid_cfg_converter_mode = { 0x745d7ac3, 0x80c1, 0x41c1, { 0xb9, 0x8, 0xbd, 0x6, 0xd6, 0xa2, 0x87, 0x82 } };
static cfg_int g_cfg_converter_mode(g_guid_cfg_converter_mode, 0);

static const GUID g_guid_cfg_decimation = { 0x508e375e, 0x3847, 0x4acc, { 0xab, 0x98, 0xbc, 0xa5, 0xde, 0x64, 0x2, 0x3f } };
static cfg_int g_cfg_decimation(g_guid_cfg_decimation, 0);

static const GUID g_guid_cfg_user_fir_name = { 0x35f5e4a6, 0x6cb0, 0x4e3e, { 0xb9, 0x6, 0xec, 0x46, 0xfd, 0x61, 0x24, 0x50 } };
static cfg_string g_cfg_user_fir_name(g_guid_cfg_user_fir_name, "");

static const GUID g_guid_cfg_user_fir_coef = { 0x813f1a8c, 0xdd29, 0x4087, { 0x8c, 0x49, 0xcc, 0x5b, 0x3e, 0xc5, 0xd1, 0xc6 } };
static cfg_objList<double> g_cfg_user_fir_coef(g_guid_cfg_user_fir_coef);

static const GUID g_guid_cfg_ramp = { 0x3a01b487, 0x7f96, 0x4f94, { 0xb4, 0xe7, 0x4d, 0x92, 0x70, 0xde, 0x17, 0xa7 } };
static cfg_float g_cfg_ramp(g_guid_cfg_ramp, 0);

static const GUID g_guid_cfg_log_overloads = { 0xfb56f73a, 0x3fbc, 0x420e, { 0xac, 0x28, 0x27, 0xfd, 0xc9, 0x14, 0xc9, 0x1 } };
static cfg_uint g_cfg_log_overloads(g_guid_cfg_log_overloads, BST_UNCHECKED);

static const GUID g_guid_cfg_dsddsp = { 0x2ec4ef87, 0x6890, 0x4e73, { 0x8e, 0x39, 0xd3, 0xb7, 0x6e, 0xb, 0xec, 0x68 } };
static cfg_guid g_cfg_dsddsp(g_guid_cfg_dsddsp, GUID_NULL);

static const GUID g_guid_cfg_area = { 0xa93bec29, 0xc34b, 0x4973, { 0xb7, 0x85, 0xea, 0xc8, 0xf8, 0x4e, 0x1d, 0x83 } };
static cfg_int g_cfg_area(g_guid_cfg_area, 0);

static const GUID g_guid_cfg_editable_tags = { 0x480cf6d8, 0x3512, 0x49ae, { 0xb8, 0xef, 0x3f, 0xe1, 0x84, 0x9c, 0xd7, 0x0 } };
static cfg_uint g_cfg_editable_tags(g_guid_cfg_editable_tags, BST_UNCHECKED);

static const GUID g_guid_cfg_store_tags_with_iso = { 0x4e3f3dd0, 0x7407, 0x4115, { 0xbd, 0xe8, 0x2a, 0x48, 0x2e, 0x71, 0x26, 0x7f } };
static cfg_uint g_cfg_store_tags_with_iso(g_guid_cfg_store_tags_with_iso, BST_UNCHECKED);

static const GUID g_guid_cfg_linked_tags = { 0x94053a83, 0x4d15, 0x4d57,{ 0xa1, 0xa8, 0x83, 0x77, 0xe6, 0x61, 0x4f, 0xf3 } };
static cfg_uint g_cfg_linked_tags(g_guid_cfg_linked_tags, BST_UNCHECKED);

static const GUID g_guid_cfg_emaster = { 0xb9fc1273, 0x2c44, 0x4b00, { 0x93, 0xc3, 0x56, 0x79, 0x79, 0x1a, 0x3a, 0x5f } };
static cfg_uint g_cfg_emaster(g_guid_cfg_emaster, BST_UNCHECKED);

static const GUID g_guid_cfg_std_tags = { 0x174dbf2c, 0x33ed, 0x48b7, { 0xa0, 0xc8, 0x8f, 0x13, 0x88, 0xc0, 0xa5, 0x1a } };
static cfg_uint g_cfg_std_tags(g_guid_cfg_std_tags, BST_CHECKED);

static const GUID g_guid_dop_for_converter = { 0x724178ae, 0x62c2, 0x4595, { 0xb4, 0xf6, 0x55, 0xd7, 0x81, 0xa8, 0x82, 0xbe } };
static cfg_uint g_cfg_dop_for_converter(g_guid_dop_for_converter, BST_UNCHECKED);

static const GUID g_guid_cfg_trace = { 0x21260974, 0x5312, 0x4c3f,{ 0x85, 0x18, 0x32, 0x64, 0xca, 0x6f, 0x73, 0x37 } };
static cfg_uint g_cfg_trace(g_guid_cfg_trace, BST_UNCHECKED);

static const GUID g_guid_converter_thread_count = { 0x9582548d, 0x26bf, 0x48da, { 0x92, 0xc9, 0xdd, 0x5c, 0xba, 0x67, 0x27, 0x70 } };
static t_uint64   g_converter_thread_count = 0;

GUID xor_guids(const GUID& p_guid1, const GUID& p_guid2) {
	GUID guid_xor;
	t_uint8* p_1 = (t_uint8*)&p_guid1;
	t_uint8* p_2 = (t_uint8*)&p_guid2;
	t_uint8* p_x = (t_uint8*)&guid_xor;
	for (auto i = 0u; i < sizeof(GUID); i++) {
		p_x[i] = p_1[i] ^ p_2[i];
	}
	return guid_xor;
}

CSACDPreferences*  CSACDPreferences::s_this = nullptr;
cfg_output_entry_t CSACDPreferences::s_output;

void CSACDPreferences::load() {
	outputCoreConfig_t cfg;
	output_manager::get()->getCoreConfig(cfg);
	s_output = cfg_output_entry_t();
	s_output.m_guid = xor_guids(cfg.m_output, cfg.m_device);
	for (auto i = 0u; i < g_cfg_outputs.get_size(); i++) {
		auto output = g_cfg_outputs.get_item(i);
		if (output.m_guid == s_output.m_guid) {
			s_output = output;
			break;
		}
	}
}

void CSACDPreferences::save() {
	bool found{ false };
	for (auto i = 0u; i < g_cfg_outputs.get_size(); i++) {
		auto output = g_cfg_outputs.get_item(i);
		if (output.m_guid == s_output.m_guid) {
			g_cfg_outputs.replace_item(i, s_output);
			found = true;
			break;
		}
	}
	if (!found) {
		g_cfg_outputs.add_item(s_output);
	}
}

void CSACDPreferences::CSACDPreferences::update() {
	load();
	if (s_this) {
		s_this->update_UI();
	}
}

bool CSACDPreferences::use_dsd_path() {
	return (get_output_type() == 1 || get_output_type() == 2) ? true : false;
}

bool CSACDPreferences::use_pcm_path() {
	return (get_output_type() == 0 || get_output_type() == 2) ? true : false;
}

cfg_output_entry_t& CSACDPreferences::get_output() {
	return s_output;
}

int CSACDPreferences::get_output_type() {
	return s_output.m_type;
}

int CSACDPreferences::get_samplerate() {
	switch (s_output.m_samplerate) {
	case 0:
		return 1 * 44100;
	case 1:
		return 1 * 48000;
	case 2:
		return 2 * 44100;
	case 3:
		return 2 * 48000;
	case 4:
		return 4 * 44100;
	case 5:
		return 4 * 48000;
	case 6:
		return 8 * 44100;
	case 7:
		return 8 * 48000;
	case 8:
		return 16 * 44100;
	case 9:
		return 16 * 48000;
	case 10:
		return 32 * 44100;
	case 11:
		return 32 * 48000;
	case 12:
		return 64 * 44100;
	case 13:
		return 64 * 48000;
	case 14:
		return 128 * 44100;
	case 15:
		return 128 * 48000;
	default:
		return 0;
	}
}

float CSACDPreferences::get_vol_adjust() {
	return s_output.m_vol_adjust;
}

float CSACDPreferences::get_lfe_adjust() {
	switch (s_output.m_lfe_adjust) {
	case 0:
		return -10.0f;
	case 1:
		return 0.0f;
	case 2:
		return +10.0f;
	default:
		return 0.0f;
	}
}

float CSACDPreferences::get_transition() {
	return s_output.m_transition;
}

int CSACDPreferences::get_converter_mode() {
	return g_cfg_converter_mode.get_value();
}

int CSACDPreferences::get_decimation() {
	switch (g_cfg_decimation.get_value()) {
	case 1:
		return 8;
	case 2:
		return 16;
	case 3:
		return 32;
	case 4:
		return 64;
	case 5:
		return 128;
	case 6:
		return 256;
	case 7:
		return 512;
	case 8:
		return 1024;
	}
	return 0;
}

cfg_objList<double>& CSACDPreferences::get_user_fir() {
	return g_cfg_user_fir_coef;
}

bool CSACDPreferences::get_log_overloads() {
	return g_cfg_log_overloads == 1;
}

float CSACDPreferences::get_ramp() {
	return g_cfg_ramp.get_value();
}

GUID CSACDPreferences::get_dsd_processor() {
	return g_cfg_dsddsp;
}

int CSACDPreferences::get_area() {
	return g_cfg_area.get_value();
}

bool CSACDPreferences::get_editable_tags() {
	return g_cfg_editable_tags == 1;
}

bool CSACDPreferences::get_store_tags_with_iso() {
	return g_cfg_store_tags_with_iso == 1;
}

bool CSACDPreferences::get_linked_tags() {
	return g_cfg_linked_tags == 1;
}

bool CSACDPreferences::get_emaster() {
	return g_cfg_emaster == 1;
}

bool CSACDPreferences::get_std_tags() {
	return g_cfg_std_tags == 1;
}

bool CSACDPreferences::get_dop_for_converter() {
	return g_cfg_dop_for_converter == 1;
}

bool CSACDPreferences::get_trace() {
	return g_cfg_trace == 1;
}

int CSACDPreferences::get_converter_thread_count() {
	auto std_service = std::make_unique<service_impl_t<std_service_t>>("foo_converter");
	if (std_service) {
		std_service->get_cfg_var(g_guid_converter_thread_count, g_converter_thread_count);
	}
	return (int)g_converter_thread_count;
}

CSACDPreferences::CSACDPreferences(preferences_page_callback::ptr callback) : m_callback(callback) {
	s_this = nullptr;
	CSeparator::Register();
	load();
}

CSACDPreferences::~CSACDPreferences() {
	s_this = nullptr;
}

t_uint32 CSACDPreferences::get_state() {
	t_uint32 state = preferences_state::resettable;
	if (HasChanged()) {
		state |= preferences_state::changed;
	}
	state |= preferences_state::dark_mode_supported;
	return state;
}

void CSACDPreferences::apply() {
	s_output.m_type = (unsigned)SendDlgItemMessage(IDC_OUTPUT_TYPE_COMBO, CB_GETCURSEL, 0, 0);
	s_output.m_samplerate = (unsigned)SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_GETCURSEL, 0, 0);
	s_output.m_vol_adjust = GetDlgItemFloatValue(IDC_VOLUME_ADJ_COMBO, s_output.m_vol_adjust, MIN_VOLUME_ADJ, MAX_VOLUME_ADJ);
	s_output.m_lfe_adjust = (unsigned)SendDlgItemMessage(IDC_LFE_ADJ_COMBO, CB_GETCURSEL, 0, 0);
	s_output.m_transition = GetDlgItemFloatValue(IDC_TRANSITION_COMBO, s_output.m_transition, MIN_TRANSITION, MAX_TRANSITION);
	g_cfg_converter_mode = (unsigned)SendDlgItemMessage(IDC_CONVERTER_MODE_COMBO, CB_GETCURSEL, 0, 0);
	if ((g_cfg_converter_mode == 4 || g_cfg_converter_mode == 5) && g_cfg_user_fir_coef.get_size() == 0) {
		g_cfg_converter_mode = g_cfg_converter_mode.get_value() - 4;
	}
	g_cfg_decimation = (unsigned)SendDlgItemMessage(IDC_DECIMATION_COMBO, CB_GETCURSEL, 0, 0);
	g_cfg_ramp = GetDlgItemFloatValue(IDC_RAMP_COMBO, g_cfg_ramp.get_value(), MIN_RAMP, MAX_RAMP);
	g_cfg_log_overloads = (unsigned)SendDlgItemMessage(IDC_LOG_OVERLOADS, BM_GETCHECK, 0, 0);
	g_cfg_dsddsp = get_dsddsp_guid_by_item(SendDlgItemMessage(IDC_DSD_PROCESSOR_COMBO, CB_GETCURSEL, 0, 0));
	g_cfg_area = (unsigned)SendDlgItemMessage(IDC_AREA_COMBO, CB_GETCURSEL, 0, 0);
	g_cfg_editable_tags = (unsigned)SendDlgItemMessage(IDC_EDITABLE_TAGS, BM_GETCHECK, 0, 0);
	g_cfg_store_tags_with_iso = (unsigned)SendDlgItemMessage(IDC_STORE_TAGS_WITH_ISO, BM_GETCHECK, 0, 0);
	g_cfg_linked_tags = (unsigned)SendDlgItemMessage(IDC_LINKED_TAGS, BM_GETCHECK, 0, 0);
	g_cfg_emaster = (unsigned)SendDlgItemMessage(IDC_EMASTER, BM_GETCHECK, 0, 0);
	g_cfg_std_tags = (unsigned)SendDlgItemMessage(IDC_STD_TAGS, BM_GETCHECK, 0, 0);
	g_cfg_dop_for_converter = (unsigned)SendDlgItemMessage(IDC_DOP_FOR_CONVERTER, BM_GETCHECK, 0, 0);
	g_cfg_trace = (unsigned)SendDlgItemMessage(IDC_TRACE, BM_GETCHECK, 0, 0);
	update_UI();
	save();
	OnChanged();
}

void CSACDPreferences::reset() {
	g_cfg_outputs.remove_all();
	load();
	SetDlgItemFloatValue(IDC_VOLUME_ADJ_COMBO, s_output.m_vol_adjust, true);
	SetDlgItemFloatValue(IDC_TRANSITION_COMBO, s_output.m_transition, false);
	SetOutputControls();
	g_cfg_converter_mode = 0;
	g_cfg_decimation = 0;
	g_cfg_user_fir_name.reset();
	g_cfg_user_fir_coef.remove_all();
	g_cfg_ramp = 0;
	g_cfg_log_overloads = BST_UNCHECKED;
	g_cfg_dsddsp = GUID_NULL;
	g_cfg_area = 0;
	g_cfg_editable_tags = BST_UNCHECKED;
	g_cfg_store_tags_with_iso = BST_UNCHECKED;
	g_cfg_linked_tags = BST_UNCHECKED;
	g_cfg_emaster = BST_UNCHECKED;
	g_cfg_std_tags = BST_CHECKED;
	g_cfg_dop_for_converter = BST_UNCHECKED;
	g_cfg_trace = BST_UNCHECKED;
	update_UI();
	OnChanged();
}

void CSACDPreferences::CSACDPreferences::update_UI() {
	SendDlgItemMessage(IDC_OUTPUT_TYPE_COMBO, CB_SETCURSEL, s_output.m_type, 0);
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_SETCURSEL, s_output.m_samplerate, 0);
	SendDlgItemMessage(IDC_LFE_ADJ_COMBO, CB_SETCURSEL, s_output.m_lfe_adjust, 0);
	SetDlgItemFloatValue(IDC_VOLUME_ADJ_COMBO, s_output.m_vol_adjust, true);
	SetDlgItemFloatValue(IDC_TRANSITION_COMBO, s_output.m_transition, false);
	SetOutputControls();
	SendDlgItemMessage(IDC_CONVERTER_MODE_COMBO, CB_SETCURSEL, g_cfg_converter_mode.get_value(), 0);
	SendDlgItemMessage(IDC_DECIMATION_COMBO, CB_SETCURSEL, g_cfg_decimation.get_value(), 0);
	SetUserFirState();
	SetDlgItemFloatValue(IDC_RAMP_COMBO, g_cfg_ramp.get_value(), false);
	SendDlgItemMessage(IDC_LOG_OVERLOADS, BM_SETCHECK, g_cfg_log_overloads, 0);
	SendDlgItemMessage(IDC_DSD_PROCESSOR_COMBO, CB_SETCURSEL, get_dsddsp_item_by_guid(g_cfg_dsddsp.get_value()), 0);
	SendDlgItemMessage(IDC_AREA_COMBO, CB_SETCURSEL, g_cfg_area.get_value(), 0);
	SendDlgItemMessage(IDC_EDITABLE_TAGS, BM_SETCHECK, g_cfg_editable_tags, 0);
	GetDlgItem(IDC_STORE_TAGS_WITH_ISO).EnableWindow((BOOL)SendDlgItemMessage(IDC_EDITABLE_TAGS, BM_GETCHECK, 0, 0));
	GetDlgItem(IDC_LINKED_TAGS).EnableWindow((BOOL)SendDlgItemMessage(IDC_EDITABLE_TAGS, BM_GETCHECK, 0, 0));
	SendDlgItemMessage(IDC_STORE_TAGS_WITH_ISO, BM_SETCHECK, g_cfg_store_tags_with_iso, 0);
	SendDlgItemMessage(IDC_LINKED_TAGS, BM_SETCHECK, g_cfg_linked_tags, 0);
	SendDlgItemMessage(IDC_EMASTER, BM_SETCHECK, g_cfg_emaster, 0);
	SendDlgItemMessage(IDC_STD_TAGS, BM_SETCHECK, g_cfg_std_tags, 0);
	SendDlgItemMessage(IDC_DOP_FOR_CONVERTER, BM_SETCHECK, g_cfg_dop_for_converter, 0);
	SendDlgItemMessage(IDC_TRACE, BM_SETCHECK, g_cfg_trace, 0);
}

HFONT CSACDPreferences::create_separator_font() {
	LOGFONT lf;
	if (GetObject(GetFont(), sizeof(lf), &lf)) {
		auto height = (LONG)floor(lf.lfHeight * 1.3 + 0.5);
		if (height == 0) {
			if (lf.lfHeight > 0) {
				height = +1;
			}
			if (lf.lfHeight < 0) {
				height = -1;
			}
		}
		lf.lfHeight = height;
		lf.lfWeight = 700;
		return CreateFontIndirect(&lf);
	}
	return NULL;
}

void CSACDPreferences::translate_string(const char* p_inp, array_t<TCHAR>& p_out) {
	if (p_inp) {
		string8 str = string8(p_inp);
		p_out.set_count(str.get_length() + 1);
#ifdef UNICODE
		convert_utf8_to_wide(p_out.get_ptr(), p_out.get_count(), str.get_ptr(), str.get_length());
#else
		convert_utf8_to_ascii(p_out.get_ptr(), p_out.get_count(), str.get_ptr(), str.get_length());
#endif
	}
	else {
		p_out.set_count(0);
	}
}

t_size CSACDPreferences::get_dsddsp_item_by_guid(GUID p_guid) {
	for (t_size item = 0; item < m_dsddsps.get_count(); item++) {
		if (m_dsddsps[item].m_guid == p_guid) {
			return item;
		}
	}
	return 0;
}

GUID CSACDPreferences::get_dsddsp_guid_by_item(t_size p_item) {
	if (p_item < m_dsddsps.get_count()) {
		return m_dsddsps[p_item].m_guid;
	}
	return GUID_NULL;
}

BOOL CSACDPreferences::OnInitDialog(CWindow, LPARAM) {
	m_dark.AddDialogWithControls(m_hWnd);
	m_separator1.Attach(GetDlgItem(IDC_SEPARATOR1));
	m_separator2.Attach(GetDlgItem(IDC_SEPARATOR2));
	m_separator3.Attach(GetDlgItem(IDC_SEPARATOR3));
	m_separator4.Attach(GetDlgItem(IDC_SEPARATOR4));
	m_separator_font = create_separator_font();
	if (m_separator_font) {
		m_separator1.SetFont(m_separator_font);
		m_separator2.SetFont(m_separator_font);
		m_separator3.SetFont(m_separator_font);
		m_separator4.SetFont(m_separator_font);
	}
 	GetOutputModeList();
	GetSamplerateList();
	GetVolumeList();
	GetLFEAdjustList();
	GetTransitionList();
	GetConverterModeList();
	GetDecimationList();
	GetRampList();
	GetDSDDSPList();
	GetAreaList();
	s_this = this;
	update_UI();
	return FALSE;
}

void CSACDPreferences::OnActivate(UINT, int, CWindow) {
}

void CSACDPreferences::OnDestroy() {
	s_this = nullptr;
}

void CSACDPreferences::OnOutputTypeChange(UINT, int, CWindow) {
	SetOutputControls();
	OnChanged();
}

void CSACDPreferences::OnDoPForConverterClicked(UINT, int, CWindow) {
	OnChanged();
}

void CSACDPreferences::OnVolumeAdjustChange(UINT, int, CWindow) {
	OnChanged();
}

void CSACDPreferences::OnLFEAdjustChange(UINT, int, CWindow) {
	OnChanged();
}

void CSACDPreferences::OnTransitionChange(UINT, int, CWindow) {
	OnChanged();
}

void CSACDPreferences::OnRampChange(UINT, int, CWindow) {
	OnChanged();
}

void CSACDPreferences::OnLogOverloadsClicked(UINT, int, CWindow) {
	OnChanged();
}

void CSACDPreferences::OnSamplerateChange(UINT, int, CWindow) {
	OnChanged();
}

void CSACDPreferences::OnConverterModeChange(UINT, int, CWindow) {
	SetUserFirState();
	OnChanged();
}

void CSACDPreferences::OnDecimationChange(UINT, int, CWindow) {
	OnChanged();
}

void CSACDPreferences::OnLoadFirClicked(UINT, int, CWindow) {
	CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, FIR_FILTER);
	if (dlg.DoModal(dlg) == IDOK) {
		bool fir_name_is_read = false;
		FILE* fir;
		if (_tfopen_s(&fir, dlg.m_szFileName, _T("r")) == 0) {
			g_cfg_user_fir_name.reset();
			g_cfg_user_fir_coef.remove_all();
			while (!feof(fir)) {
				double coef;
				TCHAR str[4096];
				if (_fgetts(str, sizeof(str) / sizeof(*str), fir)) {
					if (_tcslen(str) > 0) {
						if (str[0] == _T('#')) {
							if (_tcslen(str) > 1 && !fir_name_is_read) {
								string_utf8_from_wide(su);
								su = &str[1];
								g_cfg_user_fir_name.set_string(su);
								while (g_cfg_user_fir_name.get_length() > 0 && g_cfg_user_fir_name.get_ptr()[0] == ' ') {
									g_cfg_user_fir_name.remove_chars(0, 1);
								}
								fir_name_is_read = true;
							}
						}
						else {
							if (_stscanf_s(str, _T("%lg"), &coef) == 1) {
								g_cfg_user_fir_coef.add_item(coef);
							}
						}
					}
				}
			}
			fclose(fir);
		}
		string_wide_from_utf8(filter_description);
		if (fir_name_is_read) {
			filter_description = g_cfg_user_fir_name.get_ptr();
		}
		else {
			string_utf8_from_wide(file_path);
			file_path = dlg.m_szFileName;
			g_cfg_user_fir_name.set_string(string_filename_ext(file_path));
		}
		filter_description = g_cfg_user_fir_name.get_ptr();
		SendDlgItemMessage(IDC_INSTALLABLE_FILTER_TEXT, WM_SETTEXT, 0, (LPARAM)filter_description.get_ptr());
	}
	OnChanged();
}

void CSACDPreferences::OnSaveFirClicked(UINT, int, CWindow) {
	CFileDialog dlg(FALSE, _T("txt"), _T("untitledfir"), OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, FIR_FILTER);
	if (dlg.DoModal(dlg) == IDOK) {
		FILE* fir;
		if (_tfopen_s(&fir, dlg.m_szFileName, _T("w")) == 0) {
			auto fir_coefs  = g_cfg_user_fir_coef.get_ptr();
			auto fir_length = g_cfg_user_fir_coef.get_size();
			if (g_cfg_user_fir_name.get_length() > 0) {
				_ftprintf(fir, _T("# %S\n"), g_cfg_user_fir_name.get_ptr());
			}
			for (auto i = 0u; i < fir_length; i++) {
				_ftprintf(fir, _T("%.16lg\n"), fir_coefs[i]);
			}
			fclose(fir);
		}
	}
	OnChanged();
}

void CSACDPreferences::OnAreaChange(UINT, int, CWindow) {
	OnChanged();
}

void CSACDPreferences::OnEditableTagsClicked(UINT, int, CWindow) {
	GetDlgItem(IDC_STORE_TAGS_WITH_ISO).EnableWindow((BOOL)SendDlgItemMessage(IDC_EDITABLE_TAGS, BM_GETCHECK, 0, 0));
	GetDlgItem(IDC_LINKED_TAGS).EnableWindow((BOOL)SendDlgItemMessage(IDC_EDITABLE_TAGS, BM_GETCHECK, 0, 0));
	OnChanged();
}

void CSACDPreferences::OnStoreWithIsoClicked(UINT, int, CWindow) {
	OnChanged();
}

void CSACDPreferences::OnLinkedTagsClicked(UINT, int, CWindow) {
	OnChanged();
}

void CSACDPreferences::OnEMasterClicked(UINT, int, CWindow) {
	OnChanged();
}

void CSACDPreferences::OnStdTagsClicked(UINT, int, CWindow) {
	OnChanged();
}

void CSACDPreferences::OnDSDDSPChange(UINT, int, CWindow) {
	OnChanged();
}

void CSACDPreferences::OnTraceClicked(UINT, int, CWindow) {
	OnChanged();
}

bool CSACDPreferences::HasChanged() {
	if (s_output.m_type != SendDlgItemMessage(IDC_OUTPUT_TYPE_COMBO, CB_GETCURSEL, 0, 0)) {
		return true;
	}
	if (s_output.m_samplerate != SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_GETCURSEL, 0, 0)) {
		return true;
	}
	if (s_output.m_vol_adjust != GetDlgItemFloatValue(IDC_VOLUME_ADJ_COMBO, s_output.m_vol_adjust, MIN_VOLUME_ADJ, MAX_VOLUME_ADJ)) {
		return true;
	}
	if (s_output.m_lfe_adjust != SendDlgItemMessage(IDC_LFE_ADJ_COMBO, CB_GETCURSEL, 0, 0)) {
		return true;
	}
	if (s_output.m_transition != GetDlgItemFloatValue(IDC_TRANSITION_COMBO, s_output.m_transition, MIN_TRANSITION, MAX_TRANSITION)) {
		return true;
	}
	if (g_cfg_converter_mode.get_value() != SendDlgItemMessage(IDC_CONVERTER_MODE_COMBO, CB_GETCURSEL, 0, 0)) {
		return true;
	}
	if (g_cfg_decimation.get_value() != SendDlgItemMessage(IDC_DECIMATION_COMBO, CB_GETCURSEL, 0, 0)) {
		return true;
	}
	if (g_cfg_ramp.get_value() != GetDlgItemFloatValue(IDC_RAMP_COMBO, g_cfg_ramp.get_value(), MIN_RAMP, MAX_RAMP)) {
		return true;
	}
	if (g_cfg_log_overloads.get_value() != SendDlgItemMessage(IDC_LOG_OVERLOADS, BM_GETCHECK, 0, 0)) {
		return true;
	}
	if (g_cfg_area.get_value() != SendDlgItemMessage(IDC_AREA_COMBO, CB_GETCURSEL, 0, 0)) {
		return true;
	}
	if (g_cfg_editable_tags.get_value() != SendDlgItemMessage(IDC_EDITABLE_TAGS, BM_GETCHECK, 0, 0)) {
		return true;
	}
	if (g_cfg_store_tags_with_iso.get_value() != SendDlgItemMessage(IDC_STORE_TAGS_WITH_ISO, BM_GETCHECK, 0, 0)) {
		return true;
	}
	if (g_cfg_linked_tags.get_value() != SendDlgItemMessage(IDC_LINKED_TAGS, BM_GETCHECK, 0, 0)) {
		return true;
	}
	if (g_cfg_emaster.get_value() != SendDlgItemMessage(IDC_EMASTER, BM_GETCHECK, 0, 0)) {
		return true;
	}
	if (g_cfg_std_tags.get_value() != SendDlgItemMessage(IDC_STD_TAGS, BM_GETCHECK, 0, 0)) {
		return true;
	}
	if (g_cfg_dsddsp.get_value() != get_dsddsp_guid_by_item(SendDlgItemMessage(IDC_DSD_PROCESSOR_COMBO, CB_GETCURSEL, 0, 0))) {
		return true;
	}
	if (g_cfg_dop_for_converter.get_value() != SendDlgItemMessage(IDC_DOP_FOR_CONVERTER, BM_GETCHECK, 0, 0)) {
		return true;
	}
	if (g_cfg_trace.get_value() != SendDlgItemMessage(IDC_TRACE, BM_GETCHECK, 0, 0)) {
		return true;
	}
	return false;
}

void CSACDPreferences::OnChanged() {
	m_callback->on_state_changed();
}

void CSACDPreferences::GetOutputModeList() {
	SendDlgItemMessage(IDC_OUTPUT_TYPE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("PCM"));
	SendDlgItemMessage(IDC_OUTPUT_TYPE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("DSD"));
	SendDlgItemMessage(IDC_OUTPUT_TYPE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("DSD+PCM"));
}

void CSACDPreferences::GetVolumeList() {
	SendDlgItemMessage(IDC_VOLUME_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("0"));
	SendDlgItemMessage(IDC_VOLUME_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("+0.5"));
	SendDlgItemMessage(IDC_VOLUME_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("+1"));
	SendDlgItemMessage(IDC_VOLUME_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("+1.5"));
	SendDlgItemMessage(IDC_VOLUME_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("+2"));
	SendDlgItemMessage(IDC_VOLUME_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("+2.5"));
	SendDlgItemMessage(IDC_VOLUME_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("+3"));
	SendDlgItemMessage(IDC_VOLUME_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("+3.5"));
	SendDlgItemMessage(IDC_VOLUME_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("+4"));
	SendDlgItemMessage(IDC_VOLUME_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("+4.5"));
	SendDlgItemMessage(IDC_VOLUME_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("+5"));
	SendDlgItemMessage(IDC_VOLUME_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("+5.5"));
	SendDlgItemMessage(IDC_VOLUME_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("+6"));
}

void CSACDPreferences::GetLFEAdjustList() {
	SendDlgItemMessage(IDC_LFE_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("-10"));
	SendDlgItemMessage(IDC_LFE_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("0"));
	SendDlgItemMessage(IDC_LFE_ADJ_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("+10"));
}

void CSACDPreferences::GetTransitionList() {
	SendDlgItemMessage(IDC_TRANSITION_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("0"));
	SendDlgItemMessage(IDC_TRANSITION_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("0.2"));
	SendDlgItemMessage(IDC_TRANSITION_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("0.5"));
	SendDlgItemMessage(IDC_TRANSITION_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("1"));
}

void CSACDPreferences::GetRampList() {
	SendDlgItemMessage(IDC_RAMP_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("0"));
	SendDlgItemMessage(IDC_RAMP_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("1"));
	SendDlgItemMessage(IDC_RAMP_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("2"));
	SendDlgItemMessage(IDC_RAMP_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("3"));
}

void CSACDPreferences::GetSamplerateList() {
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("44100"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("48000"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("88200"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("96000"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("176400"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("192000"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("352800"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("384000"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[705600]"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[768000]"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[1411200]"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[1536000]"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[2822400]"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[3072000]"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[5644800]"));
	SendDlgItemMessage(IDC_SAMPLERATE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[6144000]"));
}

void CSACDPreferences::GetConverterModeList() {
	SendDlgItemMessage(IDC_DECIMATION_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("Auto"));
	SendDlgItemMessage(IDC_DECIMATION_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("8"));
	SendDlgItemMessage(IDC_DECIMATION_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[16]"));
	SendDlgItemMessage(IDC_DECIMATION_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[32]"));
	SendDlgItemMessage(IDC_DECIMATION_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[64]"));
	SendDlgItemMessage(IDC_DECIMATION_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[128]"));
	SendDlgItemMessage(IDC_DECIMATION_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[256]"));
	SendDlgItemMessage(IDC_DECIMATION_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[512]"));
	SendDlgItemMessage(IDC_DECIMATION_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("[1024]"));
}

void CSACDPreferences::GetDecimationList() {
	SendDlgItemMessage(IDC_CONVERTER_MODE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("Multistage (32fp)"));
	SendDlgItemMessage(IDC_CONVERTER_MODE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("Multistage (64fp)"));
	SendDlgItemMessage(IDC_CONVERTER_MODE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("Direct (32fp, 30kHz lowpass)"));
	SendDlgItemMessage(IDC_CONVERTER_MODE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("Direct (64fp, 30kHz lowpass)"));
	SendDlgItemMessage(IDC_CONVERTER_MODE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("Installable FIR (32fp)"));
	SendDlgItemMessage(IDC_CONVERTER_MODE_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("Installable FIR (64fp)"));
}

void CSACDPreferences::GetAreaList() {
	SendDlgItemMessage(IDC_AREA_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("None"));
	SendDlgItemMessage(IDC_AREA_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("Stereo"));
	SendDlgItemMessage(IDC_AREA_COMBO, CB_ADDSTRING, 0, (LPARAM)_T("Multichannel"));
}

void CSACDPreferences::GetDSDDSPList() {
	service_enum_t<dsd_processor_service> e_dsddsp;
	service_ptr_t<dsd_processor_service> dsddsp;
	m_dsddsps.append_single(cfg_dsddsp_entry_t(GUID_NULL, "None"));
	while (e_dsddsp.next(dsddsp)) {
		m_dsddsps.append_single(cfg_dsddsp_entry_t(dsddsp->get_guid(), dsddsp->get_name()));
	}
	for (t_size i = 0; i < m_dsddsps.get_count(); i++) {
		array_t<TCHAR> dsddsp_name;
		translate_string(m_dsddsps[i].m_name.get_ptr(), dsddsp_name);
		SendDlgItemMessage(IDC_DSD_PROCESSOR_COMBO, CB_ADDSTRING, 0, (LPARAM)dsddsp_name.get_ptr());
	}
}

void CSACDPreferences::SetOutputControls() {
	auto output_mode = (unsigned)SendDlgItemMessage(IDC_OUTPUT_TYPE_COMBO, CB_GETCURSEL, 0, 0);
	BOOL enabled = (output_mode == 0 || output_mode == 2) ? TRUE : FALSE;
	GetDlgItem(IDC_SAMPLERATE_COMBO).EnableWindow(enabled);
	GetDlgItem(IDC_VOLUME_ADJ_COMBO).EnableWindow(enabled);
	GetDlgItem(IDC_LFE_ADJ_COMBO).EnableWindow(enabled);
}

void CSACDPreferences::SetUserFirState() {
	string_wide_from_utf8(sw);
	switch (SendDlgItemMessage(IDC_CONVERTER_MODE_COMBO, CB_GETCURSEL, 0, 0)) {
	case 4:
	case 5:
		GetDlgItem(IDC_DECIMATION_COMBO).EnableWindow(TRUE);
		GetDlgItem(IDC_LOAD_FIR_BUTTON).EnableWindow(TRUE);
		GetDlgItem(IDC_SAVE_FIR_BUTTON).EnableWindow(TRUE);
		sw = g_cfg_user_fir_name.get_ptr();
		SendDlgItemMessage(IDC_INSTALLABLE_FILTER_TEXT, WM_SETTEXT, 0, (LPARAM)sw.get_ptr());
		break;
	default:
		GetDlgItem(IDC_DECIMATION_COMBO).EnableWindow(FALSE);
		GetDlgItem(IDC_LOAD_FIR_BUTTON).EnableWindow(FALSE);
		GetDlgItem(IDC_SAVE_FIR_BUTTON).EnableWindow(FALSE);
		SendDlgItemMessage(IDC_INSTALLABLE_FILTER_TEXT, WM_SETTEXT, 0, (LPARAM)0);
		break;
	}
}

static constexpr int FLOAT_SIZE = 16;

float CSACDPreferences::GetDlgItemFloatValue(int dlgId, float value, float minValue, float maxValue) {
	TCHAR value_str[FLOAT_SIZE];
	GetDlgItemText(dlgId, value_str, FLOAT_SIZE);
	float value_flt{ value };
	_stscanf_s(value_str, _T("%f"), &value_flt);
	if (value_flt < minValue) {
		value_flt = minValue;
	}
	if (value_flt > maxValue) {
		value_flt = maxValue;
	}
	return value_flt;
}

void CSACDPreferences::SetDlgItemFloatValue(int dlgId, float value, bool forceSign) {
	TCHAR value_str[FLOAT_SIZE];
	_stprintf_s(value_str, (value && forceSign) ? _T("%+g") : _T("%g"), value);
	SetDlgItemText(dlgId, value_str);
}

const char* preferences_page_sacd_t::get_name() {
	return "SACD";
}

GUID preferences_page_sacd_t::get_guid() {
	return GUID({ 0x532b5e38, 0x267, 0x4a2d, { 0xa7, 0x89, 0xb0, 0x88, 0x99, 0xd2, 0xb1, 0x48 } });
}

GUID preferences_page_sacd_t::get_parent_guid() {
	return guid_tools;
}

static preferences_page_factory_t<preferences_page_sacd_t> g_preferences_page_sacd_factory;
