(function() {

	'use strict';



	var L = require('leaflet');



	module.exports = L.Class.extend({

		options: {

			timeout: 500,

			blurTimeout: 100,

			noResultsMessage: 'No results found.'

		},



		initialize: function(elem, callback, context, options) {

			L.setOptions(this, options);



			this._elem = elem;

			this._resultFn = options.resultFn ? L.Util.bind(options.resultFn, options.resultContext) : null;

			this._autocomplete = options.autocompleteFn ? L.Util.bind(options.autocompleteFn, options.autocompleteContext) : null;

			this._selectFn = L.Util.bind(callback, context);

			this._container = L.DomUtil.create('div', 'leaflet-routing-geocoder-result');

			this._resultTable = L.DomUtil.create('table', '', this._container);



			// TODO: looks a bit like a kludge to register same for input and keypress -

			// browsers supporting both will get duplicate events; just registering

			// input will not catch enter, though.

			L.DomEvent.addListener(this._elem, 'input', this._keyPressed, this);

			L.DomEvent.addListener(this._elem, 'keypress', this._keyPressed, this);

			L.DomEvent.addListener(this._elem, 'keydown', this._keyDown, this);

			L.DomEvent.addListener(this._elem, 'blur', function() {

				if (this._isOpen) {

					this.close();

				}

			}, this);

		},



		close: function() {

			L.DomUtil.removeClass(this._container, 'leaflet-routing-geocoder-result-open');

			this._isOpen = false;

		},



		_open: function() {

			var rect = this._elem.getBoundingClientRect();

			if (!this._container.parentElement) {

				// See notes section under https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollX

				// This abomination is required to support all flavors of IE

				var scrollX = (window.pageXOffset !== undefined) ? window.pageXOffset

					: (document.documentElement || document.body.parentNode || document.body).scrollLeft;

				var scrollY = (window.pageYOffset !== undefined) ? window.pageYOffset

					: (document.documentElement || document.body.parentNode || document.body).scrollTop;

				this._container.style.left = (rect.left + scrollX) + 'px';

				this._container.style.top = (rect.bottom + scrollY) + 'px';

				this._container.style.width = (rect.right - rect.left) + 'px';

				document.body.appendChild(this._container);

			}



			L.DomUtil.addClass(this._container, 'leaflet-routing-geocoder-result-open');

			this._isOpen = true;

		},



		_setResults: function(results) {

			var i,

			    tr,

			    td,

			    text;



			delete this._selection;

			this._results = results;



			while (this._resultTable.firstChild) {

				this._resultTable.removeChild(this._resultTable.firstChild);

			}



			for (i = 0; i < results.length; i++) {

				tr = L.DomUtil.create('tr', '', this._resultTable);

				tr.setAttribute('data-result-index', i);

				td = L.DomUtil.create('td', '', tr);

				text = document.createTextNode(results[i].name);

				td.appendChild(text);

				// mousedown + click because:

				// http://stackoverflow.com/questions/10652852/jquery-fire-click-before-blur-event

				L.DomEvent.addListener(td, 'mousedown', L.DomEvent.preventDefault);

				L.DomEvent.addListener(td, 'click', this._createClickListener(results[i]));

			}



			if (!i) {

				tr = L.DomUtil.create('tr', '', this._resultTable);

				td = L.DomUtil.create('td', 'leaflet-routing-geocoder-no-results', tr);

				td.innerHTML = this.options.noResultsMessage;

			}



			this._open();



			if (results.length > 0) {

				// Select the first entry

				this._select(1);

			}

		},



		_createClickListener: function(r) {

			var resultSelected = this._resultSelected(r);

			return L.bind(function() {

				this._elem.blur();

				resultSelected();

			}, this);

		},



		_resultSelected: function(r) {

			return L.bind(function() {

				this.close();

				this._elem.value = r.name;

				this._lastCompletedText = r.name;

				this._selectFn(r);

			}, this);

		},



		_keyPressed: function(e) {

			var index;



			if (this._isOpen && e.keyCode === 13 && this._selection) {

				index = parseInt(this._selection.getAttribute('data-result-index'), 10);

				this._resultSelected(this._results[index])();

				L.DomEvent.preventDefault(e);

				return;

			}



			if (e.keyCode === 13) {

				L.DomEvent.preventDefault(e);

				this._complete(this._resultFn, true);

				return;

			}



			if (this._autocomplete && document.activeElement === this._elem) {

				if (this._timer) {

					clearTimeout(this._timer);

				}

				this._timer = setTimeout(L.Util.bind(function() { this._complete(this._autocomplete); }, this),

					this.options.timeout);

				return;

			}



			this._unselect();

		},



		_select: function(dir) {

			var sel = this._selection;

			if (sel) {

				L.DomUtil.removeClass(sel.firstChild, 'leaflet-routing-geocoder-selected');

				sel = sel[dir > 0 ? 'nextSibling' : 'previousSibling'];

			}

			if (!sel) {

				sel = this._resultTable[dir > 0 ? 'firstChild' : 'lastChild'];

			}



			if (sel) {

				L.DomUtil.addClass(sel.firstChild, 'leaflet-routing-geocoder-selected');

				this._selection = sel;

			}

		},



		_unselect: function() {

			if (this._selection) {

				L.DomUtil.removeClass(this._selection.firstChild, 'leaflet-routing-geocoder-selected');

			}

			delete this._selection;

		},



		_keyDown: function(e) {

			if (this._isOpen) {

				switch (e.keyCode) {

				// Escape

				case 27:

					this.close();

					L.DomEvent.preventDefault(e);

					return;

				// Up

				case 38:

					this._select(-1);

					L.DomEvent.preventDefault(e);

					return;

				// Down

				case 40:

					this._select(1);

					L.DomEvent.preventDefault(e);

					return;

				}

			}

		},



		_complete: function(completeFn, trySelect) {

			var v = this._elem.value;

			function completeResults(results) {

				this._lastCompletedText = v;

				if (trySelect && results.length === 1) {

					this._resultSelected(results[0])();

				} else {

					this._setResults(results);

				}

			}



			if (!v) {

				return;

			}



			if (v !== this._lastCompletedText) {

				completeFn(v, completeResults, this);

			} else if (trySelect) {

				completeResults.call(this, this._results);

			}

		}

	});

})();

