/**
 * Copyright (c) 2009 - 2011, Arly Rampen.
 * All rights reserved.
 * 
 * -------
 * XR API
 * -------
 * 
 * Specially designed for full-ajax driven websites.
 *
 * v1.1.4b (11/4/2011)
 * - Fixed anonymous from not working right.
 * - Removed beforeSend and onSuccess function in Net.ChangeState.
 *
 * v1.1.3b (25/3/2011)
 * - Changed XR from singleton to namespace.
 * - Changed XR.Net and XR.Event as singleton instead of namespace.
 * - Changed Net.RequestCommand to Net.Request.
 * - Upgraded Net.Request method to adapt CI controller architecture and able to do GET and POST.
 * - Changed Event.CustomAddEvent to Event.Add.
 * - Removed Event.AddEvent. Use Event.Add instead.
 *
 * v1.1b
 * - Specially optimized for CodeIgniter.
 * - Created namespaces based on function role, currently Net and Event.
 *
 * v1.1
 * - Anonymous state mode. Allows state change without leaving history address.
 * - Naming invention from 'Page' to 'State'.
 * - Enhanced State Bookmarking System; Changes in escapedFragment, RemoveEscapedFragment(), new state callbacks
 *   such as OnSuccess, OnSync, OnError, and merged ChangePageWithoutHash() and ChangePage() to ChangeState()
 * - Analogy of state requests is now sending the whole GET parameters in a whole string.
 * 
 */

// XR namespace
var XR = window.XR || {};

// Net: Handle XHR/AJAX states
XR.Net = function() {
	return {
		// anonymous: Prepare state for anonymous mode if turned on.
		// This allows user not to see the loaded page link when page is initialized.
		// Anonymous mode only works at landing page (such as www.abc.com) or manually by calling ChangeState.
		// This only happens once and anonymous will set to false.
		// Default is true.
		anonymous: true,
		// state: [DO NOT CHANGE] Current running state.
		state: " ",
		// initState: Initial state/landing state when website first opened. Override to customize.
		initState: "home",
		// ajaxUrl: Default URL path to perform AJAX request.
		ajaxUrl: "index.php/",
		// appCommandUrl: Default URL path to call application logic commands.
		appCommandUrl: "app_logic/",
		// fps: Frame per second. Currently used for page sync speed.
		// Standards:
		// - high performance = 1000/60 - 1000/99.
		// - animation = 1000/40 - 1000/60
		// - normal = 1000/15 - 1000/30
		// - safe (non-heavy effects) = 1000/4 - 1000/10
		// WARNING: changing fps to more than 1000/99 is not recommended cause it might crash the client's computer.
		fps: 1000/30,
		// escapedFragment: Store custom escaped fragment.
		// Consecutive words not allowed. For example, "!#!" will not work, but "#!]" will work.
		// The reason is because we only need one character at to erode the state, else you're wasting memory.
		// Forbidden chars: [a-z][A-Z][0-9][?&=+-_;/\%]
		escapedFragment: "#/",
		// BeforeSend: Callback before change state performed. Override to customize.
		BeforeSend: function() {},
		// OnSuccess: Callback after change state success. Override to customize.
		OnSuccess: function(html) {
			$(".content").html(html);
		},
		// OnSync: Callback during state synchronization. It is called game loop in game development. Override to customize.
		OnSync: function() {},
		// OnError: Callback when any XHR fails. This is jquery ajaxError callback. Override to customize.
		OnError: function(xhr) {
			// DEBUG MODE ONLY
			//alert((xhr.responseText != undefined ? xhr.responseText : "Request timeout, please try again."));
		},
		// SyncState: Synchronize state.
		SyncState: function() {
			var _hash = this.RemoveEscapedFragment(window.location.hash);
	
			if (_hash != this.RemoveEscapedFragment(this.state))
				this.ChangeState(_hash);
		},
		// ChangeState: requests for the new state along with hash params
		ChangeState: function(state, anonymous) {
			// validate parameters
			var _state = (state == "" ? this.initState : state);
			var anonymous = ((anonymous == undefined) || (anonymous == false) ? this.anonymous : anonymous);
	
			// Sync hash with new state and make state concrete. If it's anonymous state, then empty state. Else fill state.
			if (anonymous) {
				this.state = "#";
				location.hash = this.state;
				this.anonymous = false;
			} else {
				this.state = this.escapedFragment + _state;
				location.hash = this.state;
				this.anonymous = true;
			}
			
			$(".content").html("<p class='content_loading'>Loading...</p>");
			$.ajax({
				async: true,
				url: this.ajaxUrl + _state,
				type:"GET",
				dataType:"html",
				error: this.OnError, 
				beforeSend: this.BeforeSend,
				success: this.OnSuccess
			});
		},
		// RemoveEscapedFragment: Remove escaped fragment from a state or from url
		// This only happens once per character, which is efficient cause all escaped fragments are all in front,
		// makes the parameters saved from being replaced by the same escaped fragments. :-)
		RemoveEscapedFragment: function(state) {
			var _state = state;
	
			for (var i = 0; i < this.escapedFragment.length; i++)
				_state = _state.replace(this.escapedFragment.charAt(i), "");
	
			return _state;
		},
		// GetStateFromHref: Get full state from href.
		// Ex.: www.example.com/en/main/?key1=val1 should retrieve /en/main/?key1=val1.
		GetStateFromHref: function() {
			var hrefPath = location.pathname;
			var hrefParam = location.href;
	
			hrefPath = (hrefPath.lastIndexOf("/") == (hrefPath.length - 1) ? hrefPath.substr(0, hrefPath.length - 1) : hrefPath);
			hrefParam = (hrefParam.indexOf("?") >= 0 ? hrefParam.substr(hrefParam.indexOf("?"), hrefParam.length) : "");
	
			return hrefPath + hrefParam;
		},
		// GetPageStateName: Get state name from state or directly from url
		GetPageStateName: function(state) {
			var _state = this.RemoveEscapedFragment(((state != null) || (state != undefined) ? state : window.location.hash));
	
			if (_state.indexOf("/") >= 0)
				return _state.substr(0, _state.indexOf("/"));
	
			return _state;
		},
		// GetStateParam: Retrieve all state parameter value from state or directly from url
		GetStateParam: function(state) {
			if (state.indexOf("?") > 0)
				return state.substr(state.indexOf("?")+1, state.length-1);
			
			return "";
		},
		// GetSingleStateParam: Retrieve one parameter value from state or directly from url based on key
		GetSingleStateParam: function(key, state) {
			key = key.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
			var regexS = "[\\?&]" + key + "=([^&#]*)";
			var regex = new RegExp(regexS);
			var results = regex.exec((state != (null || undefined) ? state : window.location.hash));
	
			if (results == null)
				return "";
	
			return results[1];
		},
		// CreateStateParam: Create state parameter from a literal object
		CreateStateParam: function(obj) {
			var params = "";
	
			for (var key in obj)
				params += (key + "=" + obj[key] + "&");
				
			params = "?" + params.substr(0, params.length-1);
	
			return params;
		},
		// LinkToState: Convert a link, such as href, to XR state
		LinkToState: function(link) {
			return link.substr(this.ajaxUrl.length, link.length);
		},
		// Request: Request server-side app logic command and receive returned data. It's always POST due to security reasons.
		// @appCommand 	- Path to CI controller methods.
		// 				  Ex.: controller/method/val1/val2/val3/val(n).
		// @returnType 	- Type of data (XML, JSON, Text, HTML, etc.).
		// @param 		- POST parameter to be sent.
		// @callBack 	- Function to call after successful request.
		Request: function(appCommand, returnType, param, callBack) {
			$.ajax({
				url: this.ajaxUrl + this.appCommandUrl + appCommand,
				type: "POST",
				dataType: returnType,
				data: param,
				error: this.onError,
				success: callBack
			});
		}
	}
}();

// Handles UI events
XR.Event = function() {
	return {
		// Add: Bind an event to a DOM with parameter. It is created to replace the first binded event.
		// @obj			- Object to be binded.
		// @eventType	- Event to bind.
		// @param		- Additional parameters to delegate. make null if not used.
		// @callBack	- 
		Add: function(obj, eventType, param, callBack) {
			obj.unbind(eventType);
			obj.bind(eventType, param, callBack);
		}
	}
}();
