var txt_inp, txt_out, txt_out_extra, txt_hist, data_div;
var val;

// The data object will contain all variables
var data = new Object;
data.retval = 0;
data.pi = Math.PI;
data.pi_2 = Math.PI/2;
data.pi_4 = Math.PI/4;
data.M_PI = Math.PI;
data.M_PI_2 = Math.PI/2;
data.M_PI_4 = Math.PI/4;
data.M_1_PI = 1/Math.PI;
data.M_2_PI = 2/Math.PI;
data.M_2_SQRTPI = 2/Math.sqrt(Math.PI);
data.e = Math.E;
data.M_E = Math.E;
data.log2e = Math.LOG2E;
data.M_LOG2E = Math.LOG2E;
data.log10e = Math.LOG10E;
data.M_LOG10E = Math.LOG10E;
data.ln2 = Math.LN2;
data.M_LN2 = Math.LN2;
data.ln10 = Math.LN10;
data.M_LN10 = Math.LN10;
data.sqrt2 = Math.SQRT2;
data.M_SQRT2 = Math.SQRT2;
data.sqrt1_2 = Math.SQRT1_2;
data.M_SQRT1_2 = Math.SQRT1_2;
data.CHAR_MIN = -128;
data.CHAR_MAX = 127;
data.UCHAR_MAX = 0xff;
data.SHRT_MIN = -32768;
data.SHRT_MAX = 32767;
data.USHRT_MAX = 0xffff;
data.INT_MIN = -2147483648;
data.INT_MAX = 2147483647;
data.UINT_MAX = 0xffffffff;
data.LONG_MIN = -2147483648;
data.LONG_MAX = 2147483647;
data.ULONG_MAX = 0xffffffff;

// The func object will contain all functions
var func = new Object;
func.abs = function(x)		{ return Math.abs(x) };
func.acos = function(x)		{ return Math.acos(x) };
func.asin = function(x)		{ return Math.asin(x) };
func.atan = function(x)		{ return Math.atan(x) };
func.atan2 = function(y,x)	{ return Math.atan2(y,x) };
func.ceil = function(x)		{ return Math.ceil(x) };
func.cos = function(x)		{ return Math.cos(x) };
func.exp = function(x)		{ return Math.exp(x) };
func.floor = function(x)	{ return Math.floor(x) };
func.log = function(x)		{ return Math.log(x) };
func.max = function()		{ return Math.max.apply(this,arguments); };
func.MAX = function()		{ return Math.max.apply(this,arguments); };
func.min = function()		{ return Math.min.apply(this,arguments); };
func.MIN = function()		{ return Math.min.apply(this,arguments); };
func.pow = function(b,x)	{ return Math.pow(b,x) };
func.random = function()	{ return Math.random() };
func.round = function(x,d)	{ if(!d) return Math.round(x); var s=pow(10,d); return Math.round(s*x)/s; };
func.sin = function(x)		{ return Math.sin(x) };
func.sqrt = function(x)		{ return Math.sqrt(x) };
func.tan = function(x)		{ return Math.tan(x) };

// Color functions
func.color_from_hash = function(c)		{ setColor("#" + c.substr(2)); return HashToRGB(c.substr(2)); };
func.rgb = function(r,g,b)				{ setColor("rgb(" + r + "," + g + "," + b + ")"); return RGBToHash(r,g,b); };
func.rgba = function(r,g,b,a)			{ var rgba = "rgba(" + r + "," + g + "," + b + "," + a + ")"; setColor(rgba); return rgba; };

// List functions
function sortNumberInc(a, b) { return a - b; }
function sortNumberDec(a, b) { return b - a; }

func.list = function() {
	if (!arguments || arguments.length == 0)
		return "";
	return "<small>min:</small>" + this.min.apply(this,arguments) +
			" <small>max:</small>" + this.max.apply(this,arguments) +
			" <small>median:</small>" + this.median.apply(this,arguments) +
			" <small>mean:</small>" + this.mean.apply(this,arguments) +
			" <small>sum:</small>" + this.sum.apply(this,arguments);
}
func.sum = function() {
	if (!arguments || arguments.length == 0)
		return "";
	var s = arguments[0];
	for(var i = 1; i < arguments.length; i++)
		s += arguments[i];
	return s;
}
func.mean = function() {
	if (!arguments || arguments.length == 0)
		return "";
	return this.sum.apply(this,arguments) / arguments.length;
}
func.median = function() {
	if (!arguments || arguments.length == 0)
		return "";
	var array = Array.prototype.slice.call(arguments, 0);
	array = array.sort(sortNumberInc);
	return (array.length % 2 == 1)	?	array[Math.floor(array.length / 2)]
									:	(array[array.length / 2 - 1] + array[array.length / 2]) / 2;
}
func.sort = function() {
	var array = Array.prototype.slice.call(arguments, 0);
	return array.sort(sortNumberInc);
}
func.sortr = function() {
	var array = Array.prototype.slice.call(arguments, 0);
	return array.sort(sortNumberDec);
}

function getColorComponentString(c) {
	var str = c.toString(16);
	if (str.length == 1)
		return "0" + str;
	return str;
}
function RGBToHash(r, g, b) {
	if (r == undefined || g == undefined || b == undefined)
		return "Not enough parameters";
	return "#" + getColorComponentString(r) + getColorComponentString(g) + getColorComponentString(b);
}
function HashToRGB(col) {
	if (col.length == 3)
		return "rgb(" + parseInt(col.substr(0, 1) + col.substr(0, 1), 16) + "," +
						parseInt(col.substr(1, 1) + col.substr(1, 1), 16) + "," +
						parseInt(col.substr(2, 1) + col.substr(2, 1), 16) + ")";
	else if (col.length == 6)
		return "rgb(" + parseInt(col.substr(0, 2), 16) + "," +
						parseInt(col.substr(2, 2), 16) + "," +
						parseInt(col.substr(4, 2), 16) + ")";
	return "Not enough digits to represent a color";
}

function create_variable(parent, name) {
	// Initialize the new variable
	var ret = eval("data." + name);
	if (ret == undefined)
		eval("data." + name + " = 0;");

	// Create input for the new variable
	var inp_div = document.createElement("div");
	inp_div.innerHTML = "<h2>" + name + ":</h2><input type='text' value='0' onkeyup='set_variable(event)'><input type='checkbox'>";
	inp_div.childNodes[1].value = eval("data." + name);
	inp_div.childNodes[1].id = "data." + name;
	parent.appendChild(inp_div);
}
function rebuild_and_update() {
	val = txt_inp.value.replace(/(\d) (\d)/g, "$1$2");                           // Remove single spaces in numbers (f.ex from 10 000)
	val = val.replace(/[-+*\/] ?$/g, "");										 // Remove trailing unfinished input
	val = val.replace(/^#(.+)/g, "color_from_hash('0x$1')");                     // Make #colors work. Temporarily as string hex to pass it correctly.
	var val_without_func = val.replace(/(\b[a-zA-Z_]+[0-9a-zA-Z_]* ?\()/g, "("); // Remove functions so we can find only variables
	var variables = val_without_func.match(/\b[a-zA-Z_]+[0-9a-zA-Z_]*\b/g);      // Find variables
	val = val.replace(/(\b[a-zA-Z_]+[0-9a-zA-Z_]* ?\()/g, "func.$1");            // use func object to access functions
	val = val.replace(/(\b[a-zA-Z_]+[0-9a-zA-Z_]*\b)/g, "data.$1");              // use data object to access data
	val = val.replace(/(data.func.data.)/g, "func.");                            // hack :)
	if (!variables)
		variables = new Array();

	// Create all variable inputs
	var used_variables = new Array();
	for(var i = 0; i < variables.length; i++)
	{
		var id = "data." + variables[i];
		used_variables[id] = 1;
		if (document.getElementById(id))
			continue;

		create_variable(data_div, variables[i]);
	}
	var unused_variables = data_div.getElementsByTagName("div");
	for(var i = 0; i < unused_variables.length; i++)
	{
		if (used_variables[unused_variables[i].childNodes[1].id] != 1 &&
			unused_variables[i].childNodes[2].checked != true)
		{
			unused_variables[i].parentNode.removeChild(unused_variables[i]);
			i--;
		}
	}
	
	// Create variable retval
	if (!document.getElementById("data.retval"))
		create_variable(data_div, "retval");

	document.getElementById("txt_debug").value = val;
	update();
}
function set_variable(event) {
	var target = event.target ? event.target : event.srcElement; // IE compability
	var name = target.id;
	var value = Number(target.value);
	eval(name + "=" + value);
	update();
}
function setText(elm, text, as_html) {
	if (as_html == true)
		elm.innerHTML = text;
	else if (elm.textContent == undefined)
		elm.innerText = text;
	else
		elm.textContent = text;
}
function getText(elm) {
	if (elm.textContent == undefined)
		return elm.innerText;
	return elm.textContent;
}
function setColor(color) {
	document.getElementById("color_out1").style.setProperty("background-color", color, "");
	switch_tab(document.getElementById("toggle_color"));
}
function update(evaluate_assignments) {
	if (evaluate_assignments == undefined)
		evaluate_assignments = false;

	try {
		// Eval and set result
		var result;
		if (!evaluate_assignments && val.match(/(\w|\s)=(\w|\s)/g))
			// It's a assignment and we should only return the value.
			result = eval(val.substr(val.indexOf("=") + 1));
		else
 			result = val == "" ? "" : eval(val);
		setText(txt_out, result, typeof result != "number");
		// Set result as hex
		if (parseInt(result) == result && result > 0)
			result = "0x" + result.toString(16);
		else
			result = "";
		setText(txt_out_extra, result);
	} catch(err) {
		setText(txt_out, err.description);
		setText(txt_out_extra, "");
	}

	// Update variables value inputs
	var variables = data_div.getElementsByTagName("div");
	for(var i = 0; i < variables.length; i++)
		variables[i].childNodes[1].value = eval(variables[i].childNodes[1].id);

	// Update graph
	graph_update();
}

function newcalc() {
	if (txt_inp.value == "")
		return;

	// If it's a variable assignment, this will set the variable.
	update(true);

	// Make used variables stick
	var variables = data_div.getElementsByTagName("div");
	for(var i = 0; i < variables.length; i++)
		variables[i].childNodes[2].checked = true;

	// Add to history
	txt_hist.innerHTML += "<div class='calc'>" + txt_inp.value + "</div><div class='result'>" + getText(txt_out) + "</div>";
	txt_hist.scrollTop = txt_hist.scrollHeight;

	// Update retval variable
	data.retval = Number(getText(txt_out));

	// Clear and rebuild
	txt_inp.value = "";
	rebuild_and_update();
	txt_inp.focus();
}
function inp_keydown(event) {
	var key = event.which ? event.which : event.keyCode;
	if (key == 13)
	{
		rebuild_and_update();
		newcalc();
		return;
	}
	if (key == 38) // up (fetch last entry from the history)
	{
		var divs = txt_hist.getElementsByTagName("div");
		if (divs.length >= 2)
			txt_inp.value = getText(divs[divs.length - 2]);
		rebuild_and_update();
	}
	// Hide example text
	document.getElementById("txt_inp_example").style.display = "none";
}
function inp_keyup(event) {
	var key = event.which ? event.which : event.keyCode;

	if(txt_inp.value == " ")
		txt_inp.value = "retval" + txt_inp.value;
	rebuild_and_update();
}
function handle_ref_click(event) {
	var target = event.target ? event.target : event.srcElement; // IE compability
	if (target.childNodes[0].nodeType == 3)
	{
		var text = getText(target.childNodes[0]);
		txt_inp.value += text;
		// Fake key event so we rebuild and update
		var dummy_event = new Object;
		dummy_event.which = 0;
		inp_keydown(dummy_event);
		inp_keyup(dummy_event);
		txt_inp.focus();
	}
}
 function handle_tab_click(event) {
	var target = event.target ? event.target : event.srcElement; // IE compability
	switch_tab(target);
}
function switch_tab(target) {
	if (target.parentNode.className != "tabs_below")
		return;
	var toggleid = target.id.substr(7);
	var divs = target.parentNode.parentNode.childNodes;
	for(var i = 0; i < divs.length; i++)
	{
		if (divs[i].nodeType == 3) // Skip text nodes
			continue;
		if (divs[i].id == toggleid)
		{
			divs[i].style.display = "block";
			target.className = "active";
		}
		else if (divs[i].className != "tabs_below")
		{
			divs[i].style.display = "none";
			document.getElementById("toggle_" + divs[i].id).className = "";
		}
	}
	graph_update();
}
function init() {
	txt_inp = document.getElementById("txt_inp");
	txt_out = document.getElementById("txt_out");
	txt_out_extra = document.getElementById("txt_out_extra");
	data_div = document.getElementById("data_div");
	txt_hist = document.getElementById("txt_hist");
	txt_inp.value = ""; // Firefox refill inputfields on reload.

	graph_init();

	rebuild_and_update();
	setTimeout("txt_inp.focus()", 200);
}

// == Graph code =============================================
// == This code just feed data arrays into flot canvas library

var graph_plot;
var graph_quality_step = 1;
var graph_inp_x1;
var graph_inp_x2;
var g_f1_a = [];
var g_f2_a = [];
var g_f3_a = [];
var g_f4_a = [];
var g_data_a = [];
var graph_func_idx = 0;
var graph_func_val = ["", "", "", "", ""];
var graph_func_txt = ["", "", "", "", ""];
var graph_update_labels = true;

function clone_object(obj) {
	var new_obj = new Object();
	for(var key in obj)
		new_obj[key] = obj[key];
	return new_obj;
}
function graph_set_active(index) {
	// Set active graph (the editfield updates only one graph)
	document.getElementById("btn_f1").className = index == 0 ? "active" : "";
	document.getElementById("btn_f2").className = index == 1 ? "active" : "";
	document.getElementById("btn_f3").className = index == 2 ? "active" : "";
	graph_func_idx = index;
	txt_inp.value = graph_func_txt[graph_func_idx];
	rebuild_and_update();
}
function graph_update_data() {
	// Parse the data input and push into the data array.
	g_data_a = [];
	var array = document.getElementById("txt_data").value.split("\n");
	for(var i = 0; i < array.length; i++)
	{
		var p = array[i].split(",");
		g_data_a.push([Number(p[0]), Number(p[1])]);
	}
	graph_update();
}
function graph_indata_toggle() {
	var graph_indata = document.getElementById("graph_indata");
	var isoff = graph_indata.style.display != "block";
	graph_indata.style.display = isoff ? "block" : "none";
	document.getElementById("btn_f4").className = isoff ? "active" : "";
}
function graph_update() {
	if (document.getElementById("toggle_graph").className != "active")
		return;

	// Clear function arrays and prepare some data
	g_f1_a = [];
	g_f2_a = [];
	g_f3_a = [];
	g_f4_a = [];
	graph_func_val[graph_func_idx] = val;
	graph_func_txt[graph_func_idx] = txt_inp.value;
	var graph_x1 = Number(graph_inp_x1.value);
	var graph_x2 = Number(graph_inp_x2.value);
	var old_data = clone_object(data);

	// Create functions that will be used in the calculation loop
	var f1_on = false;
	var f2_on = false;
	var f3_on = false;
	if (graph_func_val[0] != "")
		try { eval("var F1 = function(){return " + graph_func_val[0] + ";}"); f1_on = true; } catch(err) { }
	if (graph_func_val[1] != "")
		try { eval("var F2 = function(){return " + graph_func_val[1] + ";}"); f2_on = true; } catch(err) { }
	if (graph_func_val[2] != "")
		try { eval("var F3 = function(){return " + graph_func_val[2] + ";}"); f3_on = true; } catch(err) { }

	// Calculation loop (calculate and increase x)
	for (var i = 0; i <= 300; i += graph_quality_step)
    {
    	data.x = (graph_x2 - graph_x1) * i / 300 + graph_x1;
		if (f1_on) g_f1_a.push([data.x, F1()]);
		if (f2_on) g_f2_a.push([data.x, F2()]);
		if (f3_on) g_f3_a.push([data.x, F3()]);
	}
	data = old_data;

	// Clip data graph within x limits. Flot behaves buggy if we don't do this.
	for(var i = 0; i < g_data_a.length; i++)
	{
		if (g_data_a[i][0] >= graph_x1 && g_data_a[i][0] <= graph_x2)
			g_f4_a.push([g_data_a[i][0], g_data_a[i][1]]);
	}

	// Update graphs with the new data and redraw.
	graph_plot.setData([g_f1_a, g_f2_a, g_f3_a, {data: g_f4_a, lines: { show: false }, points: { show: true }} ]);
	graph_plot.setupGrid(graph_update_labels);
	graph_plot.draw();
}
function graph_export_data_get() {
	var data = "";
	var g_arrays = [ g_f1_a, g_f2_a, g_f3_a, g_f4_a ];
	for(var g = 0; g < g_arrays.length; g++)
	{
		var a = g_arrays[g];
		if (a.length)
		{
			data += "<table><thead><tr><td><h1>F" + (g + 1) + "</h1></td><td></td></tr><tr><th>X</th><th>Y</th></tr></thead>";
			for(var i = 0; i < a.length; i++)
				data += "<tr><td>" + a[i][0] + "</td><td>" + a[i][1] + "</td></tr>";
			data += "</table>";
		}
	}
	return data;
}
function graph_export_data() {
	window.open("export.html", "_blank", "");
}
function graph_reposition() {
	graph_update();
}
function graph_mousedown(event, zoom) {
	var down_x = event.clientX;
	var down_x1 = Number(graph_inp_x1.value);
	var down_x2 = Number(graph_inp_x2.value);
	var down_qstep = graph_quality_step;
	graph_quality_step = 1;
	graph_update_labels = false;
	document.body.parentNode.onmousemove = mousemove;
	document.body.parentNode.onmouseup = mouseup;
	function mousemove(event) {
		var speed_factor = (down_x2 - down_x1) / 100;
		var dx = (event.clientX - down_x) * speed_factor;
		if (zoom && down_x1 + dx >= (down_x2 - dx))
			return;
		graph_inp_x1.value = down_x1 + dx;
		graph_inp_x2.value = zoom ? (down_x2 - dx) : (down_x2 + dx);
		graph_update();
	}
	function mouseup(event) {
		document.body.parentNode.onmousemove = 0;
		document.body.parentNode.onmouseup = 0;
		graph_quality_step = down_qstep;
		graph_update_labels = true;
		graph_update();
	}
}
function graph_quality(q) {
	graph_quality_step = q;
	graph_update();
}
function graph_qualityrender(q) {
	var old_q = graph_quality_step;
	graph_quality_step = q;
	graph_update();
	graph_quality_step = old_q;
}
function showTooltip(x, y, color, contents) {
    $('<div id="tooltip">' + contents + '</div>').css( {
        position: 'absolute',
        display: 'none',
        'z-index': 100,
        top: y + 5,
        left: x + 5,
        border: '1px solid #000',
        'border-radius': '4px',
        '-moz-border-radius': '4px',
        padding: '2px',
        'background-color': color,
        opacity: 0.80
    }).appendTo("body").fadeIn(500);
}
var previousPoint = null;

function graph_init() {
	if (!window.CanvasRenderingContext2D) {
		document.getElementById("nosupport").style.display = "block";
	}
	graph_inp_x1 = document.getElementById("graph_x1");
	graph_inp_x2 = document.getElementById("graph_x2");
	graph_inp_x1.value = "-1";
	graph_inp_x2.value = "1";
	graph_plot = $.plot($("#plot_placeholder"), [ [] ], { grid: { tickColor: "#444", color: "#333", hoverable: true, clickable: true },
													//lines: { lineWidth: 1 },
													colors: ["#edc240", "#ff009c", "#4da74d", "#aaa"],
													shadowSize: 0
													});

	$("#plot_placeholder").bind("plothover", function (event, pos, item) {
	    $("#x").text(pos.x);
	    $("#y").text(pos.y);

        if (item) {
            if (previousPoint != item.datapoint) {
                previousPoint = item.datapoint;

                $("#tooltip").remove();
                var x = item.datapoint[0].toFixed(5),
                    y = item.datapoint[1].toFixed(5);
                showTooltip(item.pageX, item.pageY, item.series.color, "F" + (item.seriesIndex+1) + "(" + x + ") = " + y);
            }
        }
        else {
            $("#tooltip").remove();
            previousPoint = null;            
        }
	});

	$("#plot_placeholder").bind("plotclick", function (event, pos, item) {
	    if (item) {
	        //$("#clickdata").text("You clicked point " + item.dataIndex + ".");
	        plot.highlight(item.series, item.datapoint);
	    }
	});
}
