/**
 * XApp factory
 * 
 * @param object $el - jQuery object
 * @returns object XApp
 */
var XApp = function (el, options) {
	
	/**
	 * Find container object
	 */
	if( typeof el == 'undefined' || ! $(el).length ) {
		$el = $(window);
	} else {
		$el = $(el);
	}
	
	/**
	 * Check if XApp instance already exists
	 */
	if(_XApp = $el.data('XApp')) {
		
		if( typeof options == 'object' ) {
			_XApp.initOptions(options);
		}
		
		return _XApp;
	}
	
	/**
	 * Create new XApp instance
	 */
	var _XApp = new XApp.fn.init($el, options);
	
	$el.data('XApp', _XApp);
	
	return _XApp;
};

XApp.fn = XApp.prototype = {
		
	_hooks : {},
	
	_plugins : {},
	
	_initLoadedScripts : false,
	_loadedJS : [],
	_loadedCSS : [],
	
	$el : false,
	
	/**
	 * XApp instance options
	 * 
	 * @var object
	 */
	options : {
		
	},
	
	init : function ($el)
	{
		this.$el = $el;
		this._hooks = {};
		this._plugins = {};
		
		if(! this._initLoadedScripts){
			this._initLoaded();
		}
		
		this.listen('redraw', this.redraw, this);
		
		this.draw();
		
		return this;
	},
	
	_initLoaded : function()
	{
		var self = this;
		$('head').find('link[href]').each(function(){
			self._loadedCSS.push( $(this).attr('href') );
		});
		$('head').find('script[src]').each(function(){
			self._loadedJS.push( $(this).attr('src') );
		}); 
	},
	
	draw : function()
	{
		this._initPlugins();
		
		return this;
	},
	
	redraw : function()
	{
		this.draw();
		
		this.drawParents();
		
		return this;
	},
	
	initOptions : function(options)
	{
		this.options = $.extend(this.options, options);
		
		return this;
	},
	
	drawParents : function()
	{
		var self = this;
		
		this.$el.parents().each(function(){
			var _XApp = $(this).data('XApp');
			
			if( _XApp ) {
				$.each(_XApp._plugins, function(name, plugin) {
					if( plugin.options.recursive ) {
						plugin.init.apply(plugin, [self]);
					}
				});
			}
		});
		
		return this;
	},
	
	_initPlugins : function ()
	{
		var self = this;
		$.each(this._plugins, function(name, plugin){
			plugin.init.apply(plugin, [self]);
		});
	},
	
	/**
	 * @returns Plugin Object
	 */
	plugin : function(name, options) 
	{
		if(!this._plugins[name]) {
		
			var self = this;
			
			eval('var plugin =  XApp_' + name + '( self, options );');
			
			plugin.init(this);
			
		} else {
			var plugin = this._plugins[name];
			
			if( options ) {
				$.extend(plugin.options, options);
			}
		}
		
		return this._plugins[name] = plugin;
	},	
	
	showError: function(msg) 
	{
		var msg = msg || '<b>Неизвестная ошибка!</b><p>Попробуйте позже или свяжитесь с администрацией.</p>';
		
		XApp('<div>'+msg+'</div>').plugin('dialog', {dlg: {modal: true}}).addErrorClass();
	},
	
	ask: function(callback, question)
	{
		XApp($('<div />').html(typeof question == 'string' ? question : 'Вы уверены?' )).plugin('dialog', {dlg:{
			modal: true,
			buttons: {
				"Да": function() {
					$( this ).dialog( "close" );
					callback();
				},
				"Нет": function() {
					$( this ).dialog( "close" );
				}
			}
		}});
	},
	
	_prepareAjax : function(settings)
	{
		var self = this;
		
		var settings = $.extend({
			crossDomain : 	false,
			type		:	'GET',
			dataType	: 	'text',
			data 		: 	{}, 
			target		:	false,
			block		:	false,
			autoeval	:	true,
			beforeSend 	:	function()
			{
				if( settings.block ) {
					XApp(settings.block).plugin('block').block();			
				}
			},			
			complete 	:	function()
			{
				if( settings.block ) {
					XApp(settings.block).plugin('block').unblock();			
				}
			},			
			error		: 	function(jqXHR, textStatus, errorThrown) {
				//TODO : default error handle
                self.showError(errorThrown);
			},
			success 	: 	function(data, context, textStatus, jqXHR) {
				// success callback example
			}
		}, settings);
		
		/**
		 * Override success callback
		 */
		var settingsSuccess = settings.success;
		
		settings.success = function(data, textStatus, jqXHR) {
			try {
			
				if((data = $.parseJSON(data)) && data.content) {
					
					$target = false;
					
					if(settings.autoeval) {
						self._evalPreload(data);
					}
					
					if( settings.target ) {
						if(typeof settings.target == 'function') {
							$target = settings.target(data.content, data);
						} else if ($(settings.target).length) {
							$target = $(settings.target).html(data.content);
						}
					}
					
					if(settings.autoeval) {
						if( $target && $target.length ) {
							self._evalContent($target, data);
							self._evalResponce(data, $target);
						} else {
							self._evalResponce(data);
						}
					}
					
					settingsSuccess(data.content, data, textStatus, jqXHR);
					
				} else {
					jQuery.error('Couldn\'t parse XApp JSON');
				}
				
			} catch(e) {
				settings.error(jqXHR, "parsererror", e);
			}
		}
		
		if(! settings.url) {
			settings.url = location.href;
		}
		
		return settings;
	},	
	
	_evalPreload : function(context)
	{
		var self = this;

		if( typeof context.css_files == 'object' ) {
			$.each(context.css_files, function(i, s) {
				if( $.inArray( s, self._loadedCSS ) < 0 ){ 
					
					$.ajax({
						url: s,
						async: false,
						dataType: "text",
						cache: true,
						success: function(data){
							self._appendStyle( data, s ); 
						}
					});
					
					self._loadedCSS.push( s ); 
				}
			});
		}
		if( typeof context.js_files == 'object' ) {
			$.each(context.js_files, function(i, s) {
				if( $.inArray( s, self._loadedJS ) < 0 ){
					
					$.ajax({
						url: s,
						async: false,
						cache: true,
						dataType: "script"
					});
					
					self._loadedJS.push( s ); 
				}
			});
		}
	},
	
	_evalContent : function ($content, context)
	{
		XApp($content).hook('redraw', context);
	},
	
	
	_evalResponce : function(context, $target)
	{
		var self = this;

		if( typeof context.css == 'object' ) {
			$.each(context.css, function(i, s){
				self._appendStyle(css);
			});
		}
		if( typeof context.js == 'object' ) {
			$.each(context.js, function(i, s){
				//$.globalEval(s);
				eval(s);
			});
		}
	},
	
	_appendStyle : function(css, url)
	{
		if( url ) {
			$('<link rel="stylesheet" type="text/css" media="screen" href="'+url+'" />').appendTo('head');
		} else {
			$('<style />').html(css).appendTo('head');
		}
	},
	
	ajax : function(settings)
	{
		
		settings = this._prepareAjax(settings);
		
		$.ajax(settings);
		
		return this;
	},
	
	listen : function (target, callback, thisObj)
	{
		if( typeof target == 'string') {
			target = [target];
		}
		if( typeof thisObj == 'undefined') {
			thisObj = this;
		}
		
		var self = this;
	
		$.each(target, function(i, v) {
			if( ! self._hooks[v] ) {
				self._hooks[v] = [];
			}
			
			var found = false;
			
			$.each(self._hooks[v], function(i, v) {
				if( ! found && v.obj == thisObj && v.func == callback ) {
					found = true;
				}
			});
			
			if(! found) {
				self._hooks[v].push({'obj': thisObj, 'func': callback});
			}
		});
		
		return this;
	},	
	
	hook : function ( hook )
	{
		if(this._hooks[hook]) {
			var self = this;
			var args = [];
			$.each(arguments, function(i, v){ args.push(v); });
			args.shift();
		
			$.each(this._hooks[hook], function(i, v){
				v['func'].apply(this['obj'], args);
			});
		}
		
		return this;
	},
	
	/**
	 * @deprecated - use .serialize instead
	 */
	paramConvert : function(obj)
	{
		var o = {}, 
			add = function(name, val) {
				if(name.match(/\[.*\]/)) {
					var key = name.match(/^[^\[]+/);
					if(key && (key = key[0] )) {
						var ks = [];
						var path = name.substr(key.length);
						while(path.length && path.indexOf(']') > 0) {
							var k = path.substr(1, path.indexOf(']') - 1);
							ks.push(k);
							path = path.substr(k.length + 2);
						}
						
						var i = 0;
						var v = 'o["'+key+'"]';
						for(i = 0; i < ks.length; i++) {
							
							eval('if(! '+v+') { '+v+' = '+(ks[i].length ? '{}' : '[]')+'; }');
							
							if(ks[i].length) {
								v += '["'+ ks[i] + '"]';
							} else {
								break;
							}
						}
						
						if(typeof ks[i] != 'undefined' && ks[i] == "") {
							eval( v+'.push('+JSON.stringify(val)+');');
						} else {
							eval( v+' = '+JSON.stringify(val)+';');
						}
					}
				} else {
					o[name] = val;
				}
			}
		;
		
		$.each(obj, function(i, v){
			add(v.name, v.value);
		});
		
		return o;
	},
	
	
	log : function (msg)
	{
		console.log(msg);
		
		return this;
	}
};

XApp.fn.init.prototype = XApp.fn;
