var app = {
	parentNode: window,
	widgets: {},

	// app.ready must be inserted at the bottom of the page
	// <script type="text/javascript" charset="utf-8">app.ready()</script>
	ready: function(){
		window.setTimeout(function(){
			events.dispatch({
				type: "ready",
				target: app
			})
		}, 0);
	},
	destroy: function(){
		events.dispatch({
			type: "destroy",
			target: app
		});

		for (var type in app.__events)
			if (type != "ready")
				for (var handl in app.__events[type])
					delete app.__events[type][handl];
	}
};

new function(){
	/**
	 * Application widget module
	 */

	// create new widget and assign it to app
	app.newWidget = function(prototype) {
		// add widget class to application name space
		return app.widgets[prototype.name] = new Widget(prototype);
	};

	// WidgetClass object constructor
	function Widget(prototype) {
		if (!(this.name = prototype.name))
			throw new Error("Widget Class must have name defined.");

		// create new widget class
		this.__class = extend(function(element, settings) {
			// forse cleanup
			events.add(app, "destroy", bind(destroy, this));
			this.__bound = [];

			// wrap element with widget object
			extend(this, settings, { element: element })
				.construct();
		}, {
			prototype: extend(
				object(this._proto),
				prototype
			)
		});
	}

	function destroy(){
		this.destroy();
		this.removeEvent();
		this.unbind.apply(this.__bound);
		delete this.element.__widgets[this.name];
		this.element = null;
	}

	Widget.prototype = {
		run: function(select, settings) {
			// select dom elements
			select = select instanceof Array
				? select : [select];

			// wrap widget on each element
			for (var i = 0; i < select.length; i++) {
				var widgets = select[i].__widgets;
				if (!widgets)
					widgets = select[i].__widgets = {};

				(widgets[this.name] = widgets[this.name]
					// extend widget
					? extend(widgets[this.name], settings)
					// create widget
					: new this.__class(select[i], settings)
				).initialize();
			}
		},
		// abstract widget class prototype
		_proto: {
			construct: function(){},
			initialize: function(){},
			destroy: function(){},

			addEvent: function(type, method) {
				events.add(this.element, type, method = bind(method, this));
				return method;
			},
			removeEvent: function(type, method) {
				events.remove(this.element, type, method);
			},
			dispatchEvent: function(event) {
				if (typeof event == 'string')
					event = { type: event, target: this.element };
				else if (!event.target)
					event.target = this.element;
				return events.dispatch(event);
			},
			bind: function() {
				for (var i = 0, a = arguments; i < a.length; i++) {
					this.element[a[i]] = bind(this[a[i]], this);
					this.__bound.push(a[i]);
				}
			},
			unbind: function() {
				for (var i = 0, a = arguments; i < a.length; i++)
					delete this.element[a[i]]
			}
		}
	};

	app.newAnimator = function(frame, dur, inter){
		if (typeof frame != "function") {
			return new Animator(frame, dur);
		} else {
			var ani = new Animator(dur, inter);
			events.add(ani, "frame", frame);
			return ani;
		}
	}

	function Animator(dur, inter) {
		extend(this, {
			duration: dur || 1000,
			interval: inter || 13,
			frame: bind(this.frame, this)
		});
	}

	Animator.prototype = {
		duration: 1000,
		interval: 13,
		position: 0,
		forward: function(position){ this.start(position); },
		backward: function(position){ this.start(position, true); },
		frame: function(){
			this.position = ((new Date).getTime() - this.timestamp) / this.duration;
			if (this.position >= 1) { this.position = 1; this.stop(); }
			events.dispatch({
				type: 'frame', target: this,
				position: this.back ? 1 - this.position : this.position,
				finish: this.position == 1
			});
		},
		start: function(position, back){
			this.stop();
			this.position = position = position != undefined ? position : this.position;
			this.timestamp = (new Date).getTime() - this.duration * ((this.back = back) ? 1 - this.position : this.position);
			this.timer = window.setInterval(this.frame, this.interval);
		},
		stop: function(){ window.clearInterval(this.timer); }
	};

	app.ease = {
		cos: function(p){ return -Math.cos(p*Math.PI)/2 + .5 },

		bez: function(p,s){ if (s == undefined) s = 1; return (s=2*s*p)+p*(p-s) },

		// bezIn: function(p){ var a=[0,0,1]; return a[0]*(1-p)*(1-p) + 2*a[1]*(1-p)*p + a[2]*p*p }, // regularIn
		regularIn: function(p){ return p*p },
		regularOut: function(p){ return p*(2-p) },
		regularInOut: function(p){ if ((p=p*2) < 1) return .5*p*p; return -.5*((--p)*(p-2)-1); },

		backIn: function(p,s){ if (s == undefined) s = 1.70158; return p*p*((s+1)*p-s); },
		backOut: function(p,s){ if (s == undefined) s = 1.70158; return ((p=p-1)*p*((s+1)*p + s) + 1); },
		backInOut: function(p,s){ if (s == undefined) s = 1.70158; if ((p=p*2) < 1) return .5*(p*p*(((s*=(1.525))+1)*p - s)); return .5*((p-=2)*p*(((s*=(1.525))+1)*p + s) + 2); },

		bounceOut: function(p){
			if (p < (1/2.75))
				return 7.5625*p*p;
			else if (p < (2/2.75))
				return 7.5625*(p-=(1.5/2.75))*p + .75;
			else if (p < (2.5/2.75))
				return 7.5625*(p-=(2.25/2.75))*p + .9375;
			else
				return 7.5625*(p-=(2.625/2.75))*p + .984375;
		},
		bounceIn: function(p){ return 1 - app.ease.bounceOut(1-p); },
		bounceInOut: function(p){ if (p < .5) return app.ease.bounceIn(p*2)*.5; return app.ease.bounceOut (p*2-1)*.5 + .5; },

		strongIn: function(p){ return p*p*p*p*p; }, 
		strongOut: function(p){ return (p=p-1)*p*p*p*p + 1; }, 
		strongInOut: function(p){ if ((p=p*2) < 1) return .5*p*p*p*p*p; return .5*((p-=2)*p*p*p*p + 2); }
	};

	app.newTask = function(target, command, complete, exetime, interval) {
		if (typeof complete != "function") {
			return new Task(target, command, complete, exetime);
		} else {
			var task = new Task(target, command, exetime, interval);
			events.add(task, "complete", complete);
			return task;
		}
	};

	function Task(target, command, exetime, interval) {
		extend(this, {
			target: target,
			command: command,
			exetime: exetime || 500,
			interval: interval || 20,
			length: target.length,
			execute: bind(this.execute, this)
		}).execute();
	}
	Task.prototype = {
		index: 0,
		execute: function(){
			for (
				var t = (new Date).getTime() + this.exetime;
				this.index < this.length && (new Date).getTime() < t;
				this.index++
			)
				this.command(this.target[this.index], this.index);

			if (this.index < this.length)
				window.setTimeout(this.execute, this.interval);
			else
				events.dispatch({ type: "complete", target: this });
		}
	};

	app.SearchIndex = SearchIndex;
	function SearchIndex(charMerge, type, formatOutput) {
		this._data = [];
		this.type = {'1':['[^\\n]*?',''], '2':['','']}[type] || ['[^\\n]*?','[^\\n]*?'];
		this.formatOutput = formatOutput;
		if (charMerge) {
			this.merge = {};
			for (var i=0,a=charMerge.split(/[,=]/); i < a.length; i+=2)
				this.merge[(a[i] +"||").rescape()] = a[i+1] +"|";
		}
	}
	SearchIndex.prototype = {
		merge: null,
		_index: "",
		data: null,

		_formatQuery: function(query) {
			this.data = [];

			var r = query.replace(/[\|\/\\]+/g, '').split('').join('||') +"||";

			// add character merging to query
			for (var i in this.merge)
				r = r.replace(new RegExp(i, 'ig'), this.merge[i]);

			// format query by type
			return ("(\\d+)\\t((%1)((["+ r.replace(/\|+$/, '').split(/\|+/).join('])(%2)([') +"]))([^\\n]*))\\n")
				.format(this.type[0], this.type[1]);
		},
		_sort: function(a, b){
			return a.strength < b.strength ? 1 : a.strength > b.strength ? -1
				: a.input < b.input ? 1 : a.input > b.input ? -1 : 0;
		},
		find: function(input, resCount) {
			if (!input) return null;

			var r = this._formatQuery(input),
				m = this._index.match(new RegExp(r, "gi"));
			if (!m) return null;

			// create sortable array
			for (var i=0,output=[]; i < m.length; i++) {
				var t = m[i].match(new RegExp(r, 'i'));
				output[i] = {
					index: t[1],
					input: t[2],
					strength:
						Math.max(9 - (t[4].length - input.length), 0) * 1000
						+ Math.max(9 - t[3].length, 0) * 100
						+ (100 - t[2].length)
				};
				if (this.formatOutput) output[i].result = t;
			}

			output.sort(this._sort);
			if (resCount > 0 && resCount < output.length) output.length = resCount;

			// format output
			for (var i=0,t; i < output.length; i++) {
				output[i].index = parseInt(output[i].index);
				if (t = output[i].result)
					output[i].result = [].concat(t[3], t.slice(5)).join("<<").replace(/(<<.)<</g, "$1>>").replace(/>><</g, "");
			}

			return output;
		},
		add: function() {
			this._data.push.apply(this._data, arguments);
			this.reindex();
		},
		remove: function() {
			var a = "\\d+\\t("+ array(arguments).join("\t").rescape().replace(/\t/g, "\\n|") +"\\n)?";
			this._data = this._index.replace(new RegExp(a, "g"), '').split("\n");
			this.reindex();
		},
		reindex: function() {
			var i = 0;
			this._index = "0\t"+ this._data.join("\t").replace(/\t/g, function(){ return "\n"+ ++i +"\t" }) +"\n";
		}
	};
};

/**
 *  app.ready & widget usage
 **

app.newWidget({
	// widget class name
	name: 'title',

	// default initalization parameter
	title: 'default',

	// empty event handler to avoid error
	onchange: function(){},

	// widget constructor
	construct: function(){
		// create title change watcher
		this.timer = window.setInterval(bind(function(){
			if (this.title != this.element.title) {
				var oldTitle = this.title;
				this.title = this.element.title;

				// dispatch ontitlechange event
				this.dispatchEvent({
					type: 'titlechange',
					oldValue: oldTitle,
					newValue: this.title
				});
			}
		}, this), 20);
	},

	// initilze on run
	initialize: function(){
		this.element.title = this.title;

		// remove old ontitlechange event
		this.removeEvent('titlechange', this._onchange);

		// add new ontitlechange event
		this._onchange = this.addEvent('titlechange', this.onchange);
	},

	destroy: function(){
		// stop title change watcher
		window.clearInterval(this.timer);
	}
});

events.add(app || window, 'ready', function(){
	app.widgets.title.run(document, {
		title: 'from run'
	});
	app.widgets.title.run(document, {
		title: 'reloaded'
	});
});
 */

events.add(window, 'load', function(){
	// force IE background images cache
	if (!window.opera) try { document.execCommand("BackgroundImageCache", false, true); } catch(e) {};
});
// run memleak unitilizer
events.add(window, "unload", app.destroy);
