// license:BSD-3-Clause
// copyright-holders:Vas Crabb
'use strict';

var slot_info = Object.create(null);
var bios_sets = Object.create(null);
var machine_flags = Object.create(null);
var softwarelist_info = Object.create(null);


function make_slot_popup_id(name) { return ('select-slot-choice-' + name).replace(/:/g, '-'); }
function get_slot_popup(name) { return document.getElementById(make_slot_popup_id(name)); }


function update_cmd_preview()
{
	var inifmt = document.getElementById('select-options-format').value == 'ini';
	var result = '';
	var first = true;
	function add_option(flag, value)
	{
		if (first)
			first = false;
		else if (inifmt)
			result += '\n';
		else
			result += ' ';

		if (inifmt)
		{
			result += flag + ' ';
			if (flag.length < 25)
				result += ' '.repeat(25 - flag.length);
			result += value;
		}
		else
		{
			result += '-' + flag + ' ';
			if (value == '')
				result += '""';
			else
				result += value;
		}
	}
	var elide_defaults = !document.getElementById('check-explicit-defaults').checked;

	// add system BIOS if applicable
	var sysbios = document.getElementById('select-system-bios');
	if (sysbios && (!elide_defaults || (sysbios.selectedOptions[0].getAttribute('data-isdefault') != 'yes')))
		add_option('bios', sysbios.value);

	// add RAM option if applicable
	var ramopt = document.getElementById('select-ram-option');
	if (ramopt && (!elide_defaults || (ramopt.selectedOptions[0].getAttribute('data-isdefault') != 'yes')))
		add_option('ramsize', ramopt.value);

	var slotslist = document.getElementById('list-slot-options');
	if (slotslist)
	{
		for (var item = slotslist.firstChild; item; item = item.nextSibling)
		{
			if (item.nodeName == 'DT')
			{
				// need to set the slot option if it has non-default card and/or non-default card BIOS
				var slotname = item.getAttribute('data-slotname');
				var selection = get_slot_popup(slotname).selectedOptions[0];
				var biospopup = document.getElementById(('select-slot-bios-' + slotname).replace(/:/g, '-'));
				var defcard = selection.getAttribute('data-isdefault') == 'yes';
				var defbios = !biospopup || (biospopup.selectedOptions[0].getAttribute('data-isdefault') == 'yes');
				if (!elide_defaults || !defcard || !defbios)
				{
					var card = selection.value;
					add_option(slotname, card + ((biospopup && (!elide_defaults || !defbios)) ? (',bios=' + biospopup.value) : ''));
				}
			}
		}
	}

	// replace the preview with appropriate element
	var target = document.getElementById('para-cmd-preview');
	var replacement = document.createElement(inifmt ? 'pre' : 'tt');
	replacement.setAttribute('id', 'para-cmd-preview');
	replacement.textContent = result;
	target.parentNode.replaceChild(replacement, target);
}


function set_default_system_bios()
{
	// search for an explicit default option
	var sysbios = document.getElementById('select-system-bios');
	var len = sysbios.options.length;
	for (var i = 0; i < len; i++)
	{
		if (sysbios.options[i].getAttribute('data-isdefault') == 'yes')
		{
			// select it and add a button for restoring it
			sysbios.selectedIndex = i;
			var dflt = make_restore_default_button('default', 'btn-def-system-bios', sysbios, i);
			sysbios.onchange = make_slot_bios_change_handler(dflt);
			sysbios.parentNode.appendChild(document.createTextNode(' '));
			sysbios.parentNode.appendChild(dflt);
			break;
		}
	}
	update_cmd_preview();
}


function set_default_ram_option()
{
	// search for an explicit default option
	var ramopt = document.getElementById('select-ram-option');
	var len = ramopt.options.length;
	for (var i = 0; i < len; i++)
	{
		if (ramopt.options[i].getAttribute('data-isdefault') == 'yes')
		{
			// select it and add a button for restoring it
			ramopt.selectedIndex = i;
			var dflt = make_restore_default_button('default', 'btn-def-ram-option', ramopt, i);
			ramopt.onchange = make_slot_bios_change_handler(dflt);
			ramopt.parentNode.appendChild(document.createTextNode(' '));
			ramopt.parentNode.appendChild(dflt);
			break;
		}
	}
	update_cmd_preview();
}


var fetch_bios_sets = (function ()
		{
			var pending = Object.create(null);
			return function (device)
			{
				if (!Object.prototype.hasOwnProperty.call(bios_sets, device) && !Object.prototype.hasOwnProperty.call(pending, device))
				{
					pending[device] = true;
					var req = new XMLHttpRequest();
					req.open('GET', appurl + 'rpc/bios/' + encodeURIComponent(device), true);
					req.responseType = 'json';
					req.onload =
							function ()
							{
								delete pending[device];
								if (req.status == 200)
								{
									bios_sets[device] = req.response;
									var slotslist = document.getElementById('list-slot-options');
									if (slotslist)
									{
										for (var item = slotslist.firstChild; item; item = item.nextSibling)
										{
											if ((item.nodeName == 'DT') && (item.getAttribute('data-slotcard') == device))
												add_bios_row(item.getAttribute('data-slotname'), item.nextSibling.firstChild, device);
										}
									}
								}
							};
					req.send();
				}
			};
		})();


var fetch_machine_flags = (function ()
		{
			var pending = Object.create(null);
			return function (device)
			{
				if (!Object.prototype.hasOwnProperty.call(machine_flags, device) && !Object.prototype.hasOwnProperty.call(pending, device))
				{
					pending[device] = true;
					var req = new XMLHttpRequest();
					req.open('GET', appurl + 'rpc/flags/' + encodeURIComponent(device), true);
					req.responseType = 'json';
					req.onload =
							function ()
							{
								delete pending[device];
								if (req.status == 200)
								{
									machine_flags[device] = req.response;
									var slotslist = document.getElementById('list-slot-options');
									if (slotslist)
									{
										for (var item = slotslist.firstChild; item; item = item.nextSibling)
										{
											if ((item.nodeName == 'DT') && (item.getAttribute('data-slotcard') == device))
												add_flag_rows(item.nextSibling.firstChild, device);
										}
									}
								}
							};
					req.send();
				}
			};
		})();


var fetch_softwarelist_info = (function ()
		{
			var pending = Object.create(null);
			return function (device)
			{
				if (!Object.prototype.hasOwnProperty.call(softwarelist_info, device) && !Object.prototype.hasOwnProperty.call(pending, device))
				{
					pending[device] = true;
					var req = new XMLHttpRequest();
					req.open('GET', appurl + 'rpc/softwarelists/' + encodeURIComponent(device), true);
					req.responseType = 'json';
					req.onload =
							function ()
							{
								delete pending[device];
								if (req.status == 200)
								{
									softwarelist_info[device] = req.response;
									var slotslist = document.getElementById('list-slot-options');
									if (slotslist)
									{
										for (var item = slotslist.firstChild; item; item = item.nextSibling)
										{
											if ((item.nodeName == 'DT') && (item.getAttribute('data-slotcard') == device))
											{
												var slotname = item.getAttribute('data-slotname');
												add_softwarelist_rows(
														device,
														slotname,
														document.getElementById('select-slot-choice-' + slotname.replace(/:/g, '-')).value);
											}
										}
									}
								}
							};
					req.send();
				}
			};
		})();


function add_flag_rows(table, device)
{
	var sorted_features = Object.keys(machine_flags[device].features).sort();
	var imperfect = [], unemulated = [];
	var len = sorted_features.length;
	for (var i = 0; i < len; i++)
		((machine_flags[device].features[sorted_features[i]].overall == 'unemulated') ? unemulated : imperfect).push(sorted_features[i]);

	function add_one(flags, title)
	{
		var len = flags.length;
		if (len > 0)
		{
			var row = document.createElement('tr');
			row.appendChild(document.createElement('th')).textContent = title;
			var cell = row.appendChild(document.createElement('td'));
			cell.textContent = flags[0];
			for (i = 1; i < len; i++)
				cell.textContent += ', ' + flags[i];
			if (table.lastChild.getAttribute('class') == 'devbios')
				table.insertBefore(row, table.lastChild);
			else
				table.appendChild(row);
		}
	}

	add_one(unemulated, 'Unemulated features:');
	add_one(imperfect, 'Imperfect features:');
}


function add_softwarelist_rows(device, slot, card)
{
	var sorted_softwarelists = Object.keys(softwarelist_info[device]).sort();
	var cardtag = slot + ':' + card;
	if (sorted_softwarelists.length)
	{
		var table = document.getElementById('tbl-softwarelists').tBodies[0];
		sorted_softwarelists.forEach(
				function (tag)
				{
					var cell, link;
					var info = softwarelist_info[device][tag];
					var href = appurl + 'softwarelist/' + encodeURIComponent(info.shortname);
					var row = table.appendChild(document.createElement('tr'));
					row.setAttribute('id', 'row-softwarelists-' + (cardtag + tag).replace(/:/g, '-'))
					row.appendChild(document.createElement('td')).textContent = cardtag;
					link = row.appendChild(document.createElement('td')).appendChild(document.createElement('a'));
					link.setAttribute('href', href);
					link.textContent = info.shortname;
					link = row.appendChild(document.createElement('td')).appendChild(document.createElement('a'));
					link.setAttribute('href', href);
					link.textContent = info.description;
					row.appendChild(document.createElement('td')).textContent = info.status;
					total = info.total;
					cell = row.appendChild(document.createElement('td'));
					cell.textContent = total.toString(10);
					cell.style.textAlign = 'right';
					if (!total)
						total = 1;
					cell = row.appendChild(document.createElement('td'));
					cell.textContent = (info.supported * 100.0 / total).toFixed(1) + '%';
					cell.style.textAlign = 'right';
					cell = row.appendChild(document.createElement('td'));
					cell.textContent = (info.partiallysupported * 100.0 / total).toFixed(1) + '%';
					cell.style.textAlign = 'right';
					cell = row.appendChild(document.createElement('td'));
					cell.textContent = (info.unsupported * 100.0 / total).toFixed(1) + '%';
					cell.style.textAlign = 'right';
				});
		document.getElementById('heading-softwarelists').style.removeProperty('display');
		document.getElementById('tbl-softwarelists').style.removeProperty('display');
	}
}


function add_bios_row(slot, table, device)
{
	var sorted_sets = Object.keys(bios_sets[device]).sort();
	var len = sorted_sets.length;
	if (len > 0)
	{
		// create table row, add heading
		var row = document.createElement('tr');
		row.setAttribute('class', 'devbios');
		row.appendChild(document.createElement('th')).textContent = 'BIOS:';
		var cell = document.createElement('td');

		// make the BIOS popul itself
		var popup = document.createElement('select');
		popup.setAttribute('id', ('select-slot-bios-' + slot).replace(/:/g, '-'));
		for (var i = 0; i < len; i++)
		{
			var set = sorted_sets[i];
			var detail = bios_sets[device][set];
			var option = document.createElement('option');
			option.setAttribute('value', set);
			option.setAttribute('data-isdefault', detail.isdefault ? 'yes' : 'no');
			option.textContent = set + ' - ' + detail.description;
			popup.appendChild(option);
			if (detail.isdefault)
				popup.selectedIndex = i;
		}
		cell.appendChild(popup);

		// make a button to restore the default
		var dflt;
		if (popup.selectedOptions[0].getAttribute('data-isdefault') == 'yes')
		{
			dflt = make_restore_default_button('default', ('btn-def-slot-bios-' + slot).replace(/:/g, '-'), popup, popup.selectedIndex);
			cell.appendChild(document.createTextNode(' '))
			cell.appendChild(dflt);
		}

		// drop the controls into a cell, add it to the table, keep the command line preview up-to-date
		popup.onchange = make_slot_bios_change_handler(dflt);
		row.appendChild(cell);
		table.appendChild(row);
		update_cmd_preview();
	}
}


function make_slot_term(name, slot, defaults)
{
	var len, i;

	// see if we can find a default, outer layers take precedence
	var defcard = '';
	len = defaults.length;
	for (i = 0; (i < len) && (defcard == ''); i++)
	{
		if (Object.prototype.hasOwnProperty.call(defaults[i], name))
			defcard = defaults[i][name];
	}

	// create a container with the slot name and popup
	var term = document.createElement('dt');
	term.setAttribute('id', ('item-slot-choice-' + name).replace(/:/g, '-'));
	term.setAttribute('data-slotname', name);
	term.setAttribute('data-slotcard', '');
	term.textContent = name + ': ';
	var popup = document.createElement('select');
	popup.setAttribute('id', make_slot_popup_id(name));
	term.appendChild(popup);

	// add the empty slot option
	var option = document.createElement('option');
	option.setAttribute('value', '');
	option.setAttribute('data-isdefault', ('' == defcard) ? 'yes' : 'no');
	option.textContent = '-';
	popup.appendChild(option);
	popup.selectedIndex = 0;

	// add options for the cards
	var sorted_choices = Object.keys(slot).sort();
	len = sorted_choices.length;
	for (i = 0; i < len; i++)
	{
		var choice = sorted_choices[i];
		var card = slot[choice];
		option = document.createElement('option');
		option.setAttribute('value', choice);
		option.setAttribute('data-isdefault', (choice == defcard) ? 'yes' : 'no');
		option.textContent = choice + ' - ' + card.description;
		popup.appendChild(option);
		if (choice == defcard)
			popup.selectedIndex = i + 1;
	}

	// make a button for restoring the default and hook up events
	var dflt = make_restore_default_button('default', ('btn-def-slot-choice-' + name).replace(/:/g, '-'), popup, popup.selectedIndex);
	term.appendChild(document.createTextNode(' '));
	term.appendChild(dflt);
	popup.onchange = make_slot_change_handler(name, slot, defaults, dflt);
	return term;
}


function add_slot_items(root, device, defaults, slotslist, pos)
{
	// add another layer of defaults for this device
	var defvals = Object.create(null);
	for (var key in slot_info[device].defaults)
		defvals[root + key] = slot_info[device].defaults[key];
	defaults = defaults.slice();
	defaults.push(defvals);
	var defcnt = defaults.length;

	// add controls for each subslot
	var slots = slot_info[device].slots;
	var sorted_slots = Object.keys(slots).sort();
	var len = sorted_slots.length;
	for (var i = 0; i < len; i++)
	{
		var slotname = sorted_slots[i];
		var slotabs = root + slotname;
		var slot = slots[slotname];
		var term = make_slot_term(slotabs, slot, defaults);
		var def = document.createElement('dd');
		def.setAttribute('id', ('item-slot-detail-' + slotabs).replace(/:/g, '-'));
		if (pos)
		{
			slotslist.insertBefore(term, pos);
			slotslist.insertBefore(def, pos);
		}
		else
		{
			slotslist.appendChild(term);
			slotslist.appendChild(def);
		}

		// force a change event to populate subslot controls if the default isn't empty
		get_slot_popup(slotabs).dispatchEvent(new Event('change'));
	}

	update_cmd_preview();
}


function make_slot_change_handler(name, slot, defaults, dfltbtn)
{
	var selection = null;
	return function (event)
	{
		var choice = event.target.value;
		var slotslist = event.target.parentNode.parentNode;
		var def = event.target.parentNode.nextSibling;
		var slotname = event.target.parentNode.getAttribute('data-slotname');
		selection = (choice == '') ? null : slot[choice];
		dfltbtn.disabled = event.target.selectedOptions[0].getAttribute('data-isdefault') == 'yes';

		// clear out any subslots from previous selection
		var prefix = slotname + ':';
		for (var candidate = def.nextSibling; candidate && candidate.getAttribute('data-slotname').startsWith(prefix); )
		{
			var next = candidate.nextSibling;
			slotslist.removeChild(candidate);
			candidate = next.nextSibling;
			slotslist.removeChild(next);
		}

		// clear out any software lists from previous selection
		var softwarelist_rowid_prefix = 'row-softwarelists-' + prefix.replace(/:/g, '-');
		for (var candidate = document.getElementById('tbl-softwarelists').tBodies[0].rows[0]; candidate; )
		{
			var next = candidate.nextSibling;
			if ((candidate.nodeName == 'TR') && candidate.getAttribute('id').startsWith(softwarelist_rowid_prefix))
				candidate.parentNode.removeChild(candidate);
			candidate = next;
		}

		if (selection === null)
		{
			// no selection, remove the slot card details table
			event.target.parentNode.setAttribute('data-slotcard', '');
			if (def.firstChild)
				def.removeChild(def.firstChild);
		}
		else
		{
			// stash the selected device where it's easy to find
			event.target.parentNode.setAttribute('data-slotcard', selection.device);

			// create details table and add a link to the device's page
			var tbl = document.createElement('table');
			tbl.setAttribute('class', 'sysinfo');
			var row = tbl.appendChild(document.createElement('tr'));
			row.appendChild(document.createElement('th')).textContent = 'Short name:';
			var link = row.appendChild(document.createElement('td')).appendChild(document.createElement('a'));
			link.textContent = selection.device;
			link.setAttribute('href', appurl + 'machine/' + encodeURIComponent(selection.device));

			// if we have emulation flags, populate now, otherwise fetch asynchronously
			if (!Object.prototype.hasOwnProperty.call(machine_flags, selection.device))
				fetch_machine_flags(selection.device);
			else
				add_flag_rows(tbl, selection.device);

			// if we have BIOS details, populate now, otherwise fetch asynchronously
			if (!Object.prototype.hasOwnProperty.call(bios_sets, selection.device))
				fetch_bios_sets(selection.device);
			else
				add_bios_row(slotname, tbl, selection.device);

			// if we have software list info, populate now, otherweise fetch asynchronously
			if (!Object.prototype.hasOwnProperty.call(softwarelist_info, selection.device))
				fetch_softwarelist_info(selection.device);
			else
				add_softwarelist_rows(selection.device, slotname, choice);

			// drop the details table into the list
			if (def.firstChild)
				def.replaceChild(tbl, def.firstChild);
			else
				def.appendChild(tbl);

			// create controls for subslots
			add_slot_items(slotname + ':' + choice, selection.device, defaults, slotslist, def.nextSibling);
		}
		update_cmd_preview();
	};
}


function make_slot_bios_change_handler(dflt)
{
	return function (event)
	{
		if (dflt)
			dflt.disabled = event.target.selectedOptions[0].getAttribute('data-isdefault') == 'yes';
		update_cmd_preview();
	}
}


function populate_slots(machine)
{
	var placeholder = document.getElementById('para-slots-placeholder');
	var slotslist = document.createElement('dl');
	slotslist.setAttribute('id', 'list-slot-options');
	placeholder.parentNode.replaceChild(slotslist, placeholder);
	add_slot_items('', machine, [], slotslist, null);
}


function slot_retrieve_error(device)
{
	var errors;
	var placeholder = document.getElementById('para-slots-placeholder');
	if (placeholder)
	{
		errors = document.createElement('div');
		errors.setAttribute('id', 'div-slots-errors');
		placeholder.parentNode.replaceChild(errors, placeholder);
	}
	else
	{
		errors = document.getElementById('div-slots-errors');
	}
	var message = document.createElement('p');
	message.textContent = 'Error retrieving slot information for ' + device + '.';
	errors.appendChild(message);
}


function fetch_slots(machine)
{
	function make_request(device)
	{
		var req = new XMLHttpRequest();
		req.open('GET', appurl + 'rpc/slots/' + encodeURIComponent(device), true);
		req.responseType = 'json';
		req.onload =
				function ()
				{
					if (req.status == 200)
					{
						slot_info[device] = req.response;
						delete pending[device];
						for (var slotname in req.response.slots)
						{
							var slot = req.response.slots[slotname];
							for (var choice in slot)
							{
								var card = slot[choice].device
								if (!Object.prototype.hasOwnProperty.call(slot_info, card) && !Object.prototype.hasOwnProperty.call(pending, card))
								{
									pending[card] = true;
									make_request(card);
								}
							}
						}
						if (!Object.keys(pending).length)
							populate_slots(machine);
					}
					else
					{
						slot_retrieve_error(device);
					}
				};
		req.send();
	}
	var pending = Object.create(null);
	pending[machine] = true;
	make_request(machine);
}
