// If the dependencies are not found, throw an exception
if (typeof mcd === undefined || !mcd.dom || !mcd.event) {
	throw 'Can\'t initialize discover.liveSearch. mcd.dom and mcd.event are required dependencies';
}
 
// If the discover namespace doesn't exists, create it
if (typeof discover === 'undefined') {
	var discover = {};
}

/**
 * The Discover Live Search API
 * 
 * @requires mcd.dom
 * @requires mcd.event
 * @author Michael Girouard (mgirouard@mcdpartners.com)
 */
discover.liveSearch = (function () {
	
	// ========================================================================
	// ===== Private API  =====================================================
	// ========================================================================
	
	/**
	 * Black-listed pages which live search is NOT allowed on
	 * 
	 * @type {Array}
	 * @private
	 */
	var pageBlackList = [
		'/search/results-advanced.html', 
		'/search/results-tips.html'
	];

	/**
	 * Callback timeout reference
	 * @private
	 * @type {Integer}
	 */
	var requestTimeout = null;
	
	/**
	 * Checks to see if a document is blacklisted
	 * 
	 * @param {String} path
	 * @return {Boolean}
	 */
	var isBlackListed = function (path) {
		var delim = '|';
		return (delim + pageBlackList.join('|') + delim).indexOf(delim + path + delim) > -1;
	}

	/**
	 * The Callback which performs the search
	 * @private
	 */
	var queryTimeoutCallback = function () {
		discover.liveSearch.query(discover.liveSearch.elements.searchBox.value);
	};

	/**
	 * Cancels a search
	 */
	var cancelTimeoutCallback = function () {
		if (requestTimeout !== null) {
			window.clearTimeout(requestTimeout);
		}
	};

	/**
	 * Parses a response and delegates the page update
	 * 
	 * @param {mixed} responseData
	 */
	var parseResponse = function (responseData) {
		var listItems;

		/* XML Response */
		if (responseData.childNodes) {
			listItems = getResultsFromXML(responseData);
			appendResults(listItems);
		}

		/* JSON Response */
		else if (responseData.constructor === Object) {
			listItems = getResultsFromJSON(responseData);
			appendResults(listItems);
		}

		/* String Response (Sets innerHTML) */
		else if (responseData.constructor === String) {
			discover.liveSearch.elements.searchResults.getElementsByTagName('ul')[0].innerHTML = responseData;
		}
	};

	/**
	 * Returns a template collection which can be appended into the DOM
	 * 
	 * @return {HTMLElement}
	 */
	var getTemplateCollection = function () {
		var collection, title, list;

		/* Create the Nodes */
		collection = document.createElement('li');
		title      = document.createElement('h3');
		list       = document.createElement('ul');

		/* Build the Structure */
		collection.className = 'category';
		collection.appendChild(title);
		collection.appendChild(list);

		/* collection, title, and list are now available via closure. We simply
		 * need to deep clone the collection each time we need to add another
		 * collection to the results stack. */
		return function () {
			var out = {};
			
			out.nodes = collection.cloneNode(true);
			out.title = out.nodes.getElementsByTagName('h3')[0];
			out.list  = out.nodes.getElementsByTagName('ul')[0];
			
			return out;
		};
	}();

	/**
	 * Returns a template result item which can be appended into the DOM
	 * 
	 * @return {HTMLElement}
	 */
	var getTemplateItem = function () {
		var item, imageAnchor, image, title, titleAnchor, description, descriptionAnchor;

		/* Create the nodes */
		item              = document.createElement('li');
		descriptionAnchor = document.createElement('a');
		description       = document.createElement('p');

		/* Build the structure */
		description.appendChild(descriptionAnchor);
		
		item.className = 'item';
		item.appendChild(description);

		return function () {
			var out = {};
			
			out.nodes = item.cloneNode(true);
			out.descriptionAnchor = out.nodes.getElementsByTagName('a')[0];
			
			return out;
		};
	}();

	/**
	 * Appends elements to the search results list
	 * 
	 * @param {Array} results
	 */
	var appendResults = function (results) {
		var i;

		for (i = 0; i < results.length; i++) {
			discover.liveSearch.elements.searchResults.getElementsByTagName('ul')[0].appendChild(results[i]);
		}
	};

	/**
	 * Returns an array of html collections from JSON data
	 * 
	 * @param  {Object} json
	 * @return {Array}
	 */
	var getResultsFromJSON = function (json) {
		var collectionName, collection, itemName, item;

		var out = [];
		
		clearResults();

		for (collectionName in json.results) {
			collection = getTemplateCollection();
			collection.title.innerHTML = collectionName;

			for (itemName in json.results[collectionName]) {
				item = getTemplateItem();

				item.descriptionAnchor.innerHTML = itemName;
				item.descriptionAnchor.title = json.results[collectionName][itemName].description;
				item.descriptionAnchor.href = json.results[collectionName][itemName].href;

				collection.list.appendChild(item.nodes);
			}

			out.push(collection.nodes);
		}

		return out;
	};

	/**
	 * Returns an array of html collections from XML data
	 * 
	 * @param  {XML} json
	 * @return {Array}
	 */
	var getResultsFromXML = function (xml) {
		var collection, results, item;

		var out = [];
		var collections = xml.getElementsByTagName('collection');

		for (var i = 0; i < collections.length; i++) {
			collection = getTemplateCollection();
			collection.title.innerHTML = collections[i].getAttribute('name');
			out.push(collection.nodes);
			
			results = collections[i].getElementsByTagName('result');
			
			for (var j = 0; j < results.length; j++) {
				item = getTemplateItem();

				item.descriptionAnchor.innerHTML = results[j].getAttribute('name');
				item.descriptionAnchor.title = results[j].getAttribute('description');
				item.descriptionAnchor.href = results[j].getAttribute('href');

				collection.list.appendChild(item.nodes);
			}
		}

		return out;
	};

	/**
	 * Clears the search results list
	 */
	var clearResults = function () {
		var resultList = discover.liveSearch.elements.searchResults.getElementsByTagName('ul')[0];

		while (resultList.firstChild) {
			resultList.removeChild(resultList.firstChild);
		}
	};

	/**
	 * Generates a cross-browser request object
	 * 
	 * Note the self-invoking method
	 * 
	 * @return {XMLHttpRequest|ActiveXObject}
	 */
	var getRequestObject = function () {
		if (window.XMLHttpRequest) {
			return function () {
				return new XMLHttpRequest;
			};
		}
		else if (window.ActiveXObject) {
			return function () {
				try {
					return new ActiveXObject('Microsoft.XMLHTTP');
				}
				catch (e) {
					try {
						return new ActiveXObject('Msxml2.XMLHTTP');
					}
					catch (e) {
						try {
							return new ActiveXObject('Msxml3.XMLHTTP');
						}
						catch (e) {}
					}
				}
			};
		}
	}();

	/**
	 * An XHR Instance
	 * 
	 * @type {XMLHttpRequest|ActiveXObject}
	 */
	var request;

	/**
	 * Callback for readystatechange
	 */
	var requestCallback = function () {
		if (request.readyState !== 4) {
			return;
		}

		var contentType = request.getResponseHeader('Content-type');

		// We have to match against a regex because IE adds charset data
		if (contentType.match(/^text\/xml/)) {
			parseResponse(request.responseXML);
		}
		else if (contentType.match(/^text\/html/)) {
			parseResponse(request.responseText);
		}
		else if (contentType.match(/^application\/x\-javascript/)) {
			eval('var json = ' + request.responseText)
			parseResponse(json);
		}
	};

	/**
	 * Sends an XHR
	 * 
	 * @param {String} term
	 * @param {String} format
	 */
	var sendRequest = function (term, format) {
		var endPoint;

		format = format || discover.liveSearch.outputFormat;
		endPoint = discover.liveSearch.endPoint + '?output=' + format + '&q=' + term;

		request = getRequestObject();
		request.open('GET', endPoint, true);
		request.onreadystatechange = requestCallback;
		request.send('');
	};

	// ========================================================================
	// ===== Public API  ======================================================
	// ========================================================================

	var liveSearch = {
		
		/**
		 * The live-search endpoint
		 * @type {String}
		 */
		/* 'endPoint' : '/search/endpoint.php',  */
		'endPoint' : null,
		
		/**
		 * The default value in the search box
		 * @type {String}
		 */
		'defaultValue' : 'Search',
		
		/**
		 * The preferred output format from the service
		 */
		'outputFormat' : 'json',

		/**
		 * Named element shortcuts
		 * @type {Object}
		 */
		'elements' : {
			'searchBox'     : 'searchbox',
			'clearSearch'   : 'clear-livesearch',
			'searchResults' : 'search-results'
		},

		/**
		 * The main query method
		 * @return {Object}
		 */
		'query' : function (term) {
			sendRequest(term);
			return;
		},

		/**
		 * Clears the search box and cancels any active search in queue
		 */
		'clearSearch' : function () {
			mcd.dom.addClass(this.elements.clearSearch, 'hide');
			mcd.dom.addClass(this.elements.searchResults, 'hide');

			cancelTimeoutCallback();
			this.elements.searchBox.value = this.defaultValue;

			clearResults();
		}
	};

	// ========================================================================
	// ===== Initialization Routine ===========================================
	// ========================================================================
	
	mcd.dom.ready(function () {
		var i, defaultValue, searchWrap, searchResults, ul;
		var elements = discover.liveSearch.elements;
				
		// Discover Financial
		var searchContainer = document.body;
		
		/* Fail fast, if possible
		 * 	- If the page is blacklisted
		 *  - If the searchContainer was not found */
		if (isBlackListed(document.location.pathname) || !searchContainer) {
			return;
		}
		
		/* If there's no place to put the search results, cancel execution */
		if (!searchContainer) {
			return;
		}

		/* Set up the results container */
		searchWrap = document.createElement('div');
		searchResults = document.createElement('div');
		ul = document.createElement('ul');

		searchWrap.id = 'search-wrap';
		searchWrap.appendChild(searchResults);
		
		/* If we're on the DFS site, add the dfs class to the searchWrap */
		mcd.dom.addClass(searchResults, 'dfs');

		searchResults.id = 'search-results';
		mcd.dom.addClass(searchResults, 'hide')
		searchResults.appendChild(ul);

		searchContainer.appendChild(searchWrap);

		/* Create element references */
		for (i in elements) {
			elements[i] = mcd.dom.getElement(elements[i]);
		}

		/* Set the default search value */
		defaultValue = discover.liveSearch.defaultValue;

		/* Apply a click event for canceling the search */
		mcd.event.add(elements.clearSearch, 'click', function (event) {
			mcd.event.preventDefault(event);
			discover.liveSearch.clearSearch();
		});

		/* Apply focus event */
		mcd.event.add(elements.searchBox, 'focus', function () {
			if (this.value == defaultValue) {
				this.value = '';
			}
			else {
				this.select();
			}
		});

		/* Apply blur event */
		mcd.event.add(elements.searchBox, 'blur', function () {
			if (this.value == '') {
				this.value = defaultValue;
			}
		});

		/* Apply the keyup event */
		mcd.event.add(elements.searchBox, 'keyup', function (event) {
			if (!discover.liveSearch.endPoint) {
				return;
			}
			
			var clearSearch   = discover.liveSearch.elements.clearSearch,
				searchBox     = discover.liveSearch.elements.searchBox,
				searchResults = discover.liveSearch.elements.searchResults;

			cancelTimeoutCallback();

			if (searchBox.value !== '') {
				mcd.dom.removeClass(clearSearch, 'hide');
				mcd.dom.removeClass(searchResults, 'hide');
				
				// FIXME: Should be CSS
				searchResults.style.top = '43px';
			}
			else {
				mcd.dom.addClass(clearSearch, 'hide');
			}

			if (event.keyCode !== 27) {
				requestTimeout = window.setTimeout(queryTimeoutCallback, 600);
			}
			else {
				discover.liveSearch.clearSearch();
			}
		});
	});
	
	return liveSearch;
})();

