// Javascript for custom widgets for standard SODA parameters, and
// other JS support for the improvised soda interface.
// See https://github.com/msdemlei/datalink-xslt.git
//
// The needs jquery loaded before it.


///////////// Micro templating.  
/// See http://docs.g-vo.org/DaCHS/develNotes.html#built-in-templating
function htmlEscape(str) {
	return String(str).replace(/&/g, '&amp;').replace(/"/g, '&quot;')
		.replace(/'/g, '&apos;').replace(/</g, '&lt;')
		.replace(/>/g, '&gt;');
}

(function () {
	var _tmplCache = {};
	this.renderTemplate = function (templateId, data) {
		var err = "";
		var func = _tmplCache[templateId];
		if (!func) {
			str = document.getElementById(templateId).innerHTML;
			var strFunc =
				"var p=[],print=function(){p.push.apply(p,arguments);};"
				+ "with(obj){p.push('"
				+ str.replace(/[\r\t\n]/g, " ")
				.split("'").join("\\'")
				.split("\t").join("'")
				.replace(/\$([a-zA-Z_]+)/g, "',htmlEscape($1),'")
				.replace(/\$!([a-zA-Z_]+)/g, "',$1,'")
				+ "');}return $.trim(p.join(''));";
				func = new Function("obj", strFunc);
				_tmplCache[str] = func;
		}
		return func(data);
	}
})()


/////////////////// misc. utils

// set the contents of clsid within container to val
function update_class_elements(container, clsid, val) {
	container.find("."+clsid).map(
		function(i, el) {
			$(el).text(val);
		});
}

// update a SODA (interval) widget for par name form a -low/-high/-unit
// split widget.
// soda_name is the name of the SODA parameter to be built.  conversions
// is a mapping going from -unit strings to converter functions to
// the SODA units.
function update_SODA_widget(input, soda_name, conversions) {
	var form = input.form;
	var low_element = form[soda_name+"-low"];
	var high_element = form[soda_name+"-high"];
	var unit_element = form[soda_name+"-unit"];
	var converter = conversions[unit_element.value];

	var low_val = low_element.value;
	if (low_val) {
		low_val = converter(parseFloat(low_val));
	} else {
		low_val = '-Inf';
	}

	var high_val = high_element.value;
	if (high_val) {
		high_val = converter(parseFloat(high_val));
	} else {
		high_val = '+Inf';
	}

	form[soda_name].value = low_val+" "+high_val;
}


/////////////////// Unit conversion

LIGHT_C = 2.99792458e8;
PLANCK_H_EV = 4.135667662e-15;

// conversions from meters to
TO_SPECTRAL_CONVERSIONS = {
	'm': function(val) { return val; },
	'µm': function(val) { return val*1e6; },
	'Ångström': function(val) { return val*1e10; },
	'MHz': function(val) { return LIGHT_C/val*1e-6; },
	'keV': function(val) { return LIGHT_C*PLANCK_H_EV/val*1e-3; }};

// conversions to meters from
FROM_SPECTRAL_CONVERSIONS = {
	'm': function(val) { return val; },
	'µm': function(val) { return val/1e6; },
	'Ångström': function(val) { return val/1e10; },
	'MHz': function(val) { return LIGHT_C/val/1e-6; },
	'keV': function(val) { return LIGHT_C*PLANCK_H_EV/val/1e-3; }};


// set properly marked up limits.
// this assumes that el is the unit select and the whole widget is
// within a div.
function convert_spectral_units(el, low, high) {
	var converter = TO_SPECTRAL_CONVERSIONS[el.value];
	var input_group = $(el).parents("div").first();
	update_class_elements(input_group, "low-limit", converter(low));
	update_class_elements(input_group, "high-limit", converter(high));
}


/////////////////// Individual widgets

function add_BAND_widget() {
	var old = $(".BAND-m-em_wl");
	old.map(function(index, el) {
		el = $(el);
		var form = el.parents("form");
		var low_limit = parseFloat(el.find(".low-limit").text());
		var high_limit = parseFloat(el.find(".high-limit").text());
		// TODO: validate limits?

		var new_widget = renderTemplate(
			"fancy-band-widget", {
				low_limit: low_limit,
				high_limit: high_limit});
		el.parent().prepend(new_widget);
	});
	old.hide();
}


function _draw_POLYGON_widget(poly_widget) {
	// A callback to draw the aladin light window once the required
	// javascript code is loaded are there

	var new_widget = $('#aladin-lite-div').detach();
	// I'd like to use poly_widget.parent() here, but that silently fails.
	$('div.inputpars').prepend(new_widget);
	new_widget.show();
	// do we want the region editor to be global?  I wouldn't mind.
	var regionEditor = new RegionEditor_mVc("aladin-lite-div",
		function(data) {
			var form = new_widget.parents("form");
			var poly_name = poly_widget.find("input").attr("name");
			form[0][poly_name].value = data.region.points.flat().join(" ");
		}
	);
	regionEditor.init();
	initPoly = $.map(
		poly_widget.find(".high-limit").text().split(" "),
		parseFloat);
	regionEditor.setInitialValue({
		"type": "array",
		"value": initPoly});
	regionEditor.setEditionFrame({
		"type": "array",
		"value": initPoly});
}


function add_POLYGON_widget() {
	// An aladin-light-based widget letting people draw polygons to
	// cut out.  Yes, this is expensive, but it's hard to make something
	// like this with less tooling and JS madness.

	var poly_widget = $(".POLYGON-deg-phys_argArea_obs");
	
	if (poly_widget.length) {
		$("head").append(
			"<link rel='stylesheet' href='https://aladin.u-strasbg.fr/"+
				"AladinLite/api/v2/latest/aladin.min.css' type='text/css' />");
		$.getScript(
			"https://aladin.u-strasbg.fr/AladinLite/api/v2/latest/aladin.js").done(
		function() {
			$.getScript("/static/js/regioneditor.js").done(
				function() {_draw_POLYGON_widget(poly_widget);})
		})
	}
}

// call the various handler functions for known three-factor widgets.
// (this is called from the document's ready handler and thus is the
// main entry point into the magic here)
function add_custom_widgets() {
	add_BAND_widget();
	add_POLYGON_widget();
	// in order to hide the extra inputs from the browser when sending
	// off the form, we need to override the submit action
	$("form.service-interface").bind("submit",
		function(event) {
			event.preventDefault();
			window.open(
				build_result_URL(event.target));
		});
}


//////////////////////////// SAMP interface/result URL building

// The thing sent to the SAMP clients is a URL built from all input
// items that have a soda class.  The stylesheet must arrange it so
// all input/select items generated from the declared service  parameters
// have a soda class.

// return a list of selected items for a selection element for URL inclusion
function get_selected_entries(select_element) {
	var result = new Array();
	var i;

	for (i=0; i<select_element.length; i++) {
		if (select_element.options[i].selected) {
			result.push(select_element.name+"="+encodeURIComponent(
				select_element.options[i].value))
		}
	}
	return result;
}

// return a URL fragment for a form item
function make_query_item(form_element, index) {
	var val = "";

	if (! $(form_element).hasClass("soda")) {
		return;
	}
	switch (form_element.nodeName.toUpperCase()) {
		case "INPUT":
		case "TEXTAREA":
			if (form_element.type=="radio" || form_element.type=="checkbox") {
				if (form_element.checked) {
					val = form_element.name+"="+encodeURIComponent(form_element.value);
				}
			} else if (form_element.name && form_element.value) {
				val = form_element.name+"="+encodeURIComponent(form_element.value);
			}
			break;
		case "SELECT":
			return get_selected_entries(form_element).join("&");
			break;
	}
	return val;
}


// return the URL that sending off cur_form would retrieve
function build_result_URL(cur_form) {
	var fragments = $.map(cur_form.elements, make_query_item);
	dest_url = cur_form.getAttribute("action")+"?"+fragments.join("&");
	return dest_url;
}


// send the current selection as a FITS image
function send_SAMP(conn, cur_form) {
	var msg = new samp.Message("image.load.fits", {
		"url": build_result_URL(cur_form),
		"name": "SODA result"});
	conn.notifyAll([msg]);
}

function completeURL(uriOrPath) {
	if (uriOrPath[0]=="/") {
		return window.location.protocol+"//"+window.location.host+uriOrPath;
	}
	return uriOrPath;
}

// return the callback for a successful hub connection
// (which disables-re-registration and sends out the image link)
function _make_SAMP_success_handler(samp_button, cur_form) {
	return function(conn) {
		conn.declareMetadata([{
			"samp.description": "SODA processed data from"+document.URL,
			"samp.icon.url": completeURL("/favicon.png")
		}]);

		// set the button up so clicks send again without reconnection.
		$(samp_button).unbind("click");
		$(samp_button).click(function(e) {
			e.preventDefault();
			send_SAMP(conn, cur_form);
		});

		// make sure we unregister when the user leaves the page
		$(window).on("unload", function() {
			conn.unregister();
		});

		// send the stuff once (since the connection has been established
		// in response to a click alread)
		send_SAMP(conn, cur_form);
	};
}

// connect to a SAMP hub and, when the connection is established,
// send the current cutout result.
function connect_and_send_SAMP(samp_button, cur_form) {
	samp.register("SODA processor",
		_make_SAMP_success_handler(samp_button, cur_form),
				function(err) {
					alert("Could not connect to SAMP hub: "+err);
				}
			);
		}


// create a samp sending button in a SODA form
function enable_SAMP_on_form(index, cur_form) {
	try {
		var samp_button = $("#samp-template").clone()[0]
		$(samp_button).show()
		$(samp_button).attr({"id": ""});
		$(cur_form).prepend(samp_button);
		$(samp_button).click(function (e) {
			e.preventDefault();
			connect_and_send_SAMP(samp_button, cur_form);
		});
	} catch (e) {
		throw(e);
		// we don't care if there's no SAMP.  Log something?
	}
}

// enable SAMP sending for all forms that look promising
function enable_SAMP() {
	$("form.service-interface").each(enable_SAMP_on_form);
}

$(document).ready(add_custom_widgets);
$(document).ready(enable_SAMP);
