/**
 * js-f - simple framework
 * 
 * @author listiki.com
 */


/**
 * function - implementation inheritance
 */ 
function extend(Child, Parent) {
	var F = function() {};
    
	F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    Child.superclass = Parent.prototype;
};

//@lexicus
//TODO _config_default and _config are bad variables names.
//Replace them into something more specific in future

/**
 * Framework default configuration
 * You can override default configuration by creating _config global variable
 */
_config_default = {
	debug				: true,
	alertError			: false,
	rpcTimeOut			: 10000
};

// init config
if(!_config) {
	_config = _config_default;
};

/**
 * Error management class
 * You can easily set and get application error
 * Errors types are supported
 */
function Error() {
	/**
	 * Objects errors storage
	 * @var Array private
	 */
	var errors = new Array();
	
	/**
	 * Prints errors into browsers console
	 * 
	 * @param string|null type Error type. If specified, only errors of certain type will be printed 
	 * @return void
	 */
	this.getError = function(type) {
		if(type)
			console.log(errors[type]);
		else
			console.log(errors);
	};

	/**
	 * Set the error
	 * If _config.debug is set to "true" eror will be logged into console
	 * If _config.alertError is set to "true", alert message with error will be shown
	 * 
	 * @param string type Type of error. Any veluable name are allowed (e.g. 'notice','warning','user')
	 * @return void
	 */
	this.setError =  function(type, error) {
		
		if(typeof(errors[type]) == 'undefined') {
			errors[type] = new Array(error);
		} else {
			errors[type].push(error);
		}
		
		if (_config.debug) log(type + ' : '+ error);
		if (_config.alertError) alert(type + ' : '+ error);
	};
	
};

//global error management object
var _error = new Error();

/**
 * Global objects ans objects data storage class
 * 
 * @returns {Global}
 */
function Global() {
	/**
	 * Counter for objects ids
	 * @var Integer private
	 */
	var _ids = 1;
	/**
	 * Storage for objects
	 * @var Array private
	 */
	var _objs = new Array();
	/**
	 * Storage for plugins
	 * @var Array private
	 */
	var _plugin = new Array();
	/**
	 * Storage for objects data
	 * @var Array private
	 */
	var _storage = new Array();
	
	//setting up the global data storage
	//it's "id" is 0 
	_storage[0] = new Array();
	
	/**
	 * Adds object to the global scope
	 * @param Object obj Object to store
	 * @return Integer Id of the object in the global scope class
	 */
	this.addObj = function(obj) {
		//@lexicus
		//TODO make _id variable local, but double check it won't damage anything
		_id = _ids++;
		_objs[_id] = obj;
		_storage[_id] = new Array();
		return _id;
	};
	
	/**
	 * Fetches the object by it's id
	 * 
	 * @param Integer _id Objects id
	 * @return Object|null Object from the global scope or null,if there is no such object
	 */
	this.getObj = function(_id) {
		return _objs[_id];
	};
	
	/**
	 * Fetches the data of the object which is stored in specific property
	 * 
	 * @param Integer _id Id of the object which data will be fetched
	 * @param String name Property name to fetch
	 * @return mixed|null Objects property data or null, if there is no such property
	 */
	this.getLocData = function(_id, name) {

		if (typeof(_storage[_id][name]) == 'undefined') {
			_error.setError('undefinedData', name + ' in ' + _id + ' not found');
			return null;
		} else {
			return _storage[_id][name];
		}
	};

	/**
	 * Sets the object property data
	 * 
	 * @param Integer _id Id of the object where data will be stored
	 * @param String name Name of the property
	 * @data mixed Stored data
	 * @return mixed parameter, specified as 'data' 
	 */
	this.setLocData = function(_id, name, data) {
		return _storage[_id][name] = data;
	};
	
	/**
	 * Fetches the global data, which is stored in the global scope
	 * 
	 * @param String name Property of the scope from where data will be fetched
	 * @return mixed|null Stored data or null, if there is no such property
	 */
	this.getGlobalData = function(name) {
		if (typeof(_storage[0][name]) == 'undefined') {
			_error.setError('undefinedData', name + ' in global not found');
			return null;
		} else {
			return _storage[0][name];
		}
		//@lexicus we will never be at this line
		//TODO check this and remove line below
		return _objs;
	};

	/**
	 * Sets the global data property
	 * 
	 * @param String name Data property name
	 * @param mixed data Specified data
	 * @return mixed Specified data
	 */
	this.setGlobalData = function(name, data) {
		return _storage[0][name] = data;
	};
	
	/**
	 * Cleans objects data and removes object from global storage
	 * 
	 * @param Integer _id Id of the object to be cleaned
	 * @return void
	 */
	this.cleanData = function(_id) {
		delete _storage[_id];
		delete _objs[_id];
	};
	
	/**
	 * Logs storage (for all objects) into browsers console
	 * 
	 * @return void
	 */
	this.logStorage = function(){
		log (_storage);
	};
};

//global storage object
var _global = new Global();

/**
 * Class for the global events management
 * Event listeners can be attached as a functions, 
 * that will be executed in context of specific object
 * (specified by object id from the Global class)
 * 
 * @returns {EventsBus}
 */
function EventsBus() {
	/**
	 * Events callbacks storage
	 * @var Array private
	 */
	var _storage = new Array();
	
	/**
	 * Adds listener to the specific Application event
	 * Event can be fired by EventBus.{eventname} method
	 * 
	 * @param Integer objId id of the object - context of the event
	 * @param String eventName Name of the event
	 * @param Function callback Function to attach to the event
	 * @return void
	 */
	this.addListener = function(objId, eventName, callback) {
		if (!_storage[eventName]) {
			_storage[eventName] = new Array();
			_storage[eventName].push( {'objId' : objId, 'func' : callback});
			this[eventName] = function() {
				for (a in _storage[eventName]) {
					_storage[eventName][a].func.call(_global.getObj(_storage[eventName][a].objId));
				}
			};
		} else {
			_storage[eventName].push({'objId' : objId, 'func' : callback});
		}
	};
	
	/**
	 * Removes listeners, that are associated with specific object, from the event
	 * If no event name is specified, listeners will be deleted from all events
	 * 
	 * @param Integer objId Id of the object. Al listeners of this object will be deleted
	 * @param String eventName Name of the event
	 * @return void
	 */
	this.removeListener = function(objId, eventName) {
		if (eventName) {
			for (a in _storage[eventName]) {
				if (_storage[eventName][a].objId == objId) {
					//remove element with index a
					_storage[eventName].splice(a, 1);
				}
			}
		} else {
			for (a in _storage) {
				for (b in _storage[a]) {
					if (_storage[a][b].objId == objId) {
						_storage[a].splice(b, 1);
					}
				}
			}
		}
	};
};

//global EventBus object
var _eventsBus = new EventsBus();

/**
 * Class for initiating events from EventBus object
 * 
 * @returns {GlobalEvent}
 */
function GlobalEvent() {
	
	/**
	 * Fires the event
	 * If the is no such event, error is set (depending on the debug mode logging or alerting are available)
	 * 
	 * @param String funcName Name of the event to fire
	 * @return void
	 */
	this.trigger = function(funcName) {
		if (typeof(_eventsBus[funcName]) == 'undefined') {
			_error.setError('eventTrigger', funcName + " not found;");
		} else {
			_eventsBus[funcName]();	
		}
	};
}

/**
 * Base Model class
 * Minipulates with the objects data
 * 
 * @returns {Model}
 */
function Model() {
	/**
	 * Object id. Object is added to the global scope class
	 * through Global::addObj method, which returns object unique identifier
	 * 
	 * @var Integer private
	 */
	var _id = _global.addObj(this);

	/**
	 * Model configuration
	 * 
	 * @var Object public
	 */
	this.config = {};
	
	/**
	 * Fetches object data.
	 * If global parameter is pecified, 
	 * data from the global scope will be fetched (not from object storage)
	 * 
	 * @param String name data property name
	 * @param Boolean global Defaults to null; should the data be fetches from the global scope
	 * 
	 *  @return mixed Data
	 */
	this.get = function(name, global) {		
		if (global == true) 
			return _global.getGlobalData(name);
		else
			return _global.getLocData(_id, name);
	};
	
	/**
	 * Sets the objects data. Or sets global data, if global paramater is specifies as 'true'
	 * 
	 * @param String name Property name
	 * @parama mixed data Data to set
	 * @param Boolean global should the data be set within the object or the global storage
	 * 
	 * @return mixed Data
	 */
	this.set = function(name, data, global) {
		if (global === true) 
			return _global.setGlobalData(name, data);
		else
			return _global.setLocData(_id, name, data);
		
	};

	/**
	 * Adds event listener, associated with object, to the EventBus object
	 *  
	 * @param String name Event name
	 * @param Function callback Function to call, when event is fired
	 * @return void
	 */
	this.addListener = function(name, callback) {
		_eventsBus.addListener(_id, name, callback)
	};
	
	/**
	 * Removes all listeners, associalted with the object
	 * If event name is specified, listeners will be removed only from specified event,
	 * Otherwise all object listeners will be removed
	 * 
	 * @param String name Event name
	 * @return  void 
	 */
	this.removeListener = function(name) {
		_eventsBus.removeListener(_id, name ? name : null);
	};
	
	/**
	 * Triggers specific event from EventBus object
	 * If there is no event error will be set
	 * Depending on the configuration error may be looged and alerted
	 * 
	 * @param String eventName The name of the event
	 * @return void
	 */
	this.trigger = function(eventName) {
		if (typeof(_eventsBus[eventName]) == 'undefined') {
			_error.setError('eventTrigger', eventName + " not found;")
		} else {
			_eventsBus[eventName]();	
		}
	};
	
	/**
	 * Sets error
	 * Depending on the configuration error may be looged and alerted
	 * 
	 * @param String type Error type
	 * @param String error Error content
	 * 
	 * @return void
	 */
	this.addError = function(type, error) {
		_error.setError(type, error);
	};
	
	/**
	 * Deletes object. Clears al objects event listeners, clears object data and deletes object
	 * 
	 * @return void
	 */
	this.del = function() {
		_eventsBus.removeListener(_id);
		_global.cleanData(_id);
		//@lexicus
		//TODO Check if we can write code like this. the code seems to be unneccessary
		delete this;
	};
	
	/**
	 * Creates function, that is executed within the objects context
	 * 
	 * @param Function func Function to be executed
	 * @return Function Executed in the objects context function
	 */
	this.context = function(func){
		function _func() {
			var funcObj = arguments.callee;
			return func.apply(funcObj.obj, arguments);
		}
		_func.obj = this;

		return 	_func;
	};
	
	/**
	 * Executes Ajax request, that operates with the object data
	 * 
	 * @param String url URL to make Ajax request
	 * @param Object data Data to send
	 * @param Function callback Function to execute, when response is returned
	 * @param Object context Ajax request context
	 * @param Boolean loadPopup Should we display "processing" popup
	 * @param Boolean notAsych Should request be synchronous or asynchronous
	 * @return void
	 */
	this.rpc = function(url, data, callback, context, loadPopUp, notAsync) {
		if (loadPopUp)
			modalWindow.showLoading();
			
		$.ajax({
			url 	: url,
			async	: notAsync ? false : true,
			type 	: "post",
			context	: context || this,
			timeout	: _config.rpcTimeOut,
			data	: data,
			dataType: "json",
			success	: function (data) {
				callback.call(this, data);
			},
			error 	: function (msg) {
				data = {status: 'error', message: msg};
				callback.call(this, data);
			}
		});
	};
}
