/******************************************/
// These are handy functions not "app" specific
	
	YAHOO.util.Dom.getElementsByAttribute = function(atr, val, tag, root, applyFn, includeRoot) {
			if(!root){
				root = document;
			}
			var method = function(el) {
				if(el){
					var elementAttribute = el.getAttribute(atr);
					if (elementAttribute) {
						if(YAHOO.lang.isNull(val)){
							return true;
						}else if(YAHOO.lang.isString(val)){
							if(elementAttribute === val){
								return true;
							}
						}else if(YAHOO.lang.isArray(val)){
							for(var a = 0; a < val.length; a++){
								if(elementAttribute === val[a]){
									return true;
								}
							}
						}
					}
				}
				return false;
				
			};
			if(root && includeRoot && method(root)){
				if(applyFn){
					applyFn(root);
				}
				var returnArray = this.getElementsBy(method, tag, root, applyFn);
				returnArray.splice(0, 0, root);

				return returnArray;
			}else{
				return this.getElementsBy(method, tag, root, applyFn);
			}
		};

	YAHOO.util.Dom.getChildAttribute = function(atr, tag, root, includeRoot){
		var element = this.getElementsByAttribute(atr, null, tag, root, null, includeRoot)[0]; // grab first child with attribute
		if(element){
			var attribute = element.getAttribute(atr);
			return attribute;
		}
	}
	YAHOO.util.Dom.getAncestorIfAttribute = function(atr, root, includeRoot) {
		if(root){
			var method = function(el) { 
				if(el && el.getAttribute(atr)){
					return true;
				}
				return false;
			};
			
			if(includeRoot && method(root)){
				return root;
			}
			
			return this.getAncestorBy(root, method);
		}
		
	};
	YAHOO.util.Dom.getAncestorIfClass = function(classname, root, includeRoot) {
		if(root){
			var method = function(el) { 
				if(el && YAHOO.util.Dom.hasClass(el, classname)){
					return true;
				}
				return false;
			};
			
			if(includeRoot && method(root)){
				return root;
			}
			
			return this.getAncestorBy(root, method);
		}
	};
	YAHOO.util.Dom.getAncestorAttribute = function(atr, root, includeRoot) {
		if(root){
			var element = this.getAncestorIfAttribute(atr, root, includeRoot);
			if(element){
				return element.getAttribute(atr);
			}
		}
	};
	// Swap two elements, return the removed node
	YAHOO.util.Dom.swap = function(newElement, referenceElement){
			if(referenceElement.parentNode){
				referenceElement.parentNode.insertBefore(newElement, referenceElement);
				return referenceElement.parentNode.removeChild(referenceElement);
			}
		};

	//  creates a eventType event on the document element, and executes processFn with the scope of it's constructor
	//	handy for widget creation
	YAHOO.util.Event.onDOMReadyConstructor = function(eventType, processFn, optionalObject){
			YAHOO.util.Event.onDOMReady(
				function(event){
					YAHOO.util.Event.addListener(document, eventType, 
						function(event){
							var target = YAHOO.util.Event.getTarget(event);
							processFn(target, event);
						}, 
					optionalObject, this); 
				},
			optionalObject, processFn.constructor);
		};

	YAHOO.util.Connect.createPostString = function(postNameValuePairs){
		var postArray = [];
		for(var key in postNameValuePairs){
			if(postNameValuePairs.hasOwnProperty(key)){ // broken in Safari!
				postArray.push(key + "=" + postNameValuePairs[key]);
			}
		}
		return postArray.join('&');
	};

/******************************************/

var LIGHTHOUSE = LIGHTHOUSE || {};
LIGHTHOUSE.util = LIGHTHOUSE.util || {};


(function (){

	var Dom = YAHOO.util.Dom;
	var Event = YAHOO.util.Event;
	var Lang = YAHOO.lang;

	LIGHTHOUSE.util.getRowId = function(element){
		
		var rowId = Dom.getAncestorAttribute('rowId', element, true);
		var childTableName = Dom.getChildAttribute('tableName', null, element, true);
		if(!childTableName){ // accounts for instances like table > rowId > table > action, which would return the rowId of the parent
			return rowId;
		}

	};
	LIGHTHOUSE.util.getRowIdElement = function(element){
		return Dom.getAncestorIfAttribute('rowId', element, true);
	};
	LIGHTHOUSE.util.getTableName = function(element){
		
		var tableName = Dom.getAncestorAttribute('tableName', element, true);

		return tableName;

	};
	LIGHTHOUSE.util.getTableNameElement = function(element){
		return Dom.getAncestorIfAttribute('tableName', element, true);
	};

	LIGHTHOUSE.util.getElementsByTableAndRowId = function(tableName, rowId, applyFn){

		var elements = [];

		Dom.getElementsByAttribute('tableName', tableName, null, document, function(tableElement){
			var rowLevel = Dom.getElementsByAttribute('rowId', rowId, null, tableElement, function(rowElement){
				applyFn(rowElement);
				elements.push(rowElement);	
			}, true);
		});

		return elements

	};
})();
LIGHTHOUSE.util.Connect = function (){
	
	// dependencies, and convient variables
	var Dom = YAHOO.util.Dom;
	var Event = YAHOO.util.Event;
	var Lang = YAHOO.lang;
	var Connect = YAHOO.util.Connect;

	var Config = LIGHTHOUSE.config;


	// Constants.. not really protected though,
	var RETRY_TIMES = 1; // How many times should it retry the request?
	var RETRY_WAIT_MS = 1000; // How long should I wait between retry requests;

	return function (model){
		
		// data-base contains a preceeding /
		var urlBase = Config.protocol + '//' + Config.domain + document.body.getAttribute('data-base') + '/' + model + '/';

		// why a whole object you ask? i'm not entirely sure myself -- onFailure needs to be able to call
		// make request, and make request needs to be able to call onFailure, all without introducing global
		// variables -- so my solution was a connection object
		var connectionObject = {
			makeRequest : function(method, variables, retryCount, events, eventObject, params) {
			
					if(typeof method === "string" && method !== ""){
						
						var requestParams = '';
						retryCount = retryCount || 0;
						
						if(params){
							requestParams = '/' + params.join('/');
						}
						var url = urlBase + method + requestParams;
						// url should look like "contacts/add/extra/123/asdf"

						var postString = Connect.createPostString(variables);
						Connect.initHeader('X-Request-Type', 'asynchronous'); // tell the app were requesting via ajax

						return Connect.asyncRequest("POST", url, {
							success : function(o){
								this.onSuccess(o, method, variables, events, eventObject, params);
							},
							failure : function(o){
								this.onFailure(o, method, variables, retryCount, events, eventObject, params);	
							},
							scope : this
						}, postString);
					}
			},
		
			onFailure : function(responseObject, method, variables, retryCount, events, eventObject, params){
				// Server / request failure
				if(retryCount < RETRY_TIMES){
					Lang.later(RETRY_WAIT_MS, this, this.makeRequest, [method, variables, retryCount + 1, events, eventObject]);
				}else{
					// server error -- /almost/ the same thing as a database error, so we should treat
					// it similarly 
					var onFailure = events[method + 'Failure'];
					responseObject.tIdPrimary = responseObject.tId - retryCount;
					onFailure.fire({response : responseObject,
									model : model,
									method : method, 
									variables : variables,
									sourceEvent : eventObject,
									params : params});
									// doesn't need fromServer, failures can be assumed to come
									// from the server
					
				}
			},
			onSuccess : function(responseObject, method, variables, events, eventObject, params){
				var onFailure = events[method + 'Failure'];
				var onSuccess = events[method + 'Success'];
				
				try{ 

					var resultObject = Lang.JSON.parse(responseObject.responseText);

					// if the database query was successfull
					if(!resultObject.isFailure){
						onSuccess.fire({response : resultObject,
										model : model,
										method : method, 
										variables : variables,
										isFromServer : true,
										sourceEvent : eventObject,
										params : params});
					}else{
					// couldn't
						onFailure.fire({response : resultObject,
										model : model,
										method : method, 
										variables : variables,
										isFromServer : true,
										sourceEvent : eventObject,
										params : params});
					}

				}catch(err){
					// Server error, probably a PHP syntax issue
					onFailure.fire({response : responseObject,
									model : model,
									method : method, 
									variables : variables,
									isFromServer : true,
									sourceEvent : eventObject,
									params : params});
				}
			}
		};

		return {
			request : function(method, variables, formElement, sourceEvent, params){
				if(formElement){
					Connect.setForm(formElement);
				}
				// where should the connection object look for to find what it does with the data
				var events = LIGHTHOUSE[model].database.events; 

				// We don't need to specify a retryCount argument on the public method.
				var transaction = connectionObject.makeRequest(method, variables, null, events, sourceEvent, params); 
				
				// assume a successful request
				// this way we can increase apparent speed
				var onSuccess = events[method + 'Success'];
				var onFailure = events[method + 'Failure'];
				if(!onSuccess){
					throw new Error("onSuccess event handler for controller "+model+" and method "+method+" was not found, please check that LIGHTHOUSE."+model+".database has the "+method+" permissions");
				}
				if(!onFailure){
					throw new Error("onFailure event handler for controller "+model+" and method "+method+" was not found, please check that LIGHTHOUSE."+model+".database has the "+method+" permissions");
				}

				onSuccess.fire({model : model,
								method : method, 
								variables : variables,
								fromServer : false,
								transaction : transaction,
								sourceEvent : sourceEvent,
								params : params});


				return transaction;

				
			}
		};
	};
}();



(function (){

	// Creates a CRUD interface

	LIGHTHOUSE.util.DatabaseInterface = function(table, functions){

		var Connect = new LIGHTHOUSE.util.Connect(table);
		var CustomEvent = YAHOO.util.CustomEvent;
		var fieldValuePairs = {};
		
		function minusS(str){
			if(str.substr(str.length - 1) === "s"){
				return str.substr(0, str.length - 1);
			}
		}
	
		if(!functions){
			functions = "CRUD";
		}

		// Empty object to build from
		var databaseAPI = {
			events : {}
		};

		for(var a = 0; a < functions.length; a++){
			switch(functions.charAt(a).toUpperCase()){
				case "C":
					databaseAPI.events.addSuccess = new CustomEvent("onAddSuccess", null, null, CustomEvent.FLAT);
					databaseAPI.events.addFailure = new CustomEvent("onAddFailure", null, null, CustomEvent.FLAT);
					databaseAPI.add = 
						function(fieldValuePairs, optionalSourceElement) { 
							var eventObject = {
								sourceElement : optionalSourceElement
							};
							Connect.request('add', fieldValuePairs, this.events, eventObject);
						};
					break;
				case "R":
					databaseAPI.events.readSuccess = new CustomEvent("onReadSuccess", null, null, CustomEvent.FLAT);
					databaseAPI.events.readFailure = new CustomEvent("onReadFailure", null, null, CustomEvent.FLAT);
					databaseAPI.read = 
						function(rowId, optionalSourceElement){
							var eventObject = {
								sourceElement : optionalSourceElement
							};
							fieldValuePairs[minusS(table)+"Id"] = rowId;
							Connect.request('read', fieldValuePairs, this.events, eventObject);
						};
					break;
				case "U":
					databaseAPI.events.updateSuccess = new CustomEvent("onUpdateSuccess", null, null, CustomEvent.FLAT);
					databaseAPI.events.updateFailure = new CustomEvent("onUpdateFailure", null, null, CustomEvent.FLAT);
					databaseAPI.update = 
						function(rowId, fieldValuePairs, optionalSourceElement){
							var eventObject = {
								sourceElement : optionalSourceElement
							};
							fieldValuePairs[minusS(table)+"Id"] = rowId;
							Connect.request('update', fieldValuePairs, this.events, eventObject);
						};
					break;
				case "D":
					databaseAPI.events.updateSuccess = new CustomEvent("onDestroySuccess", null, null, CustomEvent.FLAT);
					databaseAPI.events.updateFailure = new CustomEvent("onDestroyFailure", null, null, CustomEvent.FLAT);
					databaseAPI.destroy = 
						function(rowId, optionalSourceElement){
							var eventObject = {
								sourceElement : optionalSourceElement
							};
							fieldValuePairs[minusS(table)+"Id"] = rowId;
							Connect.request('destroy', fieldValuePairs, this.events, eventObject);
						};
					break;
			}
		}
		return databaseAPI;
	};

})();



(function (){
	
	// dependencies, and convient variables
	var Dom = YAHOO.util.Dom;
	var Event = YAHOO.util.Event;
	var Lang = YAHOO.lang;

	var Util = LIGHTHOUSE.util;

	LIGHTHOUSE.util.replaceFieldValues = function(){

		// is used by the HistoryManager, and implements the historyManagerAction interface
		return {
			undo : function(params) { 
				var variables = params.variables;
				var fieldValueArray = [];

				for(var key in variables){
					if(variables.hasOwnProperty(key)){
						fieldValueArray.push(key);
					}
				}			
				Util.getElementsByTableAndRowId(params.table, params.rowId, function(element){
					Dom.getElementsByAttribute('fieldName', fieldValueArray, null, element, function(fieldElement){ 
						
						var fieldName = fieldElement.getAttribute('fieldName');
						var newHMTL = Lang.trim(variables[fieldName].replace(/\n/gi, "<br>"));
						if(newHMTL === ""){
							newHMTL = "&nbsp;";
						}
						fieldElement.innerHTML = newHMTL;		

					}, true);
				});
			},
			execute : function(params){
				var variables = params.variables;
				var fieldValueArray = [];
				var newVariables = {};
				var undoParams = params;

				for(var key in variables){
					if(variables.hasOwnProperty(key)){
						fieldValueArray.push(key);
					}
				}			
				Util.getElementsByTableAndRowId(params.table, params.rowId, function(element){
					Dom.getElementsByAttribute('fieldName', fieldValueArray, null, element, function(fieldElement){ 
						var fieldName = fieldElement.getAttribute('fieldName');
						var newHMTL = Lang.trim(variables[fieldName].replace(/\n/gi, "<br>"));
						if(newHMTL === ""){ newHMTL = "&nbsp;"; }
						
						newVariables[fieldName] = fieldElement.innerHTML;
						fieldElement.innerHTML = newHMTL;
					}, true);
				});
				
				undoParams.variables = newVariables;

				// return anything the undo function needs
				return undoParams;
			}
		}
	}();
})();

(function (){
	
	// dependencies, and convient variables
	var Dom = YAHOO.util.Dom;
	var Event = YAHOO.util.Event;
	var Lang = YAHOO.lang;

	var Util = LIGHTHOUSE.util;

	LIGHTHOUSE.util.removeFieldElements = function(){

		// is used by the HistoryManager, and implements the historyManagerAction interface

		return {
			undo : function(params) { 
				for(var a = 0; a < params.length; a++){
					Dom.insertAfter(params[a].element, params[a].previousSibling);
				}
			},
			execute : function(params){
				var undoParams = [];

				Util.getElementsByTableAndRowId(params.table, params.rowId, function(element){
					
					var removedElement = {};
					if(!element.previousSibling){ // mostly for IE, who dosen't whitespace as empty text nodes
						var emptyTextNode = document.createTextNode("");
						element.parentNode.insertBefore(emptyTextNode, element);
					}
					removedElement.previousSibling = element.previousSibling;
					removedElement.element = element;
					element.parentNode.removeChild(element);

					undoParams.push(removedElement);
					
				});
				var x;
				// return anything the undo function needs
				return undoParams;
			}
		}
	}();
})();

///
(function(){

	LIGHTHOUSE.util.HistoryManager = function(){
		
		var historyStack = {};

		return {
			add : function(id, commandObject, params){
				var undoParams = commandObject.execute(params);
				
				historyStack[id] = {commandController : commandObject,
									  undoParams : undoParams};
			},
			undo : function(id){
				if(historyStack[id]){
					var undoParams = historyStack[id].undoParams;
					
					historyStack[id].commandController.undo(undoParams);
					delete historyStack[id];
				}else{
					throw new Error("ID not found");
				}
			}
			
		}

	};

})();
//*/

(function(){

	LIGHTHOUSE.util.setSubscribers = function(eventContainer, subscriberContainer){
		
		for(var event in eventContainer){
			if(eventContainer.hasOwnProperty(event)){
				var camelCaseEvent = event.substr(0, 1).toUpperCase() + event.substr(1);
				if(subscriberContainer['on'+camelCaseEvent]){
					eventContainer[event].subscribe(subscriberContainer['on'+camelCaseEvent]);
				}
			}
		}

	}

})();


(function(){

	var Dom = YAHOO.util.Dom;
	var Event = YAHOO.util.Event;
	var Lang = YAHOO.lang;

	LIGHTHOUSE.util.applyCurrentStylesToRTE = function(event, editorInstance){
		var stylesheets = document.getElementsByTagName('link');
		var editorDoc = editorInstance._getDoc();
		var editorHead = editorDoc.getElementsByTagName('head')[0];
		for(var a = 0; a < stylesheets.length; a++){
			if(YAHOO.env.ua.ie > 0){
				// IE can't appendChildren across frames, so we have to ask the frame to create the stylesheet for us.
				editorDoc.createStyleSheet(stylesheets[a].getAttribute('href'));	
			}else{
				editorHead.appendChild(stylesheets[a].cloneNode(true));
			}
		}
		var editorHTML = editorDoc.getElementsByTagName('html')[0];
		Dom.addClass(editorHTML, editorInstance.get('element').getAttribute('id').replace('editor-', ''));
		Dom.addClass(editorHTML, 'editor');
	}

})();



(function(){

	var Dom = YAHOO.util.Dom;
	var Event = YAHOO.util.Event;
	var Lang = YAHOO.lang;
	var Util = LIGHTHOUSE.util;

	LIGHTHOUSE.util.autoSave = function(formId, duration, msgElement, beforeSave){
		var form = document.getElementById(formId);
		Lang.later(duration, form, function(){
			var requestLocation = this.getAttribute('action').replace(document.body.getAttribute('data-base'), '').split('/');
			var controller = requestLocation[1];
			var method = requestLocation[2];
			var params = requestLocation.splice(3, requestLocation.length);
			// I know I could pull the callback out, and access msgElement directly through
			// closure.. but by passing it through the event object, any of the other listeners
			// have access to it if they need it
			LIGHTHOUSE[controller].database.events.updateSuccess.subscribe(function(o){
				if(o.isFromServer && o.sourceEvent.type === 'autoSave'){
					var now = new Date();
					var hours = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
					var amPm;
					var minutes;

					if(now.getMinutes() < 10){
						minutes = "0" + now.getMinutes();
					}else{
						minutes = now.getMinutes();
					}
					if(now.getHours() >= 12){ amPm = "pm"; }else{ amPm = "am"; }
					var time = ( hours[now.getHours() % 12] ) + ':' + minutes + " "+ amPm;
					o.sourceEvent.msgElement.innerHTML = 'Autosave successful at '+time;
				}
			});
			var Connect = Util.Connect(controller);
			// in this case, the 'event' is the autosave, nothing super special
			// but we should define a type, and maybe a target if we are feeling spunky - anything else partianing
			// to the autosave function that needs to get passed to the success handler, should go in the event object.
			if(beforeSave){
				beforeSave();
			}
			Connect.request(method, null, this, {type: 'autoSave', msgElement : document.getElementById(msgElement)}, params);
		}, null, true);

	}

})(); 
