// window.onerror = function(){ log(window.onerror.caller);  }

app.newWidget({
	name: 'autoComplete',

	searchIn: null,
	resultsIn: null,
	outputTarget: null,

	srcObject: null,
	dropdownButton: null,

	construct: function(){
		this.bind('add', 'show', 'cancel', 'drop', 'toggle', 'remove');

		this.element.setAttribute("autocomplete", "off");

		app.widgets.keyNavigation.run(this.element);
		this.addEvent('keynavigation', this.navigate);

		app.widgets.inputChange.run(this.element);
		this.addEvent('inputchange', this.onchange);

		this.srcObject = {};
		this.index = new app.SearchIndex("a=aą,c=cč,e=eęė,i=iįy,s=sš,u=uųū,z=zž,\"=\"'”“„,'=\"'”“„", 1, true);

		this.select = bind(this.select, this);

		this.dropdownButton = dom.create('<input style="font-size: .8em; margin-left: 3px;" type="button" value="&#x25BC;|&#x25B2;" />');
		var dbv = this.dropdownButton.value.split('|');
		this.dropdownButton.show = dbv[0];
		this.dropdownButton.hide = dbv[1];
		this.dropdownButton.value = this.dropdownButton.show;
		dom.after(this.dropdownButton, this.element);

		events.add(this.dropdownButton, 'mousedown', this.element.toggle);

		this.output = dom.create('<div class="autocomplete-result-container">');
		if (this.outputTarget) this.outputTarget.appendChild(this.output);
		else dom.after(this.output, this.element);

		app.widgets.resultList.run(this.output);
		events.add(this.output, 'searchselect', this.select);

		// Void blur event trigered by mousedown on IE	
		var _this = this;
		events([this.dropdownButton, this.output], 'mousedown', function(){
			_this.voidBlur = true;
			setTimeout(function(){
				_this.element.focus();
				_this.voidBlur = false;
			},0);
			return false;
		});

		this.addEvent('dblclick', this.show);

		this.addEvent('blur', this.cancelEvent);
		this.result = {};
	},
	toggle: function(e){
		e.preventDefault();
		e.stopPropagation();
		if (this.dropdownButton.value == this.dropdownButton.show)
			this.show();
		else this.cancel();
		return false;
	},
	all: null,
	show: function(){
		if (!this.all) {
			this.all = [];
			for (var i in this.srcObject)
				this.all.push({ result: i, input: i });
		}
		this.searchOutput = this.all;
		this.drop();
		this.dropdownButton.value = this.dropdownButton.hide;
	},
	result: null,
	add: function(searchIn, resultsIn){
		this.all = null;
		for (var i=0, l=searchIn.length; i < l; i++)
			this.srcObject[searchIn[i]] = resultsIn[i];
		this.index.add.apply(this.index, searchIn);
	},
	remove: function(text){
		this.all = null;
		delete this.srcObject[text];
		this.index.remove(text);
	},
	cancelEvent: function(e){
		if (this.voidBlur) return;
		if (e) {
			e.preventDefault();
			e.stopPropagation();
		}
		this.output.cancel();
		this.dropdownButton.value = this.dropdownButton.show;
		this.dispatchEvent({
			type: 'cancel',
			cancelBubble: true,
			text: this.element.value,
			value: this.getValue()
		});
		return false;
	},
	cancel: function(){
		this.output.cancel();
		this.dropdownButton.value = this.dropdownButton.show;
	},
	getValue: function(){
		return this.srcObject[this.element.value];
	},
	select: function(e){
		this.element.value = this.searchOutput[e.value].input;
		this.searchOutput = [this.searchOutput[e.value]];
		this.element.voidChange();
		this.dropdownButton.value = this.dropdownButton.show;
		this.dispatchEvent({
			type: 'searchselect',
			cancelBubble: true,
			text: this.element.value,
			value: this.getValue()
		});
	},
	navigate: function(e){
		if (this.output[e.direction]() && e.direction == 'select') {
			this.dispatchEvent({
				type: 'submit',
				cancelBubble: true,
				text: this.element.value,
				value: this.getValue()
			});
		} else if (e.direction == 'next' && !this.searchOutput) {
			this.show();
		} else if (e.direction == 'cancel') {
			this.dropdownButton.value = this.dropdownButton.show;
			this.dispatchEvent({
				type: 'cancel',
				cancelBubble: true,
				text: this.element.value,
				value: this.getValue()
			});
		}
	},
	drop: function() {
		var t = '';
		if (this.searchOutput) {
			for (var i=0; i < this.searchOutput.length; i++) {
				//dropdown-selected
				t += '<div class="dropdown-item result-item value-'+ i + (this.element.value == this.searchOutput[i].input ? ' dropdown-selected' : '') +'">'+ this.searchOutput[i].result +"</div>";
			}
			t = '<div class="dropdown-mac-style">'+ t.replace(/<<([^>]+)>>/g, '<b>$1</b>');
		}
		this.output.update(t);
	},
	onchange: function(){
		this.dropdownButton.value = this.dropdownButton.show;
		this.searchOutput = this.index.find(this.element.value, 20);
		if (this.dispatchEvent({
			type: 'search',
			cancelBubble: true,
			results: this.searchOutput,
			text: this.element.value,
			value: this.getValue()
		})) this.drop();
	}
});

app.newWidget({
	name: "resultList",

	itemClass: 'result-item',
	hoverClass: 'dropdown-selected',

	construct: function(){
		this.bind('update', 'previous', 'next', 'select', 'cancel');
		this.addEvent('mousedown', this.select);
		this.resetMouseOver = bind(this.resetMouseOver, this);
		this.addEvent('mouseover', this.onmuoseover);
		this.addEvent('mousewheel', this.onmousewheel);
		events.mousewheel(this.element);
	},
	setActive: function(elem){
		if (this.active != elem) {
			if (this.active) dom.removeClass(this.active, this.hoverClass);
			this.active = elem;
			dom.addClass(this.active, this.hoverClass);
		}
	},
	update: function(html){
		this.element.innerHTML = html;
		this.active = null;
		this.items = dom.getByClass(this.itemClass, this.element);
		if (this.items.length) {
			for (var i=0; i < this.items.length; i++) {
				this.items[i].resultIndex = i;
				if (dom.hasClass(this.items[i], this.hoverClass))
					this.active = this.items[i];
			}

			var p = this.scroll = this.items[0].parentNode;
			this.scrollHeight = p.scrollHeight - p.offsetHeight;
			if (!this.active) this.setActive(this.items[0]);
			else this.moveBy(0);
		}
	},
	onmuoseover: function(e) {
		if (!this.voidMouseOver) {
			e = e.target;
			if (dom.hasClass(e, this.itemClass) || (e = dom.parent(e, this.itemClass))) {
				this.setActive(e);
			}
		}
	},
	onmousewheel: function(e){
		e.preventDefault();
		var delta = e.wheelDelta || 0;
		if (window.opera) delta = -delta;

		this.moveBy(-delta / 120);
	},
	moveTo: function(i){ this.setActive(this.items[Math.max(Math.min(i, this.items.length - 1), 0)]) },
	moveBy: function(c){
		if (this.active) {
			this.moveTo(this.active.resultIndex + c);
			this.voidMouseOver = true;
			window.setTimeout(this.resetMouseOver, 100);

			var sT = this.scroll.scrollTop, vH = this.scroll.offsetHeight, aT = this.active.offsetTop, aH = this.active.offsetHeight;
			var aST = aT - sT, aH2 = aH * 2;
			if (aST > vH - aH2) this.scroll.scrollTop = aT + aH2 - vH;
			else if (aST < aH2) this.scroll.scrollTop = aT - aH;
		}
	},
	next: function(){ this.moveBy(1); },
	previous: function(){ this.moveBy(-1); },
	resetMouseOver: function(){ this.voidMouseOver = false; },

	select: function(e){
		if (e) {
			e.preventDefault();
			this.onmuoseover(e);
		}
		if (this.active && (!e || (dom.hasClass(e.target, this.itemClass) || dom.parent(e.target, this.itemClass)))) {
			var m = this.active.className.match(/\svalue-(\S+)/);
			if (m) this.dispatchEvent({
				type: 'searchselect',
				value: m[1],
				index: this.active.resultIndex
			});
			this.cancel();
			return false;
		}
		return !e;
	},
	cancel: function(){ this.update(''); }
});

// track input change as type
app.newWidget({
	name: "inputChange",
	construct: function(){
		this.bind('voidChange');
		this.addEvent("keyup", this.delay);
		this.track = bind(this.track, this);
		this.element.__value = this.element.value;
	},
	voidChange: function(){ this.element.__value = this.element.value; },
	delay: function(){
		window.clearTimeout(this.timer);
		this.timer = window.setTimeout(this.track, 100);
	},
	track: function(){
		if (this.element.value != this.element.__value) {
			this.dispatchEvent("inputchange");
			this.element.__value = this.element.value;
		}
	}
});

// add keyboard navigation events to element
app.newWidget({
	name: "keyNavigation",
	construct: function(){
		this.addEvent("keydown", this.keydown);
		this.addEvent("keyup", this.preventDefault);
		this.addEvent("keypress", this.preventDefault);
		this.element.keynavigation = "on";
	},
	keys: { "38":"previous", "40":"next", "13":"select", "27":"cancel" },
	pdefault: false,
	preventDefault: function(e){
		if (this.pdefault) {
			e.preventDefault();
			e.stopPropagation();
			this.pdefault = false;
		}
	},
	keydown: function(e){
		if (/on/i.test(this.element.keynavigation) && this.keys[e.keyCode]) {
			this.dispatchEvent({
				type: "keynavigation",
				direction: this.keys[e.keyCode]
			});
			e.preventDefault();
			e.stopPropagation();
			this.pdefault = true;
			return false;
		}
	}
});

/*
app.newWidget({
	name: 'animatedHover',

	// must be defined
	over: function(p){},
	out: function(p){},

	// customizable properties
	overDelay: 0,
	outDelay: 0,
	overDuration: 1000,
	outDuration: 1000,
	target: null,

	construct: function(){
		this.addEvent('over', this.handle);
		this.addEvent('out', this.handle);
		events.hover(this.element);

		events.add(this, 'frame', bind(this.frame, this));

		this.animator = app.newAnimator();
		this.animator.parentNode = this;
	},
	initialize: function(){
		if (!this.target) this.target = this.element;
	},
	handle: function(e){
		this.fn = this[e.type];
		var dir = e.type == 'over' ? 'forward' : 'backward',
			dur = this[e.type +"Duration"];

		window.clearTimeout(this.hoverTimer);
		this.hoverTimer = window.setTimeout(bind(function(){
			this.animator.duration = dur;
			this.animator[dir](this.position);
		}, this), this[e.type +"Delay"]);
	},
	position: 0,
	frame: function(e){
		this.fn.call(this.target, this.position = e.position);
	},
	destroy: function(){ this.fn = this.over = this.out = this.target = null; }
});

// add keyboard navigation events to element
app.newWidget({
	name: "keyNavigation",
	construct: function(){
		this.addEvent("keydown", this.keydown);
		this.element.keynavigation = "on";
	},
	keys: { "38":"previous", "40":"next", "13":"select" },
	keydown: function(e){
		if (/on/i.test(this.element.keynavigation) && this.keys[e.keyCode]) {
			this.dispatchEvent({
				type: "keynavigation",
				direction: this.keys[e.keyCode]
			});
			return false;
		}
	}
});

// track input change as type
app.newWidget({
	name: "inputChange",
	construct: function(){
		this.addEvent("keyup", this.delay);
		this.track = bind(this.track, this);
		this.element.__value = this.element.value;
	},
	delay: function(){
		window.clearTimeout(this.timer);
		this.timer = window.setTimeout(this.track, 100);
	},
	track: function(){
		if (this.element.value != this.element.__value) {
			this.dispatchEvent("inputchange");
			this.element.__value = this.element.value;
		}
	}
});

// under development
app.newWidget({
	name: 'localSearch',

	placeholder: 'Navigate',
	results: 10,
	searchType: 0,

	merge: "a=aą,c=cč,e=eęė,i=iįy,s=sš,u=uųū,z=zž",
	// { group: function(){ return [{ text: "text", data: {} }] } }
	addToIndex: null,

	construct: function(){
		this.element.setAttribute("autocomplete", "off");
		this.addEvent("inputchange", this.search);
	},
	index: [],
	initialize: function(){
		for (var group in this.addToIndex)
			this.index.push([group, this.addToIndex[group]]);
	},
	search: function(){
		var result = null;
		if (this.element.value) {
			if (this.index.length) this.createIndex();

			var output = this._index.find(this.element.value, this.results);

			if (output) {
				// split search result in to groups
				for (var i=0,result={}; i < output.length; i++) {
					var g = this.group(output[i].index);
					if (!result[g]) result[g] = [];
					result[g].push({ text: output[i].result, data: this.data[output[i].index] });
				}
			}
		}
		this.dispatchEvent({ type: "searchresult", data: result });
	},
	searchIndex: null,
	data: [],
	createIndex: function(){
		this._index = new app.SearchIndex(this.merge, this.searchType, true);

		for (var i=0,t=""; i < this.index.length; i++) {
			for (var k=0, a=this.index[i][1](); k < a.length; k++) {
				this.data.push(a[k].data);
				this._index.push(a[k].text);
			}
			t += "if(i<"+ this.data.length +")return'"+ this.index[i][0] +"';";
		}

		// alert(this.data.length);
		this.index = [];
		this.group = new Function("i", t);
	}
});

// temporary solution
app.newWidget({
	name: "searchResult",
	results: null,

	body: "<pre>%1</pre>",
	group: '%1\n',
	result: '  <span id="%1">%2</span>\n',
	wrapp: '<b style="color:red">%1</b>',
	idPrefix: "search-result-",
	selectedClass: "selected",
	empty: "",

	construct: function(){
		this.bind("next", "previous", "select");
	},
	next: function(){
		this.move((this.last + 1) % this.data.length);
	},
	previous: function(){
		this.move((this.data.length + this.last - 1) % this.data.length);
	},
	move: function(i){
		dom.removeClass(dom(this.idPrefix + this.last), this.selectedClass);
		dom.addClass(dom(this.idPrefix + (this.last = i)), this.selectedClass);
	},
	select: function(){
		this.dispatchEvent({
			type: "select",
			data: this.data[this.last][0],
			group: this.data[this.last][1]
		});
	},
	initialize: function(){
		if (!this.results) return this.element.innerHTML = this.empty;
		var output = "", top, r = /<<([\s\S]+?)>>/g, w = this.wrapp.format('$1');
		this.data = [];
		for (var group in this.results) {
			output += this.group.format(group);
			for (var i=0, a=this.results[group]; i < a.length; i++) {
				output += this.result.format(this.idPrefix + this.data.length, a[i].text.replace(r, w));
				this.data.push([a[i].data, group]);
			}
		}

		this.element.innerHTML = "";
		this.element.appendChild(dom.create(this.body.format(output)));
		this.last = 0;
		dom.addClass(dom(this.idPrefix + this.last), this.selectedClass);
	}
});
*/