/**
 * A base class for pastel controllers.
 * To setup the controller use setOpts(opts) method with the following options:
 * <ul>
 *	<li>viewClass - The view class constructor associated with this controller</li>
 * 	<li>viewSelector - the selector of the current view. Default: _none_</li>
 * 	<li>wrapperSelector - the selector of the container which will contain the current view. 
 * 		Default: body</li>
  
 *  <li>applyWaitingState - Set this to false if you want to custom handle a waiting state. 
 *  	Default: true</li>
 *  <li>viewRemoveOnHide - Set this to true if you want the view to remove itself upon hide.
 *  	Default: false</li>
 *  <li>showEffect - Set an object with properties name and duration to set. 
 *  	Default: {name: "css", duration: 0, opts: {key:"display", val: "block"}} </li>
 *  <li>hideEffect - see showEffect. 
 *  	Default: {name: "css", duration: 0, opts: {key:"display", val: "none"}}</li>
 *  <li>ajaxEnabled - set to false if you dont want to retrieve the view via ajax. Default: true</li>
 *  <li>ajaxSingleRequest - set to false if you want to retrieve the view on every call to showView
 *		Default: true</li>
 *	<li>ajaxUrl</li>
 * </ul>
 *  
 * @constructor
 */

PastelBaseController.prototype.constructor = PastelBaseController;

// handle events ==============================================================================
/**
 * Generate event id. 
 */
PastelBaseController.prototype._eventId = function(eventId) {
	return _APP.getEventQueue().eventId(eventId);
};

/**
 * Register an event.
 * @param eventId - the id of the event. Create it with this._eventId(eventId)
 * @param callback - the event handler function. function(event, payload){}
 * @param sender - the initiator of the event
 * @param context - the context for the handler function (optional)
 */
PastelBaseController.prototype._registerEvent = function(eventId, callback, sender, context) {
	_APP.getEventQueue().register(eventId, callback, sender, context);
};

/**
 * Unregister event.
 * @param eventId
 */
PastelBaseController.prototype._unregisterEvent = function(eventId) {
	_APP.getEventQueue().unregister(eventId);
};

/**
 * Trigger the event with payload
 * @param eventId - the event id
 * @param payload - the payload to pass to the handler
 */
PastelBaseController.prototype._triggerEvent = function(eventId, payload) {
	_APP.getEventQueue().trigger(eventId, payload);
};

// handle popups ==============================================================================
/**
 * Show the given popup with the popup manager and register it for autohide 
 * @param popup - the popup object
 * @param callback - callback to invoke when finished showing
 */
PastelBaseController.prototype._showPopup = function(data, callback) {
	_APP.popupManager.show(data, callback);
};

/**
 * Register already shown popup for autohide in the popup manager  
 * @param popup - the popup object
 */	
PastelBaseController.prototype._markShownPopup = function(data) {
	_APP.popupManager.markAsLastShown(data);
};

/**
 * Hide last shown popup
 */
PastelBaseController.prototype._hideLastShownPopup = function() {
	_APP.popupManager.hideLastShown();
};

/**
 * Hide all shown popups
 */
PastelBaseController.prototype._hideAllShownPopups = function() {
	_APP.popupManager.hideAll();
};

/**
 * Default ajax options
 */
PastelBaseController.prototype._ajaxOpts = {
	url: "",
	type: "POST",
	data: {},
	dataType: "text",
	cache: false,
	success: function(data, textStatus) {},
	error: function(responseText){
		return true;
	}
};

PastelBaseController.prototype._ajaxUrls = {};

/**
 * Create an ajax call. This is a wrapper for $.ajax
 * @param {Object} ajaxOpts
 * @param context - the context of the success and error functions
 */
PastelBaseController.prototype.ajax = function(ajaxOpts, context, abortable) {
	// if there is no context set it to this
	if (context === undefined || context == null) {
		context = this;
	}
	
	if (abortable === undefined) {
		abortable = true;
	}
	
	// merge options
	for (var prop in ajaxOpts) {
		this._ajaxOpts[prop] = ajaxOpts[prop]; 
	}
	
	// wrap functions
	var success = this._ajaxOpts.success;
	success = $.proxy(success, context);
	var error = this._ajaxOpts.error;
	error = $.proxy(error, context);
	 
	var url = "/" + _APP.lang + this._ajaxOpts.url;
	
	// return if request with same params is already in progress
	var arrKey = url + "|" + $.param(this._ajaxOpts.data);
	if(this._ajaxUrls[arrKey] !== undefined) {
		return;
	}
	this._ajaxUrls[arrKey] = true;

	// abort any previous requests
	if (_APP.getXHR() != null) {
		_APP.getXHR().abort();
		_APP.setXHR(null);
	}
	
	if (_APP._sId != "") {
		this._ajaxOpts.data.sId = _APP._sId; 
		_APP._sId = "";
	}

	
	// do the ajax call
	var ajaxReq = $.ajax({
		url: url,
		type: this._ajaxOpts.type,
		data: this._ajaxOpts.data,
		success: function(data, textStatus, XMLHttpRequest) {
			if (XMLHttpRequest.status == 0) {
				return;
			};

			success(data, textStatus);
		},
		error: function(XMLHttpRequest, textStatus, errorThrown) {
			var failedAction = $.proxy(function() {
				this.ajax(ajaxOpts, context);
			}, this);
			
			var responseText = XMLHttpRequest.responseText;
			var defaultErrorHandler = _APP.getDefaultAjaxErrorHandler();
			if($.isFunction(defaultErrorHandler)) {
				defaultErrorHandler(responseText, failedAction);
			}
			error(responseText);
		},
		complete: function(XMLHttpRequest, textStatus) {
			_APP.setXHR(null);
			delete this._ajaxUrls[arrKey];
		},
		context: this
	});
	
	if (abortable) {
		_APP.setXHR(ajaxReq);
	}
};
 
function PastelBaseController() {
 
	// setup ======================================================================================
	this.opts = {
		viewClass: "_none_",
		
		wrapperSelector: "body",
		viewSelector: "_none_",
 
		applyWaitingState: true,
		viewRemoveOnHide: false,
		 
		showEffect: {name: "css", duration: 0, opts: {key:"display", val: "block"}},
		hideEffect: {name: "css", duration: 0, opts: {key:"display", val: "none"}},
		
		ajaxEnabled: true,
		ajaxSingleRequest: true,
		ajaxUrl: ""	
	};
 
	/**
	 * Override this method to apply custom ajax params.
	 * @return object that represents the ajax params
	 * @type Object
	 */
	this.getAjaxData = function() {
		return {};
	};

	this.setOpts = function(opts) {
		//TODO refactor in separated util method
		for (var prop in opts) {
			this.opts[prop] = opts[prop]; 
		}
	};
		
	// handle states ==============================================================================
	this._waitingState = null;
	this._waitingLastState = null;
	
	/**
	 * Override this method to apply states to the current controller
	 * @param {Object} currState
	 * @param {Object} lastState
	 */
	this._doSetState = function(currState, lastState) {};
	
	/**
	 * This method is called by the state notifier to set the new state
	 * @param {Object} state
	 */
	this.setState = function(currState, lastState) {
		if (!this.initDone) {
			this._waitingState = currState;
			this._waitingLastState = lastState;
		} else {
			this._doSetState(currState, lastState);
		}
	};
	
	this._applyWaitingState = function() {
		if (this._waitingState != null) {
			this.setState(this._waitingState, this._waitingLastState);
			this._waitingState = null;
			this._waitingLastState = null;
		}
	};
 
	// handlers =================================================================================== 
	var E_PostInit = this._eventId("E_PastelBasePostInit");
	this.addPostInitHandler = function(callback, context) {
		this._registerEvent(E_PostInit, callback, this, context);
	};

 
	// fields =====================================================================================
	/**
	 * Holds the view object (Warning: it holds an MVC view object not a jQuery object or html)
	 */
	this.view = null;
 
	this.initInProgress = false;
	this.initDone = false;
 
	this.showAbort = false;	
	this.showOnInit = false;
  
	// methods ====================================================================================
	/**
	 * Initializes the controller for display
	 * @param {Function} errorCallback
	 */
 	this.init = function(errorCallback) {
 		if (this.initInProgress) {
			return;
		}
 		
 		this.initDone = false;
		this.initInProgress = true;
 		
		if (this.opts.ajaxEnabled) {
			this.ajax({
				url: this.opts.ajaxUrl,
				data: this.getAjaxData(),
				success: this._postCallback,
				error: function(responseText) {
					this.initInProgress = false;
					if($.isFunction(errorCallback)) {
						errorCallback();
					}
				}
			}, this);
		} else {
			this._postCallback("");
		}
	};
 
	/**
	 * This method is called from init after ajax call to setup the retrieved html.
	 * @private
	 * @param {String} html
	 */
	this._postCallback = function(html) {
		// init view container selector
		var selector = this.opts.viewSelector;
		var wrapper = this.opts.wrapperSelector;
		// if the view was retrieved with ajax append it
		if (this.opts.ajaxEnabled) {
			this.removeView();
			this._appendView($(wrapper), html);
		}
		
		// create the view class and assign it to this.view
		this._createView(selector, wrapper);
		
		// set up the view, register handlers initialize elements
		this._setupView(this.view);
		  
		// set init done to true and initialization finished
		this.initDone = true;
		this.initInProgress = false;
		
		// if there is a waiting state apply it
		if (this.opts.applyWaitingState) {
			this._applyWaitingState();
		}
		
		// if init was called from showView() show the view after initialization
		if (!this.showAbort && this.showOnInit) {
			this.view.show(waitingCallback);
			this.showOnInit = false;
		}
		
		// fire post init event
		this._triggerEvent(E_PostInit, {});
	};
  
	/**
	 * Creates and initializes the view
	 * @param selector
	 */
	this._createView = function(viewSelector, wrapperSelector) {
		var viewClass = this.opts.viewClass;
		this.view = new viewClass();
		this.view.setContainerSelectors(viewSelector, wrapperSelector);
		this.view.setShowEffectDesc(this.opts.showEffect);
		this.view.setHideEffectDesc(this.opts.hideEffect);
		this.view.init();
	};
	
	/**
	 * @private
	 * @param {PastelBaseView} view
	 */
	this._setupView = function(view) {};
	
	/**
	 * @private
	 * @param {PastelBaseView} view
	 */
	this._resetupView = function(view) {};
 
	/**
	 * Call this method to show the view. If the controller is not initialized this method will 
	 * call init() and after the initialization will show the view.
	 * @param {Function} successCallback
	 * @param {Function} errorCallback
	 */
	var waitingCallback = undefined;
	this.showView = function(successCallback, errorCallback) {
		// if initialization is in progress return
		if (this.initInProgress) {
			return;
		}
		// if you need to get the html with ajax set the initDone to false here 
		if (!this.opts.ajaxSingleRequest && this.initDone) {
			this.initDone = false;
		}
		
		// if the controller is not initilized
		if (!this.initDone && !this.initInProgress) {
			this.showAbort = false;
			this.showOnInit = true;
			waitingCallback = successCallback;
			this.init(errorCallback);
		} else if (this.initDone) {
			this._insertView();
			this._resetupView(this.view);
			this.view.show(function() {
				if ($.isFunction(successCallback)) {
					successCallback();
				}
			});
		}
	};
	
	/**
	 * Hides the view
	 * @param {Function} callback
	 */
	this.hideView = function(callback) {
		if(this.initInProgress) {
			this.showAbort = true;
		}
		
		// if you need to get the html with ajax set the initDone to false here 
		if (!this.opts.ajaxSingleRequest && this.initDone) {
			this.initDone = false;
		}
		
		for(var i = 0; i < this.children.length; i++) {
			this.children[i].hideView();
		}
		
		if(this.view != null) {
			this.view.hide($.proxy(function(){
				if ($.isFunction(callback)) {
					callback();
				}
				this._hideViewHook(this.view);
				if (this.opts.viewRemoveOnHide) {
					this.removeView();
				}
			}, this));
		}
	};
	
	/**
	 * @private
	 * @param {PastelBaseView} view
	 */	
	this._hideViewHook = function(view) {};
	
	/**
	 * Removes the view from its wrapper container
	 */
	this.removeView = function() {
		$view = $(this.opts.viewSelector);
		if ($view.length > 0) {
			$view.remove();
		}
	};
	
	/**
	 * Reinserts an already cached view
	 * @private
	 */
	this._insertView = function() {
		$view = $(this.opts.viewSelector);
		if ($view.length == 0) {
			$wrapper = $(this.opts.wrapperSelector);
			this._appendView($wrapper, this.view.container);
			this._setupView(this.opts.viewSelector);
		}
	};
	
	/**
	 * Override to custom append the view
	 * @param {jQuery} $wrapper
	 * @param {String} html
	 */
	this._appendView = function($wrapper, html) {
		$wrapper.append(html);
	};
	
	this.children = [];
	this.parent = null;

	this.addChild = function(controller) {
		this.children.push(controller);
		controller.parent = this;
	};
	 
	this.isChanged = function() {
		for(var i = 0; i < this.children.length; i++) {
			var isChanged = this.children[i].isChanged();
			if (isChanged) {
				return true;
			}
		}
		
		if (this.view instanceof PastelBaseView) {
			return this.view.isChanged();
		}
		
		return false;
	};
}
