/**
 * @fileoverview eventBindings provides event 
 * handling and method chaining. This is a substitution 
 * for more traditional methods of direct event assignment 
 * and script assignment (i.e. onclick="" or 
 * attachEvent()/addEventListener()). This is a simple 
 * "signal and slots" style implementation.
 *
 * @author Michael R. Havard mhavard@express-scripts.com
 * @filename eventBinding.js
 * @version	2.0.0
 */
 
/* *********************
 **   Global Objects  **
 ********************* */

	/**
	 * Define "undefined" for consistent testing 
	 * against undefined objects and variables.
	 */
	var undefined;
	
 	if(typeof(com)==="undefined" || com == undefined)
	{
		/**
		 * Begins creation of the namespace to help 
		 * encapsulate internal functions from possible
		 * third party script inclusions.
		 * @constructor
		 */
		var com = {
		
			/**
			 * Creates the Express Scripts namespace
			 * @class Namespace for all Express Scripts, Inc. specific javascript.
			 * @extends com
			 */
			express_scripts : {}
		};
		
		/* DEPRECATED: short alias used by some files */
		var esi = com.express_scripts;
	}

	/**
	 * Used to determine if a given object has been previously 
	 * defined.
	 * @param {object} oObject Object reference to test.
	 * @return {boolean}
	 * @deprecated see com.express_scripts.isDefined
	 */
    com.express_scripts.isUndefined = function(oObject)
    {
        return (typeof oObject === "undefined");
    };

    var isUndefined = com.express_scripts.isUndefined;
	
	/**
	 * Used to determine if a given object has been previously 
	 * defined.
	 * @alias com.express_scripts.isDefined
	 * @param {object} oObject Object reference to test.
	 * @return {boolean}
	 */
    com.express_scripts.isDefined = function(oObject)
    {
        return (typeof oObject != "undefined");
    };
	
	var isDefined = com.express_scripts.isDefined;

	/* Set boolean value identifying whether the 
		current page is a subpage. This is used 
		within the bindings to lock subpages from 
		ing/executing unnecessary bindings */	
	var bSubpage = (parent!=top && self!=top);
	var bLock = true;


/* **********************
 **  Logging Methods **
 ********************** */

	var DEBUG = {level:0,text:"debug"};
	var INFO = {level:1,text:"info"};
	var WARNING = {level:2,text:"warning"};
	var ERROR = {level:3,text:"error"};
	var FATAL = {level:4,text:"fatal"};
	var bLogging = false;
	var sLoggingLevel = DEBUG;
	var oLoggingWindow = null;

	/**
	 * Create new log handling namespace (automatically created when API loads).
	 * @class space for handling client side logging.
	 * @constructor
	 * @extends com.express_scripts
	 */
	com.express_scripts.logger = {
		
		/**
		 * Assemble and output log messaging.
		 * @param {string} sMethodClass Class or namespace of the method.
		 * @param {string} sMethodName Name of the method/function logging the message.
		 * @param {string} sMessage Message text to log.
		 * @param {string} oLevel Logging level for the logged message.
		 */
		log : function(sMethodClass, sMethodName, sMessage, oLevel)
		{
			if(typeof(oLevel)=="object")
			{
				var sLevel = oLevel.text;
				var iLevel = oLevel.level;
			}
			if(bLogging === true && sLoggingLevel.level <= oLevel.level)
			{
				if(oLoggingWindow === null)
				{
					var winprops = 'height=400,width=600,top=0,left=0,scrollbars=1,resizable=1,status=1';
					oLoggingWindow = window.open("", "oLoggingWindow", winprops);
					if(!oLoggingWindow.opener)
					{
						oLoggingWindow.opener = self;
					}
				}
	
				var oDoc = oLoggingWindow.document;
				var oElement = oDoc.createElement("div");
				oElement.className = sLevel;
				var sDate = new Date();
				var sTimestamp = "["+sDate.getFullYear() + "-" + sDate.getMonth() + "-" + sDate.getDate() + 
								" " + sDate.getHours() + ":" + sDate.getMinutes() + ":"+ sDate.getSeconds() + 
								":" + sDate.getMilliseconds()+"]";
				var sString = sTimestamp + " " + sLevel.toUpperCase() + ": " + sMethodClass + "." + 
								sMethodName + ": " + sMessage;
				var sText = oDoc.createTextNode(sString);
				oElement.appendChild(sText);
				oDoc.body.appendChild(oElement);
			}
		},

		/**
		 * Convenience method for logging the entry into a method/function.
		 * @param {string} sMethodClass Class or namespace of the method.
		 * @param {string} sMethodName Name of the method/function logging the message.
		 */
		entering : function(sMethodClass, sMethodName)
		{
			com.express_scripts.logger.log(sMethodClass, sMethodName, "entering", DEBUG);
		},
		
		/**
		 * Convenience method for logging the exiting of a method/function.
		 * @param {string} sMethodClass Class or namespace of the method.
		 * @param {string} sMethodName Name of the method/function logging the message.
		 */
		exiting : function(sMethodClass, sMethodName)
		{
			com.express_scripts.logger.log(sMethodClass, sMethodName, "exiting", DEBUG);
		},
		
		/**
		 * Log debug level messages.
		 * @param {string} sMethodClass Class or namespace of the method.
		 * @param {string} sMethodName Name of the method/function logging the message.
		 * @param {string} sMessage Message text to log.
		 */
		debug : function(sMethodClass, sMethodName, sMessage)
		{
			com.express_scripts.logger.log(sMethodClass, sMethodName, sMessage, DEBUG);
		},
	
		/**
		 * Log info level messages.
		 * @param {string} sMethodClass Class or namespace of the method.
		 * @param {string} sMethodName Name of the method/function logging the message.
		 * @param {string} sMessage Message text to log.
		 */
		info : function(sMethodClass, sMethodName, sMessage)
		{
			com.express_scripts.logger.log(sMethodClass, sMethodName, sMessage, INFO);
		},
		
		/**
		 * Log warning level messages.
		 * @param {string} sMethodClass Class or namespace of the method.
		 * @param {string} sMethodName Name of the method/function logging the message.
		 * @param {string} sMessage Message text to log.
		 */
		warning : function(sMethodClass, sMethodName, sMessage)
		{
			com.express_scripts.logger.log(sMethodClass, sMethodName, sMessage, WARNING);
		},
	
		/**
		 * Log error level messages.
		 * @param {string} sMethodClass Class or namespace of the method.
		 * @param {string} sMethodName Name of the method/function logging the message.
		 * @param {string} sMessage Message text to log.
		 */
		error : function(sMethodClass, sMethodName, sMessage)
		{
			com.express_scripts.logger.log(sMethodClass, sMethodName, sMessage, ERROR);
		},
		
		/**
		 * Log fatal level messages.
		 * @param {string} sMethodClass Class or namespace of the method.
		 * @param {string} sMethodName Name of the method/function logging the message.
		 * @param {string} sMessage Message text to log.
		 */
		fatal : function(sMethodClass, sMethodName, sMessage)
		{
			com.express_scripts.logger.log(sMethodClass, sMethodName, sMessage, FATAL);
		}
	};

   	/* provide a shorter alias for quick logging statements */
   	var Logger = com.express_scripts.logger;


/* **********************
 **   Binding Methods  **
 ********************** */

 	/* stores object references to later cleanup */
	var aTracker = [];

	/**
	 * Empty function used by bindings when no real function is available.
	 */
	com.express_scripts.emptyFunction = function(event){/*empty method used as place holder */ return true;};
	
 	/**
	 * Create new binding namespace (automatically created when API loads).
	 * @class Object space for creation, deletion, and other binding management functionality.
	 * @constructor
	 * @extends com.express_scripts
	 */
	com.express_scripts.bindings = {
	
	    /**
	     * Registers an object and event/method as a listener for another object and event/method
	     *
	     * @param {object} 	oEmitter				Object or namespace to register as event producer
	     * @param {string}	sEmitterEventName		Name of event or method to register as producer
	     * @param {object}	oReceptor			Object or namespace to register as event listener
	     * @param {string} 	sReceptorFunctionName	Name of event or method to register as event listener
	     * @param {boolean}	[bRunOnce]			Indicates whether the event or method should be executed once and 
	     *                              						then discarded from the bindings registry
	     * @return {boolean}
	     */
	    bind : function(oEmitter, sEmitterEventName, oReceptor, sReceptorFunctionName, bRunOnce)
	    {
	    	var sMCName = "com.express_scripts.bindings", sMName = "bind";
	    	Logger.entering(sMCName, sMName);
	    	
	    	if(bLock === true && bSubpage === true && sReceptorFunctionName !== "initFrame")
	    	{
		    	Logger.exiting(sMCName, sMName);
	    		return true;
	    	}
	        var bRunOnce = (isUndefined(bRunOnce))? false: bRunOnce;
	    	if(oEmitter === null)
	    	{
	    		Logger.debug(sMCName, sMName, "Emitter is null:"+oEmitter + sEmitterEventName + 
	    					oReceptor + sReceptorFunctionName);
	    	}
	        var aBindings = com.express_scripts.bindings.add(oEmitter, sEmitterEventName);
	        aBindings.push([oReceptor, sReceptorFunctionName, bRunOnce]);
	    	Logger.exiting(sMCName, sMName);
	        return true;
	    },
	    
	    /**
	     * Executes a method or event when called. Primarily used to call onload event 
	     * between subpage and parent page.
	     *
	     * @param {object}	oEmitter	Object or namespace to use in call (option - can be string)
	     * @param {string}	oEmitterEventName	Name of event or method to execute.
	     */
	    emit : function(oEmitter, sEmitterEventName)
	    {
	       	var sMCName = "com.express_scripts.bindings", sMName = "emit";
	    	Logger.entering(sMCName, sMName);
	    
	    	var oEmitter = (typeof(oEmitter)==="string")?eval(oEmitter):oEmitter;

			oEmitter[sEmitterEventName](event);

	    	Logger.entering(sMCName, sMName);
	    },
	    
	    /**
	     * Adds an object and event/method to the bindings object
	     * @param {object}   	oObject			Object or namespace to add to bindings
	     * @param {string}	sFunctionName	Name of event or method to add to bindings
	     * @return {array}	aBindings			Array of bindings
	     */
	    add : function(oObject, sFunctionName)
	    {
	    	var sMCName = "com.express_scripts.bindings", sMName = "add";
	    	Logger.entering(sMCName, sMName);
	        var sBindingName = sFunctionName + "__bound__";
	        try
	        {
		        var aBindings = (isUndefined(oObject[sBindingName]))? null: oObject[sBindingName];
			}
			catch(e)
			{
				Logger.error(sMCName, sMName, "Object not found: " + oObject +
							"::"+sFunctionName+"::"+sBindingName+"::"+oObject[sBindingName]);
			}
	        /* if the binding already exists AND the original object is not equal to the 
	            wrapped __original__ (this would signify that the original object had been 
	            overwritten) return the bindings array for manipulation */
	        if(aBindings !== null && (oObject[sFunctionName].toString() !== oObject[sFunctionName + "__original__"].toString()) )
	        {
	        	Logger.exiting(sMCName, sMName);
	            return aBindings;
	        }
	        
	        /* before making the new binding array add the object and method name to
	            the tracker array for later cleanup (because of IE) */
	        aTracker.push([oObject, sFunctionName]);
	        
	        /* hide away the original method */
	        var sOriginalName = sFunctionName + "__original__";
	        oObject[sOriginalName] = oObject[sFunctionName] || com.express_scripts.emptyFunction;
	        
	        /*create the bindings array that will hold method references */
	        aBindings = oObject[sBindingName] = [];/*new Array();*/
	        
	        /* reassign the original method to the new generic method that
	            will help invoke the chain of bindings */
	        oObject[sFunctionName] = function(event)
	        {
	        	var sMCNameIn = sFunctionName, sMNameIn = sFunctionName;
	        	Logger.entering(sMCNameIn, sMNameIn);
	            /* if the object or the function is undefined it cannot be 
	                executed correctly */
	            if(isUndefined(oObject) || isUndefined(oObject[sOriginalName]))
	            {
	            	Logger.exiting(sMCNameIn, sMNameIn);
	                return false;
	            }
	
	            /* set the current return value to match the return value
	                of the mapped method */
					
				var aArguments = arguments;
				if(aArguments.length == 0){
					aArguments = [window.event];
				}
				else if (aArguments[0] != event)
				{
					aArguments[aArguments.length] = event;
				}
	            var oReturn = oObject[sOriginalName].apply(oObject, aArguments);
	            
	            /* if the previous call returned false we don't want to continue
	                executing the chain of methods */
	            if(oReturn === false)
	            {	
	            	Logger.exiting(sMCNameIn, sMNameIn);
	                return false;
	            }
	            var iLen = aBindings.length;
	            for(var i = 0; i < iLen; i++)
	            {
	                /* get a handle on the related bindings for this method
	                    and begin walking the tree */
	                var oReceptor = aBindings[i][0];
	                oReceptor[aBindings[i][1]].apply(oReceptor, aArguments);
	                if(aBindings[i][2]==true)
	                {
	                   bindings.remove(oObject, sFunctionName, aBindings[i][0], aBindings[i][1], aBindings[i][2]); 
	                   iLen--;
	                   i--;
	                }
	            }
	            Logger.exiting(sMCNameIn, sMNameIn);
	            return oReturn;
	        };
	        Logger.exiting(sMCName, sMName);
	        return aBindings;
	    },
	    
	    /** 
	     * Removes an entry from the bindings registry
	     *
	     * @param {object}  	oEmitter				Object or namespace to register as event producer
	     * @param {string}	sEmitterEventName		Name of event or method to register as producer
	     * @param {object}	oReceptor			Object or namespace to register as event listener
	     * @param {string} 	sReceptorFunctionName	Name of event or method to register as event listener
	     * @param {boolean}	bRunOnce			Indicates whether the event or method should be executed once and 
	     *                              						then discarded from the bindings registry
	     */
	    remove : function(oEmitter, sEmitterFunctionName, oReceptor, sReceptorFunctionName, bRunOnce)
	    {
	    	var sMCName = "com.express_scripts.bindings", sMName = "remove";
	    	Logger.entering(sMCName, sMName);
	        var aBindings = oEmitter[sEmitterFunctionName + "__bound__"];
	        var iBindLen = aBindings.length;
	        for(var i=0; i < iBindLen; i++)
	        {
	            if((aBindings[i][0] == oReceptor) && (aBindings[i][1] == sReceptorFunctionName))
	            {
	                aBindings.splice(i,1);
	                break; 
	            }
	        }
	        Logger.exiting(sMCName, sMName);
	    },
	
	    /**
	     * Sets object references and arrays to null to avoid
	     * memory leaks within Internet Explorer
	     */
		cleanup : function()
		{
			var sMCName = "com.express_scripts.bindings", sMName = "cleanup";
			Logger.entering(sMCName, sMName);
		    var iLen = aTracker.length-1;
		    do
		    {
	            var oObject = aTracker[iLen][0];
		        var sFunction = aTracker[iLen][1];
	            oObject[sFunction+"__original__"] = null;
		        oObject[sFunction] = null;
	            oObject[sFunction+"__bound__"] = null;
		    }
		    while(--iLen);
	
	       	var aProperties = ["onclick","onchange","onload","onkeypress","onblur","onfocus","onkeyup","onkeydown"];	
			if(document.all)
			{
	       		iLen = document.all.length-1;
	       		do
	       		{
	        		oObject = document.all[iLen];
	        		var iLen2 = aProperties.length-1;
	        		do
	        		{
	            		if(oObject[aProperties[iLen2]]!==null)
	            		{
	                		oObject[aProperties[iLen2]] = null;
	            		}
	        		}
	        		while(--iLen2);
	       		}
	       		while(--iLen);
			}
		    aTracker = null;
		    Logger.exiting(sMCName, sMName);
		}
	};
	
	/* Alias the new object for use by existing calls */
    var bindings = com.express_scripts.bindings;
   	bindings.bind(window, "onunload", bindings, "cleanup", true);
    

/* **********************
 **  Exception Methods **
 ********************** */

	/**
	 * Create new exception handling namespace (automatically created when API loads).
	 * @class space for handling client side exceptions.
	 * @constructor
	 * @extends com.express_scripts
	 */
	com.express_scripts.exceptionHandler = {
	
		/**
		 * Captures the message and generates the display.
		 * @param {String} sMessage Description of exception or message text.
		 * @param {String} sURL URL or file name generating the exception.
		 * @param {Integer} iLine Line number where exception is thrown from. 
		 */
		capture : function(sMessage, sURL, iLine)
		{
			var sMClass = "com.express_scripts.exceptionHandler", sMName = "capture";
			if(typeof(sMessage)!="string")
			{
				var sString = "";
				for(var i in sMessage)
				{
					sString += sMessage[i];
				}
				/* Commented out Member doesn't use general.js yet
				 * com.express_scripts.messaging.set("pageMessage", "exception", "error", sString);*/
				Logger.error(sMClass, sMName, sString);
				return;
			}
			var sError = "ERROR: Browser scripting error has occurred. " +
						 "It may be possible to continue. Please try again. " +
						 "If the problem persists notify support. " +
						 "Details - Message: " + sMessage + 
						 " File: " + sURL + 
						 " Line #: " + iLine;
			Logger.error(sMClass, sMName, sError);

			/*com.express_scripts.messaging.set("pageMessage", "exception", "error", sError);*/
		}
	};

	/*window.onerror = com.express_scripts.exceptionHandler.capture;*/
	
	if(document.location.href.indexOf("localhost")==-1 && 
		document.location.href.indexOf("file://")==-1)
	{
		/* DO NOT CHANGE THE LOGGING HERE 
		 * THIS SETTING IS FOR PRODUCTION USE 
		 */
		bLogging = false;
	}
    
    