var elements = new Array();
var terminal_array = new Array();
var history_array = new Array();
var toolTips = new Array();
var current_history_index = 0;
var elmWidth = 56;
var container;
var transformationheight_array = new Array();
var terminalheight_array = new Array();
var imageloc = "images/icons/";
var imageprefix = "icon_";
var imagesuffix = ".jpg";
var idIsUpperCase = false;
var tips;
var masterCategories;
var imported_sequence_index;
var status; // status container

var grammar = new Array();


// animation utility structures
var elementsToDelete = new Array();
var elementsToDisplay = new Array();

// starts the initialization of the available categories. 
function init(){
	status = $("status");
	get_categories();
}

//Gets the list of available categories
function get_categories()
{
	var url = "categories.php";
	var myAjax = new Ajax(url,{method:'get',onComplete: process_categories}).request();
}

// Updates the categories on the page after we've gotten the list from 
// the get_categories() AJAX call.
function process_categories(returned)
{
	masterCategories = eval("("+returned+")");
	prep_designer(); 
}

// Prepares the designer either by initializing the space or by reloading the history.
function prep_designer(){
	container = $("thecontainer");	
	if(!imported_history)
	{
		elements[0] = {"label":"S","node":elementToNode("S","element")};
		updateElement(elements[0],0);
		container.appendChild(elements[0].node);
		update_history("START");
		redraw_history();
	} else {
		// note: imported_history is generated by php and defined on the designer page.
		rebuild_history(imported_history); 
		imported_sequence_index = current_history_index;
		revert(current_history_index);
	}	
}

// rebuilds history from a saved set of history items.
function rebuild_history(saved)
{
	//create nodes for each line of history
	for(var i = 0; i < saved.length; i++)
	{
		var elmArchive = saved[i].elements;
		var elmHistoryLine = new Array();
		// step through the elements in the nodes
		for(var j=0; j < elmArchive.length; j++)
		{
			tNode = elementToNode(elmArchive[j],"element")
			elmHistoryLine[j] = {"label":elmArchive[j],"node":tNode};
		}
		history_array[i] = new Array();
		history_array[i]["description"] = saved[i].description;
		history_array[i]["elements"] = elmHistoryLine;
	}
	
	// check the terminators on the last set and restore state.
	current_history_index = saved.length - 1;
	var currentElements = history_array[current_history_index]["elements"];
	for (i=0; i < currentElements.length; i++)
	{
		if(imported_sequence[i] != '')
		{
			currentElements[i].terminal = imported_sequence[i];
		}
	}
}

// empties an element of child nodes
function empty(elm){
	if (elm)
	{
		while(elm.hasChildNodes())
		{
			elm.removeChild(elm.lastChild);	
		}
	} else {
		alert("Undefined element passed to 'empty'");	
	}
}

// redraws the display
function redraw(){
	empty(container);
	for(i = 0; i < elements.length; i++)
	{
		if(elements[i])
		{
			container.appendChild(elements[i].node);
			elements[i].node.setStyles({width:0,opacity:0});
			var myEffects = elements[i].node.effects({duration: 200,transition:Fx.Transitions.linear});
			myEffects.start({width:elmWidth,opacity:1,marginRight:1});	
		}
	}
	return;
}


// draw a specific history state
function revert(which){
	elements = new Array();
	elements = history_array[which]['elements'];
	
	// make sure the elements have everything they need
	for(var i = 0; i < elements.length; i++)
	{
		if (!elements[i].updated)
		{
			updateElement(elements[i],i);
		}
	}
	
	// set the current history point to the new history point
	current_history_index=which;
	
	redraw();
	redraw_history();
	var containerEffects = container.effects({duration:100,transition:Fx.Transitions.linear});
	containerEffects.start({width:((elmWidth + 1) * (elements.length)) + 10});	// Here is where we need to check terminal state.
	update_finished_status();
}

function redraw_history(){
	var hist = $('history');
	var histwrap = $('historywrapper');
	empty($('history'));
	
	var linelen = 0;
	
	for (i=0;i<history_array.length;i++){
		linelen = history_array[i]['elements'].length + 1;
		//hist.setStyle("width",(linelen * 1.3) + "em"); 
		
		var telm = new Element('li');
		
		if (current_history_index==i)
		{
			var hitem = new Element('a',{"class":"active"});
		} else{
			var hitem = new Element('a');
		}
		
		//var elmLst = elementToList(history_array[i]['elements']);
		//var elmLst = "<ul class=\"history-list-item\"><li>Step"+i+"</li></ul>";
		var elmLst = new Element('ul',{"class":"history-list-item"});
		var hli = "<li>Step " + (i+1) + "</li>";
		var lists = hli;
		elmLst.innerHTML = lists;
		
		elmLst.histIndex = i;
		elmLst.addEvent("click",function(){revert(this.histIndex)});
		telm.appendChild(elmLst);
		hist.appendChild(telm);
	}
}

function elementToList(elm)
{//deprecated
	var elmLst = new Element('ul',{"class":"history-list-item"});
	/* DOM process */
	var lists = "";
	for(var i=0;i<elm.length;i++)
	{
		var hli = "<li>" + elm[i].label + "</li>"
		//var hli = "<li>Step " + (i+1) + "</li>"
		lists += hli;
	}
	elmLst.innerHTML = lists;
	return elmLst;
}

function elementToStr(elm){
	var elmStr = "";
	for(var i=0; i<elm.length; i++)
	{
		elmStr += elm[i].label;		
	}
	return elmStr;
}

function elementToNode(elm,id,idx){
	
	/* containing DIV */
	var elem = new Element("div",{"class":id + " pattern-" + elm});
	var elemNode = new Element("div",{"class":"elemWrapper"});
	//alert("Element to node" + elemNode.effect);
	
	/* main divs in the node */
	var details = new Element("div",{"class":"detail"});
	var subs = new Element("div",{"class":"subs"});
	subs.setStyle("display","none");
	var terminals = new Element("div",{"class":"terminals"});
	terminals.setStyle("display","none");
	var codeDiv = new Element("div",{"class":"code"});
	codeDiv.setStyle("display","none");
	
	/* inner nodes */
	var letter = new Element("h3",{"class":"elementLetter"});
	var varList = new Element("ul",{"class":"list-variable"});
	var termList = new Element("ul",{"class":"list-terminal"});
	
	/* The image. I'd prefer to do this in CSS later. */
	var elmStr = (idIsUpperCase) ? elm.toUpperCase() : elm.toLowerCase();
	var letterImage = new Element("img",{"title": masterCategories[elm],"src":imageloc + imageprefix + elmStr + imagesuffix,"class":"elementIcon"});
	/* make a tooltip for the image */
	letterImage.addClass("tooltip");
	letterImage.ttip = new Tips(letterImage);
	//letterImage.setProperty("class","tooltip");
	
	/* text nodes */
	letter.innerHTML = elm;
	
	details.appendChild(letter);
	details.appendChild(letterImage);
	
	//subs.appendChild(varHeader);
	subs.appendChild(varList);
	
	//terminals.appendChild(termHeader);	
	terminals.appendChild(termList);
	
	//elemNode.appendChild(letterPara);
	
	elemNode.appendChild(details);
	elemNode.appendChild(subs);
	elemNode.appendChild(terminals);
	elemNode.appendChild(codeDiv);
	
	elem.appendChild(elemNode);
	
	return elem;
}

//This function updates an li with links as fed by the content array
function update_list(elementId, listClass, content_array,type) {
	var elm=elements[elementId];
	var elementBlock = elm.node;
	var elementList = elementBlock.getElementsByTagName("UL");
	var listToChange = null;


	if(content_array.length > 0)
	{
		var targDiv = null;
		switch(listClass)
		{
			case 'list-variable':
				var targDiv = "subs";
			break;
			case 'list-terminal':
				var targDiv = "terminals";
			break;
		}
		listToChange = elementBlock.getElement("div[class="+targDiv+"]");
		listToChange.setStyle('display','block');
	} else {
		return 0;	
	}

	empty(listToChange);
		
	for (i=0;i<content_array.length;i++){
		var listItem = new Element("LI");
		var listItemLink = new Element("A");
		var linkText = "";
		/* add some identifying elements to the link */
		listItemLink.elementID = elementId;
		listItemLink.node = elementBlock;
		
		if (listClass == "list-terminal")
		{
			listItemLink.contentItem = content_array[i].terminal;
			listItemLink.contentDescription = content_array[i].description;
			listItemLink.setProperties({title:content_array[i].description});
			listItemLink.addClass("tooltip");
			attachTip(listItemLink,{showDelay:200,hideDelay:0});
			var myNewString = content_array[i].terminal.replace(/[A-Za-z]+/, "");//phil
			linkText = myNewString;
		} else { //transform
			listItemLink.contentItem = content_array[i].transform_name;
			listItemLink.contentDescription = "Change to "+content_array[i].transform_to+" ("+content_array[i].transform_description+")";
			listItemLink.transformTo = content_array[i].transform_to;//phils
			listItemLink.setProperties({title:"Change to "+content_array[i].transform_to+" ("+content_array[i].transform_description+")"});
			listItemLink.addClass("tooltip");
			attachTip(listItemLink,{showDelay:200,hideDelay:0});
			linkText = content_array[i].transform_name; //DISPLAY NAME OF THE TRANSFORM
		}
		listItemLink.setAttribute("href", "#");		
		
		if (type=='terminate'){
			listItemLink.onclick = function() { terminate_me(this); return false; }	
			if(elm.terminal == listItemLink.contentItem)
			{
				var terminator = listItemLink;
			}
		}else if (type=='transform'){			
			listItemLink.onclick = function() { transform_me(this); return false;}
		}
		
		listItemLink.innerHTML = linkText;
		listItem.appendChild(listItemLink);
		listToChange.appendChild(listItem);
	}
	
	if (terminator) terminate_me(terminator);
	
	return 0;
}//update_list
	
//update the two lists in an element.
function updateElement(element,place){
		element.updated = true;
		get_details(element,place);
		/*
		get_transforms(element,place);
		get_terminals(element,place);
		*/
}	

function find_me(whoami){
	for(i = 0; i < container.childNodes.length; i++)	{
		if (whoami == container.childNodes[i]) { 
			return i;
		}
	}
	return false;
}

function transform_me(whoami){ 
	
	var to_what = whoami.transformTo;
	
	var index = find_me(whoami.node);
	var letter_array = to_what.split(" ");
	var new_array = new Array();
	var changedElements = new Array();
	var divPointer = elements[index].node;
	
	/* create the new node representations in the data structure
	 * and integrate them with the new element list. */
	for (i=0;i<elements.length;i++){
		if (i==index){
			for (j=0;j<letter_array.length;j++){
				var label = letter_array[j];
				var node = elementToNode(letter_array[j],"element");
				new_array.push({"label":label,"node":node});
				changedElements.push(j + i);
			}
		}else{
			//alert(elements[i].node.innerHTML)
			new_array.push(elements[i]);
		}
	}
	
	
	// initialize animation queues
	elementsToAdd = new Array();
	elementsToDelete = new Array();
	
	for (var i = 0; i < changedElements.length; i++){
		var telm = new_array[changedElements[i]];
		// get the transforms, terminals
		updateElement(telm,changedElements[i]);
		// add element to rendering queue
		elementsToAdd.push(telm.node);		
		telm.node.setStyles({display:"none",width:0});
		// insert into the structure. We do this after setting the styles
		// to prevent a flash of the element on the screen.
		container.insertBefore(telm.node,divPointer);		
	}
	
	elementsToDelete.push(divPointer);
	elements = new_array;	
	update_history("TRANSFORM TO "+ to_what);
	clearTips();
	animateTransform();
	redraw_history();
	
	
}

function terminate_me(whoami){

	var index = find_me(whoami.node);
	var which_part = whoami.contentItem;
	var elementBlock = elements[index].node;
	var elementList = elementBlock.getElementsByTagName("DIV");
	
	codeblock_to_update=elementBlock.getElement("div[class=code]");
		
	elements[index].terminal = which_part; // save the termination code on the element itself
	
	/* make the block visible with a cinematic effect */
	codeblock_to_update.setStyles({opacity:'0',display:'block'});
	var blockeffects = codeblock_to_update.effects({duration: 200, transition: Fx.Transitions.linear});
	blockeffects.start({opacity: 1});
	
	codeblock_to_update.innerHTML=which_part;
	terminal_array[index]=which_part;
	codeblock_to_update.addEvent("click",function(){reveal_terminals(whoami)});
	update_history();
	redraw_history();
	update_finished_status();
	clearTips();
	animate_termination(whoami);
	
}

// add a new row to the history
function update_history(description)
{
	// first, if I am not at the end of the history clear out the entries
	// above this one.
	if ((current_history_index+1)<history_array.length){ //if i'm NOT at the end of the history array
		var num_to_delete = history_array.length - (current_history_index+1);
		var new_length=history_array.length-num_to_delete;
		for(i=1;i<=num_to_delete;i++)
		{
			//TODO: More aggressive garbage collection
			history_array.pop();
		}
		history_array.length=new_length;
	}
	
	if(description != null)
	{
		current_history_index++;
		var action_array=new Array();
		action_array['description']=description;
		action_array['elements']= new Array();
		for (i=0;i<elements.length;i++){
			action_array['elements'][i] = elements[i];
		}
		history_array.push(action_array);
	} else {
		for (i=0;i<elements.length;i++){
			history_array[history_array.length-1]['elements'][i] = elements[i];
		}
	}
}

/* ==========================================
   AJAX data gathering and processing routines
   =========================================*/

function get_details(element,place)
{
	//var transform_html = '';
	//var terminal_html = '';
	
	if(grammar[element.label])
	{
		update_list(place,"list-variable",grammar[element.label]['transforms'],"transform");
		update_list(place,"list-terminal",grammar[element.label]['terminals'],"terminate");
	} else {
		var url = "partdetails.php?div_to_update="+place+"&item="+element.label;
		var myAjax = new Ajax(url,{method:'get',onComplete: process_details}).request();
	}
}

function process_details(returned)
{
	var resultSet = eval("("+returned+")");
	var current_div = resultSet.updatediv;
	var transform_array = resultSet.transforms;
	var terminal_array = resultSet.terminals;
	var part = resultSet.part;
	
	/*
	if(!grammar[part])
	{
		grammar[part] = new Array();
		grammar[part]['transforms'] = transform_array;
		grammar[part]['terminals'] = terminal_array;
	}
	*/
	
	update_list(current_div,"list-variable",transform_array,"transform");
	update_list(current_div,"list-terminal",terminal_array,"terminate");
	return 0;
}

//Gets the list of available transforms
function get_transforms(element,place){
	var transform_html = '';
	var url = "transforms.php?div_to_update="+place+"&item="+element.label;
	//window.alert(url);
	var myAjax = new Ajax(url, {method: 'get',onComplete: process_transforms}).request();
}

//Updates the transforms on the page after we've gotten the list
function process_transforms(returned_content){
	var resultSet = eval("("+returned_content+")");
	var current_div = resultSet.updatediv;
	var transform_array = resultSet.transforms;
	update_list(current_div,"list-variable",transform_array,"transform");
	return 0;
}

//Gets the list of available terminals
function get_terminals(element,place){
	var terminal_html;
	var url = "terminals.php?div_to_update="+place+"&item="+element.label;
	var myAjax = new Ajax(url, {method: 'get',onComplete: process_terminals}).request();
}

//Updates the terminals on the page after we've gotten the list
function process_terminals(returned_content){
	var resultSet= eval("("+returned_content+")");
	var current_div = resultSet.updatediv;
	var terminal_array = resultSet.terminals;
	update_list(current_div,"list-terminal",terminal_array,"terminate");
	return 0;
}

/* ==========================================
   Tooltip routines
   =========================================*/

function attachTip(targ,tipOptions)
{
	targ.ttip = new Tips([targ],tipOptions);	
	toolTips.push(targ.ttip);
}

/* Safari, IE do not appear to fire onmouseout when an element slides out from under the mouse. 
 * Therefore, anytime we do something that might cause an element to move we need to clear the tips.
 */
function clearTips()
{
	for(var i = 0; i< toolTips.length; i++)
	{
		toolTips[i].hide();	
	}
}

/* ==========================================
   Animation routines
   =========================================*/

// turn one element into two
// activated when a transformation is clicked.
function animateTransform()
{
	var addEffects = new Array();
	var deleteEffects = new Array();

	var containerEffects = container.effects({duration:1,transition:Fx.Transitions.linear});
	containerEffects.start({width:((elmWidth + 2) * (elements.length))});

	while(elementsToAdd.length != 0)
	{
		var elm = elementsToAdd.pop();
		elm.setStyles({width:0,display:'block',opacity:0});
		if (elementsToAdd.length == 1)
		{
			var myEffects = elm.effects({duration: 500,transition:Fx.Transitions.linear});
		} else {
			var myEffects = elm.effects({duration: 500,transition:Fx.Transitions.linear});	
		}
		myEffects.start({width:elmWidth,opacity:1, marginRight:1});
	}
	while(elementsToDelete.length != 0){
		var elm = elementsToDelete.pop();
		var myEffects = elm.effects({duration: 500,transition: Fx.Transitions.linear,onComplete:function(){container.removeChild(elm);}});
		myEffects.start({width:0,opacity:0,marginRight:0});
	}
	redraw_history();
}

// show hidden terminal blocks
function reveal_terminals(whoami)
{ 
	var index = find_me(whoami.node);
	//window.alert(index);
	var elm = elements[index].node;
	var varList = elm.getElement("div[class=subs]");
	var termList = elm.getElement("div[class=terminals]");

	codeblock_to_update = elm.getElement("div[class=code]");
	codeblock_to_update.setStyle('display','none');

	elements[index].terminal = null; // clear any saved termination

	var varDestHeight = varList.calcHeight;
	var termDestHeight = termList.calcHeight;

	
	var varEffects = varList.effects({duration:200, transition: Fx.Transitions.linear});
	var termEffects = termList.effects({duration:200,transition: Fx.Transitions.linear,
			onComplete:function(){varEffects.start({height: varDestHeight, opacity: 1, marginTop: 0, marginBottom: 0, borderWidth: 1});}});
	
	termEffects.start({height: termDestHeight, opacity: 1, marginTop: 0, marginBottom: 0, borderWidth: 1});
	
	update_history();
	redraw_history();
	update_finished_status();
}

// runs when a terminal is selected. 
// hide component transfornations and highlight the terminal.
function animate_termination(whoami)
{
	var index = find_me(whoami.node);
	var elm = elements[index].node;
	var varList = elm.getElement("div[class=subs]");
	var termList = elm.getElement("div[class=terminals]");
	
	varList.calcHeight = varList.getCoordinates()["height"];
	termList.calcHeight = termList.getCoordinates()["height"];

	var termEffects = termList.effects({duration:200,transition: Fx.Transitions.linear,
			onComplete:function(){varEffects.start({height: 0, opacity: 1, marginTop: 0, marginBottom: 0, borderWidth: 0});}});
	var varEffects = varList.effects({duration:200,transition: Fx.Transitions.linear});
	
	termEffects.start({height: 0, opacity: 1, marginTop: 0, marginBottom: 0, borderWidth: 0});
	
}


/* ==========================================
   File ops and record keeping routines
   =========================================*/

// mark whether or not a sequence has been completed.
function update_finished_status(){
	var finished=true;
	var thecontainer = document.getElementById("thecontainer");
	var elementBlock =thecontainer;
	var elementList = elementBlock.getElementsByTagName("DIV");
	for(var i = 0; i < elementList.length && finished; i++)	{
		if(elementList[i].className.indexOf("code") == -1) continue;
		finished = (elementList[i].style.display!="none"); // if an item is not finished, this should break it out of the loop. we really don't need to check any more.
	}
	var status = document.getElementById('status');
	if (finished == true)
	{
		
		status.setStyles({opacity:0});
		status.appendChild(document.createTextNode("Sequence ready"));
		status.innerHTML = '<a href="#" onclick="download_sequence()"><img src="images/sequence_ready.gif" alt="Sequence ready! Download your sequence." /></a>';
		var statusEffects = status.effects({duration:600,transition: Fx.Transitions.linear});
		statusEffects.start({opacity:.9999});
	}
	else
	{
		status.innerHTML = '';
	}	
}

//save the design to the database
function save_design()
{
	var design_name = $('design_name').value;
	var library_id = $('library_id').value;
	var design_description = $('design_description').value;
	var history_array_new = Array();
	for (i=0;i<history_array.length;i++){
		var element_array = Array();
		for (k=0;k<history_array[i]['elements'].length;k++){
			element_array[k] = history_array[i]['elements'][k]['label'];
		}
		history_array_new[i]={"description":history_array[i]['description'],"elements":element_array};
	}
	

	terminal_array_new = Array();
	
	var thecontainer = document.getElementById("thecontainer");
	var elementBlock =thecontainer;
	var elementList = elementBlock.getElementsByTagName("DIV");
	element_status_array = Array();
	for(var i = 0; i < elementList.length; i++)	{
		if(elementList[i].className.indexOf("code") == -1) continue;
		if (elementList[i].style.display!="none")element_status_array[element_status_array.length]=0;
		else element_status_array[element_status_array.length]=1;
	}
	
	var terminal_array_refactored = Array();
	for (var i=0;i<terminal_array.length;i++) {
		if (terminal_array[i]){
			terminal_array_refactored.push(terminal_array[i]);
		}
	}
	
	var index = 0;
	for (var i=0;i<elements.length;i++){
		if (element_status_array[i])
			terminal_array_new[i]="";	
		else
			terminal_array_new[i]=terminal_array_refactored[index++];	
	}
	
	// TODO: replace this anonymous function with a declared one.
	var url = "save_design.php";
	var myAjax = new Ajax(url,{method:'post',postBody:"library_id="+library_id+"&design_name="+design_name+"&design_description="+design_description+"&history="+JSON.encode(history_array_new)+"&terminals="+Json.toString(terminal_array_new),onComplete: function(){
		$('save_form').style.display='none';
		$('statusmessage').innerHTML="Design saved!";
		$('statusmessage').setStyle('opacity', '1');
		$('statusmessage').setStyle('display', 'block');
		new Fx.Style($('statusmessage'), 'opacity', {duration: 1000} ).start(0);	
		}}).request();
}

// assemble the sequence into a string for downloading
function download_sequence(){
	var terminals="";
	for(var i = 0; i < terminal_array.length; i++)	{
		terminals += terminal_array[i]+"|";
	}
	location.href="fetch_sequence.php?terminals="+terminals;
}

// change which part library is being used
function change_library(library_id,confirmed){
	var postbody = 'library_id='+library_id+'&confirmed='+confirmed;
	var url='change_library.php';
	var current_selectedIndex = $('library_id').selectedIndex;
	// TODO: replace this anonymous function with a declared one.
	var myAjax = new Ajax(url,{method:'post',postBody:postbody,onComplete: function(response){
		var return_array = eval("("+response+")");
		if (return_array['changed']==1){
			window.location.reload( false );
		}else {
			if (return_array['need_confirmation']==1){
				if (confirm('Are you sure?  Changing libraries will clear your current design.')){
					change_library(library_id,1);//confirm
					current_library_index = parseInt($('library_id').selectedIndex);
				}else{
					$('library_id').options[current_library_index].selected=true;
				}
			}else{
				$('grammar_id').options[0].selected=true;
			}
		}
		}}).request();
}


function update_grammar_id(){//updates library popup based on grammar
		first_load=0;
		if (!first_load && confirm('Are you sure?  Changing libraries will clear your current design.')){
			var grammar_id = $('grammar_id').value;
			var url ="get_libraries_grammar.php?grammar_id="+grammar_id;
			new Ajax(url, 
			{
		  		method: 'get',
				onComplete: function(returned) {
		    		var results = eval("("+returned+")");
					$('library_id').options.length = 0;
					for (i=0;i<results.length;i++){
						$('library_id').options[i] = new Option(results[i]['name'],results[i]['library_id']);
					}
					change_library($('library_id').options[0].value,1);//confirm
				}
			}).request();
			current_grammar_index = parseInt($('grammar_id').selectedIndex);
		}else{
			//if (!current_grammar_index)var current_grammar_index = 0;
			$('grammar_id').options[current_grammar_index].selected=true;
			first_load=0;
		}
	}