/*
 * Jquery UI city selector widget.
 *
 * Depends on:
 * 	jquery.core.js v1.7
 *	jquery.ui.core.js v1.8
 *	jquery.ui.widget.js v1.8
 *
 * You can grab the rest of the code at https://bitbucket.org/JustDelight/city_selector
 */

(function($) {
	"use strict";

	var META = {
			RES: {
				COUNTRIES	: 'countries',
				REGIONS		: 'regions',
				CITIES		: 'cities'
			}
		},

	CachItem = {
		store: function (data) {
			if (!this.isNotEmpty()) {
				this.data = data
				Object.freeze(this)
			}
		},

		isNotEmpty: function () {
			return Object.isFrozen(this)
		}
	},

	AbstractLoader = {
		cache: {},
		queues: {},

		_getNestedItem: function (obj, descriptor) {
			var nested = obj;

			descriptor.forEach(function (item) {
				if (!(item in nested)) {
					nested[item] = {}
				}
				nested = nested[item]
			})

			return nested
		},

		_getQueue: function (descriptor) {
			var last = descriptor.pop(),
				nested = this._getNestedItem(this.queues, descriptor);

			if (!(nested[last] instanceof Array)) {
				nested[last] = []
			}

			return nested[last]
		},

		getCacheItem: function (descriptor) {
			var item = this._getNestedItem(this.cache, descriptor);
			if (item.__proto__ != CachItem) {
				item.__proto__ = CachItem;
			}
			return item;
		},

		_getData: function (descriptor, callback, path_item) {
			var res, queue,
				cache_item = this.getCacheItem(descriptor);

			if (cache_item.isNotEmpty()) {
				callback.call(null, cache_item.data)
			} else {
				queue = this._getQueue(descriptor)

				if (queue.push(callback) == 1) {
					$.ajax({
						context: this,
						url: this.CONFIG.base_url + path_item,
						dataType: 'json',
						success: function (data, status, xhr) {
							var callback;

							while (callback = queue.pop()) {
								callback.call(null, data)
							}
							cache_item.store(data)
						},
						error: function (e) {
							console.error(e)
						}
					})
				}
			}
		},
	},

	GeoItemsLoader = {
		CONFIG: {
			base_url: '/some_path/'
		},

		__proto__: AbstractLoader,

		load: function (resource_description, callback) {
			this._getData(resource_description, callback, resource_description.join('/') + '/')
		}

	};

	$.widget("ui.city_selector", {
		options : {
			position: 'bottom',
			base_url: '/select/',
			locale: 'ru'
		},

		strings_pack: {
			'ru': {
				city: 'Город',
				region: 'Регион',
				country: 'Страна',
				prompt: 'выберите город'
			},

			'en': {
				city: 'City',
				region: 'Region',
				country: 'Country',
				prompt: 'select the city'
			}
		},

		_create: function () {
			this._setLocale(this.options.locale)
			GeoItemsLoader.CONFIG.base_url = this.options.base_url
			this._buildWidget()

			if ('data' in this.options) {
				this.options.data.forEach(function (item) {
					GeoItemsLoader.getCacheItem(item[0]).store(item[1])
				})
			}

			if ('initial' in this.options) {
				this._loadInitialData('data' in this.options)
			}

			this._loadCountires()

			this._kbdHideHandler = this._handleDocMouseDown.bind(this)
			this._mouseHideHandler = this._handleDocKeyPress.bind(this)
		},

		destroy: function () {
			//TODO: implement it!
		},

		_setOption: function(option, value) {
			if (option == 'locale') {
				this._setLocale(value)
			} else if ((option == 'position') && ((value != 'top') || (value != 'bottom'))) {
				throw 'unsupported postion'
			}
			this.options[option] = value
		},

		_: function(name) {
			return this._localeStrings[name]
		},

		_setLocale: function(locale) {
			if (!(locale in this.strings_pack)) {
				throw 'Unsupported locale: ' + locale
			} else {
				this._localeStrings = this.strings_pack[locale]
			}
		},

		_loadInitialData: function () {
			var res, value;

			for (res in this.options['initial']) {
				value = this.options['initial'][res]
				$(this).bind(res, (function (res, value, e) {
					$(this).unbind(e)
					this._setSelectedValue(res, value)
				}).bind(this, res, value))
			}
		},

		_buildWidget: function () {
			var title  = this.element.attr('value') ? this.element.attr('value') : this._('prompt'),
				value, name, id, comp_style,
				show_handler = (function () {
					this._root.is(":visible") ? this.hide() : this.show()
				}).bind(this),
				country_change_handler = this._loadRegions.bind(this),
				region_change_handler = this._loadCities.bind(this);


			this.anchor = $('<a href="#" class="ui-city_celector">' + title + '</a>');
			this.element.after(this.anchor)
			this.element.hide()
			this.anchor.click(show_handler)
			this.anchor.addClass(this.options.position == 'top' ? 'bottom' : 'top')

			this._root = $('<div></div>').addClass("ui-widget ui-widget-content ui-city_celector").appendTo($(document.body)).hide()

			this._selects = {}
			this._selects[META.RES.COUNTRIES] = $('<select/>').attr('name', 'country').appendTo(this._root)
			this._selects[META.RES.REGIONS] = $('<select/>').attr('name', 'region').appendTo(this._root).hide()
			this._selects[META.RES.CITIES] = $('<select/>').attr('name', 'city').appendTo(this._root).hide()

			this._selects[META.RES.COUNTRIES].change(function (e) {
				country_change_handler($(this).attr('value'))
			})

			this._selects[META.RES.REGIONS].change(function (e) {
				region_change_handler($(this).attr('value'))
			})

			this._selects[META.RES.CITIES].change(this._setValue.bind(this))
		},

		show: function () {
			var root_element = this._root, root_style = {
					left:	this.anchor.offset().left
				};

			switch (this.options.position) {
				case 'bottom':
					root_style.top = parseInt(this.anchor.offset().top) - 1
					root_style.paddingTop = this.anchor.outerHeight()
				break;

				case 'top':
					root_style.bottom = parseInt($(document).height() - this.anchor.offset().top - this.anchor.outerHeight())
					root_style.paddingBottom = this.anchor.outerHeight()
			}

			root_element.css(root_style)

			window.setTimeout(function () {
				root_element.show()
				root_element.focus()
			}, 0)

			$(document).bind('mousedown', this._kbdHideHandler)
			$(document).bind('keypress', this._mouseHideHandler)

			this.anchor && this.anchor.toggleClass('ui-state-active')

			return false;
		},

		_handleDocMouseDown: function (e) {
			//TODO: try to simplify hiding
			if (!($(e.target).closest('.ui-city_celector').is(this._root) || $(e.target).is(this.anchor))) {
				this.hide()
			}
		},

		_handleDocKeyPress: function (e) {
			if (e.keyCode == 27) {
				this.hide()
			}
		},

		hide: function () {
			$(document).unbind('mousedown', this._kbdHideHandler)
			$(document).unbind('keypress', this._mouseHideHandl)
			this.anchor.toggleClass('ui-state-active')
			this._root.hide()
		},

		_generateOptionTag: function (title, value) {
			return $('<option/>').text(title).attr('value', value)
		},

		_fillSelect: function (kind, value_field, title, options) {
			var select = this._selects[kind];

			select.children().detach()

			for(var i = options.length - 1; i >= 0; i--) {
				this._generateOptionTag(options[i].name, options[i][value_field]).prependTo(select)
			}

			if (title !== null) {
				this._generateOptionTag(title, 0).prependTo(select)
			}

			select.show()
			select.children(':first-child').attr('selected', true)
			$(this).trigger(kind)
		},

		_lockSelect: function (kind) {
			var select = this._selects[kind];

			select.children().detach()
			select.hide()
		},

		_setSelectedValue: function (kind, value, silently) {
			var select = this._selects[kind];

			$('option[value=' + value + ']', select).attr('selected', true)
			!silently && select.change()
		},

		_loadCountires: function () {
			GeoItemsLoader.load([META.RES.COUNTRIES], this._fillSelect.bind(this, META.RES.COUNTRIES, 'code', padString(this._('country'), 50, '-')))
		},

		_loadRegions: function (country_code) {
			this._lockSelect(META.RES.CITIES)
			if (country_code != 0) {
				GeoItemsLoader.load([META.RES.REGIONS, country_code], this._fillSelect.bind(this, META.RES.REGIONS, 'id', padString(this._('region'), 50, '-')))
			} else {
				this._lockSelect(META.RES.REGIONS)
			}
		},

		_loadCities: function (region_id) {
			if (region_id != 0) {
				GeoItemsLoader.load([META.RES.CITIES, region_id], this._fillSelect.bind(this, META.RES.CITIES, 'id', padString(this._('city'), 50, '-')))
			} else {
				this._lockSelect(META.RES.CITIES)
			}
		},

		_setValue: function (e) {
			var select = this._selects[META.RES.CITIES],
				value = select.attr('value'), city_name = $('option[value=' + value + ']', select).text();

			if (value != 0) {
				this.element.attr('value', city_name)
				this.anchor.text(city_name)
			}
		}
	});

	function padString(str, len, spacer) {
		var pad_len = (len - str.length) / 2, res = [];
		spacer = spacer || ' '

		for (var i = Math.ceil(pad_len); i >=0; i--) {
			res.push(spacer)
		}
		res.push(str)
		for (var i = Math.floor(pad_len); i >=0; i--) {
			res.push(spacer)
		}

		return res.join('')
	}
})(jQuery);