motorola.controls.ProductSearcher = new Class({
	Implements: [Events,Options],
	activeTweens: [],
	current: null, // int
	defaultFieldValue: '', // String
	docEvents: null, // Object
	events: null, // Object
	field: null, // String
	ff: Browser.Engine.gecko, // boolean
	found: [],  // <Element>
	hasFocus: false, // Boolean
	highlighted: null, // Element
	lastInput: '', // String
	options: {
		backButtonClass: 'back',
		defaultState: '#F2F2F2',
		duration: 250,
		forwardBackButtonBuffer: 0,
		forwardButtonClass: 'forward',
		highlightState: '#8ECFF4',
		highlightStyle: 'backgroundColor',
		resultsContainerSelector: '.resultsList',
		resultsListSelector: '.results',
		scrollBarClass: 'scrollBar',
		scrollEnableCount: 15,
		scrubberClass: 'scrubber',
		trackBarClass: 'track',
		transition: Fx.Transitions.Sine.easeOut
	},
	products: [], // <Product>
	resetFocusEvent: false,
	results: null, // String
	resultsEnableCount: 100,
	searchResults: null, // String
	searchResultsList: null, // String

	activateSearch: function(e) {

		// Just return if this is an event to reset
		// the focus to the field (ie workaround)
		if ( this.resetFocusEvent || this.current !== null ) {
			return;
		}

		var field =	$(this.field);
		var value = field.get('value');
		this.hasFocus = true;

		// Clear field if current value is default value
		if (value === this.defaultFieldValue) {
			field.set('value','');
		}
		// Otherwise, filter products by specified value
		else {
			this.filterProducts(e);
		}
	},

	addTween: function(tween) {
		this.activeTweens.push(tween);
	},

	decrementCurrent: function() {

		if ( this.current <= 0 ) {
			return;
		}
		this.current--;
	},

	destroyScrollbar: function() {

		if (this.slider) { this.slider.destroy(); }
		this.slider = null;
		this.activeTweens.each(function(tween) { tween.cancel(); });
		this.activeTweens.empty();

		var searchResultsList = $(this.searchResultsList);
		searchResultsList.dispose();

		var searchResults = $(this.searchResults);
		searchResults.empty();

		searchResults.adopt(searchResultsList);

		$(this.results).setStyle('top', 0);
	},

	disable: function() {
		var field = $(this.field);
		var evt;
		var events = this.events;
		for (evt in events) { field.removeEvent(evt,events[evt]); }
		events = this.docEvents;
		for (evt in events) { document.removeEvent(evt,events[evt]); }
	},

	dispatch: function(e) {

		// Handle filter actions - alphanumeric
		if ( this.isFilterAction(e) ) {
			this.filterProducts(e);
		}
		// Handle reset back to last input - esc
		else if ( this.isSetToLastInput(e) ) {
			this.setToLastInput();
		}

	},

	enable: function() {
		$(this.field).addEvents(this.events);
		document.addEvents(this.docEvents);
	},

	executeKeyAction: function(e) {

		var evt = new Event(e);
		var code = evt.code;
		var key = evt.key;
		this.filterAction = true;
		// Ignore enter key action if there are
		// no results 
		if ( this.found.length === 0 ) {
			if ( code === 13 ) {
				evt.stop();
			}
			return;
		}

		if ( code >= 33 && code <= 36 && !evt.shift) {
			this.filterAction = false;
		}

		// Up: scroll to previous
		if ( (code === 9 && evt.shift) || code === 38 && !evt.shift ) {
			this.previous();
			evt.stop();
		}
		// Tab, down: scroll to next
		else if ( code === 9 || ( code === 40 && !evt.shift ) ) {
			this.next();
			evt.stop();
		}
		// Enter: view product detail
		else if ( code === 13 ) {
			this.selectProduct();
			evt.stop();
		}
		this.valueLength = $(this.field).get('value').length;
	},

	filterProducts: function(e) {

		// Don't filter by the default value!
		var value = $(this.field).get('value');
		if (value === this.defaultFieldValue) {
			return;
		}

		// Save the input - we may need to display
		// this value again if user navigates away
		// with arrow keys and then back again...
		this.lastInput = value;

		// Always compare on lower case
		value = value.toLowerCase();

		// Check if the value is filterable
		if ( !this.isFilterable(value) ) {
			this.hideResults();
			this.resetValueLength();
			return;
		}

		// Get the results list - we will
		// insert matching elements into it
		var results = $(this.results);

		// Clear existing results
		results.empty();

		// Remove any existing scrollbars
		this.destroyScrollbar();

		// Find matching products
		// We need to do this first to determine if there are more
		// than the max number of results allowed to be shown
		var products = this.products;
		var resultsEnableCount = this.resultsEnableCount;
		var matchingProducts = [];
		var i, product, index;
		for ( i = 0 ; i < products.length ; i++ ) {

			product = products[i];

			// Check if the product name contains the specified
			// characters

			// Ignore any products that do not match
			index = product.name.toLowerCase().indexOf(value);
			if (index === -1) {
				continue;
			}

			// Otherwise, add product to list of matches
			matchingProducts.push(product);

			// Do not continue to search for matches if we have already
			// reached the max number of allowed results
			if ( !this.isShowResults(matchingProducts) ) {
				break;
			}
		}

		// Do not continue if we have more than the maximum allowed results
		if ( !this.isShowResults(matchingProducts) ) {
			this.hideResults();
			return;
		}

		// Otherwise, iterate over list of matching products
		// and create list of results
		var found = [];
		var optionElement;
		for ( i = 0 ; i < matchingProducts.length ; i++ ) {

			product = matchingProducts[i];

			// Setup corresponding element for product
			optionElement = new Element('li', {
				events: {
					'click': function(e) { new Event(e).stop() },
					'mousedown': function(e) { new Event(e).stop(); },
					'mouseenter': this.hover.bind(this),
					'click': this.selectProduct.bind(this)
				},
				html: product.name
			});

			// Store the URL for this product on element
			optionElement.store('productData', product);

			// Add option to results
			optionElement.inject(results);

			// Also add option to found list
			found.push(optionElement);
		}

		// Hide results if there are no matches
		if ( found.length === 0 ) {
			this.hideResults();
			this.resetValueLength();
		}
		// Otherwise show results
		else {
			this.showResults(found);
		}

		this.found = found;

		// Reset indices etc
		this.resetHighlights();
	},

	getOptionIndex: function(option) {

		// Get the index of this option in the list
		// of "found" products
		for ( var i = 0 ; i < this.found.length ; i ++ ) {
			if ( this.found[i] === option ) {
				return i;
			}
		}
		return -1;
	},

	hideResults: function() {

		// Hide results container and remove any existing results
		var results = $(this.results);
		results.empty();
		this.highlighted = null;
		$(this.searchResults).setStyle('display','none');


		// Clear scroll bar
		this.destroyScrollbar();

		// Clear found list
		this.found = [];

		// Clear indices etc
 		this.resetHighlights();

		// Set reset focus to be false
		// as we want any future onfocus
		// events to execute filter
		this.resetFocusEvent = false;
	},

	highlight: function(option) {

		var tween = option.get('tween',{
			duration:this.options.duration,
			transition:this.options.transition
		});
		tween.addEvent('onStart',this.addTween.pass([tween],this));
		option.tween(this.options.highlightStyle,this.options.highlightState);
		this.highlighted = option;
	},

	hover: function(e) {

		// Remove current highlight
		if ( this.highlighted ) {
			this.removeHighlight(this.highlighted);
		}

		// Get and set the index of the hover option
		// and highlight the option
		var evt = new Event(e);
		var option = evt.target;
		this.current = this.getOptionIndex(option);
		this.highlight(option);
	},

	incrementCurrent: function() {

		if ( this.current >= this.found.length ) {
			return;
		}
		this.current++;
	},

	initialize: function(field,results,options) {

		this.setOptions(options);

		// Do not proceed if there is no
		// text box or no search results
		if ( !field || !results ) {
			return;
		}

		// Get the default value
		this.defaultFieldValue = field.get('title') || '';

    //set the text to title or empty string
		field.set('value',this.defaultFieldValue);
		if (Browser.Engine.trident) {
			addEvent('load',field.set.pass(['value',this.defaultFieldValue],field));
			addEvent('load',this.quitOrActivate.bind(this));
		}

		// Determine field ID
		// Field: text box
		this.field = motorola.utils.generateElementId(field);

		// Determine results ID
		// Results: list (ul)
		this.results = motorola.utils.generateElementId(results.getElement(this.options.resultsListSelector));

		// Determine searchResults ID
		// Results: div
		this.searchResults = motorola.utils.generateElementId(results);

		// Determine searchResultsList ID
		// Results: div
		this.searchResultsList = motorola.utils.generateElementId(results.getElement(this.options.resultsContainerSelector));

		// Setup events on text box
		var events = this.events = {
			'focus': this.activateSearch.bind(this),
			'keyup': this.dispatch.bind(this)
		};
		if ( this.ff ) {
			events.keypress = this.executeKeyAction.bind(this);
		}
		else {
			events.keydown = this.executeKeyAction.bind(this);
		}

		// Add event on document to close results list
		this.docEvents = {
			'mousedown': this.quitSearch.bind(this),
			'mouseup': this.resetFocus.bind(this)
		};

		this.enable();

	},

	initializeScroller: function(optionCount, optionHeight) {

		// Update height of search results
		var resultsHeight = this.setResultsHeight(optionCount, optionHeight);

		// Create scroll bar
		var scrollBar = new Element('div',{
			'class': this.options.scrollBarClass,
			'styles': {
				'height': resultsHeight + 'px'
			}
		});

		// Setup back button
		var backButton = new Element('a',{
			'href': '#',
			'class': this.options.backButtonClass
		});
		backButton.addEvents({
			'mouseenter': backButton.addClass.pass([this.options.backButtonClass + 'Over'],backButton),
			'mouseleave': backButton.removeClass.pass([this.options.backButtonClass + 'Over'],backButton)
		});
		backButton.inject(scrollBar);

		// Setup track
		var track = new Element('div', {
			'class': this.options.trackBarClass
		});

		// Setup scrubber
		var scrubber = new Element('div',{
			'class': this.options.scrubberClass
		});
		scrubber.inject(track);
		track.inject(scrollBar);

		// Setup forward button
		var forwardButton = new Element('a',{
			'href': '#',
			'class': this.options.forwardButtonClass
		});
		forwardButton.addEvents({
			'mouseenter': forwardButton.addClass.pass([this.options.forwardButtonClass + 'Over'],forwardButton),
			'mouseleave': forwardButton.removeClass.pass([this.options.forwardButtonClass + 'Over'],forwardButton)
		});
		forwardButton.inject(scrollBar);

		scrollBar.inject($(this.searchResults));

		var scrollBarHeight = scrollBar.getStyle('height').toInt();
		var backButtonHeight = backButton.getSize().y;
		var forwardButtonHeight = forwardButton.getSize().y;
		var trackHeight = scrollBarHeight - backButtonHeight - forwardButtonHeight;
		track.setStyles({
			'height': trackHeight,
			'top': backButtonHeight
		});

		// Calculate the forward plus back button size
		this.forwardBackButtonBuffer = backButtonHeight + forwardButtonHeight;

		// Create slider
		var resultsParent = $(this.searchResultsList);
		var scrubberRatio = trackHeight / resultsParent.getScrollSize().y;
		var slider = this.slider = new MOTO.StyledForm.Slider(scrollBar,scrubberRatio,{
			'itemSize': optionHeight,
			'horizontal': false,
			'onSlideTo': this.slideContent.bind(this),
			'onMoveTo': this.moveContent.bind(this)
		});

		// Setup mousewheel events
		var mousewheel = this.mousewheelScroller.bindWithEvent(this);
		resultsParent.addEvents({
			'mouseenter': document.addEvent.pass(['mousewheel',mousewheel],document),
			'mouseleave': document.removeEvent.pass(['mousewheel',mousewheel],document)
		});
	},

	isShowResults: function(matchingProducts) {

		return matchingProducts.length <= this.resultsEnableCount;
	},

	isFilterAction: function(e) {

		// This is a filter action if key is alphanumeric
		// or if this is a mouse action
		var evt = new Event(e);
		var code = evt.code;
		var length = $(this.field).get('value').length;
		// not up arrow, down arrow, tab key, return/enter key
		return !(code === 13 || code === 38 || code === 9 || code === 40 || code === 27) && this.filterAction
						&& code && this.valueLength !== length;
	},

	isFilterable: function(value) {

		// Only show results if the input is more than two characters
		// or is the letter q
		return value !== '' && (value.length >= 2 || value.toLowerCase() === 'q' );
	},

	isOptionInScrollArea: function(option) {

		var forwardBackButtonBuffer = this.forwardBackButtonBuffer;
		var slider = this.slider;
		var trackPosition = slider.getTrackPosition(option.getPosition().y);
		var visible =
			trackPosition > -(forwardBackButtonBuffer) &&
			trackPosition < $(this.results).getParent().getSize().y - forwardBackButtonBuffer;
			return visible;
	},

	isSetToLastInput: function(e) {

		// Return true if the key is
		// escape
		var evt = new Event(e);
		return evt.code === 27;
	},

	mousewheelScroller: function(e) {
		if (this.slider) { this.slider.mouseWheel(e); }
	},

	moveContent: function(positionRatio) {

		var results = $(this.results);
		var position = -positionRatio * results.getScrollSize().y;
		results.setStyle('top', position);
	},

	next: function() {

		// If the current index is the last in the list,
		// clear the current - we want the "focus" to be
		// in the text box
		if ( this.current === this.found.length - 1 ) {

			this.removeHighlight(this.found[this.current]);
			this.current = null;

			// Update field to display the user's last
			// input
			this.updateFieldValue(this.lastInput);
			
			// Clear highlights
			this.resetHighlights();			

			// Reset scrollbar back to top
			this.scrollToTop();

			return;
		}

		// If there is no current index, set current
		// to be the first option in list
		var selectedOption;
		if ( !this.current && this.current !== 0 ) {

			this.current = 0;
			selectedOption = this.found[this.current];
		}
		// Otherwise increment the current index
		else {

			this.removeHighlight(this.found[this.current]);
			this.incrementCurrent();

			selectedOption = this.found[this.current];
		}

		// Setup the selected option - need to update
		// highlight and possibly scrollbar
		this.setupSelectedOption();
	},

	previous: function() {

		// If the current index is the first item
		// in the list, clear the current - we
		// want the "focus" to be in the text box
		if ( this.current === 0 ) {

			this.removeHighlight(this.found[this.current]);
			this.current = null;

			// Update field to display the user's last
			// input
			this.updateFieldValue(this.lastInput);
			
			// Clear highlights
			this.resetHighlights();

			return;
		}

		// If there is no current index, set
		// the current to be the last option in
		// the list
		var selectedOption;
		if ( !this.current && this.current !== 0 ) {

			this.current = this.found.length - 1;
			selectedOption = this.found[this.current];

			// Scroll to bottom
			this.scrollToBottom();
		}
		// Otherwise decrement current index
		else {

			this.removeHighlight(this.found[this.current]);
			this.decrementCurrent();

			selectedOption = this.found[this.current];
		}

		// Setup the selected option - need to update
		// highlight and possibly scrollbar
		this.setupSelectedOption();
	},

	quitOrActivate: function() {
		if (this.hasFocus) {
			$(this.field).set('value','');
			this.activateSearch();
			if (Browser.Engine.trident) { $(this.field).set.delay(100,$(this.field),['value','']); }
		} else {
			this.quitSearch();
			if (Browser.Engine.trident) { $(this.field).blur(); $(this.field).set.delay(100,$(this.field),['value',this.defaultFieldValue]); }
		}
	},

	quitSearch: function(e) {

		// Only quit search if the text box
		// is not in focus
		this.setToLastInput();
		if ( e && e.target === $(this.field) ) {
			return;
		}
		this.hasFocus = false;

		// Hide results - set text box
		// value back to default if necessary
		var field = $(this.field);
		var value = field.get('value');
		if (value === '') {
			field.set('value',this.defaultFieldValue);
		}
		this.hideResults();
	},

	removeHighlight: function(option) {

		var tween;
		if (option) {
			tween = option.get('tween',{
				duration:this.options.duration,
				transition:this.options.transition
			});
			tween.addEvent('onStart',this.addTween.pass([tween],this));
			option.tween(this.options.highlightStyle,this.options.defaultState);
			this.highlighted = null;
		}
	},

	reset: function() {
		$(this.field).set('value',this.defaultFieldValue);
		this.hideResults();
	},

	resetHighlights: function() {

		// Reset current index
		this.current = null;

		// Reset highlighted option
		this.highlighted = null;
	},

	resetFocus: function(e) {

		// Ignore this event if there are no results
		if ( !this.found || this.found.length === 0 ) {
			return;
		}

		// Indicate that this is a reset
		// focus event
		this.resetFocusEvent = true;

		// Set focus back on field
		$(this.field).focus();
	},

	resetValueLength: function() {

		this.valueLength = null;
	},

	scrollToTop: function() {

		// Reset scrollbar to top
		if (this.slider) {
			this.slider.setScrubberPosition(0,false);
		}
	},

	scrollToBottom: function() {

		// Move scrollbar to bottom
		var slider = this.slider;
		if ( slider ) {
			slider.setScrubberPosition(slider.trackSize - slider.scrubberSize, false);
		}
	},

	selectProduct: function() {

		this.fireEvent('onSelect',this);
	},

	setResultsHeight: function(optionCount, optionHeight) {

		var searchResultsList = $(this.searchResultsList);
		var resultsHeight = optionHeight * optionCount;

		searchResultsList.setStyles({
			'height': resultsHeight + 'px',
			'overflow': 'hidden'
		});

		$(this.searchResults).setStyles({
			'overflow': 'hidden'
		});

		return resultsHeight;
	},

	setupSelectedOption: function() {

		// Slide to option if necessary
		var slider = this.slider;
		var selectedOption = this.found[this.current];
		if ( slider && !this.isOptionInScrollArea(selectedOption) ) {
			this.slideToOption(selectedOption);
		}

		// Highlight the next option
		this.highlight(selectedOption);

		// Update search box with matching value
		this.updateFieldValue(selectedOption.getText().trim());
	},

	setToLastInput: function() {

		if ( $defined(this.current) ) {
			this.removeHighlight(this.highlighted);
			this.updateFieldValue(this.lastInput);
			this.resetHighlights();
		}
	},

	showResults : function(found) {

		// Show the results
		var resultsLeft = $(this.field).getPosition($(this.searchResults).getParent()).x;
		$(this.searchResults).setStyles({'display':'block','left':resultsLeft});

		// Get the height of the options
		var optionHeight = found[0].getSize().y;

		// Get the number of options
		var optionCount = found.length;

		// Initialize scroll bar if necessary
		var scrollEnableCount = this.options.scrollEnableCount;
		if ( optionCount > scrollEnableCount ) {

			// Initialize the scroller
			this.initializeScroller(scrollEnableCount, optionHeight);
		}
		// Otherwise, udpate height of results box to be
		// "neat" up against the last result
		else {

			this.setResultsHeight(optionCount, optionHeight);

			// Clear any existing scrollbars
			//this.slider = null;
		}
	},

	slideContent: function(positionRatio) {

		var results = $(this.results);
		var position = -positionRatio * results.getScrollSize().y;
		results.tween('top', position);
	},

	slideToOption: function(option) {

		var slider = this.slider;
		if ( !slider ) {
			return;
		}
		var results = $(this.results);
		var yoffset = option.getPosition(results).y;
		var positionRatio = yoffset / results.getScrollSize().y;
		var scrubberPosition = positionRatio * slider.trackSize;
		slider.setScrubberPosition(scrubberPosition,false);
	},

	updateFieldValue: function(value) {

		// Set the field's value
		var field = $(this.field);
		field.set('value', value);
	}
});
