/// <reference path="../../../../jQuery/1.3.2/jquery-1.3.2-vsdoc.js" />

// ********************************************************************
// 
// Manheim Retail Marketing
// ********************************************************************
// Copyright © 2010 Manheim Retail Marketing.
//
//  Summary
// ******************************
// $Workfile: DynamicLookupList.js $
// Notes:		Binders the JQ slider plugin to the mrm formatted html produced from the server side control builder.
//
// File History Information
// ************************
// Created on:					    
// Last Modified:					$Modtime: 24/01/11 11:35 $
// Original Author:					Marc Lancashire
// Last Modified by:				$Author: Lancashirem $
// Last JS Lint:					07/12/2009 11:40
//
// Source Control Information
// **************************
// File Version:					$Revision: 22 $
// VSS Location:					$Archive: /Manheim.Portfolio/Manheim.Portfolio.Web.UI.Client.Assets/assets/js/mrm/lib/ui/controls/1.0/DynamicLookupList.js $
//
// ********************************************************************

// Check / create namespace
if (!mrm.global.isNamespaceDefined("mrm.lib.ui.controls"))
{
	mrm.global.createNamespace("mrm.lib.ui.controls", "1.0");
}


// Class definition
mrm.lib.ui.controls.DynamicLookupList = mrm.lib.ui.controls.BaseControl.subClass
(
{
	/*
	=============================
	CONSTANTS
	=============================
	*/
	"BINDER_NAME": "DynamcicLookupList",

	"TRACE_PREFIX": "mrm.lib.ui.controls.DynamcicLookupList",

	"ALLOW_ERRORS": true,

	// selectors
	"SELECTOR_CONTROL": "div.dyanmiclookuplist-container", // please add selector, this is used to filter the bindable controls
	"SELECTOR_INPUT": "input.mp-control-data",
	"SELECTOR_OPTIONLIST": "div.lookup-list",
	"SELECTOR_OPTIONITEM": "div.lookup-option",
	"SELECTOR_LOOKUPSTATUS": "div.lookup-status",
	"SELECTOR_LOOKUP_STATUS": "div.lookup-status",


	// html
	"HTML_OPTIONITEM_PREFIX": "<div class='lookup-option'>",
	"HTML_OPTIONITEM_SUFFIX": "</div>",

	// classes
	"CLASS_SHOW": "show",
	"CLASS_HIDE": "hide",
	"CLASS_VALID": "lookup-valid",
	"CLASS_INVALID": "lookup-invalid",
	"CLASS_SELECTED": "selected",
	"CLASS_FIELD_ITEM_ERROR": "fieldError",


	// events
	"EVENT_ITEM_SELECTED": "item.selected",
	"EVENT_TYPE_UPDATE": "DYNAMICLOOKUPLIST_UPDATE",
	"EVENT_TYPE_CHANGE_INPUT": "DYNAMICLOOKUPLIST_CHANGE_INPUT",

	// event properties
	"EVENT_PROP_VALID": "Valid",

	// key codes
	"KC_ARROW_DOWN": 40,
	"KC_ARROW_UP": 38,
	"KC_TAB": 9,
	"KC_ENTER": 13,
	"KC_ESCAPE": 27,
	"KC_SPACE": 32,
	"KC_PREVENT_DEFAULT": [13],

	// sub classes
	"AJAX_MANAGER": null,

	// settings
	"URL_HANDLER": "/geolookup/",
	"URL_HANDLER_PAGE": "cta.ashx",
	"PARM_LOOKUP_NAME": "location",

	// below settings are user base therfore should be set in an instance dictionary

	// timer settings
	"INPUT_TIMER": 0,
	"INPUT_DELAY": 500,

	"PREVENT_BLUR": false,

	// location user settings
	"LOCATIONS_LIST": null,
	"LOCATION_VALUE": null,
	"SELECTED_INDEX": -1,
	"DEFAULT_INDEX": -1,
	"DEFAULT_INPUT_TEXT": "Location",

	// data object properties
	"do_Status": "status",
	"do_ValidationStatus": "lookupStatus",
	"do_Options": "locationOptions",

	// jq elements
	"jqContainer": null,
	"jqInputText": null,
	"jqOptionList": null,
	"jqLookupStatus": null,


	/*
	=============================
	CONSTRUCTOR
	=============================
	*/

	// construct instance of class, add tracing and ajax manager
	"init": function (tracing, settings) {
		this._super(tracing, settings); // call base init

		// set default ajax manager
		if ($.ajaxManager) {
			this.AJAX_MANAGER = $.ajaxManager;
		}
	},

	/*
	=============================
	PUBLIC MEMBERS
	=============================
	*/

	// handles an event before the call back is executed
	"onCallBackBefore": function (jqEvent) {

	},


	// handles an error occuring when the call back is executed
	"onCallBackError": function (jqEvent, XMLHttpRequest, textStatus, errorThrown) {
		// if response fail we should set input to be valid, allow server side validation to kick in
		if (this.ALLOW_ERRORS) {
			this.switchValidInvalid(this.jqLookupStatus, true);
		}
	},

	// handles the successful response of the call back execution
	"onCallBackSuccess": function (jqEvent, response, isInitcontrol) {
		// if the response does not come back then go to the call error state
		if (!response) {
			this.onCallBackError(jqEvent, null, null, null);
			return;
		}

		var __this = this;
		this.tracing().addTrace("End ajax request - onCallBackSuccess.");

		// did the call to refresh option list successful
		var callbackStatus = response[this.do_Status];

		// was our option lookup valid
		var lookupStatus = response[this.do_ValidationStatus];

		// get state of option list
		var openOptionList = !isInitcontrol;

		this.tracing().addTrace("Lookup Status - " + callbackStatus + ", Lookup Validation - " + lookupStatus + ".");

		// clear options
		this.jqOptionList.empty();

		if (callbackStatus) {
			// update location list
			if ((response[this.do_Options]) && (response[this.do_Options].length > 0)) {
				this.LOCATIONS_LIST = response[this.do_Options];

				if (response[this.do_Options].length > 1) {
					for (var i = 0; i < response.locationOptions.length; i++) {
						this.jqOptionList.append(this.HTML_OPTIONITEM_PREFIX + response.locationOptions[i] + this.HTML_OPTIONITEM_SUFFIX);
					}
				}
				else {
					//set the text value of the input
					openOptionList = false;
					$("input.postcode").val(response.locationOptions[0]);
					//set the lookupStatus to true, as the text has come from the list it will be valid
					lookupStatus = true;
				}

				// reset the selected index
				this.SELECTED_INDEX = this.DEFAULT_INDEX;
			}
			else {
				openOptionList = false
			}
		}
		else {
			openOptionList = false
		}

		// show or hide the options.  If the control is being initialised then we need to hide the options dropdown.
		this.switchShowHide(this.jqOptionList, openOptionList);

		this.checkInput(false);

		// update location status
		if (callbackStatus || (!callbackStatus && !this.ALLOW_ERRORS)) {
			this.switchValidInvalid(this.jqLookupStatus, lookupStatus);
		}
		else {
			// if callback failed we should set input to be valid, allowing server side validation to kick in
			this.switchValidInvalid(this.jqLookupStatus, true);
		}
	},

	// clear the timer that fires the ajax request, timer is used so that quick key up's aren't all fired
	"onClearValidate": function (jqEvent) {
		clearTimeout(this.INPUT_TIMER);
	},

	// when an the option list is clicked update the input with the clicked item's text and then hide the option list
	"onOptionListClick": function (jqEvent) {
		var inputText = $.trim($(jqEvent.target).text());

		clickedIndex = this.getItemLoctionItemIndex(inputText);

		if (clickedIndex !== this.DEFAULT_INDEX) {
			this.SELECTED_INDEX = clickedIndex;

			this.updateValue(this.jqInputText, inputText);

			this.switchValidInvalid(this.jqLookupStatus, true);

			this.switchShowHide(this.jqOptionList, false);
		}
		this.jqInputText.trigger("change");
		this.checkInput(true);
	},

	// creates a base url for the call back e.g. /Handlers/cta.ashx
	"getBaseCallBackUrl": function () {
		return this.URL_HANDLER + this.URL_HANDLER_PAGE;
	},

	// creates a full url for the call back, uses the base and settings adds then adds a lookup argument with the value of the provided element
	"getCallBackLinkUrl": function (jqElement) {
		var callBackLinkUrl, baseUrl, parameterName, parameterValue;

		baseUrl = this.getBaseCallBackUrl();

		// ensure base has a querystring
		if (baseUrl.indexOf("?") < 0) {
			baseUrl = baseUrl + "?";
		}

		// need to rip off the query string of jqElement and add to base
		parameterName = this.PARM_LOOKUP_NAME;
		parameterValue = jqElement.val();

		callBackLinkUrl = baseUrl + parameterName + "=" + parameterValue;

		return callBackLinkUrl;
	},

	// updates the input with a selected value from the option list
	"updateSelection": function (jqElement, keyCode) {
		var selectedIndex = this.SELECTED_INDEX;

		// if we have a selected index
		if (selectedIndex !== this.DEFAULT_INDEX && this.LOCATIONS_LIST && this.LOCATIONS_LIST.length > 0 && selectedIndex < this.LOCATIONS_LIST.length) {
			// update location to the text of the selected location option
			this.updateValue(jqElement, this.LOCATIONS_LIST[selectedIndex]);

			// as we have now set the input from a list of valid location update the status
			this.switchValidInvalid(this.jqLookupStatus, true);
		}
	},

	// updates the input with provided value, this can be overidden to provided extra functionality in different ui's
	"updateValue": function (jqElement, value) {
		jqElement.val(value);
		jqElement.attr("title", value);
	},

	// checks the input value agaist the current location list and changes the field state + boardcast a item selected event
	"checkInput": function (triggerText) {
		var __this = this, status = false, inputText = __this._formatInputText(this.jqInputText.val());

		status = (this.getItemLoctionItemIndex(inputText) !== this.DEFAULT_INDEX);

		// set the html status
		this.switchValidInvalid(this.jqLookupStatus, status);

		if (triggerText) {
			// fire a valid item selection event
			this._triggerSelectionEvent(status);
		}
		else {
			// if valid postcode then update from the server
			if (status) {
				this.updateValue(this.jqInputText, this.LOCATIONS_LIST[this.getItemLoctionItemIndex(inputText)]);
			}
		}

	},

	// gets the index of the provided input text within the current location list
	"getItemLoctionItemIndex": function (locationText) {
		var __this = this, index = this.DEFAULT_INDEX;
		if (this.LOCATIONS_LIST) {
			for (var i = 0; i < this.LOCATIONS_LIST.length; i = i + 1) {
				if (__this._formatInputText(this.LOCATIONS_LIST[i]) === __this._formatInputText(locationText)) {
					index = i;
					break;
				}
			}
		}

		return index;
	},

	// upon arrow up or arrow down key press shift the selected user's option item up or down
	"shiftSelection": function (jqElement, keyCode) {
		var selectedIndex = this.SELECTED_INDEX, jqOptionListItems = this.jqOptionList.find(this.SELECTOR_OPTIONITEM);

		// remove the selected class from options
		jqOptionListItems.removeClass(this.CLASS_SELECTED);

		// calc the selected index
		if (keyCode === this.KC_ARROW_UP) {
			selectedIndex = selectedIndex - 1;
		}
		else if (keyCode === this.KC_ARROW_DOWN) {
			selectedIndex = selectedIndex + 1;
		}

		// add selected class to correct option item
		if (selectedIndex >= 0 && selectedIndex < jqOptionListItems.length) {
			jqOptionListItems.eq(selectedIndex).addClass(this.CLASS_SELECTED);
		}
		else if (selectedIndex < 0) // select first
		{
			jqOptionListItems.eq(0).addClass(this.CLASS_SELECTED);
		}
		else // select last
		{
			jqOptionListItems.eq(jqOptionListItems.length - 1).addClass(this.CLASS_SELECTED);
		}

		this.SELECTED_INDEX = selectedIndex;
	},

	// helper method to make a field valid / invalid
	"switchValidInvalid": function (jqElement, show) {
		var inputTextbox = jqElement.parent().find("input.postcode");

		//
		//
		// TODO :: Check if already has required class and if so ESCAPE
		//
		//		

		// switch the passed in element to be valid / invalid
		this.switchClasses(jqElement, show, this.CLASS_VALID, this.CLASS_INVALID);
		// switch the field container to be valid / invalid
		this.switchClasses(this.jqContainer, show, null, this.CLASS_FIELD_ITEM_ERROR);

		// Broadcast update event
		var eData = {};
		eData[this.EVENT_PROP_VALID] = show;
		this._triggerEvent(this.EVENT_TYPE_UPDATE, eData);
	},

	// helper method to show or hide an element
	"switchShowHide": function (jqElement, show) {
		this.switchClasses(jqElement, show, this.CLASS_SHOW, this.CLASS_HIDE);
	},

	// helper method to swtich over two classes on a element
	"switchClasses": function (jqElement, show, onClass, offClass) {
		if (jqElement === null || show === null) {
			return;
		}
		if (show) {
			if (offClass !== null && jqElement.hasClass(offClass)) {
				jqElement.removeClass(offClass);
			}
			if (onClass !== null && !jqElement.hasClass(onClass)) {
				jqElement.addClass(onClass);
			}
		}
		else {
			if (offClass !== null && !jqElement.hasClass(offClass)) {
				jqElement.addClass(offClass);
			}
			if (onClass !== null && jqElement.hasClass(onClass)) {
				jqElement.removeClass(onClass);
			}
		}
	},

	/*
	=============================
	PRIVATE MEMBERS
	=============================
	*/

	// build a call back link control from the provided html element
	"_initControl": function (element, instanceId, settings) {
		var status, __this;
		__this = this;
		status = false;

		this.tracing().addTrace("Start init DynamcicLookupList");

		// setup all internal jq Items
		this.jqContainer = $(element);
		this.jqInputText = this.jqContainer.find(this.SELECTOR_INPUT);
		this.jqOptionList = this.jqContainer.find(this.SELECTOR_OPTIONLIST);
		this.jqLookupStatus = this.jqContainer.find(this.SELECTOR_LOOKUPSTATUS);

		// stop auto complete
		this.jqInputText.attr("autocomplete", "off");

		// bind key up event, this allows us to lookup / validate any user changes
		this.jqInputText.bind("keyup.mrm.dlkup.lookup",
			function (e) {
				__this._handleKeyup(e);
			}
		);

		// bind key down event, this allows us to deal with other user input other than just text,
		// we use key down so that we capture repeated arrow up and downs.
		this.jqInputText.bind("keydown.mrm.dlkup.shift",
			function (e) {
				__this._handleKeydown(e);
			}
		);

		// bind a custom clear event, to timer clear / tidy up the dyamic lookup list
		this.jqContainer.bind("clear.mrm.dlkup.hide",
			function (e) {
				__this.onClearValidate(e);
			}
		);

		// disable blur event when mouse out of option list
		this.jqOptionList.bind("mouseover",
			function (e) {
				__this.PREVENT_BLUR = true;
			}
		);

		// enable blur event when mouse in
		this.jqOptionList.bind("mouseout",
			function (e) {
				__this.PREVENT_BLUR = false;
			}
		);

		// bind on blur event, to hide the option list
		this.jqInputText.bind("blur.mrm.dlkup.hide",
			function (e) {
				/*
				__this.switchShowHide(__this.jqOptionList, false);
					
				__this._triggerSelectionEvent(!__this.jqContainer.hasClass(__this.CLASS_FIELD_ITEM_ERROR));
				*/

				//*
				if (!__this.PREVENT_BLUR) {
					__this.switchShowHide(__this.jqOptionList, false);

					__this._triggerSelectionEvent(__this._locationStatusIsValid());
				}
				//*/
			}
		);

		// bind a click event, to the option list to handle selection of an option item
		this.jqOptionList.bind("click.mrm.dlkup.update",
			function (e) {
				__this.onOptionListClick(e);
			}
		);

		// fire a load event to fire of an intialise step
		this._handleInputChange({ "currentTarget": this.jqInputText }, true);


		this.tracing().addTrace("End init DynamcicLookupList");

		status = true;

		return status;
	},

	"_triggerSelectionEvent": function (status) {
		var selectionEvent = $.Event(this.EVENT_ITEM_SELECTED);
		selectionEvent.Valid = status;
		selectionEvent.ItemText = this.jqInputText.val();

		$(document).trigger(selectionEvent);
	},


	"_triggerEvent": function (eventType, eventDataObject) {
		var e = $.Event(eventType);
		var edo = eventDataObject;
		if (edo) for (var prop in edo) { e[prop] = edo[prop]; }

		// Trigger event on control (will buble upto document level)
		// But importantly WILL HAVE e.target set correctly to control
		this.jqContainer.trigger(e);
	},


	// handle key down event, this allows us to deal with other user input other than just text,
	// we use key down so that we capture repeated arrow up and downs.
	"_handleKeydown": function (jqEvent) {
		var jqElement = $(jqEvent.currentTarget), keyCode = jqEvent.which;

		this.tracing().addTrace("Key down for '" + keyCode + "' handled.");

		this._preventKeyDefault(jqEvent, keyCode);

		switch (keyCode) {
			case this.KC_ARROW_DOWN:
			case this.KC_ARROW_UP:
				this.shiftSelection(jqElement, keyCode);
				break;
			case this.KC_TAB:
			case this.KC_ENTER:
				this.PREVENT_BLUR = false;
				this.updateSelection(jqElement, keyCode);
				this._triggerSelectionEvent(this._locationStatusIsValid());
				break;
			case this.KC_ESCAPE:
				this.switchShowHide(this.jqOptionList, false);
		}
	},

	// handle key up event, deals with user input changes, validate the change and checks if a change should be actioned
	// setups the timer if nessary
	"_handleKeyup": function (jqEvent) {
		var __this = this, jqElement = $(jqEvent.currentTarget), location = $.trim(jqElement.val());

		// only fire handle change if our prev value has changed
		if (this.LOCATION_VALUE !== location && location.length > 0) {
			// stop input changez
			clearTimeout(this.INPUT_TIMER);

			// setup input change in a timer
			this.INPUT_TIMER = setTimeout(
				function () {
					__this._handleInputChange(jqEvent, false);
				},
				this.INPUT_DELAY
			);
		}
		else if (location.length < 1) {
			this.switchShowHide(this.jqOptionList, false);
			this.switchValidInvalid(this.jqLookupStatus, false);
		}

		// update our prev value
		this.LOCATION_VALUE = location;
	},

	// internal method used to handle valid changes to the user's lookup, sets up and fires the ajax request
	"_handleInputChange": function (jqEvent, isInitControl) {
		if (!isInitControl) {
			this._triggerEvent(this.EVENT_TYPE_CHANGE_INPUT, {});
		}


		var jqElement = $(jqEvent.currentTarget), callBackLinkUrl, __this = this;

		// ensure we can contine
		if (jqElement.val().length < 1 || jqElement.val() === this.DEFAULT_INPUT_TEXT) {
			return;
		}

		// create url to fire using wflw key and handler constant
		callBackLinkUrl = this.getCallBackLinkUrl(jqElement);

		// process ajax request

		// update ajax url
		this.AJAX_MANAGER.updateUrl(callBackLinkUrl);

		// bind event handlers
		this.AJAX_MANAGER.onBeforeSend = function () {
			__this.onCallBackBefore(jqEvent);
		};

		this.AJAX_MANAGER.onSuccess = function (p_response) {
			__this.onCallBackSuccess(jqEvent, p_response, isInitControl);
		};

		this.AJAX_MANAGER.onError = function (XMLHttpRequest, textStatus, errorThrown) {
			__this.onCallBackError(jqEvent, XMLHttpRequest, textStatus, errorThrown);
		};

		// fire request
		this.tracing().addTrace("Start ajax request for '" + callBackLinkUrl + "'.");
		this.AJAX_MANAGER.makeRequest();
		this.tracing().addTrace("End ajax request for '" + callBackLinkUrl + "'.");

		this.tracing().addTrace("End click on CallBackLink");
	},

	// prevent a default key action if nessary
	"_preventKeyDefault": function (jqEvent, keyCode) {
		if (this.KC_PREVENT_DEFAULT && this.KC_PREVENT_DEFAULT.length > 0) {
			for (var i = 0; i < this.KC_PREVENT_DEFAULT.length; i = i + 1) {
				if (this.KC_PREVENT_DEFAULT[i] === keyCode) {
					jqEvent.preventDefault();
					//jqEvent.stopPropagation();
					break;
				}
			}
		}
	},

	//method that removes spaces converts to lower case and trims any leading space from text
	//added in to keep all input and validation uniform through out the control. also removes the space if the 
	//input fits the regex for a post code. 			
	"_formatInputText": function (input) {
		var __this = this;

		input = $.trim(input);
		input = input.toLowerCase();
		if (__this._isValidPostCode(input)) {
			input = input.replace(" ", "");
		}
		return input;
	},

	//check our input for valid post code
	"_isValidPostCode": function (input) {
		if (!input) {
			return false;
		}
		var regex = '^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2})$';
		return (input.match(regex)) ? true : false;
	},


	/*
	-----------------------------
	Utilities
	-----------------------------
	*/

	"_locationStatusIsValid": function () {
		// Get internal status item
		var t = this.jqContainer.find(this.SELECTOR_LOOKUP_STATUS);
		var valid = t.hasClass(this.CLASS_VALID);
		return valid;
	},


	"_locationStatusIsInvalid": function () {
		var t = this.jqContainer.find(this.SELECTOR_LOOKUP_STATUS);
		var invalid = t.hasClass(this.CLASS_INVALID);
		return invalid;
	}


}
);

