/** \file
 * \brief Code that handles the configuration of the extension
 *
 *  \author Copyright (C) 2019  Libor Polcak
 *  \author Copyright (C) 2019  Martin Timko
 *  \author Copyright (C) 2020  Peter Hornak
 *  \author Copyright (C) 2020  Pavel Pohner
 *  \author Copyright (C) 2022  Marek Salon
 *
 *  \license SPDX-License-Identifier: GPL-3.0-or-later
 */
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 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 General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


const MANDATORY_METADATA = ["level_id", "level_text", "level_description"];


function prepare_level_config(action_descr, params) {
	var configuration_area_el = document.getElementById("configuration_area");
	configuration_area_el.textContent = "";
	function find_unsupported_apis(html, wrapper) {
		if (is_api_undefined(wrapper)) {
			return html + `<li> <code>${wrapper}</code>.</li>`;
		}
		return html;
	}
	var unsupported_apis = wrapping_groups.groups.reduce((acc, group) =>
		group.wrappers.reduce(find_unsupported_apis, acc), "");
	if (unsupported_apis !== "") {
		unsupported_apis = `<div class="unsupported_api">${browser.i18n.getMessage("omittedAPIsHeading")} ${unsupported_apis}</div>`;
	}
	var fragment = document.createRange().createContextualFragment(`
<div>
	<div>
	  <h2>${action_descr}</h2>
	</div>
	<p class="alert">
			${browser.i18n.getMessage("newLevelsNotRecommended")}
	</p>
	<form>

		<!-- Metadata -->
		<div class="main-section">
			<label for="level_text">${browser.i18n.getMessage("formlabelName")}</label>
			<input id="level_text" value="${escape(params.level_text)}"></input>
			<input type="hidden" id="level_id" ${params.level_id != "" ? "disabled" : ""} value="${escape(params.level_id)}"></input>
		</div>
		<div class="main-section">
			<label for="level_description">${browser.i18n.getMessage("formlabelDescription")}</label>
			<input id="level_description" value="${escape(params.level_description)}"></input>
		</div>

		<div id="tweaks"></div>
		
		<button id="cancel" class="jsr-button">${browser.i18n.getMessage("ButtonCancel")}</button>
		<button id="save" class="jsr-button">${browser.i18n.getMessage("ButtonSaveCustomLevel")}</button>
	</form>
	${unsupported_apis}
</div>`);
	configuration_area_el.appendChild(fragment);

	delete params["wrappers"];
	let tweaks = Object.assign({}, wrapping_groups.empty_level, params);
	let tweaksContainer = document.getElementById("tweaks");
	let tweaksBusiness = Object.create(tweaks_gui);
	tweaksBusiness.get_current_tweaks = function() {
		let current = Object.assign({}, tweaks);
		for (id of MANDATORY_METADATA) {
			delete current[id];
		}
		return current;
	};
	tweaksBusiness.tweak_changed = function(group_id, desired_tweak) {
		tweaks[group_id] = desired_tweak;
	}
	tweaksBusiness.create_tweaks_html(tweaksContainer);

	document.getElementById("save").addEventListener("click", function(e) {
		e.preventDefault();
		let new_level = tweaks;
		for (id of MANDATORY_METADATA) {
			let elem = document.getElementById(id);
			new_level[id] = elem.value;
		};

		if (new_level.level_id.length > 0 && new_level.level_text.length > 0 && new_level.level_description.length) {
			async function updateLevels(new_level, stored_levels) {
				custom_levels = stored_levels.custom_levels;
				let ok = false;
				if (new_level.level_id in custom_levels) {
					ok = window.confirm(browser.i18n.getMessage("customLevelAlreadyExistsItWillBeOverridden", new_level.level_id));
				}
				else {
					ok = true;
				}
				if (ok) {
					custom_levels[new_level.level_id] = new_level;
					try {
						await browser.storage.sync.set({custom_levels: custom_levels});
						location = "";
					}
					catch (err) {
						alert(browser.i18n.getMessage("customLevelWereNotUpdated"));
					}
				}
			}
			browser.storage.sync.get("custom_levels").then(updateLevels.bind(null, new_level));
		}
		else {
			alert(browser.i18n.getMessage("NewLevelMissingNameOrDescription"));
		}
	});
	document.getElementById("cancel").addEventListener("click", function(e) {
		document.location = "options.html";
	});
}

function edit_level(id) {
	prepare_level_config(browser.i18n.getMessage("JSSeditLevelHeading", escape(levels[id].level_text)), levels[id]);
}

function restore_level(id, level_params) {
	function restoreLevel(settings) {
		var custom_levels = settings.custom_levels;
		custom_levels[id] = level_params;
		browser.storage.sync.set({"custom_levels": custom_levels});
		var existPref = document.getElementById(`li-exist-group-${escape(id)}`);
		existPref.classList.remove("hidden");
		var removedPref = document.getElementById(`li-removed-group-${escape(id)}`);
		removedPref.classList.add("hidden");
		var lielem = document.getElementById(`li-${id}`);
		lielem.classList.remove("undo");
	}
	browser.storage.sync.get("custom_levels").then(restoreLevel);
}

function show_existing_level(levelsEl, level) {
	let currentId = `level-${level}`;
	var fragment = document.createRange().createContextualFragment(`<li id="li-${escape(level)}">
		<button class="level" id="${escape(currentId)}" title="${escape(levels[level].level_description)}">
			${escape(levels[level].level_text)}
		</button><span></span>
		<p class="hidden_help_text"><label class="level_button_descr" for="${escape(currentId)}">${escape(levels[level].level_description)}</label></p>
		</li>`);
	levelsEl.appendChild(fragment);
	var lielem = document.getElementById(`li-${level}`); // Note that FF here requires unescaped ID
	if (levels[level].builtin !== true) {
		var existPref = document.createElement("span");
		existPref.setAttribute("id", `li-exist-group-${escape(level)}`);
		lielem.appendChild(existPref);
		var edit = document.createElement("button");
		existPref.appendChild(edit);
		edit.addEventListener("click", edit_level.bind(edit, level));
		edit.appendChild(document.createTextNode(browser.i18n.getMessage("ButtonEdit")));
		var remove = document.createElement("button");
		existPref.appendChild(remove);
		remove.addEventListener("click", remove_level.bind(remove, level));
		remove.appendChild(document.createTextNode(browser.i18n.getMessage("ButtonRemove")));
		var removedPref = document.createElement("span");
		removedPref.setAttribute("id", `li-removed-group-${escape(level)}`);
		removedPref.classList.add("hidden");
		lielem.appendChild(removedPref);
		var restore = document.createElement("button");
		removedPref.appendChild(restore);
		restore.addEventListener("click", restore_level.bind(restore, level, levels[level]));
		restore.appendChild(document.createTextNode(browser.i18n.getMessage("ButtonRestore")));
	}
	prepareHiddenHelpText(lielem.getElementsByClassName('hidden_help_text'), []);
	var current = document.getElementById(escape(currentId))
	current.addEventListener("click", function() {
		for (let child of levelsEl.children) {
			child.children[0].classList.remove("active");
		}
		this.classList.add("active");
		setDefaultLevel(level);
	});
	current.addEventListener("mouseenter", function() {
		if (level !== default_level.level_id) {
			lev = levels[level];
			update_level_details(lev.level_text + " level (currently not applied by default), details:", lev);
		}
	});
	current.addEventListener("mouseout", update_level_details_default);
}

function remove_level(id) {
	function remove_level(settings) {
		var custom_levels = settings.custom_levels;
		// See https://alistapart.com/article/neveruseawarning/
		var existPref = document.getElementById(`li-exist-group-${escape(id)}`);
		existPref.classList.add("hidden");
		var removedPref = document.getElementById(`li-removed-group-${escape(id)}`);
		removedPref.classList.remove("hidden");
		var lielem = document.getElementById(`li-${id}`);
		lielem.classList.add("undo");
		delete custom_levels[id];
		browser.storage.sync.set({"custom_levels": custom_levels});
	}
	browser.storage.sync.get("custom_levels").then(remove_level);
}

function update_level_details(heading, level) {
	document.getElementById("current-level-tweaks-heading").textContent = heading;
	var currentTweaksEl = document.getElementById("current-level-tweaks");
	let tweaksBusiness = Object.create(tweaks_gui);
	tweaksBusiness.get_current_tweaks = function() {
		return getTweaksForLevel(level.level_id, {});
	};
	tweaksBusiness.create_tweaks_html(currentTweaksEl);
}

function update_level_details_default() {
	update_level_details(default_level.level_text + " level (currently applied by default), details:", default_level);
}

function insert_levels() {
	// Insert all known levels to GUI
	var allLevelsElement = document.getElementById("levels-list");
	for (let level in levels) {
		show_existing_level(allLevelsElement, level);
	}
	// Select default level
	document.getElementById("level-" + default_level.level_id).classList.add("active");
	update_level_details_default();
}

window.addEventListener("load", async function() {
	if (!levels_initialised) {
		levels_updated_callbacks.push(insert_levels);
	}
	else {
		insert_levels();
	}
	await Promise.all([
		load_module_settings("nbs"),
		load_module_settings("fpd")
	])
	loadWhitelist("nbs");
	load_on_off_switch("nbs");
	loadWhitelist("fpd");
	load_on_off_switch("fpd");
});

document.getElementById("new_level").addEventListener("click", function() {
	let new_level = Object.assign({}, wrapping_groups.empty_level);
	let seq = Object.keys(levels).length;
	let new_id;
	do {
		new_id = "Custom" + String(seq);
		seq++;
	}	while (levels[new_id] !== undefined)
	new_level.level_id = new_id;
	prepare_level_config(browser.i18n.getMessage("JSSaddLevelHeading"), new_level)
});

document.getElementById("nbs-whitelist-show").addEventListener("click", () => show_whitelist("nbs"));
document.getElementById("nbs-whitelist-hide").addEventListener("click", () => hide_whitelist("nbs"));
document.getElementById("nbs-whitelist-add-button").addEventListener("click", () => add_to_whitelist("nbs"));
document.getElementById("nbs-whitelist-input").addEventListener('keydown', (e) => {if (e.key === 'Enter') add_to_whitelist("nbs")});
document.getElementById("nbs-whitelist-remove-button").addEventListener("click", () => remove_from_whitelist("nbs"));
document.getElementById("nbs-whitelist-select").addEventListener('keydown', (e) => {if (e.key === 'Delete') remove_from_whitelist("nbs")});
document.getElementsByClassName("slider")[0].addEventListener("click", () => {setTimeout(control_slider, 200, "nbs")});

document.getElementById("fpd-whitelist-show").addEventListener("click", () => show_whitelist("fpd"));
document.getElementById("fpd-whitelist-hide").addEventListener("click", () => hide_whitelist("fpd"));
document.getElementById("fpd-whitelist-add-button").addEventListener("click", () => add_to_whitelist("fpd"));
document.getElementById("fpd-whitelist-input").addEventListener('keydown', (e) => {if (e.key === 'Enter') add_to_whitelist("fpd")});
document.getElementById("fpd-whitelist-remove-button").addEventListener("click", () => remove_from_whitelist("fpd"));
document.getElementById("fpd-whitelist-select").addEventListener('keydown', (e) => {if (e.key === 'Delete') remove_from_whitelist("fpd")});
document.getElementsByClassName("slider")[1].addEventListener("click", () => {setTimeout(control_slider, 200, "fpd")});

async function load_module_settings(prefix) {
	let settings = await browser.runtime.sendMessage({purpose: prefix + "-get-settings"});
	if (settings) {
		let tweaksBusiness = Object.create(tweaks_gui);
		tweaksBusiness.previousValues = new Object();
		tweaksBusiness.tweak_changed = function(key, val) {
			let permissions = settings.def[key].params[val].permissions || [];
			browser.permissions.request({permissions: permissions}).then((granted) => {
				if (granted) {
					tweaksBusiness.previousValues[key] = val;
				}
				else {
					let inputElement = document.querySelector(`#${prefix}-${key}-setting input`);
					inputElement.value = tweaksBusiness.previousValues[key];
					inputElement.dispatchEvent(new Event("input"));
				}
				browser.runtime.sendMessage({purpose: prefix + "-set-settings", id: key, value: tweaksBusiness.previousValues[key]});
			});
		}

		let targetElement = document.getElementById(prefix + "-settings");
		for ([key, setting] of Object.entries(settings.def)) {
			tweaksBusiness.previousValues[key] = settings.val[key];
			tweaksBusiness.add_tweak_row(targetElement, {}, key, settings.val[key], setting.label, setting, true);
		}
	}
}

function show_whitelist(prefix) {
	loadWhitelist(prefix);
	var whitelist = document.getElementById(prefix + "-whitelist-container");
	whitelist.classList.toggle("hidden");
	document.getElementById(prefix + "-whitelist-show").classList.add("hidden");
	document.getElementById(prefix + "-whitelist-hide").classList.remove("hidden");
}

function hide_whitelist(prefix) {
	var whitelist = document.getElementById(prefix + "-whitelist-container");
	whitelist.classList.toggle("hidden");
	document.getElementById(prefix + "-whitelist-show").classList.remove("hidden");
	document.getElementById(prefix + "-whitelist-hide").classList.add("hidden");
}

function add_to_whitelist(prefix)
{	
	//obtain input value
	var to_whitelist = document.getElementById(prefix + "-whitelist-input").value;
	if (to_whitelist.trim() !== '')
	{
		var listbox = document.getElementById(prefix + "-whitelist-select");
		//Check if it's not in whitelist already
		for (var i = 0; i < listbox.length; i++)
		{
			if (to_whitelist == listbox.options[i].text)
			{
				alert("Hostname is already in the whitelist.");
				return;
			}
		}
		//Insert it
		listbox.options[listbox.options.length] = new Option(to_whitelist, to_whitelist);
		//Update background
		update_whitelist(listbox, prefix);

	}
	else
	{
		alert("Please fill in the hostname first.");
	}

}

function remove_from_whitelist(prefix)
{	
	var listbox = document.getElementById(prefix + "-whitelist-select");
	var selectedIndexes = getSelectValues(listbox);

	var j = 0;
	for (var i = 0; i < selectedIndexes.length; i++)
	{
		listbox.remove(selectedIndexes[i]-j);
		j++;
	}
	update_whitelist(listbox, prefix);
}

function update_whitelist(listbox, prefix)
{
	//Create new associative array
	var whitelistedHosts = new Object();
	//Obtain all whitelisted hosts from listbox
	for (var i = 0; i < listbox.length; i++)
	{
		whitelistedHosts[listbox.options[i].text] = true;
	}

	if (prefix == "nbs") setStorageAndSendMessage({"nbsWhitelist":whitelistedHosts}, {message:"whitelist updated"});
	if (prefix == "fpd") setStorageAndSendMessage({"fpdWhitelist":whitelistedHosts}, {purpose:"update-fpd-whitelist"});
}

//Overwrite the whitelist in storage and send message to background
function setStorageAndSendMessage(setter, message)
{
	browser.storage.sync.set(setter);
	browser.runtime.sendMessage(message);
}

//Auxilary function for obtaining selected values from listbox
function getSelectValues(select) 
{
  var result = [];
  var options = select && select.options;
  var opt;

  for (var i=0, iLen=options.length; i<iLen; i++) {
    opt = options[i];

    if (opt.selected) {
      result.push(i);
    }
  }
  return result;
}

//Function called on window load, obtains whitelist from storage
//Displays it in listbox
function loadWhitelist(prefix)
{	
	var listbox = document.getElementById(prefix + "-whitelist-select");
	listbox.options.length = 0;
	
	var whitelistName = prefix + "Whitelist";
	
	//Get the whitelist
	browser.storage.sync.get([whitelistName]).then(function(result)
	{
		if (result[whitelistName] != undefined)
      	{
      		//Create list box options for each item
	        var it = 0;
	        Object.keys(result[whitelistName]).forEach(function(key, index) {
	        	listbox.options[it++] = new Option(key, key);
			}, result[whitelistName]);
  		}
	});
}

//Function called on window load, obtains whether is the protection on or off
function load_on_off_switch(prefix)
{
	var checkbox = document.getElementById(prefix + "-switch");

	var flagName;
	if (prefix == "nbs") flagName = "requestShieldOn";
	if (prefix == "fpd") flagName = "fpDetectionOn";

	//Obtain the information from storage
	browser.storage.sync.get([flagName]).then(function(result)
	{
		//Check the box
		if (result[flagName] || result[flagName] == undefined)
		{
			checkbox.checked = true;
			toggleSettings(prefix, true);
		}
		else
		{
			checkbox.checked = false;
			toggleSettings(prefix, false);
		}
	});
}

//OnClick event handler for On/Off slider
function control_slider(prefix)
{
	var checkbox = document.getElementById(prefix + "-switch");
	//Send appropriate message and store state into storage
	if (checkbox.checked)	//Turn ON
	{
		if (prefix == "nbs") setStorageAndSendMessage({"requestShieldOn": true}, {message:"turn request shield on"});
		if (prefix == "fpd") setStorageAndSendMessage({"fpDetectionOn": true}, {purpose:"fpd-state-change"});
		toggleSettings(prefix, true);
	}
	else
	{
		if (prefix == "nbs") setStorageAndSendMessage({"requestShieldOn": false}, {message:"turn request shield off"});
		if (prefix == "fpd") setStorageAndSendMessage({"fpDetectionOn": false}, {purpose:"fpd-state-change"});
		toggleSettings(prefix, false);
	}
}

function toggleSettings(prefix, enable) {
	tweaksArr = document.querySelectorAll(`#${prefix}-settings input.tlev`);
	tweaksArr.forEach((node) => {
		node.disabled = !enable;
	})
}

function prepareHiddenHelpText(originally_hidden_elements, originally_visible_elements = []) {
	Array.prototype.forEach.call(originally_hidden_elements, it => it.classList.add("hidden_descr"));
	let all_elements = Array.from(originally_hidden_elements).concat(Array.from(originally_visible_elements));
	var ctrl = document.createElement("button");
	ctrl.innerText = "?";
	ctrl.classList.add("help");
	ctrl.addEventListener("click", function(ev) {
		Array.prototype.forEach.call(all_elements, it => it.classList.toggle("hidden_descr"));
		ev.preventDefault();
	});
	originally_hidden_elements[0].previousElementSibling.insertAdjacentElement("beforeend", ctrl);
}

window.addEventListener("DOMContentLoaded", function() {
	function prepareHelpText(prefix) {
		prepareHiddenHelpText(document.getElementsByClassName(prefix + "_description"));
	}

	prepareHelpText("jss");
	prepareHelpText("nbs");
	prepareHelpText("fpd");
});
