// create a class to represent selections of words in a data_container. use this 
// code to create an instance based on the current user selection:
// > var selection = new Selection(container, start_word, end_word);
var Selection = Class.create();

Selection.prototype = {
	initialize : function(container, start_word, end_word) {
		if (start_word) {
			this.container = container;
			this.start_word = start_word;
			this.end_word = end_word;
		} else {
			this.clear();
		}
	},
	
	// empties out this selection. after a call to clear(), is_empty() is true
	clear : function() {
		this.container = this.start_word = this.end_word = null;
	},
	
	// returns true if there was no selection when this object was created or
	// if the selection did not include any data
	is_empty : function() {
		return this.start_word == null;
	},
	
	// returns the selection (modified to include only data, and only data from
	// one source) as a string
	to_string : function() {
		if (this.is_empty()) {
			return null;
		} else {
			var current_paragraph = this.start_word.up(".paragraph");
			var words = [];
	    	var start = word_index(this.start_word);
			var end = word_index(this.end_word);
			var word_id_parts = this.start_word.id.split('_');

			for (count = start; count <= end; count++) {
				word_id_parts[word_id_parts.size()-1] = count;
			   	var word = $(word_id_parts.join('_'));
			   	if (word) {
				
					// if this is a new paragraph, append new lines
					var p = word.up(".paragraph");
					if (p != current_paragraph) {
						current_paragraph = p;
						words.push("\n\n");
					}
					
					words.push(word.innerHTML);
			   	}
			}
			
			return words.join(' ');
		}
	},
	
	// highlight all of the words (and spaces) contained in this selection
	highlight : function() {
		if (!this.is_empty()) {
			highlight_range(this.start_word.id, this.end_word.id);
		}
	},
	
	// show link that can be used to clear the selection
	show_controls : function() {
		if (!this.is_empty() && this.container) {
			var a = new Element('a', {'onclick':'clear_current_selection();return false;'}).update('&times;');
			var control = new Element('div', {'id':'selection_control'}).update(a);
			
			// figure out how far left to move the 'x' link
			var parent = $(this.start_word.parentNode);
			var margin = parent.getStyle('margin-right');
			margin = parseInt(margin.sub('em', '').sub('px', ''));
			var padding = parent.getStyle('padding-right');
			padding = parseInt(padding.sub('em', '').sub('px', ''));
			var leftOffset = parent.getWidth() - margin - padding + 18;
			
			this.container.appendChild(control);
			control.clonePosition(this.start_word, {setWidth:false, setHeight:false, offsetTop:-1});
			control.clonePosition(this.container, 
				{setWidth:false, setHeight:false, setTop:false, offsetLeft:leftOffset})
		}		
	},
	
	// returns the paragraph that the end_word is in
	paragraph_index : function() {
		if (!this.is_empty())
			return this.end_word.up(".paragraph").id.split('_').last();
	},
	
	// returns the origin type corresponding to this selection. note: if the
	// text data is from a code application, the real origin is encoded in the 
	// surrounding paragraph.
	origin_type : function() {
		var type = origin_type(this.start_word);
		if (type == 'CodeApplication') {
			return origin_type(this.start_word.up(".paragraph"));
		} else {
			return type;
		}
	},
	
	// returns the origin id corresponding to this selection. note: if the
	// text data is from a code application, the real origin is encoded in the 
	// surrounding paragraph.
	origin_id : function() {
		var type = origin_type(this.start_word);
		if (type == 'CodeApplication') {
			return origin_id(this.start_word.up(".paragraph"));
		} else {
			return origin_id(this.start_word);
		}
	},
	
	// returns the next inline_coding element after this selection
	inline_coding : function() {
		if (!this.is_empty()) {
			var para = this.end_word.up(".paragraph");
			var coding = para.next(".inline_coding");
			if (!coding) coding = para.parentNode.next(".inline_coding");
			return coding;
		}
	},
	
	set_form_values : function(prefix) {
		$(prefix+'_inline_coding_id').value = this.inline_coding().id;
		$(prefix+'_selection').value = this.to_string();
		$(prefix+'_paragraph_index').value = this.paragraph_index();
		$(prefix+'_start_word_index').value = word_index(this.start_word);
		$(prefix+'_end_word_index').value = word_index(this.end_word);
		$(prefix+'_origin_type').value = this.origin_type();
		$(prefix+'_origin_id').value = this.origin_id();
	}
};

// retuns the word index for the given element. assumes the id is formated like:
// word_<container_type>_<container_id>_<word_index>
function word_index(element) {
	return word_index_from_id(element.id);
}

// retuns the word index for the given element id. assumes the it is formated like:
// word_<container_type>_<container_id>_<word_index>
function word_index_from_id(element_id) {
	return parseInt(element_id.split("_").last());
}

// expects the id of the elmenet to be formated like:
// word_<container_type>_<container_id>_<word_index>
function origin_type(element) {
	return element.id.split('_')[1];
}

// expects the id of the elmenet to be formated like:
// word_<container_type>_<container_id>_<word_index>
function origin_id(element) {
	return element.id.split('_')[2];
}

// returns an inline coding element if the given element is inside of
// such an element
function coding_element(node) {
	if (node.hasClassName("inline_coding"))
	 	return node;
	
	return node.up(".inline_coding");
}

// get most specific none text node
function get_element(node) {
  if (node == null)
    return null;
    
  else if (node.nodeName == '#text') 
    return get_element(node.parentNode);
    
  return $(node);
}

// somewhat ugly code that creates a selection object based on the users current
// selection. if there is no (valid) selection this function returns an empty selection
// object.
function grab_selection() {
	var selection = new Selection(null,null,null);
	
	// get selection object based on user selection
	var user_selection = null;
	if (window.getSelection) {
  		user_selection = window.getSelection();
  	}
	else if (document.selection) { 
  		user_selection = document.selection.createRange();
  	}

	// get range object for the selection
	var range = null;
	try {
		if (user_selection.isCollapsed && user_selection.isCollapsed())
			return selection;
		
		if (user_selection.getRangeAt) {
			range = user_selection.getRangeAt(0);
		}
		else { // Safari ...
			range = document.createRange();
			range.setStart(user_selection.anchorNode,user_selection.anchorOffset);
			range.setEnd(user_selection.focusNode,user_selection.focusOffset);
		}
	}
	catch(e) {
		return selection;
	}
	
	// start and end elements
	var start_element = get_element(range.startContainer);
	var end_element = get_element(range.endContainer);
	var top_element = get_element(range.commonAncestorContainer); 
	var start_container = start_element.up(".data_container");
	var end_container = end_element.up(".data_container");
	
	if (!start_container && !end_container) {
		// neither selection is in a data container ...
		//console.log("not in data...");
		start_container = end_container = top_element.down(".data_container");
		
		// if it is still null then there is no data in the selection
		// otherwise grab all words in the container as the selection
		if (start_container) {
			start_element = start_container.down(".word");
			end_element = start_container.select(".word").last();
		} else {
			//console.log("not even containing data.");
			selection.clear();
			return selection;
		}
	}
	
	//console.log("at least one is in data...");
	
	// try to get start_word
	if (start_element.hasClassName("word")) {
		selection.start_word = start_element;
	} else {
		var coding = coding_element(start_element);
		if (coding) {
			var para = coding.next(".paragraph");
			if (para) {
				selection.start_word = para.down(".word");
			}
		} else if (start_element.hasClassName("space")) {
			selection.start_word = start_element.next(".word");
		} else {
			selection.start_word = start_element.down(".word");
		}
	}
	
	// now sort out end_word ...
	if (end_element.hasClassName("word")) {
		selection.end_word = end_element;
	} else {
		var coding = coding_element(end_element);
		
		if (end_element.id == 'selection_control') {
			var para = end_element.previous(".paragraph");
			if (para) {
				selection.end_word = para.select(".word").last();
			}
		} else if (coding) {
			var para = coding.previous(".paragraph");
			if (para) {
				selection.end_word = para.select(".word").last();
			}
		} else if (end_element.hasClassName("space")) {
			selection.end_word = end_element.previous(".word");
		} else if (end_element.hasClassName("paragraph")) {
			var para = end_element.previous(".paragraph");
			var start_para = selection.start_word.up(".paragraph");
			
			// check if end is the same paragraph as the start element
			if (start_para == end_element) {
				selection.end_word = end_element.select(".word").last();
			} else if (para) { // otherwise use previous paragraph
				selection.end_word = para.select(".word").last();
			}
		} else {
			selection.end_word = end_element.select(".word").last(); 
		}
	}
	
	if (start_container == end_container) {
		selection.container = start_container;
		
	} else {
		// figure out which data container we should be in and adjust start/end 
		// words if necessary
		if (start_container && selection.start_word) {
			selection.container = start_container;
			selection.end_word = start_container.select(".word").last();
		} else if (end_container && selection.end_word) {
			selection.container = end_container;
			selection.start_word = end_container.down(".word");
		}
	}
	
	if (selection.start_word && selection.end_word) {
		if (word_index(selection.start_word) > word_index(selection.end_word)) {
			// oops, end is before start!
			selection.clear();
		}
	}
	else  {
		// if we get here we were unable to resolve the given selection
		// in terms of a start and end word.
		selection.clear();
	}
	
	// todo: for debugging ...
	// if (!(selection.is_empty())) {
	// 	console.log(selection.container.id);
	// 	console.log(selection.start_word.id);
	// 	console.log(selection.end_word.id);
	// } else {
	// 	console.log("EMPTY SELECTION");
	// }
	
	return selection;
}

var _current_selection = new Selection(null,null,null);

function clear_current_selection() {
	clear_highlighting();
	_current_selection.clear();
}

function current_selection() {
	if (_current_selection.is_empty()) {
		_current_selection = grab_selection();
	} else {
		var user_selection = grab_selection();
		if (!user_selection.is_empty())
			_current_selection = user_selection;
	}
	return _current_selection;
}

// removes all highlighting
function clear_highlighting() {
	$$('.highlighted_word').each(function(element){
		element.removeClassName('highlighted_word')
	});
	
	if ($('selection_control'))
		$('selection_control').remove();
}

// called when mousing over a code application
function highlight_on_hover(start_word_id, end_word_id) {
	highlight_range(start_word_id, end_word_id);
}

// called when mouse leaves a code application
function clear_highlight_on_hover() {
	clear_highlighting();
	if (_current_selection) {
		// restore highlighting for current selection if there is one
		_current_selection.highlight();
		_current_selection.show_controls();
	}
}

// highlight all of the words (and spaces) contained in this selection
function highlight_range(start_id, end_id) {
	clear_highlighting();
	
   	var start = word_index_from_id(start_id);
	var end = word_index_from_id(end_id);
	var word_id_parts = start_id.split('_');

    for (count = start; count <= end; count++) {
		word_id_parts[word_id_parts.size()-1] = count;

      	var word = $(word_id_parts.join('_'));
      	if (word) {
        	word.addClassName('highlighted_word');
        	var space = word.nextSibling;
        	if (space && space.hasClassName('space') && count != end)
          		space.addClassName('highlighted_word');
      	}
	}
}

Event.observe(window, 'load', function(event) {
});

