﻿
/// <reference path="../../../../jQuery/1.3.2/jquery-1.3.2-vsdoc.js" />

/**
 *
 * DisplayManager
 *
 * This class manages the search configuration status within the client environment
 *
 * Managing status and communications between otherwise disparate page elements including:
 *	- Multiple, separate, flash display elements
 *	- Associated html display elements (checkbox links, html slider widgets etc) 
 *
 */

	// Check that namespace into which the Class definition will be creates has been defined & if not then create
	
	if (!mrm.global.isNamespaceDefined("mrm.lexus.display")) mrm.global.createNamespace("mrm.lexus.display", "1.0");

	// Class definition :

	mrm.lexus.display.DisplayManager = mrm.common.display.DisplayManager.subClass
	(
		{
			/*
			=============================
			CONSTANTS
			=============================
			*/
			MANAGED_INPUT_CLASS: "div.panel-selected-models input.selected-models",
			MANAGED_VALUE_LEVEL: 2,
			FIELD_RANGE_LEVEL: 0,
			FIELD_MODEL_LEVEL: 1,
			FIELD_DERIVATIVE_LEVEL: 2,
			FIELD: null, // this will be populated by a varible on the runtime instance
			DELIMITER: ",",
			ARGUMENT_WORKFLOW: "wflw",
			SEARCH_CRITERIA_PANEL: "SearchCriteriaPanel",
			SELECTOR_FORM: "form.mp-search-count",

			DEBUG: false,

			/*
			=============================
			MEMBER VARIBLES
			=============================
			*/
			// public
			callbackUtilities: {},

			// private
			_utilities: mrm.global.utilities,
			_managedInput: null,

			/*
			=============================
			CONSTRUCTOR
			=============================
			*/
			init: function ()
			{
				this._super(); // call base init

				this._managedInput = $(this.MANAGED_INPUT_CLASS);

				if (this._managedInput.length < 1)
				{
					this._alertDebugMessage("Can not find the managed input");
				}

			},

			/*
			=============================
			SELECTION UPDATE METHODS
			=============================
			*/
			/* EVENT FORMAT
			//exampleEvent = 
			{
			type		: "ITEM_SELECTION_EVENT",
			scope		: "SCOPE_LEVEL_3",		// "SCOPE_LEVEL_3"
			select		: true,					// true || false
			level1Id	: 1,					// uint
			level2Id	: 2,					// uint
			level3Id	: 3						// uint
			source		: "SCOPE_LEVEL_3"		// "SCOPE_LEVEL_3"
			};
			*/
			/*
			Firing
			------
			This only fires an add when you add a single item that has other siblings that are not also selected.
			If all other siblings are selected then an update selection level above will fire adding the parent item.
				
			This only fires a remove when you remove a single item that has other siblings that are still selected.
			Otherwise if all siblings are are not selected then an update selection level above will fire removing the parent item.
				
			Control Data Sync
			-----------------
			The value on the control is sync with your select if we match the event source against the current method scope
			this ensures that we only carry out syncs on the lowest level and not bubbled events.
			The source attribute comes from the check box tree but not from the graphs therefore if the source is undefined then we should also
			sync the control value.
				
			Server Sync
			-----------
			Level 1 : Carries out a range/model/derivative ajax update via a criteria update which brings back the search criteria panel for us to replace.
			Level 2 : Carries out a model/derivarive ajax update via a stock count refresh.
			Level 3 : Carries out a derivarive ajax update via a stock count refresh.
			*/


			updateSelectionLevel1: function (e, updateLevel2Selection)
			{
				this._super(e, updateLevel2Selection); // call base

				if (this._validateManageListRequirement(e, e.level1Id, this.SCOPE_LEVEL_1))
				{
					this._manageList(e.select, e.level1Id, this.FIELD_RANGE_LEVEL);

					// this is carried out by the refresh range context not the stock count
					//this._managedInput.trigger("change");
				}

				var formElement = $(this.MANAGED_INPUT_CLASS).closest(this.SELECTOR_FORM);

				this._refreshRangeContext(e, formElement);
			},

			updateSelectionLevel2: function (e)
			{
				this._super(e); // call base

				if (this._validateManageListRequirement(e, e.level2Id, this.SCOPE_LEVEL_2))
				{
					this._manageList(e.select, e.level2Id, this.FIELD_MODEL_LEVEL);

					this._managedInput.trigger("change");
				}
			},


			updateSelectionLevel3: function (e)
			{
				this._super(e); // call base

				if (this._validateManageListRequirement(e, e.level3Id, this.SCOPE_LEVEL_3))
				{
					this._manageList(e.select, e.level3Id, this.FIELD_DERIVATIVE_LEVEL);

					this._managedInput.trigger("change");
				}
			},


			/*
			=============================
			PRIVATE HEPLER METHODS
			=============================
			*/

			_validateManageListRequirement: function (e, value, onScope)
			{
				var isRequired = false;

				if (
							(onScope == e.scope) &&
							(
								(onScope == e.source) || // if i am not a bubbled event
								(!e.source) // or if i have no source, i.e. i have come from a 
							) &&
							(value != -1)
						)
				{
					isRequired = true;
				}

				return isRequired;
			},

			_manageList: function (state, item, level)
			{
				var editedItems;

				// if we are already at our managed level then we can use the incoming item
				if (level == this.MANAGED_VALUE_LEVEL)
				{
					editedItems = item;
				}
				else
				{
					// otherwise we need to work out what items to from our field
					editedItems = this._getItemsFromFieldForLevel(item, level);
				}

				var currentSelection = this._managedInput.val();

				this._alertDebugMessage('before count : ' + currentSelection.split(this.DELIMITER).length + '.\nbefore : ' + currentSelection + '.');

				var newList;

				if (state == true)
				{
					newList = this._utilities.mergeDataListsIntoUniqueList(this._managedInput.val(), editedItems, ",");
				}
				else
				{
					newList = this._utilities.removeValuesFromDataList(this._managedInput.val(), editedItems, ",");
				}

				this._managedInput.val(newList);

				this._alertDebugMessage('after count: ' + this._managedInput.val().split(this.DELIMITER).length + '\nafter: ' + this._managedInput.val() + '.');
			},

			/*
			=============================
			EXAMPLE FIELD
			Range[],Model[],Derivative[] etc...
			=============================
			[
			{
			"id": 2035,
			"title": "IS",
			"options": [
			{
			"id": 1791493,
			"title": "IS 220d Saloon 4-Door",
			"options": [
			{
			"id": 1791494,
			"title": "2.2TD"
			}
			]
			}
			]
			}
			]
			*/
			// this is the parent method to get options within a field on a speicfied level
			_getItemsFromFieldForLevel: function (item, level)
			{
				// we need field data to do this
				if (this.FIELD == null)
				{
					this._alertDebugMessage("Can not manage state as range,model,derviative field is null");
					throw new Error();
				}

				var currentLevel = 0;
				var requiredOptions = new Array();

				for (var i = 0; i < this.FIELD.length; i++)
				{
					this._getFieldOptionsForLevel(requiredOptions, item, this.FIELD[i], currentLevel, level, this.MANAGED_VALUE_LEVEL);
				}

				this._alertDebugMessage('found: ' + requiredOptions.length + ' option matches\nfor item {' + item + '}\non level {' + level + '}.');

				var optionIds = this._delmitedOptionPropertyValue(requiredOptions, ",", "id");

				this._alertDebugMessage(optionIds);

				return optionIds;
			},

			// this is a recursive method to add matching options to the [matchedOptions] varible from the option and level that you have provided
			_getFieldOptionsForLevel: function (matchedOptions, item, option, traversedLevel, matchLevel, requiredLevel)
			{
				var matchFound = false;

				// if i have traversed to my match level then check the item against the option.id
				if (traversedLevel == matchLevel)
				{
					if (new String(option.id) == item || item == null)
					{
						// change the traverse option selector
						if (requiredLevel != null)
						{
							/* 
							Now get all options on the required level below this option.
							e.g.	for the matched model go get all the derivatives.
							e.g.	for the matched range go get all the derivatives.
							i.e.	match level = 1(model), required level = 2 (derivatives),
							so find the matching option on the correct level then get all options on the required level below.
							*/
							matchFound = this._getFieldOptionsForLevel(matchedOptions, null, option, traversedLevel, requiredLevel, null);
						}
						// add this matched option
						else
						{
							matchFound = true;

							if (matchedOptions.length && matchedOptions.length > 0)
							{
								matchedOptions[matchedOptions.length] = option;
							}
							else
							{
								// this is the first time we add to the array
								matchedOptions[0] = option;
							}
						}
					}
				}
				// ensure that our current option has child options
				else if (option.options && option.options.length > 0)
				{
					// traverse the child options to the next level
					for (var i = 0; i < option.options.length; i++)
					{
						matchFound = this._getFieldOptionsForLevel(matchedOptions, item, option.options[i], traversedLevel + 1, matchLevel, requiredLevel);

						// if item is not null i.e. we are in match mode and have found a mathc then break out of loop
						if (matchFound && item != null)
						{
							break;
						}
					}
				}

				return matchFound;
			},

			_delmitedOptionPropertyValue: function (options, delimiter, propertyValue)
			{
				var delimitedOptions = new String();

				// do we have option to delimit
				if (options != null && options.length && options.length > 0)
				{
					for (var i = 0; i < options.length; i++)
					{
						delimitedOptions += options[i][propertyValue] + delimiter;
					}
				}

				// remove last delimiter
				delimitedOptions = delimitedOptions.substring(0, delimitedOptions.lastIndexOf(delimiter));

				return delimitedOptions;
			},

			// binds the derivative search criteria and refreshes controls that are dependant on range
			_refreshRangeContext: function (e, formElement)
			{
				this._managedInput.trigger("clear");

				// setup ajax call, to update range / model / derivative search criteria

				// setup ajax arguments
				var wflw = mrm.global.utilities.workflow.getScopedWorkflowKey(formElement, "_ra_up");

				//"se_na_ra_up"; // (default)search national range update

				$.ajaxManager.clearArguments();
				$.ajaxManager.ajaxSettings.arguments.add(this.ARGUMENT_WORKFLOW, wflw); // add work flow key
				$.ajaxManager.ajaxSettings.arguments.add("crtv", $(this.MANAGED_INPUT_CLASS).val()); // add criteria value
				$.ajaxManager.updateUrl($.ajaxManager.ajaxSettings.arguments.getString("&"));

				// bind ajax events
				var currentScope = this;
				$.ajaxManager.onBeforeSent = function () { currentScope._refreshBefore() };
				$.ajaxManager.onError = function (XMLHttpRequest, textStatus, errorThrown) { currentScope._refreshError(XMLHttpRequest, textStatus, errorThrown) };
				$.ajaxManager.onSuccess = function (p_response) { currentScope._refreshSuccess(p_response) };

				$.ajaxManager.makeRequest(); // fire request
			},

			// handles the before send event of the ajax call
			_refreshBefore: function ()
			{
				this._alertDebugMessage("_refreshBefore");

				// TODO set search criteria panel into a loading state
			},

			// handles the error event of the ajax call
			_refreshError: function (XMLHttpRequest, textStatus, errorThrown)
			{
				this._alertDebugMessage("_refreshError");
			},

			// handles the success event of the ajax call
			_refreshSuccess: function (p_response)
			{
				this._alertDebugMessage("_refreshSucess");

				// evaluate response
				var result = eval(p_response);

				// replace all controls
				this.callbackUtilities.processResultControls(result);

				// get reference to the search criteria panel
				var searchCriteriaControlReference;
				var $searchCriteriaContainer;
				
				searchCriteriaControl = this.callbackUtilities.findResultDataControl(result, this.SEARCH_CRITERIA_PANEL);
				if (typeof (searchCriteriaControl) != "undefined" && searchCriteriaControl != null)
				{
					$searchCriteriaContainer = $(searchCriteriaControl.targetDomId);
				}
				else
				{
					$("#SearchSubmit").click();
				}

				/* _processResultControls takes care of this 
				// PNG Fix
				if(fix_PNGs)
				{
				fix_PNGs($searchCriteriaContainer);
				}
				*/

				// Render Sliders
				if (RenderChildSliders)
				{
					RenderChildSliders($searchCriteriaContainer);
				}

				// Advanced search panel image list bind
				if (mrm.global.isNamespaceDefined("mrm.runtime.display.controls") && mrm.runtime.display.controls.advancedSearchImageListControls)
				{
					mrm.runtime.display.controls.advancedSearchImageListControls.bindContainer($searchCriteriaContainer);
				}

				//todo : lk uncommented this out
				// Advanced search panel must have total select summary bind
				if (prepareSearchCriteriaPanelContainer)
				{
					prepareSearchCriteriaPanelContainer($searchCriteriaContainer);
				}

				// Rebind open and close panel click
				if (mrm.global.isNamespaceDefined("mrm.runtime.display.controls") && mrm.runtime.display.controls.advancedSearchPanel)
				{
					mrm.runtime.display.controls.advancedSearchPanel.rebind();
				}

				// Rebind mp-search-count, form submit clicks and graph item selection updates
				if (mrm.global.isNamespaceDefined("mrm.runtime.display") && mrm.runtime.display.searchConfiguration)
				{
					mrm.runtime.display.searchConfiguration.bind($searchCriteriaContainer);
				}

				// Rebind tool tips
				/* _processResultControls takes care of this 
				if (BindToolTips)
				{
				BindToolTips($searchCriteriaContainer);
				}
				*/

				// SWF
				if (sIFR && sIFR.styleAdvancedSettingsPanel)
				{
					sIFR.styleAdvancedSettingsPanel();
				}

				// SET O/C State [Optional]

				// Broadcast data update events to graphs, this creates the graphs


				// Process updated PRICE data
				if (json_priceData_level_1)
				{
					// Create selection summary update event
					var summaryEvent = this._createSelectionSummaryEventLevel1(json_priceData_level_1);

					// Process level 1 update
					this._processDataLoadedLevel1(json_priceData_level_1, summaryEvent);

					// Create and broadcast additional level 2 selection sumamry updates
					this._createLevel2SelectionSummaryUpdates(json_priceData_level_1);
				}

				// Process updated ODOMETER data
				if (json_odometer_level_1)
				{
					// Create selection summary update event
					var summaryEvent = this._createSelectionSummaryEventLevel1(json_odometer_level_1);

					// Process level 1 update
					this._processDataLoadedLevel1(json_odometer_level_1, summaryEvent);

					// Create and broadcast additional level 2 selection sumamry updates
					this._createLevel2SelectionSummaryUpdates(json_odometer_level_1);
				}

				if (json_fuelType_level_1) this._processDataLoadedLevel1(json_fuelType_level_1, {}); // fuel type
				if (json_transmissionData_level_1) this._processDataLoadedLevel1(json_transmissionData_level_1, {}); // transmission type
			},

			_createSelectionSummaryEventLevel1: function (e)
			{
				var summaryEvent = {};

				summaryEvent.dataCategory = e.dataCategory;
				summaryEvent.dataScope = e.dataScope;

				summaryEvent.dataId = e.dataSummary.id;

				summaryEvent.selectableMax = e.selectionSummary.selectableMax;
				summaryEvent.selectableMin = e.selectionSummary.selectableMin;
				summaryEvent.selectedMax = e.selectionSummary.selectedMax;
				summaryEvent.selectedMin = e.selectionSummary.selectedMin;

				return summaryEvent;
			},


			// @param	e	Level 1 jsonData object
			_createLevel2SelectionSummaryUpdates: function (e)
			{
				// Generate and broadcast multiple level 2 selection summary updates
				var dataCategory = e.dataCategory;
				var dataScope = "SCOPE_LEVEL_2";
				var selectedMax = e.selectionSummary.selectedMax;
				var selectedMin = e.selectionSummary.selectedMin;

				var selectableMax = undefined;
				var selectableMin = undefined;

				var i;
				var a = e.data;

				// First run - store selectable min / max
				for (i = 0; i < a.length; i++)
				{
					var item = a[i];

					if (item.selected)
					{
						var iMin = item.min;
						var iMax = item.max;
						selectableMin = (selectableMin) ? Math.min(selectableMin, iMin) : iMin;
						selectableMax = (selectableMax) ? Math.max(selectableMax, iMax) : iMax;
					}

				}

				// Second run - generate update event
				for (i = 0; i < a.length; i++)
				{
					var item = a[i];
					var id = item.id;
					var updateEvent = {};

					// If selectable min / max still undefined -- i.e. no models selected set to range min / max
					var _selectableMin = (selectableMin) ? Math.max(selectableMin, item.min) : e.selectionSummary.selectableMin;
					var _selectableMax = (selectableMax) ? Math.max(selectableMax, item.max) : e.selectionSummary.selectableMax;

					//alert(" _selectableMin = " + _selectableMin + "\n _selectableMax = " + _selectableMax + "\n selectableMin = " + selectableMin + "\n selectableMax = " + selectableMax);

					updateEvent.dataCategory = dataCategory;
					updateEvent.dataScope = dataScope;
					updateEvent.dataId = id;
					updateEvent.selectableMax = _selectableMax;
					updateEvent.selectableMin = _selectableMin;
					updateEvent.selectedMax = selectedMax;
					updateEvent.selectedMin = selectedMin;

					this._broadcastSelectionSummaryUpdate(updateEvent);
				}

			},



			// handled alert trace message
			_alertDebugMessage: function (message)
			{
				if (this.DEBUG)
				{
					alert(message);
				}
			},

			_last: "last"
		}
	);
