/*
 * - make an <input type="text"> box an domain search box
 * - auto-insert <select> box for the domain extensions after the <input>
 * - post to API for the search
 * - show
 * 		1. customizable availability message <div> after the <input>
 * 		2. availability result extension list <ul> after the <input>.
 * 
 * NOTE for the "result" variable used below.
 * var result: type object, availability result for the API
 * 		e.g., {".com":0, ".net":1, ...}
 *
 * 		where for availability: 0=unavail., 1=avail, 2=maybe
 */

(function( $ ){
	$.fn.domainSearchBox = function(options) {
		
		var me = this;
		var $ext; // extension <select> box
		var $form; // the form containing this input
		var $loading; // ajax "loading..." box
		var $message; // result message box
		var $resultList; // <ul> of the search result
		var $searchButton; // submit button
		var selectedExtAvailable = 2; // 1=yes, 0=no, 2=maybe
		var searchStatus = {}; // object with key = domain group names, and value = boolean where true means search in progress
		var inputReadyForSearch = false; // true means the name passed the validation (see function validate()) and is ready to submit to search 
		
		var MAX_INPUT_LENGTH = 62;
		
		// domain extension data. to get from API (path=settings.apiUrlGetExtensionConfigs) or from user-given options.  See settings.configSource.
		var config = {};
 		config.EXTENSIONS = []; // array of extensions, e.g., [".com,".net",...]
		config.EXTENSION_GROUPS = []; // array of extension group names, e.g., ["TLD,"CC",...]
		config.EXTENSION_GROUPING = {}; // object of extension grouping, e.g., {"TLD":[".com,".net",...],"CC":[".us",".ca",...],...}
		
		var settings = {

			submitFormOnAvailable: false, // if true, auto-submit form if domain found available
			delayResult: true, // if true, delay showing of result until the selected extension is unavailable; so that if the selected domain is available, not showing of result.  If false, just show the results once they return.
			otherExtensions: true, // if true, search (and show result of) the extensions other than the user selected one
			acceptMaybe: false, // if true, accept "maybe available" in the result (backend can return "maybe").
								// if false, backend will retry "maybe" results hoping to get a yes-or-no result.  if still no answer, treat as "not available".  so the backend cannot return "maybe".
			
			transferHosting: false, //whether it is transfer hosting
			
			enterKeySubmit: true, // if true, hitting enter key when focusing on the <input> will submit domain search
			addResultAction: true, // if true, add "get this domain" link to the available extension in the result list.  see settings.resultActionHTML
			checkLocal: true, // if true, check local db for whether the domain name is already taken by a live client

			onBeforeSearch: function(){},
			onSearch: function(){},
			onSearchComplete: function(){},
			
			onAvailable: function(domainNameNoExt, extGroup, result){}, // callback when the user-selected ext is available.  see header comments about var result
			onUnavailable: function(domainNameNoExt, extGroup, result){}, // callback when the user-selected ext is unavailable.  see header comments about var result
			onMaybeAvailable: function(domainNameNoExt, extGroup, result){}, // callback when the user-selected ext is maybe-available.  see header comments about var result

			configSource: 'var', // ['api'|'var'] where does the config data come from?  api: get config data from settings.apiUrlGetExtensionConfigs.  var: get from options.config passed in.
			config: {}, // see "var config" above.

			loadingHTMLDomID: 'checkingDomain',
			loadingHTML: '',
			availableMessage: '%domainName% is available!',
			unavailableMessage: '%domainName% is not available.',
			maybeAvailableMessage: '%domainName% is not available.',
			availableTransferMessage: '%domainName% can be used for transfer hosting!',
			unavailableTransferMessage: '%domainName% is not registered and cannot be used for transfer hosting. <a href="##" id="transferHostingReg">Register</a> this domain.',
			unavailableTransferMessage: '%domainName% is not registered and cannot be used for transfer hosting. <a href="##" id="transferHostingReg">Register</a> this domain.',
			alternativeExtensionMessage: '<div>Please select another website address or choose another address with a different extension below:<br/><br/></div>',
			availableListItem: '%domainName% is available',
			unavailableListItem: '%domainName% is not available',
			maybeAvailableListItem: '%domainName% is not available',
			resultActionHTML: '&nbsp;<img src="/img/valid.png">', // to be written into <span class="action"></span> inside $resultList > li.available
			resultActionOnBeforeClick: function(e) {}, // function to insert into the result action's click event. if it returns false, further resultActionOnBeforeClick will be skipped
			avaliableListItemClick: function() {}, // function when availableListItem is clicked
	
			selectBoxName: 'extension',
			selectBoxDomID: 'domain-search-extension',
			searchButtonSelector: 'button#domain-search-button',
			messageDomID: 'domainErrorCheck',
			availableMessageClass: 'available-message',
			unavailableMessageClass: 'unavailable-message',
			maybeAvailableMessageClass: 'maybe-available-message',
			resultListDomID: 'domain-results',
			availableListItemClass: 'available-list-item',
			unavailableListItemClass: 'unavailable-list-item',
			maybeAvailableListItemClass: 'maybe-available-list-item',

			apiUrlGetExtensionConfigs: '',
			apiUrlSearch: '',
			
			searchOnLoad: false // added for the form; when window load, search if domain input exists

		};
		
		if (options) { 
			$.extend(settings, options);
		}


		/*
		 * DEFINE HELPER FUNCTIONS
		 */

			/*
			 * define function to create & insert select box
			 * @params
			 *		array exts : required. extension ('.com', '.net', etc...) to write into the select options
	 		 */

		insertSelectBox = function(exts) {
			/*var html = '<select name="' + settings.selectBoxName + '" id="' + settings.selectBoxDomID + '">';
			for (var ii=0;ii<exts.length;ii++) {
				html += '<option>'+exts[ii]+'</option>';
			}
			html += '</select>';
			$ext = $(html);
			$ext
				.insertAfter(me)
				.keydown(handleEnterKey);
			*/
			$ext = $("#"+settings.selectBoxDomID);
			$ext/*.insertAfter(me)*/
				.keydown(handleEnterKey);
		}
		
			/*
			 * define function to write result message (e.g., "abc.com is available").
			 * it'll create a dom container to hold the message, if not created yet.
			 * 
			 * @params
			 *		string message : required. the message html to write into the dom container.  if null, remove the container altogether
	 		 */
		writeMessage = function(message, className) {
			// get message container
			$message = $('#'+settings.messageDomID);
			// if not exists, create new
			if ($message.length == 0) {
				$message = $('<div id="'+settings.messageDomID+'"></div>');
				// insert after ext con, assuming $ext has been created in dom
				$message.insertAfter($loading)
			}
			// if no message given, clear message and hide container
			if (message==null) {
				$message.html('').hide();
			} else {
				$message
					.html(message)
					.removeAttr('class')
					.addClass(className)
					.show();
			}
		}

			/*
			 * define function to write domain availability result list
			 * @param
			 * 	object availability: availability result of the extensions
			 * 		e.g., {".com":0, ".net":1, ...}
			 * 		where for availability: 0=unavail., 1=avail, 2=maybe
			 */
		writeResultList = function(domainNameNoExt, extGroup, availability) {

			// get container
			$resultList = $('#'+settings.resultListDomID);
			// if not exists, create new
			if ($resultList.length == 0) {
				$resultList = $('<ul id="'+settings.resultListDomID+'"></ul>');
				// insert after message con, assuming $message has been created in dom
				$resultList
					.insertAfter($message)
					// initially hidden
					.hide();
			}

			// if no message given, clear message and hide container
			if (domainNameNoExt == null) {
				$resultList.html('').hide();
			}
			else {
			
				var html = '';
				// write action html for available extensions
				var actionHTML = ''
				if (settings.addResultAction) {
					actionHTML = '<span class="action">' + settings.resultActionHTML + '</span>';
				}
				// loop over result
				for (var oneExt in availability) {
					// check if extension offered, by checking it against config.EXTENSIONS
					// if not offered
					if ($.inArray(oneExt, config.EXTENSIONS) == -1) {
						// skip this loop
						continue;
					}
					// write extension sorting into <li>
					var order = $.inArray(oneExt, config.EXTENSIONS);
					// switch: domain availability
					switch (availability[oneExt]) {
						// unavailable
						case 0:
							html += '<li class="' + settings.unavailableListItemClass + '" rel="'+order+'" title="' + domainNameNoExt + oneExt + '">' + settings.unavailableListItem.replace(/%domainName%/ig, domainNameNoExt + oneExt) + '</li>';
							break;
						// available
						case 1:
							html += '<li class="' + settings.availableListItemClass + '" rel="'+order+'" title="' + domainNameNoExt + oneExt + '">' + actionHTML + '<input type="radio" name="alternativeDomain" id="AD' + order + '" /><label for="AD' + order + '">' + settings.availableListItem.replace(/%domainName%/ig, domainNameNoExt + oneExt) + '</label></li>';
							break;
						// maybe
						case 2:
							html += '<li class="' + settings.maybeAvailableListItemClass + '" rel="'+order+'" title="' + domainNameNoExt + oneExt + '">' + settings.maybeAvailableListItem.replace(/%domainName%/ig, domainNameNoExt + oneExt) + actionHTML + '</li>';
							break;
					}
				}
				
				// make jquery object for the <li>
				var $li = $(html)
				
				// if delay showing
				if (settings.delayResult) {
					// and if the selected ext is NOT UNavailable (i.e., either available or maybe)
					if (selectedExtAvailable != 0) {
						$li.hide();
					// else: the selected extension is now found unavailable
					} else {
						// now reveal the hidden <li>s
						$resultList.find('li').show();
					}
				}

				// write items into result list
				$resultList
					.append($li)	
					// show <ul> list, temporary
					.show();

				// bind 'click' event
				//$li.find('.action').bind('click', doResultAction);
				$li.bind('click', doResultAction);
				
				// refresh sorting
				sortResult();

				// if it has NO visible <li>
				if ($resultList.find('li:visible').length == 0) {
					// hide <ul>
					$resultList.hide();
				}

			}
			
		}

			/*
			 * define function to submit domain search and handle the returned results
			 */
		submitSearch = function(e) {
			
			var skipSearch = false;
			
			// if input not ready to submit to search, e.g., validation failed,
			if (!inputReadyForSearch) {
				return false;
			}
			
			// check if search in progress
			if (isSearchInProgress()) {
				return false;
			}
			
			// reset
			selectedExtAvailable = 2; // 2 means maybe
			
			if(settings.transferHosting) {
				
				var tlds = config.EXTENSIONS.join("$|");
				tlds = tlds.replace(/\./g,"\\.");
				var tldsRegexp = new RegExp(tlds+"$", "i");

				var domainNameWithExt = me.val();
				var domainPartIndex = domainNameWithExt.indexOf(".", 0);
				
				var domainNameNoExt = domainNameWithExt.substr(0,domainPartIndex);
				var selectedExt = domainNameWithExt.substr(domainPartIndex);
				
				if (!selectedExt.match(tldsRegexp)) {
					// invalid ext, skip search
					skipSearch = true;
				}
				
				
			} else {
				
				// get user input
				var domainNameNoExt = me.val();
				var selectedExt = $ext.val();
			}
			
			
			var domainName = domainNameNoExt + selectedExt;
			// get the ext group of selected ext
			var selectedExtGroup = '';
			for (var oneGroup in config.EXTENSION_GROUPING) {
				// look for the group that contains the ext
				if ($.inArray(selectedExt, config.EXTENSION_GROUPING[oneGroup]) > -1) {
					selectedExtGroup = oneGroup;
					break;
				}
			}
			
			// compose array of ext groups to search
				// if to search of other extensions
			if (settings.otherExtensions) {
				// re-order the extension group array to put the selected ext group at the top
					// clone config.EXTENSION_GROUPS
				var extGroups = config.EXTENSION_GROUPS.slice(0);
					// remove the selected group from array
				extGroups.splice($.inArray(selectedExtGroup, extGroups), 1);
					// prepend the selected group back to the array
				extGroups.unshift(selectedExtGroup);
				// else: only search the selected extension				
			} else {
				var extGroups = [selectedExtGroup];
			}
			
			// block input
			// console.log('block');
			block();
			
			// show loading
			$loading.show().css({
				height: $(document).height(),
				position: "fixed"
			}).children("div.loading").css({
				top: ($(window).height()/2)-50,
				position: "fixed"
			});

			// clear message
			writeMessage();
			
			// clear result list
			writeResultList();

			// do onBeforeSearch callback
			settings.onBeforeSearch();

			var acceptMaybeBit = (settings.acceptMaybe)?1:0;
			var checkLocalBit = (settings.checkLocal)?1:0;

			if (!skipSearch) {
				// loop over each domain group
				for (var ii = 0; ii < extGroups.length; ii++) {
					var oneExtGroup = extGroups[ii];
					// mark search status: in progress
					searchStatus[oneExtGroup] = true;
					// post to search API
					$.get(settings.apiUrlSearch, {
						domainNameNoExt: domainNameNoExt,
						extGroup: oneExtGroup,
						returnRequestInfo: 1,
						acceptMaybe: acceptMaybeBit,
						checkLocal: checkLocalBit
					}, function(result){
						searchAPI_Callback(selectedExt, result);
					}, 'json');
				
				}		
			} else {
				selectedExtAvailable = 1;
				if (settings.onAvailable() === false) return false;
				
			}

		}


			/*
			 * define domain search API callback function
			 */
		searchAPI_Callback = function(selectedExt, result) {
			
			if(!result) {
				unblock();
				$loading.hide();
				resetSearchStatus();
				return false;
			}
			
			var domainNameNoExt = result.domainNameNoExt;
			var extGroup = result.extGroup;
			var availability = result.availability;

			var domainName_SelectedExt = domainNameNoExt + selectedExt;
			
			// store search status: done
			searchStatus[extGroup] = false;
			
			// optional: submit form on available
			if (settings.submitFormOnAvailable == true && availability[selectedExt] == 1) {
				if ($form.length == 1) {
					// reset search status
					// resetSearchStatus();
					$form.submit();
					return false;
				}
			}

			// For the selected extension
			// execute onAvailable/onUnavailable callback
			// & show availability message
			switch (availability[selectedExt]) {
				// unavailable
				case 0:
					/*
					selectedExtAvailable = 0;
					if (settings.onUnavailable() === false) return false;
					writeMessage(settings.unavailableMessage.replace(/%domainName%/ig, domainName_SelectedExt),  settings.unavailableMessageClass);
					*/
					// if new domain, result false; otherwise result = true
					if (!settings.transferHosting) {
						selectedExtAvailable = 0;
						if (settings.onUnavailable() === false) return false;
						writeMessage(settings.unavailableMessage.replace(/%domainName%/ig, domainName_SelectedExt), settings.unavailableMessageClass);
					}else {
						selectedExtAvailable = 1;
						if (settings.onAvailable() === false) return false;
						//writeMessage(settings.availableTransferMessage.replace(/%domainName%/ig, domainName_SelectedExt), settings.availableMessageClass);
					}
				break;
				// available
				case 1:
					/*
					selectedExtAvailable = 1;
					if (settings.onAvailable() === false) return false;
					writeMessage(settings.availableMessage.replace(/%domainName%/ig, domainName_SelectedExt), settings.availableMessageClass);
					*/
					// if new domain, result true; otherwise result = false
					if (!settings.transferHosting) {
						selectedExtAvailable = 1;
						if (settings.onAvailable() === false) return false;
						writeMessage(settings.availableMessage.replace(/%domainName%/ig, domainName_SelectedExt), settings.availableMessageClass);
					}else {
						selectedExtAvailable = 0;
						if (settings.onUnavailable() === false) return false;
						writeMessage(settings.unavailableTransferMessage.replace(/%domainName%/ig, domainName_SelectedExt), settings.unavailableMessageClass);
					}
				break;
				// maybe
				case 2:
					/*selectedExtAvailable = 2;
					if (settings.onMaybeAvailable() === false) return false;
					writeMessage(settings.maybeAvailableMessage.replace(/%domainName%/ig, domainName_SelectedExt), settings.maybeAvailableMessageClass);
					*/
					// if new domain, result true; otherwise result = false
					if (!settings.transferHosting) {
						selectedExtAvailable = 2;
						if (settings.onMaybeAvailable() === false) return false;
						writeMessage(settings.maybeAvailableMessage.replace(/%domainName%/ig, domainName_SelectedExt), settings.maybeAvailableMessageClass);
					}else {
						selectedExtAvailable = 0;
						if (settings.onMaybeAvailable() === false) return false;
						writeMessage(settings.unavailableTransferMessage.replace(/%domainName%/ig, domainName_SelectedExt), settings.unavailableMessageClass);
					}	
				break;
			}
			
			// alert(extGroup + '|' + selectedExt + '|' + selectedExtAvailable);

			// show search result - ext list
			if (settings.otherExtensions && !settings.transferHosting) {
				writeResultList(domainNameNoExt, extGroup, availability);
			}
			
			// show loading
			$loading.css({
				height: $(document).height()
			});
			
			// check if all search done
			if (!isSearchInProgress()) {
				// unblock input
				// console.log('unblock');
				
				// add message to the domain result list
				$alternativeDomains = $('#'+settings.resultListDomID);
				if ($alternativeDomains.find('li').length > 0) {
					$alternativeDomains.prepend(settings.alternativeExtensionMessage);
				}
				
				unblock();
				// hide "loading" icon
				$loading.hide();
				
				settings.onSearchComplete(domainNameNoExt, extGroup, availability);
			}
			
			// do "onSearch" callback
			settings.onSearch(domainNameNoExt, extGroup, availability);
		}
		
			/*
			 * define function to handle "enter" key
			 */
		handleEnterKey = function(e) {
			if(e.keyCode == 13) {
				e.preventDefault();
				/*
				if (settings.enterKeySubmit) {
					submitSearch();
				}
				*/
				if ($(validate)) {
					submitSearch();
				}
				return false;
			}
		}

			/*
			 * define click event function for result action
			 */
		doResultAction = function(e) {
		
			if ($(this).attr("class") == settings.availableListItemClass) {
			
				// call back: onBeforeClick
				if (settings.resultActionOnBeforeClick(e) === false) {
					return false;
				}
				var domainName = $(this).closest('li').attr('title');
				var domainNameNoExt = domainName.split('.')[0];
				var thisExt = domainName.substr(domainNameNoExt.length);
				// update form input with this clicked domain name
				me.val(domainNameNoExt);
				$ext.val(thisExt);
				
				$(this).children("input[name='alternativeDomain']").attr("checked", true);
				
				settings.avaliableListItemClick();
				
				// auto submit form if needed
				if (settings.submitFormOnAvailable) {
					// reset search status
					// resetSearchStatus();
					$form.submit();
					return false;
				}
			}
		}


			/*
			 * define function to sort search result, in the order of config.EXTENSIONS's sequence
			 */
		sortResult = function() {
			// get all <li> rel-attribute for the sorting numbers
			var sorting = [];
			$.map(
					$resultList.find('li'),
					function(li, ii) {
						sorting[sorting.length] = $(li).attr('rel');
					}
				)
			// sort the numbers
			sorting.sort(function(a,b){return a - b});
			// sort <li> according to the numbers
			for (var num in sorting) {
				$resultList.append($resultList.find('li[rel="'+num+'"]'));
			}
		}
			
			/*
			 * define function to check if any search currently in progress
			 */
		isSearchInProgress = function() {
			for (var grp in searchStatus) {
				if (searchStatus[grp] == true) {
					return true;
				}
			}
			return false;
		}
			/*
			 * define function to reset searchStatus to each domain group = false
			 */
		resetSearchStatus = function() {
			for (var grp in searchStatus) {
				searchStatus[grp] = false;
			}
			inputReadyForSearch = true;
		}
		
			/*
			 * define function to validate input string
			 */
		validate = function(e) {
			// just assume input is not ready to submit to search
			inputReadyForSearch = false;
			var str = me.val();
			
			// for transfer hosting
			var ext = "";
			
			if(settings.transferHosting) {
				var domainPartIndex = str.indexOf(".", 0);
				var domainName = str.substr(0,domainPartIndex);
				ext = str.substr(domainPartIndex);
				str = domainName;	
			}
			
			// only allow alphanumeric and dash
			if (str!='') {
				
				if (settings.transferHosting && ext.length < 3) {
					writeMessage('Invalid domain extension.','error');
					return false;
				}
		
				// invalid char
				if (str.match(/[^a-z0-9-]/i)) {
					writeMessage('Cannot contain special characters.  Only 0-9, a-z and dash (-) are allowed.', 'error');
					return false;
				// can't start with dash
				}
				else 
					if (str.match(/^\-/)) {
						writeMessage('Cannot start with a dash (-).', 'error');
						return false;
					// can't end with dash
					}
					else 
						if (str.match(/\-$/)) {
							writeMessage('Cannot end with a dash (-).', 'error');
							return false;
						// max length
						}
						else 
							if (str.length > MAX_INPUT_LENGTH) {
								writeMessage('Name too long. ' + MAX_INPUT_LENGTH + ' characters max.', 'error');
								return false;
								
								
							}
							else {
								// input is passed, ready for the search
								inputReadyForSearch = true;
								// clear message
								writeMessage();
							}

			// else: input is empty
			} else {
				// clear message
				me.removeClass("inputError");
				writeMessage();
			}
			return true;
		}
		
			/*
			 * define functions to block and unblock input
			 */
		block = function() {
			me.attr('disabled', 'disabled').addClass('disabled');
			$ext.attr('disabled', 'disabled').addClass('disabled');
		}
		unblock = function() {
			me.removeAttr('disabled').removeClass('disabled');
			$ext.removeAttr('disabled').removeClass('disabled');
		}
		
		
		/*
		 * DO ACTIONS
		 */

		// assign dom to jquery var
		$searchButton = $(settings.searchButtonSelector);
		$form = me.closest('form');

		// set max length of the input
		me.attr('maxlength', MAX_INPUT_LENGTH);

		// INSERT "loading..." box
		$loading = $("#"+settings.loadingHTMLDomID);
		/*
		$loading
			.insertAfter(me).hide()
			; //.bind('ajaxStop', function(e) {$(this).hide()});
		*/
		
		// load config data and create extension select box
		switch(settings.configSource) {
			case 'api':
				// GET extensions from API, then create selection box
				$.get(
					settings.apiUrlGetExtensionConfigs,
					// create selection box
					function(data) {
						// load data into local scope
						config.EXTENSIONS = data.EXTENSIONS;
						config.EXTENSION_GROUPS = data.EXTENSION_GROUPS;
						config.EXTENSION_GROUPING = data.EXTENSION_GROUPING;
						
						// insert selection box
						insertSelectBox(config.EXTENSIONS);
						
						// set searchStatus object
						for (var ii=0;ii<config.EXTENSION_GROUPS.length;ii++) {
							searchStatus[config.EXTENSION_GROUPS[ii]] = false;
						}
					},
					'json'
				);
			break;
			case 'var':
				// localize config from settings' scope
				config = settings.config;

				// insert selection box
				insertSelectBox(config.EXTENSIONS);
				
				// set searchStatus object
				for (var ii=0;ii<config.EXTENSION_GROUPS.length;ii++) {
					searchStatus[config.EXTENSION_GROUPS[ii]] = false;
				}
				
				// search if search on page load is true
				if(settings.searchOnLoad) {
					if ($(validate)) {
						submitSearch();
					}
				}
			break;
		}
		
		
		/*
		 * ATTACH EVENT HANDLERS
		 */
		// submit button > click
		/*$searchButton.click(
			submitSearch
		);
		*/
		$searchButton.click(function(){
			//submitSearch
			if ($(validate)) {
				submitSearch();
			};
		});

		// input|select > keydown: trigger search by "enter" key
		this.keydown(handleEnterKey);
		//this.keyup(validate);
		
		// window > load: validate input in case it's already filled, or by browser-back-button
		$(validate);

	};
})( jQuery );
