/**
** Copyrights reserved. June 2011. Propritery content of Prologix Technologies, protected by International laws.
** Unautorized use of the whole or any part of the code will be procecuted under international code protection act.
**
**/
if ('undefined' === typeof PROLOGIX || null === PROLOGIX) {
	PROLOGIX = {
	};
}

/**
* PROLOGIX Core namespace/code
*/
PROLOGIX.Core = function () {

	var publicMembers = {
		/**
		* Extend, for prototypal inheritance
		* Based on code by Douglas Crockford - http://javascript.crockford.com/prototypal.html
		* @param {Object} parent
		* @alias PROLOGIX.Core.extend
		*/
		extend: function (parent) {
			function F() { }
			F.prototype = parent;
			return new F();
		},

		/**
		* Is the given object defined - Note, this is a strict test against undefined, and will not check for null
		* @param {Object} obj
		* @alias PROLOGIX.Core.isDefined
		*/
		isDefined: function (obj) {
			return ('undefined' !== typeof obj);
		},

		/**
		* Is the given object null - Note, this is a strict test against null, and will not test for undefined
		* @param {Object} obj
		* @alias PROLOGIX.Core.isNotNull
		*/
		isNotNull: function (obj) {
			return (null !== obj);
		},

		/**
		* Does the given object have a value - i.e., is it not null and not undefined.  Note, a string with value "" has a value, so this
		* function will return true in that case
		* Also note, passing an undeclared variable into this function will result in an exception.  If your variable may be undeclared(not undefined)
		* Then you will have to write your own test
		* @param {Object} obj
		* @alias PROLOGIX.Core.hasValue
		*/
		hasValue: function (obj) {
			return ('undefined' !== typeof obj && null !== obj);
		},

		/**
		* Return true if the given object is null or undefined.  Don't call this on undeclared values or you will cause an exception
		* @param {Object} obj
		* @alias PROLOGIX.Core.isNullOrUndefined
		*/
		isNullOrUndefined: function (obj) {
			return ('undefined' === typeof obj || null === obj);
		},

		/**
		* Returns the trimmed form of the string.  The extra \s's trigger an internal regex optimization
		* @param {Object} str
		* @alias PROLOGIX.Core.trim
		*/
		trim: function (str) {
			return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
		},

		/**
		* SafeCallback. A basic implementation of a safe way to callback a function.
		* Additional complexities and optimizations can be added on later, if needed.
		* @param {Object} fnName - The function that needs to be called back.
		* @alias PROLOGIX.Core.safeCallback
		*/
		safeCallback: function (fnName) {
			var args = Array.prototype.slice.call(arguments, 1);
			if (fnName && 'function' === typeof fnName) {
				try {
					fnName.apply(this, args);
				}
				catch (err) {
					PROLOGIXDebug.logError('Error occured when trying to execute callback function. ' + err);
				}
			}
		},
		

		/**
		* Returns the truncated input string with specified number of characters and truncation string 
		* @param {Object} inputStr
		* @param {Object} strLen
		* @param {Object} ellipses
		* @alias PROLOGIX.Core.truncateString
		*/
		truncateString: function (inputStr, strLen, ellipses) {
			if (PROLOGIX.Core.isNullOrUndefined(inputStr)) {
				return null;
			}
			
			if (0 > strLen || inputStr.length <= strLen) {
				return inputStr;
			}

			if (0 == strLen) {
				return '';
			}
			var myEllipses = ellipses || '';
			var ellLength = myEllipses.length;

			var truncatedLength = strLen - ellLength;
			//If the desired length is too short, truncate the ellipses and return
			if (truncatedLength <= 0) {
				return myEllipses.substring(0, strLen);
			}
			
			return inputStr.substring(0, truncatedLength) + myEllipses;
		},

		/**
		* Do a URI decode while accounting for + = ' '
		* @param {Object} s
		*/
		decodePercent: function (s) {
			// Handle application/x-www-form-urlencoded, which is defined by
			// http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
			s = s || '';
			s = s.replace(/\+/g, " ");
			return decodeURIComponent(s);
		},

		/**
		* Serialize a dictionary using the specified key-value separator and pair separator 
		* @param {Object} dict
		* @param {Object} keyValSeparator
		* @param {Object} pairSeparator
		* @param {Object} encode - Should keys and values be uri encoded
		*/
		serializeDictionary: function (dict, keyValSeparator, pairSeparator, encode) {
			var result = '';
			if (PROLOGIX.Core.isNullOrUndefined(dict)) {
				return result;
			}
			var first = true;
			jQuery.each(dict, function (key, value) {
				if (!dict.hasOwnProperty(key)) {
					return true; //continue
				}
				if (!first) {
					result += pairSeparator;
				}
				first = false;

				var finalKey = encode ? encodeURIComponent(key) : key;
				var finalValue = encode ? encodeURIComponent(value) : value;
				result += finalKey + keyValSeparator + finalValue;
			});
			return result;
		},

		/**
		*Returns an object with name-value pairs.
		* @alias PROLOGIX.Core.getParamList
		*/
		getParamList: function (param, keyValSeparator, pairSeparator) {
			var list = {};
			if (PROLOGIX.Core.isNullOrUndefined(param)) {
				return list;
			}
			pairSeparator = pairSeparator || '&';
			keyValSeparator = keyValSeparator || '=';

			var nvps = param.split(pairSeparator);
			for (var n = 0; n < nvps.length; ++n) {
				var pair, name = null, value = null, nvp = nvps[n];
				if ("" == nvp) {
					continue;
				}
				var pos = nvp.indexOf(keyValSeparator);
				if (0 >= pos) {
					continue;
				}
				name = nvp.substring(0, pos);
				if (name) {
					name = PROLOGIX.Core.trim(publicMembers.decodePercent(name));
				}
				pos++;
				value = nvp.substring(pos);
				if (value) {
					value = PROLOGIX.Core.trim(publicMembers.decodePercent(value));
				}
				list[name] = value;
			}
			return list;
		},

		/**
		* Add parameter to URL
		* @alias PROLOGIX.Core.addParameterToUrl
		*/
		addParametersToUrl: function (sourceUrl, params) {
			if (PROLOGIX.Core.isNullOrUndefined(sourceUrl)) {
				return null;
			}

			if (PROLOGIX.Core.isNullOrUndefined(params)) {
				return sourceUrl;
			}

			var url = sourceUrl;
			var splittedUrl = url.split('?');
			var noParametersFound = (1 == splittedUrl.length);

			var list;
			if (splittedUrl.length > 0) {
				list = PROLOGIX.Core.getParamList(splittedUrl[1]);
			}
			if (PROLOGIX.Core.isNullOrUndefined(list)) {
				list = {};
			}

			for (var paramName in params) {
				if (!params.hasOwnProperty(paramName)) {
					continue;
				}

				list[paramName] = params[paramName];
			}

			url = splittedUrl[0] + '?' + PROLOGIX.Core.serializeDictionary(list, '=', '&', false);

			return url;
		},

		/**
		* Add parameter to URL
		* @alias PROLOGIX.Core.addParameterToUrl
		*/
		addParameterToUrl: function (sourceUrl, parameterName, parameterValue) {
			if (PROLOGIX.Core.isNullOrUndefined(sourceUrl)) {
				return null;
			}

			if (PROLOGIX.Core.isNullOrUndefined(parameterName)) {
				return sourceUrl;
			}

			var url = sourceUrl;
			var splittedUrl = url.split('?');
			var noParametersFound = (1 == splittedUrl.length);
			var list = {};

			if (noParametersFound) {
				/* No parameters found */
				list[parameterName] = parameterValue;
			} else {
				if (splittedUrl.length > 0) {
					list = PROLOGIX.Core.getParamList(splittedUrl[1]);

					if (!PROLOGIX.Core.isNullOrUndefined(list)) {
						/* Set parameter value */
						list[parameterName] = parameterValue;
					}
				}
			}

			url = splittedUrl[0] + '?' + PROLOGIX.Core.serializeDictionary(list, '=', '&', false);

			return url;
		},

		/**
		* Builds a key value pair where the parameter name is the key and the parameter value is the value.
		* @alias PROLOGIX.Core.getKeyValuePair
		*/
		getKeyValuePair: function (parameterName, parameterValue, encode) {
			var finalKey = encode ? encodeURIComponent(parameterName) : parameterName;
			var finalValue = encode ? encodeURIComponent(parameterValue) : parameterValue;
			var keyValueSeparator = encode ? encodeURIComponent('=') : '=';

			return finalKey + keyValueSeparator + finalValue;
		},

		/**
		* Builds a string representation in key value pairs where the parameter name is the key and the parameter value
		* is the value.
		* @alias PROLOGIX.Core.getKeyValuePairs
		*/
		getKeyValuePairs: function (params, encode) {
			if (PROLOGIX.Core.isNullOrUndefined(params)) {
				return '';
			}

			var list = {};
			for (var paramName in params) {
				if (!params.hasOwnProperty(paramName)) {
					continue;
				}

				list[paramName] = params[paramName];
			}

			var keyValueSeparator = encode ? encodeURIComponent('=') : '=';
			var pairSeparator = encode ? encodeURIComponent('&') : '&';

			return PROLOGIX.Core.serializeDictionary(list, keyValueSeparator, pairSeparator, encode);
		}
	};
	return publicMembers;
} ();


/**
 * Class for basic PROLOGIX events
 */
PROLOGIX.Core.Events = function() {
    var that = this;
    var _debugAllEvents = false;

    var publicMembers = {
        /**
        * Constructor for event controller
        * @alias PROLOGIX.Core.Events.EventController
        */
        EventController: function() {
            var eventMap = {};
            var publicMembers = {
                registerEventHandler: function(eventName, fn) {
                    if (_debugAllEvents) {
                        PROLOGIXDebug.logDebug("EventController:  registering " + eventName);
                    }

                    if (PROLOGIX.Core.isNullOrUndefined(eventName) || '' === eventName) {
                        throw "Attempting to register event handler for null/undefined event";
                    }

                    if (PROLOGIX.Core.isNullOrUndefined(eventMap[eventName])) {
                        eventMap[eventName] = [];
                    }
                    eventMap[eventName].push(fn);
                },

                unregisterEventHandler: function(eventName, fn) {
                    if (_debugAllEvents) {
                        if (PROLOGIX.Core.isNullOrUndefined(fn)) {
                            PROLOGIXDebug.logDebug("EventController:  unregistering all " + eventName + " handlers");
                        } else {
                            PROLOGIXDebug.logDebug("EventController:  unregistering one " + eventName + " handler");
                        }
                    }

                    if (PROLOGIX.Core.isNullOrUndefined(eventName) || '' === eventName ||
						PROLOGIX.Core.isNullOrUndefined(eventMap[eventName])) {
                        if (_debugAllEvents) {
                            PROLOGIXDebug.logDebug("EventController:  no handlers to unregister");
                        }

                        return that;
                    }

                    if (_debugAllEvents) {
                        PROLOGIXDebug.logDebug("EventController:  " + eventMap[eventName].length + " handler(s) present");
                    }

                    if (PROLOGIX.Core.isNullOrUndefined(fn)) {
                        eventMap[eventName] = [];
                    } else {
                        var listenerList = eventMap[eventName];
                        for (var listenerIndex = 0; listenerIndex < listenerList.length; listenerIndex++) {
                            if (listenerList[listenerIndex] === fn) {
                                break;
                            }
                        }

                        if (listenerIndex < listenerList.length) {
                            listenerList = listenerList.splice(listenerIndex, 1);
                        }
                    }
                },

                unregisterEventHandlers: function(events) {
                    if (!events) {
                        return;
                    }

                    for (var eventIndex in events) {
                        if (events.hasOwnProperty(eventIndex)) {
                            this.unregisterEventHandler(events[eventIndex]);
                        }
                    }
                },

                fireEvent: function(eventName) {
                    if (PROLOGIX.Core.isNullOrUndefined(eventName)) {
						return;
                    }

                    var handlers = eventMap[eventName];
                    if (handlers) {
                        if (_debugAllEvents) {
                            PROLOGIXDebug.logDebug("EventController:  firing " + eventName + ", " + handlers.length + " handler(s)");
                        }
                        
                        var args = Array.prototype.slice.call(arguments, 1);
                        args.unshift(handlers); //Add handlers array to beginning of args list
                        PROLOGIX.Core.Functional.executeList.apply(this, args);
                    } else {
                        if (_debugAllEvents) {
                            PROLOGIXDebug.logDebug("EventController:  firing " + eventName + ", no events");
                        }
                    }

                    return that;
                },

                waitForEvents: function(eventsArray, fn) {
                    if (PROLOGIX.Core.isNullOrUndefined(eventsArray) ||
						PROLOGIX.Core.isNullOrUndefined(fn) ||
						0 === eventsArray.length) {
                        return;
                    }

                    // We'll squirrel away the event data results in this map.
                    fn.multiplexMap = {};

                    var multiplexHandler = function(eventName, eventData) {
						if (_debugAllEvents) {
							PROLOGIXDebug.logDebug("EventController:  waitForEvents caught a " + eventName + " event");
						}

                        // Store the incoming event data.
                        if ('undefined' === typeof eventData) {
                            fn.multiplexMap[eventName] = null;
                        } else {
                            fn.multiplexMap[eventName] = eventData;
                        }

                        // Have we hit all of the events at least once?
                        for (var arrayIndex = 0; arrayIndex < eventsArray.length; arrayIndex++) {
                            var eventName = eventsArray[arrayIndex];
                            if ('undefined' === typeof fn.multiplexMap[eventName]) {
                                return;
                            }
                        }

                        // We've hit everything at least once, so let's clear the result map
                        // and execute the callback function.
                        var resultMap = PROLOGIX.Core.Utils.clone(fn.multiplexMap);
                        fn.multiplexMap = {};
                        fn(resultMap);
                    };

                    // Register the multiplex handler for all the listed events.
                    for (var arrayIndex = 0; arrayIndex < eventsArray.length; arrayIndex++) {
                        var eventName = eventsArray[arrayIndex];
                        publicMembers.registerEventHandler(eventName, multiplexHandler.gCurry(eventName));
                    }
                }
            };
            return publicMembers;
        }
    };

    return publicMembers;
} ();


/**
 * Simple Timer Class. Specify time in milli seconds
 * Timer can be instantiated in three modes. 
 * 1. Normal timer that counts up from 0 to specified count and then fires a 'TIMER_COMPLETE' event. Set a valid 'counts' (>0) parameter and countDownMode to false.
 * 2. CountDown timer that counts down from the specified count until zero and then fires the 'TIMER_COMPLETE' event. Set a valid 'counts' (>0) parameter and countDownMode to true.
 * 3. Infinite timer (NOT YET IMPLEMENTED) that counts up from 0 and stops only when told to. Set 'counts' to -1 and countDownMode to false.
 *  All the times modes fire a 'TIMER_TICK' event at the set interval until timer is complete.
 * @param {int} counts - REQUIRED - Number of ticks before timer completes. set it to -1 if you want.
 * @param {bool} countDownMode - REQUIRED - Set True if you need a count down timer.
 * @param {int} interval - OPTIONAL - Tick interval. Defaults to 1 second if not specified.
 */
(PROLOGIX.Core.Timer = function(counts, countDownMode, interval){
	
	// Return if the counts are not set.
	if(PROLOGIX.Core.isNullOrUndefined(counts)){
		return null;
	}
	
	// Return if the countDownMode is not specified.
	if(PROLOGIX.Core.isNullOrUndefined(countDownMode)){
		return null;
	}
	
	// Return if the counts are improperly set for countDownMode
	if(countDownMode && 0 >= counts){
		return null;
	}
	
	var that = this;
	var targetCounts = counts, currentCount = 0, _timer = null;
	var infinteMode = (-1 === counts);
	
	if(infinteMode){
		return null;
	}
	
	/**
	 * If interval is not set, default to 1000 milliseconds.
	 */
	interval = interval || 1000;
	
	var eventController = new PROLOGIX.Core.Events.EventController();
	
	/**
	 * Timer Events.
	 */
	that.TimerEvents = {	
		TIMER_TICK : 'TIMER_TICK',
		TIMER_COMPLETE: 'TIMER_COMPLETE'};
	
	/**
	 * Internal onTick handler.
	 */
	var onTick = function(){
		if(countDownMode){
			// We are in countdown mode, so if current count is over zero continue to decrement it.
			if(0 < currentCount){
				currentCount--;
				eventController.fireEvent(that.TimerEvents.TIMER_TICK, {timerCount: currentCount});
			}
			// Else scream timer's complete.
			else{
				onComplete();			
			}
		}
		else{
			// If infinite mode, fire TIMER_TICK event
			if(-1 === targetCounts) {
				currentCount++;
				eventController.fireEvent(that.TimerEvents.TIMER_TICK, {timerCount: currentCount});
			}
			// Otherwise, if currentCount is less than targetCount, increment it and fire a TIMER_TICK event
			else if(currentCount < targetCounts){				
				currentCount++;
				eventController.fireEvent(that.TimerEvents.TIMER_TICK, {timerCount: currentCount});
			}
			// Else it means timer's complete.
			else{
				onComplete();			
			}
		}
	};
	
	/**
	 * Internal onComplete handler.
	 */
	var onComplete = function(){
		that.reset();
		eventController.fireEvent(that.TimerEvents.TIMER_COMPLETE);
	};
	
	/**
	 * Clears the timer object.
	 */
	var clearTimer = function(){
		if(!_timer){
			return;
		}
		clearInterval(_timer);
	};
	/**
	 * Reset the counter
	 */
	var resetCount = function(){
		if(countDownMode){
			currentCount = targetCounts;
		}
		else{
			currentCount = 0;
		}	
	};
	
	/**
	 * Add a event listener for the timer.
	 * @param {Object} event
	 * @param {Object} handler
	 */
	that.addEventListener = function(event, handler){
		eventController.registerEventHandler(event, handler)
	};
	
	/**
	 * Removed a registered event listener.
	 * @param {Object} event
	 * @param {Object} handler
	 */
	that.removeEventListener = function(event, handler){
		eventController.unregisterEventHandler(event, handler);
	};

	/**
	 * Function to add a onTick event handler.
	 * @param {Object} event
	 * @param {Object} handler
	 */
	that.addOnTickHandler = function(handler){
		eventController.registerEventHandler(that.TimerEvents.TIMER_TICK, handler);
	};

	/**
	 * Function to remove all onTick event handlers.
	 * @param {Object} event
	 * @param {Object} handler
	 */
	that.removeOnTickHandlers = function(){
		eventController.unregisterEventHandler(that.TimerEvents.TIMER_TICK);
	};

	/**
	 * Function to add a onComplete event handler.
	 * @param {Object} event
	 * @param {Object} handler
	 */
	that.addOnCompleteHandler = function(handler){
		eventController.registerEventHandler(that.TimerEvents.TIMER_COMPLETE, handler);
	};

	/**
	 * Function to remove all onComplete event handler.
	 * @param {Object} event
	 * @param {Object} handler
	 */
	that.removeOnCompleteHandlers = function(){
		eventController.unregisterEventHandler(that.TimerEvents.TIMER_COMPLETE);
	};

	/**
	 * Function that starts the timer.
	 */
	that.start = function(){
		_timer = setInterval(onTick, interval);
	};
	
	/**
	 * Function that stops the timer.
	 */
	that.stop = function(){
		clearTimer();
	};	
	
	/**
	 * Function that resets the timer.
	 */
	that.reset = function(){
		resetCount();
		clearTimer();	
	};
	
	/**
	 * Get Timer
	 */
	that.getCount = function(){
		return currentCount;
	};
	
	that.reset();
	return that;
})();
