// 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>

    Object.Inherits(this, 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' && Object.ConstructorName(this.RaiseEvent.arguments[2]) == '__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(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 ? Object.ConstructorName(this[n]) : this[n].toString();
        } else {
            val = this[n].toString();
        }

        s += '\n    ' + n + ' = ' + val;
    }

    s += '\n}';

    return s;
}

