/*
	dARS: dA-Riddle Solver. Requires the Prototype JavaScript library
	      (version 1.6 or later), dar_solve.js, and (optionally) graph.js.

    Copyright (c) 2007-2009 Kalle Räisänen

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero 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 Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


/*
	The dynamically-building-a-table stuff scales ridiculously poorly;
	finding the MST is lightning-fast in comparison. It would probably
	be faster to use a big pre-built table and just hiding the un-needed
	parts (though the weight of all those DOM operations would possibly
	even it out). I guess JavaScriptily messing around with tables is
	just expensive. Hence dARS.MAXNODES.
*/

var dARS = {
	MAXNODES:      64
	,inGraph:      []
	,inNames:      []
	,numNodes:     0
	,inputForm: {
		namesCont:  '',
		nodeCount:  '',
		tableCont:  '',
		graphTable: ''
	}
	,outputElement: ''
	,graphOutputElement: ''


	,_warn: function(message) {
		alert('dARS: ' + message);
	}

	,_elementExists: function(elid, silent) {
		if(!$(elid)) {
			if(typeof silent == 'undefined' || !silent)
				dARS._warn("Couldn't find #" + elid);
			return false;
		}

		return true;
	}

	,buildForm: function() {
		dARS.buildNames();
		dARS.buildTable();

		$(dARS.outputElement).update('');
	}

	,buildNames: function() {
		var ncHTML = '<h3>Node-names:</h3><p>';
		for(var i = 0; i < dARS.numNodes; i++) {
			if(typeof dARS.inNames[i] == 'undefined')
				dARS.inNames[i] = i < 26 ? String.fromCharCode('A'.charCodeAt(0) + i) : i;

			ncHTML += '<input type="text" size="5" id="dars-name-' + i
			       +  '" value="' + dARS.inNames[i] + '" /> ';
		}
		ncHTML += '</p>';

		$(dARS.inputForm.namesCont).update(ncHTML);
		$$('#' + dARS.inputForm.namesCont + ' input').each(function(el) {
			Event.observe(el, 'keyup', dARS.handleNamesChange);
		});

		dARS.handleNamesChange();
	}

	,buildTable: function() {
		var tblID   = 'dars-weight-table';
		var tblHTML = '<h3>Distances:</h3>'
		            + ' <p><em>Indicate non-connected nodes with negative, blank, zero,'
		            + ' or non-numerical distances.</em></p>'
		            + '<table id="' + tblID + '" cellspacing="0"'
		            + ' summary="Graph weight table">';


		for(var i = 0; i <= dARS.numNodes; i++) {
			tblHTML  += '<tr class="dars-table-row-' + (i % 2 ? 'odd' : 'even') + '">';

			for(var j = 0; j <= dARS.numNodes; j++) {
				var from   = i - 1;
				var to     = j - 1;
				var wFromToID = false, wToFromID = false;

				if(i == j)
					tblHTML += '<td class="dars-table-cell-blank">&nbsp;</td>';
				else if(i == 0)
					tblHTML += '<th class="dars-table-cell-name dars-name-'
					        +  to + '">' + dARS.inNames[to] + '</th>';
				else if(j == 0)
					tblHTML += '<th class="dars-table-cell-name dars-name-'
					        +  from + '">' + dARS.inNames[from] + '</th>';
				else {
					var vl = (typeof dARS.inGraph[from] != 'undefined'
					           && typeof dARS.inGraph[from][to] != 'undefined')
					         ? (dARS.inGraph[from][to] < INF ? dARS.inGraph[from][to] : '') : '';
					wFromToID = 'dars-weight-' + from + '-' + to;
					wToFromID = 'dars-weight-' + to + '-' + from;

					tblHTML += '<td class="dars-table-cell-weight">'
					        +  '<input class="dars-weight-input" type="text" '
					        +  ' id="' + wFromToID + '" '
					        +  ' value="' + vl + '" onkeyup="$(\''+ wToFromID
					        +  '\').value = this.value;" /></td>';
				}
			}
			tblHTML += '</tr>';
		}
		$(dARS.inputForm.tableCont).update(tblHTML);
		dARS.inputForm.graphTable = tblID;
	}

	,eachChild: function(parent, tagName, callback) {
		if(!dARS._elementExists(parent))
			return false;

		var parRef   = $(parent);
		var children = $(parRef).getElementsByTagName(tagName);

		for(var i = 0; i < children.length; i++)
			callback(children[i]);

		return true;
	}

	,getForm: function() {
		if(dARS._elementExists(dARS.inputForm.graphTable, true))
			dARS.getGraph();
		if(dARS.inNames.length)
			dARS.getNames();
	}

	,getNames: function() {
		dARS.inNames = [];

		return dARS.eachChild(dARS.inputForm.namesCont, 'input', function(el) {
			dARS.inNames.push($F(el));
		});
	}

	,getGraph: function() {
		dARS.inGraph = [];

		return dARS.eachChild(dARS.inputForm.graphTable, 'input', function(el) {
			var val = parseFloat($F(el));

			if(res = /dars-weight-(\d+)-(\d+)/.exec(el.id)) {
				if(typeof dARS.inGraph[res[1]] == 'undefined')
					dARS.inGraph[res[1]] = [];

				dARS.inGraph[res[1]][res[2]] = val > 0 ? val : INF;
			}
		});
	}

	,handleCountChange: function() {
		var nn = parseInt($F(dARS.inputForm.nodeCount));

		if(nn != dARS.numNodes) {
			dARS.getForm();

			dARS.numNodes = nn;

			if(dARS.numNodes > dARS.MAXNODES) {
				alert('The maximum number of nodes supported is ' + dARS.MAXNODES);
				return false;
			}
			dARS.buildForm();
		}
	}

	,handleFormSubmit: function() {
		dARS.getForm();

		if(dARS.inGraph.length) {
			var i;
			var mst = new MST_Prim(dARS.inGraph).prim(true);
			var tot = 0;
			var out = '<h2>Result:</h2><p>';

			for(i = 0; i < mst.length; i++) {
				out += (i ? ' ' : '')
					+ '[<strong>' + dARS.inNames[mst[i].from] + '</strong>'
					+ '&mdash;' + mst[i].weight + '&mdash;&gt;'
					+ '<strong>' + dARS.inNames[mst[i].to] + '</strong>]';

				tot += mst[i].weight;
			}
			out += '; Total distance: ' + tot + '</p>';

			$(dARS.outputElement).update(out);

			if(tot && dARS._elementExists(dARS.graphOutputElement, true) && typeof Graph != 'undefined'
			   && !Prototype.Browser.IE) {
				var g = new Graph();
				var added = {};
				$(dARS.graphOutputElement).update(
					'<p><input type="button" '
					+ ' onclick="$(\''+ dARS.graphOutputElement +'\').hide();" value="Close" />'
					+ '</p>'
				);
				$(dARS.graphOutputElement).show();

				$(dARS.graphOutputElement).insert(
					{top: '<canvas id="cnvs" width="500" height="500"></canvas>'}
				);

				for(i = 0; i < mst.length; i++) {
					if(typeof added[mst[i].from] == 'undefined') {
						$(dARS.graphOutputElement).insert({
							bottom: '<div id="node-' + dARS.inNames[mst[i].from] +'">'
							        + dARS.inNames[mst[i].from] +'</div>'
						});
						added[mst[i].from] = true;
					}
					if(typeof added[mst[i].to] == 'undefined') {
						$(dARS.graphOutputElement).insert({
							bottom: '<div id="node-'+ dARS.inNames[mst[i].to] +'">'
							        + dARS.inNames[mst[i].to] +'</div>'
						});
						added[mst[i].to] = true;
					}

					g.addEdge($('node-' +dARS.inNames[mst[i].from]), $('node-' +dARS.inNames[mst[i].to]));
				}
				var layouter = new Graph.Layout.Spring(g);
				layouter.layout();

				var renderer = new Graph.Renderer.Basic($('cnvs'), g);
				renderer.draw();
			}
		}
	}

	,handleNamesChange: function() {
		dARS.inNames = [];
		dARS.eachChild(dARS.inputForm.namesCont, 'input', function(el) {
			dARS.inNames.push($F(el));
			$$('.dars-name-' + (dARS.inNames.length - 1)).each(function(nme) {
				$(nme).innerHTML = $F(el);
			});
		});
		if(dARS._elementExists(dARS.inputForm.graphTable, true)) {
			$$('#' + dARS.inputForm.graphTable + ' .dars-weight-input').each(function(el) {
				var res;
				if(res = /dars-weight-(\d+)-(\d+)/.exec(el.id)) {
					var names = {from: dARS.inNames[res[1]], to: dARS.inNames[res[2]]};
					el.title = names.from + ' -- ' + names.to;
				}
			});
		}
	}


	,init: function(nodeCountInput, namesCont, tableCont, goButton, outputEl, graphEl) {

		if(!dARS._elementExists(nodeCountInput)  || !dARS._elementExists(goButton)
				|| !dARS._elementExists(namesCont) || !dARS._elementExists(tableCont)
				|| !dARS._elementExists(outputEl) )
			return false;

		dARS.inputForm.nodeCount = nodeCountInput;
		dARS.inputForm.namesCont = namesCont;
		dARS.inputForm.tableCont = tableCont;
		dARS.outputElement       = outputEl;
		dARS.graphOutputElement  = typeof graphEl != 'undefined' ? graphEl : null;

		Event.observe(nodeCountInput, 'change', dARS.handleCountChange);
		Event.observe(goButton,       'click',  dARS.handleFormSubmit);

		dARS.handleCountChange();
		dARS.handleNamesChange();
	}
}; // dARS

