#include "headers.hpp"

#include "sfx.hpp"
#include "msg.hpp"
#include "guids.hpp"
#include "utils.hpp"
#include "farutils.hpp"
#include "sysutils.hpp"
#include "common.hpp"
#include "archive.hpp"
#include "ui.hpp"
#include "rsrc.hpp"

class AttachSfxModuleProgress : public ProgressMonitor
{
private:
	std::wstring file_path;
	UInt64 completed;
	UInt64 total;

	void do_update_ui() override
	{
		const unsigned c_width = 60;

		percent_done = calc_percent(completed, total);

		std::wostringstream st;
		st << fit_str(file_path, c_width) << L'\n';
		st << Far::get_progress_bar_str(c_width, percent_done, 100) << L'\n';
		progress_text = st.str();
	}

public:
	AttachSfxModuleProgress(const std::wstring &file_path)
		: ProgressMonitor(Far::get_msg(MSG_PROGRESS_SFX_CONVERT)),
		  file_path(file_path),
		  completed(0),
		  total(0)
	{}

	void set_total(UInt64 size) { total = size; }
	void update_completed(UInt64 size)
	{
		completed += size;
		update_ui();
	}
};

static ByteVector generate_install_config(const SfxInstallConfig &config)
{
	std::wstring text;
	text += L";!@Install@!UTF-8!\n";
	if (!config.title.empty())
		text += L"Title=\"" + config.title + L"\"\n";
	if (!config.begin_prompt.empty())
		text += L"BeginPrompt=\"" + config.begin_prompt + L"\"\n";
	if (!config.progress.empty())
		text += L"Progress=\"" + config.progress + L"\"\n";
	if (!config.run_program.empty())
		text += L"RunProgram=\"" + config.run_program + L"\"\n";
	if (!config.directory.empty())
		text += L"Directory=\"" + config.directory + L"\"\n";
	if (!config.execute_file.empty())
		text += L"ExecuteFile=\"" + config.execute_file + L"\"\n";
	if (!config.execute_parameters.empty())
		text += L"ExecuteParameters=\"" + config.execute_parameters + L"\"\n";
	text += L";!@InstallEnd@!\n";
	std::string utf8_text = unicode_to_ansi(text, CP_UTF8);
	return ByteVector(utf8_text.begin(), utf8_text.end());
}

void create_sfx_module(const std::wstring &file_path, const SfxOptions &sfx_options)
{
	uintptr_t sfx_id = ArcAPI::sfx().find_by_name(sfx_options.name);
	CHECK(sfx_id < ArcAPI::sfx().size());
	std::wstring sfx_path = ArcAPI::sfx()[sfx_id].path;

	File file;
	file.open(file_path, FILE_WRITE_DATA, FILE_SHARE_READ, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL);

	File sfx_file(sfx_path, FILE_READ_DATA, FILE_SHARE_READ, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN);
	Buffer<unsigned char> buf(static_cast<size_t>(sfx_file.size()));
	sfx_file.read(buf.data(), static_cast<unsigned>(buf.size()));
	file.write(buf.data(), buf.size());

	file.close();

	if (sfx_options.replace_icon)
		replace_icon(file_path, sfx_options.icon_path);

	if (sfx_options.replace_version)
		replace_ver_info(file_path, sfx_options.ver_info);

	if (sfx_options.append_install_config) {
		file.open(file_path, FILE_WRITE_DATA, FILE_SHARE_READ, OPEN_EXISTING, 0);
		file.set_pos(0, FILE_END);

		ByteVector install_config = generate_install_config(sfx_options.install_config);
		file.write(install_config.data(), install_config.size());
	}
}

void attach_sfx_module(const std::wstring &file_path, const SfxOptions &sfx_options)
{
	AttachSfxModuleProgress progress(file_path);

	{
		OpenOptions options;
		options.arc_path = file_path;
		options.detect = false;
		options.open_ex = false;
		options.nochain = true;
		options.arc_types.push_back(c_7z);
#if 1
		if (ArcAPI::have_virt_destructor()) {
			std::unique_ptr<Archives<true>> archives(Archive<true>::open(options));
			if (archives->empty())
				FAIL_MSG(Far::get_msg(MSG_ERROR_SFX_CONVERT));
			if (!archives->front()->is_pure_7z())
				FAIL_MSG(Far::get_msg(MSG_ERROR_SFX_CONVERT));
		}
		else {
			std::unique_ptr<Archives<false>> archives(Archive<false>::open(options));
			if (archives->empty())
				FAIL_MSG(Far::get_msg(MSG_ERROR_SFX_CONVERT));
			if (!archives->front()->is_pure_7z())
				FAIL_MSG(Far::get_msg(MSG_ERROR_SFX_CONVERT));
		}
#else
		ArchivesVariant archives;

		if (ArcAPI::have_virt_destructor()) {
			archives = Archive<true>::open(options);
		} else {
			archives = Archive<false>::open(options);
		}

		std::visit([](auto&& archives) {
		    if (archives->empty()) {
		        FAIL_MSG(Far::get_msg(MSG_ERROR_SFX_CONVERT));
		    }
			if (!archives->front()->is_pure_7z()) {
				FAIL_MSG(Far::get_msg(MSG_ERROR_SFX_CONVERT));
		    }
		}, archives);
#endif

	}

	FindData file_data = File::get_find_data(file_path);
	progress.set_total(file_data.size());
	std::wstring dst_path = file_path + c_sfx_ext;
	try {
		create_sfx_module(dst_path, sfx_options);

		File dst_file(dst_path, FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, OPEN_EXISTING, 0);
		dst_file.set_pos(0, FILE_END);

		File src_file(file_path, FILE_READ_DATA, FILE_SHARE_READ, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN);
		Buffer<char> buf(1024 * 1024);
		while (true) {
			size_t size_read = src_file.read(buf.data(), static_cast<unsigned>(buf.size()));
			if (size_read == 0)
				break;
			CHECK(dst_file.write(buf.data(), size_read) == size_read);
			progress.update_completed(size_read);
		}

		File::set_attr(file_path, file_data.dwFileAttributes);
		dst_file.set_time(file_data.ftCreationTime, file_data.ftLastAccessTime, file_data.ftLastWriteTime);
	} catch (...) {
		File::delete_file_nt(dst_path);
		throw;
	}
	File::delete_file_nt(file_path);
}

struct SfxProfile
{
	std::wstring name;
	SfxOptions options;
};

typedef std::vector<SfxProfile> SfxProfiles;

class SfxOptionsDialog : public Far::Dialog
{
private:
	enum
	{
		c_client_xs = 69
	};

	SfxOptions &m_options;
	SfxProfiles profiles;

	int profile_ctrl_id{};
	int module_ctrl_id{};
	int replace_icon_ctrl_id{};
	int icon_path_ctrl_id{};
	int replace_version_ctrl_id{};
	int ver_info_version_ctrl_id{};
	int ver_info_comments_ctrl_id{};
	int ver_info_company_name_ctrl_id{};
	int ver_info_file_description_ctrl_id{};
	int ver_info_legal_copyright_ctrl_id{};
	int ver_info_product_name_ctrl_id{};
	int append_install_config_ctrl_id{};
	int install_config_title_ctrl_id{};
	int install_config_begin_prompt_ctrl_id{};
	int install_config_progress_ctrl_id{};
	int install_config_run_program_ctrl_id{};
	int install_config_directory_ctrl_id{};
	int install_config_execute_file_ctrl_id{};
	int install_config_execute_parameters_ctrl_id{};
	int ok_ctrl_id{};
	int cancel_ctrl_id{};

	void set_control_state()
	{
		DisableEvents de(*this);

		bool replace_icon = get_check(replace_icon_ctrl_id);
		for (int ctrl_id = replace_icon_ctrl_id + 1; ctrl_id < replace_version_ctrl_id; ctrl_id++)
			enable(ctrl_id, replace_icon);

		bool replace_version = get_check(replace_version_ctrl_id);
		for (int ctrl_id = replace_version_ctrl_id + 1; ctrl_id < append_install_config_ctrl_id; ctrl_id++)
			enable(ctrl_id, replace_version);

		unsigned module_id = get_list_pos(module_ctrl_id);
		bool install_config_enabled =
				module_id < ArcAPI::sfx().size() && ArcAPI::sfx()[module_id].install_config();
		enable(append_install_config_ctrl_id, install_config_enabled);

		bool append_install_config = get_check(append_install_config_ctrl_id);
		for (int ctrl_id = append_install_config_ctrl_id + 1;
				ctrl_id <= install_config_execute_parameters_ctrl_id; ctrl_id++)
			enable(ctrl_id, install_config_enabled && append_install_config);
	}

	SfxOptions read_controls()
	{
		SfxOptions options;
		const SfxModules &sfx_modules = ArcAPI::sfx();
		unsigned sfx_id = get_list_pos(module_ctrl_id);
		if (sfx_id >= sfx_modules.size()) {
			FAIL_MSG(Far::get_msg(MSG_SFX_OPTIONS_DLG_WRONG_MODULE));
		}
		options.name = extract_file_name(sfx_modules[sfx_id].path);
		options.replace_icon = get_check(replace_icon_ctrl_id);
		if (options.replace_icon) {
			options.icon_path = get_text(icon_path_ctrl_id);
			if (!File::exists(options.icon_path)) {
				FAIL_MSG(Far::get_msg(MSG_SFX_OPTIONS_DLG_INVALID_ICON_PATH));
			}
		} else
			options.icon_path.clear();
		options.replace_version = get_check(replace_version_ctrl_id);
		if (options.replace_version) {
			options.ver_info.version = get_text(ver_info_version_ctrl_id);
			options.ver_info.comments = get_text(ver_info_comments_ctrl_id);
			options.ver_info.company_name = get_text(ver_info_company_name_ctrl_id);
			options.ver_info.file_description = get_text(ver_info_file_description_ctrl_id);
			options.ver_info.legal_copyright = get_text(ver_info_legal_copyright_ctrl_id);
			options.ver_info.product_name = get_text(ver_info_product_name_ctrl_id);
		} else {
			options.ver_info.version.clear();
			options.ver_info.comments.clear();
			options.ver_info.company_name.clear();
			options.ver_info.file_description.clear();
			options.ver_info.legal_copyright.clear();
			options.ver_info.product_name.clear();
		}
		bool install_config_enabled = sfx_modules[sfx_id].install_config();
		options.append_install_config = install_config_enabled && get_check(append_install_config_ctrl_id);
		if (options.append_install_config) {
			options.install_config.title = get_text(install_config_title_ctrl_id);
			options.install_config.begin_prompt = get_text(install_config_begin_prompt_ctrl_id);
			TriState value = get_check3(install_config_progress_ctrl_id);
			if (value == triUndef)
				options.install_config.progress.clear();
			else
				options.install_config.progress = value == triTrue ? L"yes" : L"no";
			options.install_config.run_program = get_text(install_config_run_program_ctrl_id);
			options.install_config.directory = get_text(install_config_directory_ctrl_id);
			options.install_config.execute_file = get_text(install_config_execute_file_ctrl_id);
			options.install_config.execute_parameters = get_text(install_config_execute_parameters_ctrl_id);
		} else {
			options.install_config.title.clear();
			options.install_config.begin_prompt.clear();
			options.install_config.progress.clear();
			options.install_config.run_program.clear();
			options.install_config.directory.clear();
			options.install_config.execute_file.clear();
			options.install_config.execute_parameters.clear();
		}
		return options;
	}

	void write_controls(const SfxOptions &options)
	{
		DisableEvents de(*this);

		set_list_pos(module_ctrl_id, ArcAPI::sfx().find_by_name(options.name));

		set_check(replace_icon_ctrl_id, options.replace_icon);
		set_text(icon_path_ctrl_id, options.icon_path);

		set_check(replace_version_ctrl_id, options.replace_version);
		set_text(ver_info_product_name_ctrl_id, options.ver_info.product_name);
		set_text(ver_info_version_ctrl_id, options.ver_info.version);
		set_text(ver_info_company_name_ctrl_id, options.ver_info.company_name);
		set_text(ver_info_file_description_ctrl_id, options.ver_info.file_description);
		set_text(ver_info_comments_ctrl_id, options.ver_info.comments);
		set_text(ver_info_legal_copyright_ctrl_id, options.ver_info.legal_copyright);

		set_check(append_install_config_ctrl_id, options.append_install_config);
		set_text(install_config_title_ctrl_id, options.install_config.title);
		set_text(install_config_begin_prompt_ctrl_id, options.install_config.begin_prompt);
		TriState value;
		if (options.install_config.progress == L"yes")
			value = triTrue;
		else if (options.install_config.progress == L"no")
			value = triFalse;
		else
			value = triUndef;
		set_check3(install_config_progress_ctrl_id, value);
		set_text(install_config_run_program_ctrl_id, options.install_config.run_program);
		set_text(install_config_directory_ctrl_id, options.install_config.directory);
		set_text(install_config_execute_file_ctrl_id, options.install_config.execute_file);
		set_text(install_config_execute_parameters_ctrl_id, options.install_config.execute_parameters);
	}

	intptr_t dialog_proc(intptr_t msg, intptr_t param1, void *param2) override
	{
		if (msg == DN_CLOSE && param1 >= 0 && param1 != cancel_ctrl_id) {
			m_options = read_controls();
		} else if (msg == DN_INITDIALOG) {
			set_control_state();
		} else if (msg == DN_BTNCLICK) {
			set_control_state();
		} else if (msg == DN_EDITCHANGE && param1 == module_ctrl_id) {
			set_control_state();
		} else if (msg == DN_EDITCHANGE && param1 == profile_ctrl_id) {
			unsigned profile_idx = get_list_pos(profile_ctrl_id);
			if (profile_idx != (unsigned)-1 && profile_idx < profiles.size()) {
				write_controls(profiles[profile_idx].options);
				set_control_state();
			}
		}

		if (msg == DN_EDITCHANGE || msg == DN_BTNCLICK) {
			unsigned profile_idx = static_cast<unsigned>(profiles.size());
			SfxOptions options;
			bool valid_options = true;
			try {
				options = read_controls();
			} catch (const Error &) {
				valid_options = false;
			}
			if (valid_options) {
				for (unsigned i = 0; i < profiles.size(); i++) {
					if (options == profiles[i].options) {
						profile_idx = i;
						break;
					}
				}
			}
			if (profile_idx != get_list_pos(profile_ctrl_id)) {
				DisableEvents de(*this);
				set_list_pos(profile_ctrl_id, profile_idx);
				set_control_state();
			}
		}

		return default_dialog_proc(msg, param1, param2);
	}

public:
	SfxOptionsDialog(SfxOptions &options, const UpdateProfiles &update_profiles)
		: Far::Dialog(Far::get_msg(MSG_SFX_OPTIONS_DLG_TITLE), &c_sfx_options_dialog_guid, c_client_xs,
				  L"SfxOptions"),
		  m_options(options)
	{
		std::for_each(update_profiles.cbegin(), update_profiles.cend(),
				[&](const UpdateProfile &update_profile) {
					if (update_profile.options.create_sfx) {
						SfxProfile sfx_profile;
						sfx_profile.name = update_profile.name;
						sfx_profile.options = update_profile.options.sfx_options;
						profiles.push_back(sfx_profile);
					}
				});
	}

	bool show()
	{
		label(Far::get_msg(MSG_SFX_OPTIONS_DLG_PROFILE));
		std::vector<std::wstring> profile_names;
		profile_names.reserve(profiles.size() + 1);
		for (unsigned i = 0; i < profiles.size(); i++) {
			profile_names.push_back(profiles[i].name);
		}
		profile_names.emplace_back();
		profile_ctrl_id = combo_box(profile_names, profiles.size(), 30, DIF_DROPDOWNLIST);
		new_line();
		separator();
		new_line();

		label(Far::get_msg(MSG_SFX_OPTIONS_DLG_MODULE));
		std::vector<std::wstring> module_names;
		const SfxModules &sfx_modules = ArcAPI::sfx();
		module_names.reserve(sfx_modules.size() + 1);
		size_t name_width = 0;
		std::for_each(sfx_modules.begin(), sfx_modules.end(), [&](const SfxModule &sfx_module) {
			std::wstring name = sfx_module.description();
			module_names.push_back(name);
			if (name_width < name.size())
				name_width = name.size();
		});
		module_names.emplace_back();
		module_ctrl_id = combo_box(module_names, sfx_modules.find_by_name(m_options.name), name_width + 6,
				DIF_DROPDOWNLIST);
		new_line();

		replace_icon_ctrl_id =
				check_box(Far::get_msg(MSG_SFX_OPTIONS_DLG_REPLACE_ICON), m_options.replace_icon);
		new_line();
		spacer(2);
		label(Far::get_msg(MSG_SFX_OPTIONS_DLG_ICON_PATH));
		icon_path_ctrl_id = history_edit_box(m_options.icon_path, L"arclite.icon_path", AUTO_SIZE,
				DIF_EDITPATH | DIF_SELECTONENTRY);
		new_line();

		size_t label_len = 0;
		std::vector<std::wstring> labels;
		labels.reserve(7);
		labels.emplace_back(Far::get_msg(MSG_SFX_OPTIONS_DLG_VER_INFO_PRODUCT_NAME));
		labels.emplace_back(Far::get_msg(MSG_SFX_OPTIONS_DLG_VER_INFO_VERSION));
		labels.emplace_back(Far::get_msg(MSG_SFX_OPTIONS_DLG_VER_INFO_COMPANY_NAME));
		labels.emplace_back(Far::get_msg(MSG_SFX_OPTIONS_DLG_VER_INFO_FILE_DESCRIPTION));
		labels.emplace_back(Far::get_msg(MSG_SFX_OPTIONS_DLG_VER_INFO_COMMENTS));
		labels.emplace_back(Far::get_msg(MSG_SFX_OPTIONS_DLG_VER_INFO_LEGAL_COPYRIGHT));
		for (unsigned i = 0; i < labels.size(); i++)
			if (label_len < labels[i].size())
				label_len = labels[i].size();
		label_len += 2;
		std::vector<std::wstring>::const_iterator label_text = labels.cbegin();
		replace_version_ctrl_id =
				check_box(Far::get_msg(MSG_SFX_OPTIONS_DLG_REPLACE_VERSION), m_options.replace_version);
		new_line();
		spacer(2);
		label(*label_text++);
		pad(label_len);
		ver_info_product_name_ctrl_id =
				edit_box(m_options.ver_info.product_name, AUTO_SIZE, DIF_SELECTONENTRY);
		new_line();
		spacer(2);
		label(*label_text++);
		pad(label_len);
		ver_info_version_ctrl_id = edit_box(m_options.ver_info.version, AUTO_SIZE, DIF_SELECTONENTRY);
		new_line();
		spacer(2);
		label(*label_text++);
		pad(label_len);
		ver_info_company_name_ctrl_id =
				edit_box(m_options.ver_info.company_name, AUTO_SIZE, DIF_SELECTONENTRY);
		new_line();
		spacer(2);
		label(*label_text++);
		pad(label_len);
		ver_info_file_description_ctrl_id =
				edit_box(m_options.ver_info.file_description, AUTO_SIZE, DIF_SELECTONENTRY);
		new_line();
		spacer(2);
		label(*label_text++);
		pad(label_len);
		ver_info_comments_ctrl_id = edit_box(m_options.ver_info.comments, AUTO_SIZE, DIF_SELECTONENTRY);
		new_line();
		spacer(2);
		label(*label_text++);
		pad(label_len);
		ver_info_legal_copyright_ctrl_id =
				edit_box(m_options.ver_info.legal_copyright, AUTO_SIZE, DIF_SELECTONENTRY);
		new_line();

		label_len = 0;
		labels.clear();
		labels.emplace_back(Far::get_msg(MSG_SFX_OPTIONS_DLG_INSTALL_CONFIG_TITLE));
		labels.emplace_back(Far::get_msg(MSG_SFX_OPTIONS_DLG_INSTALL_CONFIG_BEGIN_PROMPT));
		labels.emplace_back(Far::get_msg(MSG_SFX_OPTIONS_DLG_INSTALL_CONFIG_PROGRESS));
		labels.emplace_back(Far::get_msg(MSG_SFX_OPTIONS_DLG_INSTALL_CONFIG_RUN_PROGRAM));
		labels.emplace_back(Far::get_msg(MSG_SFX_OPTIONS_DLG_INSTALL_CONFIG_DIRECTORY));
		labels.emplace_back(Far::get_msg(MSG_SFX_OPTIONS_DLG_INSTALL_CONFIG_EXECUTE_FILE));
		labels.emplace_back(Far::get_msg(MSG_SFX_OPTIONS_DLG_INSTALL_CONFIG_EXECUTE_PARAMETERS));
		for (unsigned i = 0; i < labels.size(); i++)
			if (label_len < labels[i].size())
				label_len = labels[i].size();
		label_len += 2;
		label_text = labels.cbegin();
		append_install_config_ctrl_id = check_box(Far::get_msg(MSG_SFX_OPTIONS_DLG_APPEND_INSTALL_CONFIG),
				m_options.append_install_config);
		new_line();
		spacer(2);
		label(*label_text++);
		pad(label_len);
		install_config_title_ctrl_id = edit_box(m_options.install_config.title, AUTO_SIZE, DIF_SELECTONENTRY);
		new_line();
		spacer(2);
		label(*label_text++);
		pad(label_len);
		install_config_begin_prompt_ctrl_id =
				edit_box(m_options.install_config.begin_prompt, AUTO_SIZE, DIF_SELECTONENTRY);
		new_line();
		spacer(2);
		label(*label_text++);
		pad(label_len);
		TriState value;
		if (m_options.install_config.progress == L"yes")
			value = triTrue;
		else if (m_options.install_config.progress == L"no")
			value = triFalse;
		else
			value = triUndef;
		install_config_progress_ctrl_id = check_box3(std::wstring(), value);
		new_line();
		spacer(2);
		label(*label_text++);
		pad(label_len);
		install_config_run_program_ctrl_id =
				edit_box(m_options.install_config.run_program, AUTO_SIZE, DIF_SELECTONENTRY);
		new_line();
		spacer(2);
		label(*label_text++);
		pad(label_len);
		install_config_directory_ctrl_id =
				edit_box(m_options.install_config.directory, AUTO_SIZE, DIF_SELECTONENTRY);
		new_line();
		spacer(2);
		label(*label_text++);
		pad(label_len);
		install_config_execute_file_ctrl_id =
				edit_box(m_options.install_config.execute_file, AUTO_SIZE, DIF_SELECTONENTRY);
		new_line();
		spacer(2);
		label(*label_text++);
		pad(label_len);
		install_config_execute_parameters_ctrl_id =
				edit_box(m_options.install_config.execute_parameters, AUTO_SIZE, DIF_SELECTONENTRY);
		new_line();

		separator();
		new_line();
		ok_ctrl_id = def_button(Far::get_msg(MSG_BUTTON_OK), DIF_CENTERGROUP);
		cancel_ctrl_id = button(Far::get_msg(MSG_BUTTON_CANCEL), DIF_CENTERGROUP);
		new_line();

		intptr_t item = Far::Dialog::show();

		return (item != -1) && (item != cancel_ctrl_id);
	}
};

bool sfx_options_dialog(SfxOptions &sfx_options, const UpdateProfiles &update_profiles)
{
	return SfxOptionsDialog(sfx_options, update_profiles).show();
}
