var Dictionary = Class.create();
Dictionary.prototype = {
	
	skipTags: ["H1", "H2", "H3", "H4", "H5", "H6", "A", "UL", "OL", "LI", "BR"],
	dict: null,
	
	linkCssClass: "dictLink",
	windowCssClass: "dictWindow",
	dictScanCssClass: "dictionary",
	
	window: null,
	title: null,
	content: null,
	ajaxLoader: null,
	
	showWindowTimerId: null,
	hideWindowTimerId: null,
	showWindowTimeout: 200,
	hideWindowTimeout: 400,
	
	activeLink: null,
	activeLinkB: null, //ref to bind handler to keep closure, need this to stop observing on active link
	
	pointerX: null,
	pointerY: null,
	
	setDictionary: function(dict) {
		this.dict = new Hash(dict);
	},
	
	setLinkCssClass: function(cssClass) {
		if (cssClass != null)
			this.linkCssClass = cssClass;
	},
	
	setWindowCssClass: function(cssClass) {
		if (cssClass != null)
			this.windowCssClass = cssClass;
	},
	
	initialize: function(dictionary, linkCssClass, windowCssClass) {
		this.setLinkCssClass(linkCssClass);
		this.setWindowCssClass(windowCssClass);
		this.setDictionary(dictionary);
		
		this.window = $$("."+this.windowCssClass)[0];
		this.title = this.window.select(".headerText")[0];
		this.content = this.window.select(".contentText")[0];	
		this.ajaxLoader = this.window.select(".ajaxLoader")[0];	
		
		Event.observe(this.window, "mouseover", this.windowOverHandler.bindAsEventListener(this));
		Event.observe(this.window, "mouseout", this.windowOutHandler.bindAsEventListener(this));
		
		var list = Element.select(document.body, "."+ this.dictScanCssClass);
		
		for (var i=0; i< list.length;i++) {
			this.recursivelyProcess(list[i]);
		}
	},
	
	recursivelyProcess: function (node) {
		
		//clean up all text nodes with only whitespaces, empty text nodes and merge adjacent text nodes
		Element.cleanWhitespace(node);
		node.normalize();
		
		if (!node.hasChildNodes() || Element.hasClassName(node, "skip-dictionary"))
			return;
		
		for (var i=0; i < node.childNodes.length; i++) {
			var currentNode = node.childNodes[i];
			
			if (!this.isValidElement(currentNode))
				continue;
			
			if (currentNode.nodeType == Node.TEXT_NODE) {
				var parent = currentNode.parentNode;
	
				//loop through each dictionary word and check if it exists in the text node
				this.dict.each(function (w) {
					var word = w.key;
					var textNode = currentNode; //for each word we start from the beginning (the first text node)
					var originalWordText = null;
					var textAfterLink = null;
					var textNodeAfterLink = null;
					
					// \b = word boundary
					var re = new RegExp("\\b"+word+"\\b", "gi");

					//words could appear multiple times in the same text node, need to process them all
					while ( (pos = textNode.nodeValue.search(re)) != -1) {
	
						//original text which matched
						originalWordText = textNode.nodeValue.substr(pos, word.length);
						
						//create link element
						var link = document.createElement("a");
						link.setAttribute("href", "javascript:void(0);");
						Element.addClassName(link, this.linkCssClass);
						Event.observe(link, "mouseover", this.linkOverHandler.bindAsEventListener(this));
						Event.observe(link, "mouseout", this.linkOutHandler.bindAsEventListener(this));
						Event.observe(link, "click", this.linkClickHandler.bindAsEventListener(this));
						//link.appendChild(document.createTextNode(word));
						link.appendChild(document.createTextNode(originalWordText));
						
						//get the text after the link
						textAfterLink = textNode.nodeValue.substr(pos+word.length, textNode.nodeValue.length);
						textNodeAfterLink = document.createTextNode(textAfterLink);
						
						//now cut off all text after the found word
						textNode.nodeValue = textNode.nodeValue.substr(0, pos);                                                                                                                                                                                                                                                                                                                                                                            
						
						//insert the link after the cutted text element
						parent.insertBefore(link, textNode.nextSibling);
						
						//the text node after the link is the remaining text were going to work with
						textNode = parent.insertBefore(textNodeAfterLink, link.nextSibling);
					}
				}.bind(this));
				
			} else if (currentNode.hasChildNodes()) {
				this.recursivelyProcess(currentNode); //recursively process all nodes
			}
		}
	},
	
	isValidElement: function(el) {
		return (this.skipTags.indexOf(el.tagName) == -1);
	},
	
	setActiveLink: function(linkElement) {
		if (linkElement != null) {
			this.activeLink = linkElement;
			this.activeLinkB = this.activeLinkMouseMoveHandler.bindAsEventListener(this);
			Event.observe(this.activeLink, "mousemove", this.activeLinkB);
			
		} else if (this.activeLink != null) {
			Event.stopObserving(this.activeLink, "mousemove", this.activeLinkB);
			this.activeLink = null;
			this.activeLinkB = null;
		}
	},
		
	stopHideWindowTimer: function() {
		window.clearTimeout(this.hideWindowTimerId);
	},
	
	stopShowWindowTimer: function() {
		window.clearTimeout(this.showWindowTimerId);
	},
	
	startShowWindowTimer: function() {
		//stop previous show window timer
		this.stopShowWindowTimer(); 
		
		var self = this;
		this.showWindowTimerId = window.setTimeout(function() { self.showWindow(); }, this.showWindowTimeout);
	},
	
	startHideWindowTimer: function() {
		//stop previous hide window timer
		this.stopHideWindowTimer(); 
		
		var self = this;
		this.hideWindowTimerId = window.setTimeout(function() { self.hideWindow(); }, this.hideWindowTimeout);
	},
	
	linkOverHandler: function(event) {
		//stop the hide window timer because we close it immediately if the mouseover link is not the active link
		//or the user moves the cursor over the active link again
		this.stopHideWindowTimer(); 		
		
		//start the show window timer when the user is moving over a different link
		if (this.activeLink != event.element()) {
			//hide window when moving over a different link
			this.hideWindow(); 
			
			this.setActiveLink(event.element());
			this.pointerX = event.pointerX();
			this.pointerY = event.pointerY();
			
			this.startShowWindowTimer();
		}
	},
	
	linkOutHandler: function(event) {
		//when the user moves the mouse cursor off the link, start the hide window timer
		if (this.window.visible()) {
			this.startHideWindowTimer();
		} else { 
			//window is not visible so cancel the show window timer
			this.stopShowWindowTimer();
			
			//set active link to null because the window hasn't been active yet
			this.setActiveLink(null);
		}
	},
	
	linkClickHandler: function (event) {
		//alert("CLIIICK");
	},
	
	windowOverHandler: function(event) {
		//stop the hide window timer when the user moves the mouse cursor over the window
		this.stopHideWindowTimer();
	},
	
	windowOutHandler: function(event) {
		//start the hide window timer when the user moves the cursor off the window
		this.startHideWindowTimer();
	},	
		
	
	activeLinkMouseMoveHandler: function(event) {
		if (this.window.visible())
			this.positionWindow(event.pointerX(), event.pointerY());
	},
	
	positionWindow: function(x, y) {
		var vps = document.viewport.getScrollOffsets();
		var w = this.window.getWidth();
		var h = this.window.getHeight();
		var marginX = 4;
		var marginY = 2;

		//default top, show window above the cursor
		var top = y - h - marginY;
		
		//default left, show on left side of cursor
		var left = x - w - marginX;
		
		//top of window is outside the viewport, show below the cursor
		if (top < 0 || top < vps.top)
			top = y + marginY;
		
		//left of window is outside the viewport, show on right side of cursor
		if (left < 0 || left < vps.left)
			left = x + marginX;
		
		this.window.setStyle({left: left+"px"});
		this.window.setStyle({top: top+"px"});
	},
	
	showWindow: function() {
		this.positionWindow(this.pointerX, this.pointerY);
		this.ajaxLoader.hide();
		
		//search lowercase
		var word = this.activeLink.childNodes[0].nodeValue;
		word = word.toLowerCase();
		
		var obj = this.dict.get(word);

		//check whether the word is cached in the dictionary from a previous query
		if (!obj.isCached) {
			var id = obj.id;
			this.title.update(word);
			this.setWindowContent(word, "Laden...");
			this.ajaxLoader.show();

			var url = "/index.php";
			var params = new Hash({"json": "GetDictionaryWord", "id": id});
	
			new Ajax.Request(url, {
				method: 'post',
				parameters: params,
				onSuccess: this.ajaxRequestSuccessHandler.bind(this),
				onFailure: this.ajaxRequestFailureHandler.bind(this)
			});
	
			this.window.show();
			
		} else {
			this.setWindowContent(obj.word, obj.text);
			this.window.show();
		}
	},

	setWindowContent: function(title, content) {
		var headerText = title.truncate(28, "...");
		this.title.update(headerText);
		this.content.update(content);
	},
	
	ajaxRequestSuccessHandler: function(response) {
		if (!this.window.visible())
			return;
		
		var data = response.responseText.evalJSON();
		if (data.id != null) {
			//cache the results in the dictionary, lookup the word lowercase
			var word = this.activeLink.childNodes[0].nodeValue;
			word = word.toLowerCase();
			
			var obj =  this.dict.get(word);
			obj.word = data.word;
			obj.text = data.text;
			obj.isCached = true;
			
			//set content to window
			this.setWindowContent(data.word, data.text);
		} else {
			this.setWindowContent("Fout", "Er is een fout opgetreden bij het laden van het woord.");
		}
		
		this.ajaxLoader.hide();
	},

	ajaxRequestFailureHandler: function(response) {
		this.setWindowContent("Fout", "Er is een fout opgetreden bij het laden van het woord.");
		this.ajaxLoader.hide();
	},

	hideWindow: function() {
		this.setActiveLink(null);
		this.window.hide();
	}
}
