﻿(function (window, $) {
	function CustomSelect(jSelect, options) {
		if (jSelect && !jSelect instanceof $) {
			jSelect = $(jSelect);
		}
		if (!(jSelect && jSelect.length && jSelect[0].tagName == 'SELECT')) {
			throw new TypeError('Необходим HTMLSelectElement или объект jQuery с ним');
		}
		var oCustomSelect = this, oOldCSelect = jSelect.data('cSelectObj'), optTemplate;
		if (oOldCSelect) {
			return oOldCSelect;
		}
		this.optValues = $.extend(true, {}, this.defOptions, options);
		this.cssC = this.optValues.cssClasses;
		this.cssS = this.optValues.cssSelectors;
		this.jSelect = jSelect;
		this.Name = this.jSelect.attr('name') || this.jSelect.data('name');
		this.Values = [];
		this.Values.Default = 0;
		this.jOptions = this.jSelect.find('option').each(function (index) {
			var jOption = $(this),
				isDisabled = jOption.is('[disabled]'),
				isSelected = jOption.is('[selected]');
			oCustomSelect.Values[index] = {
				Value : jOption.val(),
				Text : jOption.text(),
				Selected : isSelected,
				Disabled : isDisabled,
				//Data : jOption.data(),
				jSelectOpt : jOption
			};
			if (isSelected && !isDisabled) {
				oCustomSelect.Values.Default = index;
			} 
		});
		this.hasOptions = this.jOptions.length > 0; 
		this.Multiple = typeof this.jSelect.attr('multiple') != 'undefined';
		if (this.optValues.bySelectors) {
			this.getElements();
		} else {
			//this.Draw();
		}
		this.attachEvents();
		optTemplate = this.optValues.optTemplate[this.Multiple ? 'Multiple' : 'Single'];
		if (optTemplate instanceof Array) {
			this.valNoRegExp = new RegExp(this.cssC.optValNo + '([0-9]+)', 'i');
			this.drawOptsFromTmplt(optTemplate);
		} else {
			this.drawOptions();
		}
		this.onChange();
		if (!this.hasOptions) {
			this.jSelect.attr('disabled', true);
		}
		this.jSelect.data('cSelectObj', this);
	}
	
	CustomSelect.prototype = {
		defOptions : {
			cssClasses : {
				Wrapper : '',
				View : 'inp_search',
				Opener : '',
				viewText : '',
				activeOpt : '',
				disabledOpt : '',
				optionsList : 'open_div',
				Options : 'js_o',
				optValNo : 'js_v_',
				selCont : 'sel',
				optPrevCont : '',
				disabledView : 'input_off',
				oprContOverflow: 'overflow'
			},
			cssSelectors : {
				Wrapper : '',
				Options : 'div.js_o',
				Sel : 'div.sel',
				Inner : {
					View : 'div.inp_search',
					Opener : 'div.inp_search > a',
					viewText : 'div.inp_search > span',
					optionsList : 'div.open_div',
					optionsCont : 'div.open_div div.js-opt_cont',
					selCont : 'div.js_sel',
					optPrev : 'div.open_div div.js_prev',
					optPrevCont : 'div.open_div div.js_prev div.js_cont'
					//Options : 'div.open_div div.js_o',
				},
				optTemplate : {
					Text : 'label',
					Option : 'input'
				},
				selOptTemplate : {
					Text : '',
					Unselect : 'a'
				}
			},
			optTemplate: {
				Multiple: [
					'<div class="{$opt_class}">',
						'<input type="checkbox" id="{$opt_id}"{$chkd}>',
						'<label for="{$opt_id}">{$text}</label>',
					'</div>'
				],
				Single: [
					'<div class="{$opt_class}">',
						'<a href="#">{$text}</a>',
					'</div>'
				]
			},
			bySelectors : false,
			onOptDraw : false,
			onOptChange: false,
			preChoise : true,
			chEvent : 'click',
			onDraw: false,
			wrapFormCount: 10
		},
		/*Draw : function () {
			var defaultText = this.getDefaultText();
			this.jWrapper = $('<div />').addClass(this.cssC.Wrapper).insertBefore(this.jSelect.hide());
			this.jWrapper.append(this.jSelect);
			this.jView = $('<div />').addClass(this.cssC.View)
									 .appendTo(this.jWrapper);
			this.jOpener = $('<a />').attr('href', '#').addClass(this.cssC.Opener)
									 .appendTo(this.jView).append('<i />');
			this.jViewText = $('<span />').addClass(this.cssC.viewText).appendTo(this.jView).text(defaultText);
			this.jOptionsList = $('<div />').addClass(this.cssC.optionsList).appendTo(this.jWrapper).hide();
			if (this.Multiple) {
				this.jSelCont = $('<span />').addClass(this.cssC.selCont).appendTo(this.jWrapper);
				if (this.optValues.preChoise) {
					this.jOptPrevCont = $('<div>').addClass(this.cssC.optPrevCont).appendTo(this.jOptionsList);
				}
			}
			if (typeof this.optValues.onDraw == 'function') {
				this.optValues.onDraw.call(this);
			}
		},*/
		getElements : function () {
			var defaultText = this.getDefaultText();
			this.jWrapper = $(this.cssS.Wrapper);
			if (!this.jWrapper.length) {
				throw new Error('Неверный селектор кантейнера (' + this.jWrapper.selector + ')');
			}
			for (var selName in this.cssS.Inner) {
				if (this.cssS.Inner[selName].length) {
					this['j' + selName.substr(0, 1).toUpperCase() + selName.substring(1)] = this.jWrapper.find(this.cssS.Inner[selName]);
				}
			}
			if (!this.jOptionsCont.length) {
					this.jOptionsCont = this.jOptionsList;
			}
			if (!this.jOptPrevCont.length) {
				this.jOptPrevCont = this.jOptPrev;
			}
			if (this.Values.length > this.optValues.wrapFormCount) {
				this.jOptionsCont.addClass(this.cssC.oprContOverflow);
				this.optValues.preChoise = true;
			} else {
				this.optValues.preChoise = false;
			}
			this.jSelect.prependTo(this.jWrapper).hide();
			this.jViewText.text(defaultText);
			this.jOptionTemplate = this.getTemplate('Options');
			this.jSelTemplate = this.getTemplate('Sel');
			this.jSelCont.show();
			this.jOptPrev.hide();
			this.jOptPrevCont.find(this.cssS.Options).remove();
			if (!this.optValues.preChoise) {
				this.jOptPrevCont.length = 0; //прикидываемся что его нет
				this.jOptPrev.length = 0; //прикидываемся что его нет
			}
			if (typeof this.optValues.onDraw == 'function') {
				this.optValues.onDraw.call(this);
			}
		},
		getTemplate : function (mode) {
			var jElements = this['j' + mode + 'Cont'].find(this.cssS[mode]), jTemplate = jElements.first().clone();
			if (jTemplate.length) {
				jElements.remove();
				return jTemplate;	
			} else {
				return false;
			}
		},
		attachEvents : function () {
			var oCustomSelect = this, clickHandler = function (e) {
				e.stopPropagation();
				e.preventDefault();
				oCustomSelect.openList();
			};
			this.jOpener.add(this.jView).bind('click.cSelect', clickHandler);
		},
		getDefaultText : function () {
			var Text = this.jSelect.data('default') || (this.hasOptions && !this.Multiple ? this.Values[this.Values.Default].Text : '');
			return Text;
		}, 
		openList : function () {
			if (!this.isEnabled()) {
				return;
			}
			var oCustomSelect = this, optParentWidth, optListWidth;
			this.jOptionsList.toggle();
			if (this.jOptionsList.is(':visible')) {
				optListWidth = this.jOptionsList.width();
				this.jOptions.each(function () {
					optParentWidth = $(this).outerWidth(true);
					if (optParentWidth > optListWidth) {
						oCustomSelect.jOptionsList.width(optParentWidth);
						optListWidth = optParentWidth;
					}
				});
			}
			$(document).bind('mousedown.cSelect keydown.cSelect', function (e) {
				if(!$(e.target).parents().filter(oCustomSelect.jWrapper).length) {
					oCustomSelect.Hide();
				}
			});
		},
		drawOptsFromTmplt: function (optTemplate) {
			var oOptions = {Active: [], Inactive: []},
				oRegExps = {
					text: /{\$text}/g,
					id: /{\$opt_id}/g,
					cssc: /{\$opt_class}/g,
					chkd: /{\$chkd}/g
				};
			if (!this.jOptPrevCont.length) {
				oOptions.Active = oOptions.Inactive;
			} 
			for (var valNo = 0, valsCnt = this.Values.length; valNo < valsCnt; valNo++) {
				var aOptClasses = [this.cssC.Options], oOption = this.Values[valNo],
					optText = oOption.Text;
				if (oOption.Disabled) {
					aOptClasses.push(this.cssC.disabledOpt);
				}
				aOptClasses.push(this.cssC.optValNo + valNo);
				if (typeof this.optValues.onOptDraw == 'function') {
					optText = this.optValues.onOptDraw.apply(this, [oOption, aOptClasses]);
				}
				var sOption = optTemplate.join('').replace(oRegExps.text, optText)
												  .replace(oRegExps.cssc, aOptClasses.join(' '))
												  .replace(oRegExps.id, this.Name + '_chk_' + valNo)
												  .replace(oRegExps.chkd, oOption.Selected ? ' checked' : '');
				oOptions[oOption.Selected ? 'Active' : 'Inactive'].push(sOption);
			}
			if (this.jOptPrevCont.length) {
				this.jOptPrevCont.html(oOptions.Active.join(''));
				this.jOptionsCont.html(oOptions.Inactive.join(''));
			} else {
				this.jOptionsCont.html(oOptions.Active.join(''));
			}
			this.jOptions = this.jOptionsList.find('.' + this.cssC.Options);
			this.attachOptEvents(this.jOptions.find(this.cssS.optTemplate.Option));
		},
		drawOptions: function () {
			var jActOptions = $(), jInActOptions = $(), jOptions = $();
			for (var valNo = 0, valsCnt = this.Values.length; valNo < valsCnt; valNo++) {
				var oOption = this.Values[valNo];
				this.drawOption(valNo, false);
				oOption.jOptionParent.toggleClass(this.cssC.activeOpt, oOption.Selected);
				if (oOption.Selected) {
					jActOptions = jActOptions.add(oOption.jOptionParent);
				} else {
					jInActOptions = jInActOptions.add(oOption.jOptionParent);
				}
				jOptions = jOptions.add(oOption.jOption);
				if (oOption.Selected && oOption.jOption.is('input:checkbox')) {
					oOption.jOption.attr('checked', true);
				}
			}
			this[this.jOptPrevCont.length ? 'jOptPrevCont' : 'jOptionsCont'].html(jActOptions);
			jInActOptions.appendTo(this.jOptionsCont);
			this.attachOptEvents(jOptions);
		},
		drawOption : function (valNo, setState) {
			var jOption, jOptionParent,
				oOption = this.Values[valNo];
			//Options
			if (typeof setState == 'undefined') {
				setState = true;
			}
			if (this.jOptionTemplate) {
				jOptionParent = this.jOptionTemplate.clone();
				jOptionParent.find(this.cssS.optTemplate.Text).html(oOption.Text);
				jOption = jOptionParent.find(this.cssS.optTemplate.Option);
			} else {
				jOptionParent = $('<div />').addClass(this.cssC.Options);
				jOption = $('<a />').html(oOption.Text);
				jOptionParent.append(jOption);
			}
			if (oOption.Disabled) {
				jOptionParent.addClass(this.cssC.disabledOpt);
			}
			$.extend(true, oOption, {
				jOption : jOption,
				jOptionParent : jOptionParent
			});
			if (typeof this.optValues.onOptDraw == 'function') {
				this.optValues.onOptDraw.call(this, valNo);
			}
			//this.attachOptEvents(valNo);
			if (setState) {
				this.setOptState(valNo, oOption.Selected);
			}
		},
		attachOptEvents : function (jOptions) {
			var oCustomSelect = this, cssC = this.cssC;
			jOptions.bind(this.optValues.chEvent + '.cSelect', function (e) {
				var jOption = $(this), valNo = jOption.parents('.' + cssC.Options).attr('class').match(oCustomSelect.valNoRegExp)[1],
					oOption = oCustomSelect.Values[valNo];
				if (jOption.is('a')) {
					e.preventDefault();
				}
				if (!oCustomSelect.isEnabled()) {
					return false;
				}
				if (!oOption.jOption) {
					$.extend(true, oOption, {
						jOption : jOption,
						jOptionParent : jOption.parents(oCustomSelect.cssS.Options)
					});
				}
				oCustomSelect.setOptState(valNo, !oOption.Selected);
				oCustomSelect.onChange();
			});
		},
		setOptState : function (valNo, selected) {
			var oOption = this.Values[valNo], jBefore, aOldVals, jOption;
			if (!oOption.jOption) {
				jOption = this.jOptions.find('.' + this.cssC.optValNo + valNo);
				$.extend(true, oOption, {
					jOption : jOption,
					jOptionParent : jOption.parents(this.cssS.Options)
				});
			}
			if (this.jOptPrevCont.length) {
				aOldVals = this.getVals(selected);
				for (var vlNo = 0, vlCount = aOldVals.length; vlNo < vlCount; vlNo++) {
					if (aOldVals[vlNo] > valNo) {
						jBefore = this.Values[aOldVals[vlNo]].jOptionParent;
						break;
					}
				}
				if (jBefore && jBefore.length) {
					oOption.jOptionParent.insertBefore(jBefore);
				} else {
					oOption.jOptionParent.appendTo(selected ? this.jOptPrevCont : this.jOptionsCont);
				}
			}
			if (!this.Multiple && selected) { //сбрасываем выбранное
				this.getVals(true, true);
			}
			oOption.Selected = selected;
			oOption.jOptionParent[selected ? 'addClass' : 'removeClass'](this.cssC.activeOpt);
			if (!this.Multiple) {
				this.Hide();
			}
			if (typeof this.optValues.onOptChange == 'function') {
				this.optValues.onOptChange.call(this, oOption);
			}
		},
		Hide : function () {
			this.jOptionsList.hide();
			$(document).unbind('click.cSelect');
		},
		onChange : function (callHandler) {
			var aSelected = this.getVals(true), viewText = this.getDefaultText();
			if (this.jOptPrev) {
				if (aSelected.length) {
					this.jOptPrev.show();
				} else {
					this.jOptPrev.hide();
				}
			}
			if (aSelected.length) {
				if (!this.Multiple) {
					viewText = this.Values[aSelected[0]].Text; 
				} else {
					viewText = this.jSelect.data('selected');
				}
			}
			this.jViewText.text(viewText);
			this.returnValue(aSelected, callHandler);
		},
		Switch : function (enable) {
			if (typeof enable == 'undefined') {
				enable = !this.isEnabled();
			}
			var classAct = enable ? 'removeClass' : 'addClass';
			this.jView[classAct](this.optValues.cssClasses.disabledView);
			this.jSelect.attr('disabled', !enable);
			this.Hide();
		},
		isEnabled : function () {
			return !this.jSelect.attr('disabled');
		},
		returnValue : function (aSelected, callHandler) {
			this.jSelect.find('option').removeAttr('selected');
			if (this.Multiple && this.jSelCont) {
				this.jSelCont.find(this.cssS.Sel).remove();
			}
			for (var vlNo = 0, vlCnt = aSelected.length; vlNo < vlCnt; vlNo++) {
				this.Values[aSelected[vlNo]].jSelectOpt.attr('selected', 'selected');
				if (this.Multiple && this.jSelCont) {
					this.drawSel(aSelected[vlNo]);
				}
			}
			if (callHandler !== false) {
				this.jSelect.trigger('change');
			}
		},
		drawSel : function (valNo) {
			var oOption = this.Values[valNo], jSel = this.jSelTemplate.clone(), jUnsel, oCustomSelect = this;
			jUnsel = jSel.find(this.cssS.selOptTemplate.Unselect);
			if (!this.cssS.selOptTemplate.Text.length) {
				jSel.text(oOption.Text).append(jUnsel);
			} else {
				jSel.find(this.cssS.selOptTemplate.Text).text(oOption.Text);
			}
			jUnsel.bind('click.cSelect', function (e) {
				e.preventDefault();
				oCustomSelect.setOptState(valNo, false);
				oCustomSelect.onChange();
			});
			jSel.appendTo(this.jSelCont);
		},
		getVals : function (selected, toggle) {
			if (typeof selected == 'undefined') {
				selected = true;
			}
			if (typeof toggle == 'undefined') {
				toggle = false;
			}
			var aSelected = [];
			for (var valNo = 0, valCnt = this.Values.length; valNo < valCnt; valNo++) {
				if (this.Values[valNo].Selected == selected) {
					if (toggle) {
						this.Values[valNo].Selected = !selected;
					}
					aSelected.push(valNo);
				}
			}
			return aSelected;
		}
	};
	
	$.fn.goCustomSelect = function (options) {
		new CustomSelect($(this), options);
		return this;
	};
})(this, jQuery);
