// Web Control Resource Cleanup
function WebControlCleanUp() {
	var o = document.getElementsByTagName('div');
	for (var i = 0; i < o.length; i++) {
        try
        {
		    if (o[i].JSControl) {
			    o[i].JSControl.__Dispose();
			    o[i].JSControl.Control = null;
			    o[i].JSControl = null
		    }
		} catch (err)
		{		
		}    
	}	

	if (GarbageHeap != null) {
		for (var i = 0; i < GarbageHeap.Items.length; i++)
		{
			if (GarbageHeap.Items[i].__Dispose) GarbageHeap.Items[i].__Dispose();	
			GarbageHeap.Items[i] = null;
		}
	}

	GarbageHeap = null;

	CollectGarbage();			// JS Internal function
}

var GarbageHeap = new Collection();


window.onunload = WebControlCleanUp;


// OBJECT
function WebControl() {
    /// <summary>Abstract base control used by webcontrols</summary>
    
	this.Events = new Events();
		
	this.Disable = function() { 
		switch(this.Disable.arguments.length)
		{
			case 0: 
				SetControlDisable(this.Control); 
				break;
			
			case 1:
				SetControlDisable(this.Control, this.Disable.arguments[0]); 
				break;
			
			default:
				alert('unsupported amount of arguments passed to control[' + this.Control.name + '] :: Disable()');
				break;
		}
	}
	
	this.Enable = function() {
		switch(this.Enable.arguments.length)
		{
			case 0: 
				SetControlDisable(this.Control, false); 
				break;
			
			case 1:
				var val = (this.Enable.arguments[0] == true) ? false : true;
				
				SetControlDisable(this.Control, this.Enable.arguments[0]); 
				break;
			
			default:
				alert('unsupported amount of arguments passed to control[' + this.Control.name + '] :: Enable()');
				break;
		}
	}
	
	this.Encode = function() { EncodeControl(this.Control); return true; }
	
	this.Unencode = function() { UnencodeControl(this.Control); return true; }
	this.Decode  = function() { UnencodeControl(this.Control); return true; }
	
	/* Destructor */
	
	this.__Dispose = function() { 
		for (var n in this) {
			this[n] = null;
		}
	}
}

WebControl.Styles = {
                    Windows   : 1, Standard : 1,
                    Classic   : 2, XP : 2,
                    Office2003: 3,
                    Glass     : 4
                    }

WebControl.prototype.Collapse	= __BASECONTROL_Collapse;
WebControl.prototype.Expand		= __BASECONTROL_Expand;
WebControl.prototype.Show		= __BASECONTROL_Show;
WebControl.prototype.Hide		= __BASECONTROL_Hide;
WebControl.prototype.Encode		= __BASECONTROL_Encode;
WebControl.prototype.Decode		= __BASECONTROL_Decode;
WebControl.prototype.Dispose	= __BASECONTROL_Dispose;	

function __BASECONTROL_Collapse() {
	this.Control.style.display = "none";
}

function __BASECONTROL_Expand() {
	this.Control.style.display = "block";
}

function __BASECONTROL_Show() {
	this.Control.style.visibility = "visible";
}

function __BASECONTROL_Hide() {
	this.Control.style.visibility = "hidden";
}

function __BASECONTROL_Encode(){
	EncodeControl(this.Control); return true;
}

function __BASECONTROL_Decode(){
	UnencodeControl(this.Control); return true;
}

function __BASECONTROL_Dispose(){
	for (var n in this) {
		this[n] = null;
	}
}


// OBJECT
Events.prototype.toString = function() { return "[Event Handler]"; }
function Events() {
    /// <summary>Events object used for subscribing and serving events</summary>
    /// <field name="Debug" type="Boolean">When set to true, fires off alert boxes telling you events</field>
    /// <field name="AllowThrow" type="Boolean">When set to false, events fail gracefully (default is true)</field>
    /// <field name="AllowDuplicateEventListeners" type="Boolean">When set to true, multiple duplicate event listeners are allowed, otherwise they're filtered</field>
    /// <field name="VolatileMemory" type="Boolean">Set to true when an event object will have handlers on cross window referneces, 
    /// failed memory errors are gracefully handled.  The stack is otherwise lost for other errors</field>
    /// <field name="Enabled" type="Boolean">When disabled, no events fire.</field>
    
	this.Inherits(Collection);
	this.DefaultCollectionObject        = AttachableEvent;
	this.Debug                          = false;
	this.AllowThrow                     = true;
	this.AllowDuplicateEventListeners   = true;
	
	this.VolatileMemory                 = false;
	
	this.Enabled                        = true;
	
	delete this.Add;
	delete this.Remove;
}

Events.prototype.Remove     = __Events_Remove;
Events.prototype.GetEvents  = __Events_GetEvents;
Events.prototype.Add        = __Events_Add;
Events.prototype.RaiseEvent = __Events_RaiseEvent;
Events.prototype.ReleaseFreedScripts = __Events_ReleaseFreedScripts;

Cast.As.Events = function(val) {
    /// <summary>Returns a casted Events</summary>
/// <param name="val" type="Events">Value to cast</param>
/// <returns type="Events" />
    return val;
}


function __Events_Remove(EventObject) {
    /// <summary>Removes an event</summary>
    /// <param name="EventObject" type="AttachableEvent">Event instance to remove</param>
    /// <returns type="Boolean">Returns true on successfully removing the instance, false otherwise</returns>
    for (var i = this.Items.length-1; i >=0; i--) {
        if (this.Items[i] == EventObject) {
            this.Items.splice(i, 1);
            return true;
        }
    }

    return false;
}

function __Events_GetEvents(EventName) {								/* returns an array of events matching the passed in trigger */
    /// <summary>Returns an array of events based on the event name specified</summary>
    /// <param name="EventName" type="String">Event name to search for</param>
    /// <returns type="Array" elementType="AttachableEvent">Returns array of event subscriber instances</returns>
	var aEvents = new Array(0);
				
	for (var i = 0; i < this.Items.length; i++) {
		if (this.Items[i].Trigger == this.GetEvents.arguments[0] || this.Items[i].Trigger == '*') {
			aEvents.splice(aEvents.length, 0, this.Items[i]);
		}
	}
	
	return aEvents;
}	


function __Events_Add(Trigger, FunctionPointer, uArgs) {
    /// <summary>Adds a new subscribing event based on the trigger passed in</summary>
    /// <param name="Trigger" type="String">Event name to use as the trigger</param>
    /// <param name="FunctionPointer" type="Function">Function to be called when the trigger is handled</param>
    /// <param name="uArgs" type="Object" parameterArray="true">Arguments to be passed in as Args1 property (Subscriber arguments)</param>
    /// <returns type="AttachableEvent">Event subscriber instance</returns>
  
	if (Trigger == undefined) {
		var e = new Error();
		e.description = "No trigger passed for event listener";
		e.message = "No trigger passed for event listener";
		throw e;
	}

	
	if (typeof(FunctionPointer) != 'function' && FunctionPointer.toString().indexOf("function") == -1) {
	    var e = new Error();
	    e.description = "Invalid function pointer passed to event listener";
	    e.message = "Invalid function pointer passed to event listener";
	    throw e;	
	}
	
	var args = new Array(((this.Add.arguments.length < 2) ? 0 : this.Add.arguments.length - 2));
	for (var i = 2; i < this.Add.arguments.length; i++)
	{
		args[i-2] = this.Add.arguments[i];
	}
		
	var o = new AttachableEvent(Trigger, FunctionPointer, args);
	
	// Stop duplicate entries from being made
	if (!this.AllowDuplicateEventListeners)
	{
	    for (var i = 0; i < this.Items.length; i++)
	    {
	        var evt = this.Items[i];
	        // Check Trigger / Pointer setup
	        if (evt.Trigger         != Trigger) continue;
	        if (evt.FunctionPointer != FunctionPointer) continue;
	        
	        // Check arg length
	        if (evt.Arguments.length != args.length) continue;
	        
	        var bSameArgs = true;
	        
	        // Check to see if arguments are duplicate
	        for (var j = 0; j < evt.Arguments.length; j++) if (evt.Arguments[j] != args[j]) bSameArgs = false;
	    
	        if (!bSameArgs) continue;
	    
	        // Item is a complete duplicate of an existing function and will not be added.
	        return this.Items[i];
	    }
	}
	
	this.Items.splice(this.Items.length, 0, o);
	
	return o;
}


function __Events_RaiseEvent(evt, srcObject, uArgs) {
    /// <summary>Raises a new event to be picked up by subscribers</summary>
    /// <param name="evt" type="String">Trigger / Event name to raise</param>
    /// <param name="srcObject" type="Object">Source object that is raising the event</param>
    /// <param name="uArgs" type="Object" parameterArray="true">Arguments to be passed in as Args2 property (Sender arguments)</param>
    /// <returns type="Boolean">returns true on success</returns>
	var bSuccess = true;
	
	var evtObj = this.RaiseEvent.arguments[0];
	if (evtObj == undefined) return false;											/* Atleast one argument must be passed through */
    if (this.Enabled == false) return false;
    
	var PassedArgs = new Array(0);
	if (this.RaiseEvent.arguments.length > 2) {
	    if (typeof(this.RaiseEvent.arguments[2]) == 'object' && this.RaiseEvent.arguments[2].ConstructorName() == '__InternalEventArguments') {
		    PassedArgs = this.RaiseEvent.arguments[2].Arguments;
	    } else {
		    for (var n = 2; n < this.RaiseEvent.arguments.length; n++)
		    {		
			    PassedArgs.splice(PassedArgs.length, 0, this.RaiseEvent.arguments[n]);
		    }
	    }
	}
	
	    if (this.Debug == true) {
        Msgbox("       Event Raised: " + evt 
           + "\n      Source Object: " + srcObject 
           + "\n Event Arguments: \n" + PassedArgs, vbInformation + vbOKOnly, "Event Raised!");
	
	}
		
	if (typeof(evtObj) == "object") {
		evtObj.FunctionPointer(srcObject, null, PassedArgs);											/* Fire just this event object */
	} else {
		var Evts = this.GetEvents(evtObj.toString(), this); 							/* Find all events that match the argument */
		var bCleanupRequired = false;
				
		for (var i = 0; i < Evts.length; i++) {											/* Fire all found events */
	
			/* Evts[i].Arguments Represents custom arguments that are set in context
					to an event.  These are set by the developer when adding an Event
					entry to the events collection
			
					example:  control.Events.Add("EVENT_NAME", srcObject, arg1, arg2....);
			
			
				PassedArgs represent any hard coded arguments that are most likely calculated
				by an event, or to pass extra information along in the event.
				
					example:  control.Events.RaiseEvent("EVENT_NAME", srcObject, arg1, arg2
			
			*/
			
			if (this.VolatileMemory)
			{			
			    try 
			    {		     
			        if (Evts[i].FunctionPointer && (typeof(Evts[i].FunctionPointer) == 'function' || typeof(Evts[i].FunctionPointer) == 'object') ) {
				        /* Make sure the function still exists */
					    // If [false] is returned then cancel the event. 
    								
					    if (Evts[i].FunctionPointer(srcObject, Evts[i].Arguments, PassedArgs, evt) == false) {
						    bSuccess = false;
						    break;
					    }
			        } 

    		    
		        } 
		        catch (err) 
		        {
			        // Free'd script
			        switch (err.number)
			        {
			            case -2146823277:
			            case -2147418094:
			    //        case -2147418113:
			                bCleanupRequired = true;
			                Evts[i].Released = true;
			                break;
			            default:
			                var e = new Error();
			                e.CopyFrom(err);
			                e.description += " in " + Evts[i].FunctionPointer.GetFunctionName() + "()";
				            throw e;
                    }						    
			    }
            }
            else
            {
            // Non-Volatile Memory
                if (Evts[i].FunctionPointer && (typeof(Evts[i].FunctionPointer) == 'function' || typeof(Evts[i].FunctionPointer) == 'object') ) 
                {
			        /* Make sure the function still exists */
				    // If [false] is returned then cancel the event. 
								
				    if (Evts[i].FunctionPointer(srcObject, Evts[i].Arguments, PassedArgs, evt) == false) 
				    {
					    bSuccess = false;
					    break;
				    }
		        } 
            }
		}
		
		if (bCleanupRequired) {
		    this.ReleaseFreedScripts();
		}
	}
	
	return bSuccess;
}

function __Events_ReleaseFreedScripts() {
    /// <summary>Scans for released scripts on a Volatile Memory Events object
    var evts = this.Items;
    var iStart = evts.length;
    
    for (var i = evts.length-1; i >= 0; i--) {
        var e = evts[i];
        if (e.Released != true) continue;
        
        evts.splice(i, 1);
    }
    
    var iEnd = evts.length;
}

AttachableEvent.prototype.toString = function() { return "[Attachable Event]"; }

// OBJECT
function AttachableEvent(Trigger, FunctionPointer, args) {
    /// <summary>Attachable event, these should only be created as part of the Events object factory</summary>
    /// <param name="Trigger" type="String">Trigger name</param>
    /// <param name="FunctionPointer" type="Function">Callback delegate</param>
    /// <param name="args" type="Object" parameterArray="true">Subscriber arguments</param>
    /// <field name="Trigger" type="String">Trigger name</field>
    /// <field name="FunctionPointer" type="Function">Callback delegate</field>
    /// <field name="Arguments" type="Array" elementType="Object">Subscriber arguments</field>
    /// <field name="ID" type="String">Unique id for the event</field>
    
	/* Defaults */
	this.Trigger = Trigger;
	this.FunctionPointer = FunctionPointer;
	this.ID = RandomGUID();
	
	if (Trigger == undefined || FunctionPointer == undefined) {
		/* Bad Event Creation*/
		return false;
	}
	
	this.Arguments = (args == undefined || args.constructor != Array) ? new Array(0) : args;
}


function EventCollector(srcObject, evtName, evtControl, uArgs) {
    /// <summary>Used to raise events from with HTML UI elements so they bubble back to the JS Control</summary>
    /// <param name="srcObject" type="Object">Source object for the event</param>
    /// <param name="evtName" type="String">Event / Trigger to raise</param>
    /// <param name="evtControl" type="String" optional="true">If nesting controls, allows you to specify which control this event should bubble to, takes a control type</param>
    /// <param name="uArgs" type="Object" parameterArray="true">Sender arguments</param>
    
    
	if (evtControl == undefined || evtControl == null) evtControl = "";
	var oJSControl = GetParentJSControl(srcObject, evtControl);

	if (oJSControl == null || oJSControl == undefined) return;			/* Unable to handle event request */
	
	if (oJSControl.Events.Debug) {											/* [Debug] event Tracing */
		//alert(evtName + " " + oJSControl + " " + srcObject);
	}
	
	// Compile event arguments.
	var oArguments = new __InternalEventArguments();
	for (var i = 3; i < EventCollector.arguments.length; i++) 
	{
		oArguments.Arguments.splice(oArguments.Arguments.length, 0, EventCollector.arguments[i]);
	}	
	return oJSControl.Events.RaiseEvent(evtName, srcObject, oArguments);
}

function __InternalEventArguments() {
    // Empty Class
    this.Arguments = new Array(0);
}

function EventArguments(oEventArgs) {
    /// <summary>Used for creating a new event args object that contains additional helpful features for event tracing</summary>
    // Empty Class
    if (oEventArgs != undefined)
    {
        if (typeof(oEventArgs) != 'object')
            throw new Error('Only an object representing the event arguments to be applied to the EventArguments object may be passed in as an argument');            
    
        
        for (var n in oEventArgs)
        {
            if (!oEventArgs.hasOwnProperty(n)) continue;
            
            this[n] = oEventArgs[n];
        }
    }
}
EventArguments.prototype.toString = EventArguments_toString; 
var __BaseEventArguments = new EventArguments();            // Used as a reference for properties not to be shown

function EventArguments_toString() {
    var s = '[Event Arguments Object] \n{';
    for (var n in this) 
    {
        if (!this.hasOwnProperty(n)) continue;
        if (__BaseEventArguments[n] != undefined) continue; // Do not list properties that belong to the base EventArgs object.
                
        var val  = '';
        if (typeof(this[n]) == 'function' || typeof(this[n]) == 'object') {
            val = this[n].ConstructorName != undefined ? this[n].ConstructorName() : this[n].toString();
        } else {
            val = this[n].toString();
        }
        
        s += '\n    ' + n + ' = ' + val;
    }
    
    s += '\n}';
    
    return s;
}