﻿/* -----------------------------------------------------------------------------------
Bash.js : v1.1.0.1
----------------------------------------------------------------------------------- */

var Bash = {
    /// <summary>General framework to work with asynchronous JSON and XML, custom events, etc.</summary>
    /* ----------------------------------------------------------------------
    Version Name
    ---------------------------------------------------------------------- */
    Version: "1.1.0.1",    
    /* ----------------------------------------------------------------------
    Bash.Events : Use this as a base to fire custom events
    ---------------------------------------------------------------------- */
    Events: {
        /// <summary>Use the methods from this namespace as a base to fire custom events</summary>
        /* ----------------------------------------------------------------------
        Attach a custom event.  The callBack is a function that
        will be fired when the event is triggered.
        ---------------------------------------------------------------------- */
        GenerateEventProxy: function (id, callBack)
        {
            // Append a button to the page...
            // The button servers as a proxy
            // The custom event is triggered whenever the button's click event is raised
            var proxy = null;            
            proxy = document.getElementById(id);                    
            if(!proxy)
            {
                proxy = document.createElement("input");
                proxy.id = id;
                proxy.type = "button";
                document.body.appendChild(proxy);
                proxy.style.display = "none";
            }
            
            this.AttachEvent(proxy, "click", function ()
                {
                    try
                    {
                    callBack(Bash.Events.Event);
                    }
                    catch (e)
                    {}
                });       
            return proxy;       
        },
        /* ----------------------------------------------------------------------
        Fire an event.  All callback functions will be fired.
        evt can either be a string (the name of the event), or an Event object
        ---------------------------------------------------------------------- */
        BroadcastEvent: function (evt)
        {
            // Raise the event by looking for the appropriate proxy
            // If the proxy exists, trigger the event
            var eventName = "";
            
            if(typeof(evt) != "string")
            {
                this.Event = evt;
                eventName = evt.Name;
            }
            else
            {
                this.Event = false;
                eventName = evt;
            }
                        
            var trigger = document.getElementById(eventName.toUpperCase());            
            
            if(trigger)
            {
                trigger.click();
            }
        },
        /* ----------------------------------------------------------------------
        Allows attaching multiple events to a DOM object with a callback function 
        ---------------------------------------------------------------------- */
        AttachEvent: function (obj, evt, callBack, useCapture)
        {
            // Allows to attach multiple event listeners to one object
            if (!useCapture)
            {
                useCapture = false;
            }
            
            if (obj.addEventListener)
            {
	            obj.addEventListener(evt, callBack, useCapture);
	            return true;
            }
            else (obj.attachEvent)
            {
                return obj.attachEvent("on" + evt, callBack);
            }
        },
        /* ----------------------------------------------------------------------
        False by default, contains the event being fired.
        ---------------------------------------------------------------------- */
        Event: false
    },
    /* ----------------------------------------------------------------------
    Bash.Number : This class is obsolete and should not be used anymore.
    ---------------------------------------------------------------------- */
    /*
    Number: function (n)
    {
        this.Value = n;
        
        // Provide a human-friendly representation of the Object    
        this.toString = function ()
        {
            return this.Value.toString();
        };
        
        // Put commas every 3 digits from the right
        this.Format = function ()
        {
	        var str = this.toString();
	        var out = "";	
	        var pos = 0;

	        while(pos < str.length)
            {
	            out += str.charAt(pos);	
	            if (((str.length - 1 - pos) % 3 == 0) && (pos != str.length - 1))
                {
	                out += ",";
                }
	            pos++;
            }
            return out;	
        };      
        return this;
    },*/
    /* ----------------------------------------------------------------------
    Bash.Ajax : Used to perform HTTP operations
    ---------------------------------------------------------------------- */    
    Ajax: {
        /// <summary>Namespace to perform HTTP operations.</summary>
        /* ----------------------------------------------------------------------
        Send an HTTP request to the url specified.  The options allow to select
        the method (GET or POST, GET by default), the mode (asynchronous), send
        data (key/value pairs string),...
        ---------------------------------------------------------------------- */
        Get: function (url, options)
        {      
            /// <summary>Send an HTTP request to the url specified using GET or POST, asynchronous or synchronous call,...</summary>
            /// <param name="url">The target URL of the HTTP call.</param>
            /// <param name="options">A set of configuration options for the request (method, async, type, data, complete, error,.).</param>
            /// <return>A reference to the XmlHttpRequest object used to perform the request.</return>
         
            if(options == null)
            {
                options = {};
            }
                   
            var httpRequest = null;
            var method = options.method || "GET";
            var async = options.async || "false"; 
            var type = options.type || ""; 
            
            // Get the file name without the query string, if any       
            var urlInfo = new Bash.Utils.PathInfo(url);
            url = urlInfo.FullName;
            
            // If present, data attached in the query string replace data in the "data" option field                        
            var data = options.data || "";            
            data = (urlInfo.QueryString != null && data.length == 0) ? urlInfo.QueryString.toString() : data;
                        
            try
            {            
                httpRequest = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
            } 
            catch (e)
            {
                alert("Error: Could not enable XML HTTP");
                return null;            
            }
            
            httpRequest.onreadystatechange = function ()
                { 
                    if(httpRequest.readyState == 4)
                    {
                        if(httpRequest.status == 200)
                        {
                            if(options.complete)
                            {
                                // If the request went through, call the callbca function
                                options.complete(httpRequest.responseXML, httpRequest);
                            }
                        }
                        else if(httpRequest.status == 404)
                        {
                            // Handle file not found
                            if(options.error)
                            {
                                options.error({status: httpRequest.status, message: "Page not found"});
                            }
                        }
                        else if(httpRequest.status == 405)
                        {
                            // Handle request rejected
                            if(options.error)
                            {
                                options.error({status: httpRequest.status, message: "Request has been rejected by the server; try using GET."});
                            }
                        }
                        else if(httpRequest.status == 0)
                        {
                            // Suppress this error
                        }
                        else
                        {
                            // Handle other errors
                            if(options.error)
                            {
                                options.error({status: httpRequest.status, message: "An error occurred while loading the page [{0}].".Format(httpRequest.status)});
                            }
                        }
                    }
                };
                
            // Send the request
            if(method.toUpperCase() == "POST")    
            {
                // NB: POST CANNOT BE USED TO LOAD STATIC PAGES            
                httpRequest.open(method, url, true);
                if(options.type == "xml")
                {
                    httpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
                }
                else if(options.type == "json")
                {
                    httpRequest.setRequestHeader("Content-Type", "application/json; charset=utf-8");
                }
                else
                {
                    httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                }
                httpRequest.send(data);
            }
            else
            {
                httpRequest.open(method,  url + "?" + data, async); 
                httpRequest.send(null);
            }
            
            // Return the HTTPRequest object            
            return httpRequest;
        },
        /* ----------------------------------------------------------------------
        Load a JSON object from a remote url and serialize it.
        ---------------------------------------------------------------------- */
        GetJSON: function (url)
        {
            var input = null;
            var json = null;
            
            var options = {
                complete: function (xml, request)
                {
                    input = request.responseText;
                },
                error: function (e)
                {
                    alert(String.Format("Error: JSON request failed [{0}]", e.message));
                    input = null;
                    json = null;
                },
                async: false
            }
        
            // Get a JavaScrip object as a JSon string from the server
            Bash.Ajax.Get(url, options);
             
            if(input != null)
            {
                json = Bash.JSON.Get(input);
                input = 0;
            }
            
            return json;
        },
        /* -------------------------------------------------------------------------------------
        Get a JSON object asynchronously.  The serialized object is available through the
        callback function.  If the "data" parameter is null, GET is used, otherwise POST.    
        -------------------------------------------------------------------------------------*/
        GetJSONAsync: function (url, callback, data)
        {
            var json = null;
            var input = null;
            var req = null;
            
            var method = (!data) ? "GET" : "POST";
            data = data || "";
            
            var options = {
                // When the data is loaded, create a JSON object and trigger the callback function
                complete: function (xml, request)
                {
                    input = request.responseText;
                    json = Bash.JSON.Get(input);
                    input = 0;
                    
                    callback(json);
                    json = 0;
                    request = 0;
                    xml = 0;
                    callback = null;
                    req = 0;                    
                },
                // If an error occured, display a message.
                error: function (e)
                {
                    alert(String.Format("Error: JSON request failed [{0}]", e.message));
                    json = null;
                    input = null;
                },
                // Async is true
                async: true,
                method: method,
                data: data
            }
        
            // Start the asynchronous call to the server.
            req = Bash.Ajax.Get(url, options);
            
            return req;
       
        },
        // ----------------------------------------------------------------------
        // Perform a synchronous HTTP request and return an XML Document object
        // Leave the "data" parameter null to use GET.
        // ----------------------------------------------------------------------
        LoadXml: function (url, data)
        {
            var output = null;
            
            var method = (!data) ? "GET" : "POST";
            data = data || "";
            
            var options = {
                complete: function (xml, request)
                {
                    output = xml;
                },
                error: function (e)
                {
                    alert(String.Format("Error: HTTP request failed [{0}]", e.message));
                    output = null;
                },
                async: false,
                method: method,
                data: data
            }
            
            Bash.Ajax.Get(url, options);
            
            return output;
        }
        ,
        // ----------------------------------------------------------------------
        // Load content from a remote URL and return it as plain-text.
        // ----------------------------------------------------------------------
        Load: function (url)
        {
            var output = null;
            
            var options = {
                complete: function (xml, request)
                {
                    output = request.responseText;
                },
                error: function (e)
                {
                    alert(String.Format("Error: HTTP request failed [{0}]", e.message));
                    output = null;
                },
                async: false
            }
            
            Bash.Ajax.Get(url, options);
            
            return output;
        },
        // ----------------------------------------------------------------------
        // Load an external JavaScript into the current page and execute it.
        // ----------------------------------------------------------------------
        GetScript: function (url)
        {    
            var src = null;
            
            var options = {
                complete: function (xml, request)
                {
                    src = request.responseText;
                },
                error: function (e)
                {
                    alert(String.Format("Could not import the file [{0}]", e.message));
                    src = null;
                },
                async: false
            }
        
            // Get a JavaScrip object as a JSon string from the server
            Bash.Ajax.Get(url, options);
             
            if(src != null && src.length > 0)
            {
                // Append a script tag to the page and load an external JavaScript file  
                var head = document.getElementsByTagName("head")[0] || document.documentElement;
			    var	script = document.createElement("script");
    			
			    // Load the JavaScript file
			    script.type = "text/javascript";		
			    script.defer = true;
			    script.text = src;
			    head.appendChild(script);
            }
	    }
    },
    /* ----------------------------------------------------------------------
    Bash.XML : Methods to deal with XML content.
    ---------------------------------------------------------------------- */
    Xml: {
        /* ----------------------------------------------------------------------
        Create a DOMDocument object from some XML string.
        ---------------------------------------------------------------------- */
        DOMDocument: function (xmlStr)
        {
            var doc = new ActiveXObject("Msxml2.DOMDocument");
            if(xmlStr)
            {
                doc.loadXML(xmlStr);
            }
            return doc;
        },
        FreeThreadedDOMDocument: function ()
        {
            return new ActiveXObject("Msxml2.FreeThreadedDOMDocument");
        },
        XSLTemplate: function ()
        {
            return new ActiveXObject("Msxml2.XSLTemplate");
        },
        XslTransformXmlDocument: function (xmldoc, xslDocUrl, pars)
        {
            var xslt = new Bash.Xml.XSLTemplate();
            var xsldoc = new Bash.Xml.FreeThreadedDOMDocument();
            var xslproc = null;

            // Load the XSLT document	
            xsldoc.async = false;
            xsldoc.load(xslDocUrl);

            if (xsldoc.parseError.errorCode != 0)
            {
	            var myErr = xsldoc.parseError;
	            alert("Error: " + myErr.reason);
	            return null;
            }
            
            xslt.stylesheet = xsldoc; 
            
            // Transform the XML dataset
	        xslproc = xslt.createProcessor();
	        xslproc.input = xmldoc;
	        
	        if(pars)
	        {
	            for(var i=0; i<pars.length; i++)
	            {
	                xslproc.addParameter(pars[i].Name, pars[i].Value);
	            }
	        }
	        
	        xslproc.transform();
        	
	        return xslproc.output;  // : String      
        },
        // ----------------------------------------------------------------------
        // Transform an XML document using an XSL style-sheet and return the
        // resulting XML document object.
        // ----------------------------------------------------------------------
        XslTransformUrl: function (xmlDocUrl, xslDocUrl)
        {
            var output = null;
            
            try
            {        
                // Load the XML content from the URL
                var xml = Bash.Ajax.LoadXml(xmlDocUrl); 
                
                // Load the style-sheet from the URL       
                var xsl = Bash.Ajax.LoadXml(xslDocUrl);
                
                // Apply the XSL transformation to the XML document.    
                var transformedXml = xml.transformNode(xsl);   
                
                // Load the output XML string into an XML document object.  
                output = new Bash.Xml.DOMDocument(transformedXml);
                
                // Dispose
                xml = 0;
                xsl = 0; 
            }
            catch (ex)
            {
                alert(ex.message);
            }                 
                  
            return output;
        }
    },
    /* ----------------------------------------------------------------------
    Bash.JSON : Methods to deal with JSON content.
    ---------------------------------------------------------------------- */
    JSON: {
        Get: function (json)
        {
            var obj = null;
            
            try
            {
                obj = eval("(" + json + ")");
            }
            catch (ex)
            {
                obj = {StatusCode: -9999, Message: ex.message};
            }
            
            return obj;
        }
    },
    /* ----------------------------------------------------------------------
    Bash.Browser : Basic browser detection (from jQuery)
    ---------------------------------------------------------------------- */
    Browser: {
	    Version: (navigator.userAgent.toLowerCase().match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1],
	    Safari: /webkit/.test( navigator.userAgent.toLowerCase() ),
	    Opera: /opera/.test( navigator.userAgent.toLowerCase() ),
	    MSIE: /msie/.test( navigator.userAgent.toLowerCase() ) && !/opera/.test( navigator.userAgent.toLowerCase() ),
	    Mozilla: /mozilla/.test( navigator.userAgent.toLowerCase() ) && !/(compatible|webkit)/.test( navigator.userAgent.toLowerCase() ),
	    Windows: /win/.test( navigator.appVersion.toLowerCase() ) 
    },
    /* ----------------------------------------------------------------------
    Bash.Collections : Classes to deal with collections
    ---------------------------------------------------------------------- */
    Collections: {
        /* ----------------------------------------------------------------------
        Sorted dictionary collection
        ---------------------------------------------------------------------- */
        Dictionary: function ()
        {           
            return {
                Items: [],
                Keys: [],
                Add: function (key, value)
                {
                    // All keys must be of the same type
                    if (this.Keys.length > 0)
                    {
                        if(typeof(key) != typeof(this.Keys[0]))
                        {                            
                            return false;
                        }
                    }                    
                    var index = this._searchNextIndex(key);
                    
                    // Insert the item at the index before the found index...
                    this.Keys.splice(index, 0, key);
                    this.Items.splice(index, 0, {Key: key, Value: value});                 
                    
                },
                Remove: function (key)
                {
                    // Look for the item
                    var index = this._search(key, Math.floor(this.Keys.length / 2), 0, this.Keys.length, this.Keys);
                    if(index != -1)
                    {
                        this.Keys.splice(index, 1);
                        this.Items.splice(index, 1);
                    }
                },
                Contains: function (key)
                {
                    var index = this.Search(key);
                    return (index != -1);
                },
                Get: function (key)
                {
                    var index = this.Search(key);
                    if(index != -1)
                    { return this.Items[index].Value; }
                    else
                    { return null; }
                },
                _searchNextIndex: function (key)
                {
                    if(key > this.Keys[this.Keys.length - 1])
                    {
                        return this.Keys.length;
                    }
                    else if(key < this.Keys[0])
                    {
                        return 0;
                    }
                    else
                    {
                        return this._search(key, Math.floor(this.Keys.length / 2), 0, this.Keys.length, this.Keys, true);
                    }
                },
                Search: function (key)
                { 
                    // Look for a key in the dictionary list, and return its index                
                    return this._search(key, Math.floor(this.Keys.length / 2), 0, this.Keys.length, this.Keys);
                },
                _search: function(key, median, begin, length, array, b)
                {                  
                    // Binary search on a sorted list
                    // Return -1 if no matching index is found
                    var value = array[begin + median];
                    var index = -1;
                    
                    if (key == value)
                    {
                        return (begin + median);
                    }
                    else
                    {
                        if (key > value)
                        {
                            begin = begin + median;
                        }
                        else if (key < value)
                        {
                            length = length - median;
                        }
                        
                        index = Math.floor((length - begin) / 2);
                        if ((index == 0)&&(array[begin + index]!=key))
                        {
                            if(b)
                            {
                                return (begin + median);
                            }
                            else
                            {
                                return -1;
                            }
                        }
                        return this._search(key, index, begin, length, array, b);
                    }
                },
                toString: function ()
                {
                    return this.Keys.toString();
                }
            }
        }
    },
    /* ----------------------------------------------------------------------
    Bash.Utils : Utility methods
    ---------------------------------------------------------------------- */
    Utils: {
        /* ----------------------------------------------------------------------
        Provides info about the current URL
        ---------------------------------------------------------------------- */
        PathInfo: function (str)
	    {
	        var _qs = (str.indexOf("?") > -1) ? new Bash.Utils.QueryString(str.substring(str.indexOf("?") + 1, str.length)) : null;	        
	        var _fullName = (_qs!=null) ? str.substring(0, str.indexOf("?")) : str;
    	
		    return {
			    Path: str,
			    Extension: ((str.lastIndexOf("?") != -1) ? str.substring(str.lastIndexOf("."), str.lastIndexOf("?")) : str.substring(str.lastIndexOf("."), str.length)),
			    FileName: ((str.lastIndexOf("/")!=-1) ? str.substring(str.lastIndexOf("/") + 1, str.lastIndexOf(".")) : str.substring(0, str.lastIndexOf("."))),
			    QueryString: _qs,
			    FullName: _fullName
		    };	
	    },
	    /* ----------------------------------------------------------------------
        Query String object
        ---------------------------------------------------------------------- */
	    QueryString: function (path)
	    {
	        if (/^([^&=]+=[^&]*)(&[^=]+=[^&]*)*$/.test(path))
	        {
	            // Separate the data
	            var _args = path.substring(path.indexOf("?") + 1, path.length);	        
	            var _pairs = _args.split("&");
	            var _count = _pairs.length;
	            var _data = [];
	            var _keys = [];
    	        
	            var key;
	            var value;
	            var pair;
    	        	        
	            for (var i=_count - 1; i >= 0; i--)
	            {
	                pair = _pairs[i];
	                key = unescape(pair.substring(0, pair.indexOf("=")));	            
	                value = unescape(pair.substring(pair.indexOf("=") + 1, pair.length));    	            
	                _keys[i] = key;	            
	                _data[i] = {Key: key, Value: value};
	            }
	                	        
	            return {
	                Args: _args,
	                Data: _data,
	                Keys: _keys,
	                Get: function (key)
	                    {	                    
	                        for(var i=0; i<this.Data.length;i++)
	                        {
	                            if(this.Data[i].Key.toUpperCase() == key.toUpperCase()) { return this.Data[i].Value; }                       	                        	                        
	                        }    	                    
	                        return null;	                    
	                    },
	                toString: function ()
	                    {
	                       return _args;
	                    }
	                };
	        }
	        else
	        {
	            return null;
	        }
	    }
    },
    /* ----------------------------------------------------------------------
    Select a DOM element or a collection of elements
    Pass a #{string} to select by ID, .{string} to select by class or
    {string} to select by tag name.
    ---------------------------------------------------------------------- */
    Select: function (elm)
    {  
        elm = elm.Trim();
              
        if (elm.indexOf("#") == 0)
        {
            // Get an object by ID
            return document.getElementById(elm.replace("#",""));     
        }
        else if(elm.indexOf(".") == 0)
        {
            // Get all elements with that class name
            var elements = document.getElementsByTagName("*");
            var count = elements.length;
            var outArray = [];
            var className = elm.replace(".","").toUpperCase();
            
            for (var i=0; i< count; i++)
            {
                if(elements[i].className.toUpperCase() == className)
                {
                    outArray.push(elements[i]);
                }
            }            
            
            return outArray;            
        }
        else if (elm.indexOf("#") == -1 && elm.indexOf(".") == -1)
        {
            /// Get all elements with that tag name 
            return document.getElementsByTagName(elm);
        }
        
        return null;
    }
};

// --------------------------------------------------------------------------
// ENUMERATIONS
// --------------------------------------------------------------------------

var NLZFilter = {
    NetworkBuyTypesByTitleId: 1,
    LocalBuyTypesByTitleId: 2,
    ZoneBuyTypesByTitleId: 4,
    NetworkBuyTypesByMetaTitleId: 8,
    LocalBuyTypesByMetaTitleId: 16,
    ZoneBuyTypesByMetaTitleId: 32,
    SyndicatedBuyTypesByTitleId: 64,
    SyndicatedBuyTypesByMetaTitleId: 128,
    All: 255
};

var NLAFilter = {
    All: 3,
    National: 1,
    Local: 2
}

var MediaType = {
    None: 0,
    Radio: 1,
    Print: 2,
    Television: 4,
    Cable: 8,
    AllMedia: 15
}

var TVBuyTypes = {
        AAA: {Name: "All Buys", Code: "AAA", NLZ: NLZFilter.All, NLA: NLAFilter.All},
        NTN: {Name: "Network Break", Code: "NTN", NLZ: NLZFilter.NetworkBuyTypesByTitleId, NLA: NLAFilter.National},
        STA: {Name: "Syndicated Break", Code: "STA", NLZ: NLZFilter.SyndicatedBuyTypesByTitleId, NLA: NLAFilter.All},
        LTA: {Name: "Local Break", Code: "LTA", NLZ: NLZFilter.LocalBuyTypesByTitleId, NLA: NLAFilter.All},
        LTN: {Name: "National/Local", Code: "LTN", NLZ: NLZFilter.LocalBuyTypesByTitleId, NLA: NLAFilter.National},
        LTL: {Name: "Local/Local", Code: "LTL", NLZ: NLZFilter.LocalBuyTypesByTitleId, NLA: NLAFilter.Local}
    };
    
var RadioBuyTypes = {
    AAA: {Name: "All Accounts", Code: "AAA", NLZ: NLZFilter.All, NLA: NLAFilter.All},
    AAL: {Name: "Local Accounts", Code: "AAL", NLZ: NLZFilter.All, NLA: NLAFilter.Local}    
};

// -------------------------------------------------------------------------------------
// EXTEND THE DICTIONARY OBJECT
// -------------------------------------------------------------------------------------

// -------------------------------------------------------------------------------------
// Create a Dictionary from an array
// The array contains {Key: key, Value: value} pairs.
// This method is faster to create a Dictionary with a lot of objects
// -------------------------------------------------------------------------------------
Bash.Collections.Dictionary.FromArray = function (array)
{
    // Sort the array
    array = array.QuickSort(function (a, b)
        {
            if(a.Key < b.Key)
            {
                return -1;
            }
            else if(a.Key > b.Key)
            {
                return 1;
            }
            else
            {
                return 0;
            }    
        });
        
    var dico = new Bash.Collections.Dictionary();
    
    dico.Items = array;
        
    for (var i=0; i<array.length; i++)
    {
        dico.Keys.push(array[i].Key);
    }
    
    return dico;
};

// -------------------------------------------------------------------------------------
// EXTEND THE ARRAY OBJECT
// -------------------------------------------------------------------------------------

// -------------------------------------------------------------------------------------
// Copy the Array.
// -------------------------------------------------------------------------------------
Array.prototype.Copy = function ()
{
    return this.concat([]);
}

// -------------------------------------------------------------------------------------
// Sort the array and return the resulting array
// A compare function can be provided to compare custom data types
// -------------------------------------------------------------------------------------
Array.prototype.QuickSort = function (fnCompare)
{ 
    if(fnCompare == null)
    {
        fnCompare = function (a, b)
        {
            if(a < b) { return -1; }
            else if(a > b) { return 1; }
            else { return 0; }
        };
    }
  
    if(this.length <= 1)
    {
        return this;
    }
    
    var m = this[0];
    var st = [];
    var et = [];
    var gt = [];       
    var current = null;    
           
    for(var i=0; i<this.length; i++)
    {
        current = this[i];
        
        if(fnCompare(current, m) < 0)
        {
            st.push(current);
        }
        else if(fnCompare(current, m) > 0)
        {
            gt.push(current)
        }
        else
        {
            et.push(current);                
        }           
    }
    
    st = st.QuickSort(fnCompare);
    gt = gt.QuickSort(fnCompare);
    
    return st.concat(et).concat(gt);
};

// -------------------------------------------------------------------------------------
// Return the item with the highest value from the array.
// -------------------------------------------------------------------------------------
Array.prototype.Max = function (fnCompare)
{
    if(this.length == 1)
    {
        return this[0];
    }
    
    fnCompare = fnCompare || function (a, b)
    {
        if (a < b) { return -1; }
        else if (a > b) { return 1; }
        else { return 0; }
    };

    var max = this[0];
    
    for(var i=0; i<this.length; i++)
    {
        if(fnCompare(max, this[i]) == -1)
        {
            max = this[i];
        }
    }
    
    return max;
};

// -------------------------------------------------------------------------------------
// Return the item with the lowest value from the array.
// -------------------------------------------------------------------------------------
Array.prototype.Min = function (fnCompare)
{        
    fnCompare = fnCompare || function (a, b)
    {
        if (a < b) { return 1; }
        else if (a > b) { return -1; }
        else { return 0; }
    };
    
    return this.Max(fnCompare);
};

// -------------------------------------------------------------------------------------
// EXTEND THE NUMBER OBJECT
// -------------------------------------------------------------------------------------

// -------------------------------------------------------------------------------------
// Output the number as a string according to the format string.
// -------------------------------------------------------------------------------------
Number.prototype.Format = function (f)
{
    var output = [];
    var i;
    
    f = f || "#,#";

    var number = this.toString();
    
    // Get number and formatting parts --------------------------
    number = number.split(".");
    
    var nInt = number[0].split("").reverse();
    var nDec = null;
    
    var format = f.split(".");
    
    var fInt = format[0].split("").reverse();
    var fDec = null;
    
    if(number.length > 1)
    {
        nDec = number[1].split("");
    }
    
    if(format.length > 1)
    {
        fDec = format[1].split("");
    }  
    
    // Deal with thousands separators
    var addThousandSeparators = (f == "#,#");
    
    var lastFormattingChar = fInt[fInt.length - 1];
    var charOffset = 0;
    var nChar = null;
    var fChar = null;
    var closed = false;  
    
    // Build integral part --------------------------------------
    for(i=0; i < fInt.length || (i - charOffset) < nInt.length; i++)
    {
        nChar = nInt[i - charOffset];
        fChar = fInt[i];
    
        if(fChar != null && !addThousandSeparators)
        {                
            if(fChar == "#")
            {
                // Digit placeholder
                output.push(nChar);
                closed = true;
            }
            else if(fChar == "0")
            {
                // Digit placeholder  
                if(nChar == null)
                {       
                    output.push("0");
                }
                else
                {
                    output.push(nChar);
                }
                closed = false;
            }
            else
            {
                output.push(fChar);
                charOffset++;
            }
        }
        else
        {
            if(!closed)
            {
                output.push(nChar);
            }
        }
    }
    
    if(addThousandSeparators)
    {
        for(i=3; i<output.length; i+=4)
        {
            output.splice(i, 0, ",");
        }
    } 
    
    output = output.reverse(); 
    
    // Build decimal part --------------------------------------   
    if(fDec != null && nDec != null)
    {
        output.push(".");
        
        lastFormattingChar = fDec[fDec.length - 1];
        charOffset = 0;
        nChar = null;
        closed = false;
        
        for(i=0; i < fDec.length || (i - charOffset) < nDec.length; i++)
        {
            nChar = nDec[i - charOffset];
            
            if(fDec[i] != null)
            {                
                if(fDec[i] == "#")
                {
                    // Digit placeholder
                    output.push(nChar);
                    closed = true;
                }
                else if(fDec[i] == "0")
                {
                    // Digit placeholder  
                    if(nChar == null)
                    {       
                        output.push("0");
                    }
                    else
                    {
                        output.push(nChar);
                    }
                    closed = false;
                }
                else
                {
                    output.push(fDec[i]);
                    charOffset++;
                }
            }
            else
            {
                if(!closed)
                {
                    output.push(nChar);
                }
            }         
        }
    }    
    
    return output.join("");
    
}

// -------------------------------------------------------------------------------------
// EXTEND THE DATE OBJECT
// -------------------------------------------------------------------------------------

// -------------------------------------------------------------------------------------
// Convert a pseudo integer representing the date as yyyyMMddHHmmss to an actual Date object
// -------------------------------------------------------------------------------------
Date.FromPseudoInteger = function (dateInt)
{        
    var year = 0;
    var month = 0;
    var day = 0;
    var hours = 0;
    var minutes = 0;
    var seconds = 0;
    
    var remainder = 0;    
    
    year = Math.floor(dateInt / 10000000000);        
    remainder = dateInt - (year * 10000000000);        
    month = Math.floor(remainder / 100000000);        
    remainder = remainder - (month * 100000000);        
    day = Math.floor(remainder / 1000000);        
    remainder = remainder - (day * 1000000);        
    hours = Math.floor(remainder / 10000);        
    remainder = remainder - (hours * 10000);        
    minutes = Math.floor(remainder / 100);        
    seconds = remainder - (minutes * 100);
    
    return new Date(Date.parse(year + "/" + month + "/" + day + " " + hours + ":" + minutes + ":" + seconds));
}

// -------------------------------------------------------------------------------------
// [Static] Get the difference in days between two dates
// -------------------------------------------------------------------------------------
Date.Difference = function (date1, date2)
{
    var DSTAdjust = 0;
    var oneMinute = 1000 * 60;
    var oneDay = oneMinute * 60 * 24;

    date1 = date1.DateOnly();
    date2 = date2.DateOnly();
    
    if (date2 > date1)
    {
        DSTAdjust = (date2.getTimezoneOffset() - date1.getTimezoneOffset()) * oneMinute;
    }
    else
    {
        DSTAdjust = (date1.getTimezoneOffset() - date2.getTimezoneOffset()) * oneMinute;    
    }
    
    var diff = Math.abs(date2.getTime() - date1.getTime()) - DSTAdjust;
    return Math.ceil(diff/oneDay);
};

// -------------------------------------------------------------------------------------
// Get the exact number of seconds since January 1, 1970, neutralizing the timezone offset
// -------------------------------------------------------------------------------------
Date.prototype.AsSeconds = function ()
{
    return (this.valueOf() / 1000) - (this.getTimezoneOffset() * 60)
};

// -------------------------------------------------------------------------------------
// Get the date component of the Date object
// -------------------------------------------------------------------------------------
Date.prototype.DateOnly = function ()
{
    var d = new Date(this.valueOf());
    d.setHours(0);
    d.setMinutes(0);
    d.setSeconds(0);
    d.setMilliseconds(0);
    return d;
};

// -------------------------------------------------------------------------------------
// Convert a date to a MM/dd/yyyy HH:mm:ss string
// ------------------------------------------------------------------------------------- 
Date.prototype.toMilitaryString = function ()
{
    return this.Format("MM/dd/yyyy HH:mm:ss");
};

// -------------------------------------------------------------------------------------
// Returns the formatted date as a string
// -------------------------------------------------------------------------------------
Date.prototype.Format = function (format)
{    
    if (format.length==0) { format = "yyyy/MM/dd HH:mm:ss";	}	
	
	var output = format;
	var rep = "";	
	
	if(output == "D") { return this.toString(); }
	
	if(output.indexOf("yyyy") != -1)
	{
		rep = this.getFullYear();
		output = output.replace(/yyyy/g, rep);
	}
	else if(output.indexOf("yy") != -1)
	{
		rep = this.getFullYear().toString().substring(2,4);
		output = output.replace(/yy/g, rep);
	}
	
	if(output.indexOf("dd") != -1)
	{
		rep = ( this.getDate() < 10 ) ? "0" + this.getDate() : this.getDate();
		output = output.replace(/dd/g, rep);
	}
	else if(output.indexOf("d") != -1)
	{
		rep = this.getDate();
		output = output.replace(/d/g, rep);
	}
	
	if(output.indexOf("MM") != -1)
	{
		rep = (this.getMonth() + 1 < 10) ? "0" + (this.getMonth() + 1) : this.getMonth() + 1;
		output = output.replace(/MM/g, rep);
	}
	else if(output.indexOf("M") != -1)
	{
		rep = this.getMonth() + 1;
		output = output.replace(/M/g, rep);
	}
	
	var military = ((output.indexOf("P") < 0) || output.indexOf("tt"));
	var hour = 0;
	
	if(output.indexOf("hh") != -1)
	{
		hour = this.getHours();
		if(hour>12) { hour = hour - 12; }			
		if(hour==0) { hour = 12; }			
		rep = (hour < 10) ? "0" + hour : hour;
		output = output.replace(/hh/g, rep);

	}
	else if(output.indexOf("h") != -1)
	{		
		hour = this.getHours();
		if(hour>12) {hour = hour - 12};			
		rep = hour;
		output = output.replace(/h/g, rep);
	}
	
	if(output.indexOf("HH") != -1)
	{
		rep = (this.getHours() < 10) ? "0" + this.getHours() : this.getHours();
		output = output.replace(/HH/g, rep);			
	}
	else if(output.indexOf("H") != -1)
	{
		rep = this.getHours();
		output = output.replace(/H/g, rep);
	}
	
	if(output.indexOf("mm") != -1)
	{
		rep = (this.getMinutes() < 10) ? "0" + this.getMinutes() : this.getMinutes();
		output = output.replace(/mm/g, rep);
	}
	else if(output.indexOf("m") != -1)
	{
		rep = this.getMinutes();
		output = output.replace(/m/g, rep);
	}
	
	if(output.indexOf("ss") != -1)
	{
		rep = (this.getSeconds() < 10) ? "0" + this.getSeconds() : this.getSeconds();
		output = output.replace(/ss/g, rep);
	}
	else if(output.indexOf("s") != -1)
	{
		rep = this.getSeconds();
		output = output.replace(/s/g, rep);
	}
	
	if(output.indexOf("P") != -1)
	{
		if(this.getHours() < 12)
		{ output = output.replace(/P/g, "AM"); }
		else
		{ output = output.replace(/P/g, "PM"); }			
	}
	
	if(output.indexOf("aa") != -1)
	{
		if(this.getHours() < 12)
		{ output = output.replace(/aa/g, "AM"); }
		else
		{ output = output.replace(/aa/g, "PM"); }			
	}
	else if (output.indexOf("a") != -1)
	{
	    if(this.getHours() < 12)
		{ output = output.replace(/a/g, "A"); }
		else
		{ output = output.replace(/a/g, "P"); }
	}
	
	return output;
};

// --------------------------------------------------------------------------------
// Bash.Cookies: used to access client-side cookies.
// --------------------------------------------------------------------------------
Bash.Cookies = {};
    
// --------------------------------------------------------------------------------
// Get either all cookies or one cookie by its name.
// --------------------------------------------------------------------------------
Bash.Cookies.Get = function (name)
{
    var _raw = document.cookie.split(";");
    var _cookies = [];
    var i;
    
    if(name)
    {
        var c = null;
        name = name.toUpperCase();
        
        for(i=0; i < _raw.length; i++)
        {
            c = HttpCookie.Load(_raw[i]); 
            
            if(c.Name().toUpperCase() == name)
            {
                return c;
            }
        }
        
        return null; // No cookie found            
    }
    else
    {        
        for(i=0; i < _raw.length; i++)
        {
            _cookies.push(HttpCookie.Load(_raw[i]));
        }
    
        return _cookies;  
    }     
}

// --------------------------------------------------------------------------------
// Remove cookie by name
// --------------------------------------------------------------------------------
Bash.Cookies.Remove = function (name)
{
    var _cookie = Bash.Cookies.Get(name);
    
    if(_cookie != null)
    {
        _cookie = new HttpCookie(name, new Date());  
        _cookie.Save();          
    }                
};

// --------------------------------------------------------------------------------
// Create a cookie.  "expires" and "path" arguments are optional.
// --------------------------------------------------------------------------------    
var HttpCookie = function (name, expires, path)    
{
    var now = new Date();

    var _cookie     = {};           // Output object      
    var _values     = {};           // Output values dictionary      
    var _value      = "";           // Output single value
    var _name       = name.Trim();  // Name       
    var _expires    = expires;
    var _path       = path || "/";  // Domain 
    
    if(_expires == null)
    {
        _expires = new Date(now.setDate(now.getDate() + 1));
    }
           
    var toString = function ()
    {
        return "[HttpCookie:" + _name + "]" ;
    };
    
    // Add or update a value        
    var SetValue = function (keyOrValue, value)
    {                   
        if(value == null)
        {            
            // Set the unique value for the cookie.
            _value = keyOrValue;
            _values = {};                
        }
        else
        {
            _values[keyOrValue] = value;
        }               
    };
    
    // Save the cookie        
    var Save = function ()
    {
        str = _name;            
        
        if(_value.length > 0)
        {
            str += "=" + escape(_value);                
        }
        else
        {
            str += "=";
            
            var pairs = [];
        
            for(var p in _values)
            { 
                if(typeof(_values[p]) != 'function')
                {
                    pairs.push(p + "=" + escape(_values[p]));
                }
            }
            
            str += pairs.join("&");
        }
        
        if(_expires !== 0)
        {        
            str += "; expires=" + _expires.toGMTString();
        }
        
        str += "; path=" + _path;
        
        document.cookie = str;
        
        return str;                              
    };
    
    var Value = function ()
    {
        return _value;
    };
    
    var Values = function ()
    {
        return _values;  
    };
    
    var Name = function ()
    {
        return _name;  
    };
    
    var Clear = function ()
    {
        _values = {};
        _value = "";
    };
    
    _cookie.Name = Name;
    _cookie.Values = Values; 
    _cookie.Value =  Value; 
    _cookie.toString = toString;
    _cookie.Save = Save;
    _cookie.SetValue = SetValue;
    _cookie.Clear = Clear;

    return _cookie;
};

// --------------------------------------------------------------------------------
// Load data into a cookie.
// --------------------------------------------------------------------------------    
HttpCookie.Load = function (content)
{
    var _name = content;
    var _strKeys = "";
    var _cookie = null;
    var _hasValue = false;
    
    // Get name
    if(_name.indexOf("=") > -1)
    {
        _name = _name.substring(0, _name.indexOf("="));     
        
        // Get value(s)        
        _strKeys = content.substring(content.indexOf("=") + 1, content.length);    
        _hasValue = true;        
    }
    
    _cookie = new HttpCookie(_name);        
    
    if(_hasValue)
    {
        if(_strKeys.length > 0)
        {
            // HttpCookie contains keys            
            if(_strKeys.indexOf("=") > -1)
            {
                // HttpCookie has more than one key
                var keys = _strKeys.split("&");
                
                var keyName = null;
                var keyValue = null;                  
                
                for(var i=0; i<keys.length; i++)
                {
                    keyName = keys[i].substring(0, keys[i].indexOf("="));  
                    keyValue = keys[i].substring(keys[i].indexOf("=") + 1, keys[i].length);
                    _cookie.SetValue(keyName, unescape(keyValue));
                }              
            }
            else
            {
                // HttpCookie only has one value
                _cookie.SetValue(unescape(_strKeys));
            }
        }           
    }        

    return _cookie;
};

/// -------------------------------------------------------------------------------------
/// EXTEND THE STRING OBJECT
/// -------------------------------------------------------------------------------------

// -------------------------------------------------------------------------------------
// [Static] Pass a string whith indexed placeholders and replace them by the parameter at the matching index.
// -------------------------------------------------------------------------------------
String.Format = function (str)
{
    var output = str;
    var reg = null;
                   
    if(arguments.length > 1)
    {
        var arg = null;
                
        for(var i=1; i < arguments.length; i++)
        {
            reg = new RegExp("\\{" + (i-1) + "\\}","g");
            arg = (arguments[i] == null) ? "NULL" : arguments[i].toString() ;
            output = output.replace(reg, arg);
        }
    }    
    return output;    
};

// -------------------------------------------------------------------------------------
// Replace indexed placeholders from the string by the parameter at the matching index.
// -------------------------------------------------------------------------------------
String.prototype.Format = function()
{
    var output = this;
    var reg = null;   

    for(var i=0;i<arguments.length;i++)
    {
        reg = new RegExp("\\{" + (i) + "\\}","g");
        output = output.replace(reg, arguments[i]);
    }
    return output;
};

// -------------------------------------------------------------------------------------
// Replace all occurences of str1 by str2
// -------------------------------------------------------------------------------------
String.prototype.Replace = function (str1, str2)
{
    return this.replace(new RegExp(str1,"g"), str2);
};

// -------------------------------------------------------------------------------------
// Remove white spaces at the left of a string
// -------------------------------------------------------------------------------------
String.prototype.TrimLeft = function ()
{
    return this.replace(/^\s+/g, "");
};

// -------------------------------------------------------------------------------------
// Remove white spaces at the right of a string
// -------------------------------------------------------------------------------------
String.prototype.TrimRight = function ()
{
    return this.replace(/\s+$/g, "");
};

// -------------------------------------------------------------------------------------
// Remove white spaces at the left and right of a string
// -------------------------------------------------------------------------------------
String.prototype.Trim = function ()
{
    return this.TrimRight().TrimLeft();
};

// -------------------------------------------------------------------------------------
// ToJSON: Convert JavaScript object to JSON strings
// -------------------------------------------------------------------------------------

// Convert String to to JSON string.
String.prototype.ToJSON = function ()
{
    return "'" + this + "'";
}

// Convert Number to JSON string    
Number.prototype.ToJSON = function ()
{
    return this.toString();
} 

// Convert Boolean to JSON string
Boolean.prototype.ToJSON = function ()
{
    return this.toString();
}

// Convert Object to JSON string   
Object.prototype.ToJSON = function ()
{
    var obj         = this;
    var json        = "{";        
    var properties  = [];        
    var type        = null;
    var value       = null;
    
    // Null becomes an empty object        
    if(obj === null)
    {
        return "{}";
    }
    
    if(this.getDate)
    {
        // Date object
        return "'" + this.Format("yyyy/MM/dd hh:mm:ss") + "'";
    }
    else if (this.join)
    {
        // Array object
        var items = [];            
                   
        for(var i=0; i<this.length; i++)
        {  
            items[i] = this[i].ToJSON();
        } 
             
        return "[" + items.join(",") + "]";       
    }
    else
    { 
        // Object                   
        // Iterate through all properties and turn them into JSON strings.        
        for(var p in obj)
        {            
            value = obj[p];
            type = typeof(value);
            
            if(value != null) // NULL keys are ignored
            {     
                if(type == 'string')
                {
                    properties.push("'" + p + "':'" + value + "'");
                }
                else if(type == 'number' || type == 'boolean')
                {
                    properties.push("'" + p + "':" + value);
                }
                else if(value.getDate)
                {
                    properties.push("'" + p + "':'" + value.Format("yyyy/MM/dd hh:mm:ss") + "'");
                } 
                else if(type == 'object')
                {
                    properties.push("'" + p + "':" + value.ToJSON());
                }  
            }                  
        }   
                 
        return "{" + properties.join(",") + "}";
    }        
}

//-----------------------------------------------------------------------------------------
// StringBuilder class
// Used to build large strings.  Much more efficient than regular concatenation.
//-----------------------------------------------------------------------------------------
var StringBuilder = function (str)
{
    var _array = [];
    if (typeof(str) != "undefined")
        _array.push(str); 

    return {
        Append: function (str)
        {
            _array.push(str);
        },
        AppendLine: function (line)
        {
            _array.push("\n");
            _array.push(line);
        },
        toString: function ()
        {
            return  _array.join("");
        },
        ToString: function ()
        {
            return  _array.join("");
        },
        Length: function ()
        {
            return _array.length;
        }
    };
}

//-----------------------------------------------------------------------------------------
// TimeSpan class
// Represents a time interval.
//-----------------------------------------------------------------------------------------
var TimeSpan = function (seconds)
{
    var _isNegative     = (seconds < 0);    
    seconds             = Math.abs(seconds);

    var _totalSeconds   = seconds;
    var _totalMinutes   = _totalSeconds / 60;    
    var _totalHours     = _totalSeconds / 3600;
    
    var _hours          = Math.floor(_totalHours);
    var _minutes        = Math.floor((seconds % 3600) / 60);
    var _seconds        = Math.floor(seconds % 60);    
    
    return {
        TotalSeconds: _totalSeconds,
        TotalMinuts: _totalMinutes,
        TotalHours: _totalHours,
        Hours: _hours,
        Minutes: _minutes,
        Seconds: _seconds,
        IsNegative: _isNegative,
        toString: function(format)
        {
            var rep;
            var output = format || "hh:mm:ss";
        	
	        if(output.indexOf("hh") != -1)
	        {
		        rep = (_hours < 10) ? "0" + _hours : _hours;
		        output = output.replace(/hh/g, rep);			
	        }
	        else if(output.indexOf("h") != -1)
	        {
		        rep = _hours;
		        output = output.replace(/h/g, rep);
	        }
        	
	        if(output.indexOf("mm") != -1)
	        {
		        rep = (_minutes < 10) ? "0" + _minutes : _minutes;
		        output = output.replace(/mm/g, rep);
	        }
	        else if(output.indexOf("m") != -1)
	        {
		        rep = _minutes;
		        output = output.replace(/m/g, rep);
	        }
        	
	        if(output.indexOf("ss") != -1)
	        {
		        rep = (_seconds < 10) ? "0" + _seconds : _seconds;
		        output = output.replace(/ss/g, rep);
	        }
	        else if(output.indexOf("s") != -1)
	        {
		        rep = _seconds;
		        output = output.replace(/s/g, rep);
	        }  
	        
	        if(_isNegative)
	        {
	            output = "- " + output;
	        }
	        
	        return output;          
        }        
    };        
}

//-----------------------------------------------------------------------------------------
// GenericXmlResponse class
// Client side interface to the ASP.NET MediaMonitors.Web.GenericXmlResponse class
//-----------------------------------------------------------------------------------------
var GenericXmlResponse = function (xml)
{
    // Constants
    var CTYPE_TEXT      = 0;
    var CTYPE_XML       = 1;

    // Initialize the variables    
    var exceptions      = [];
    var statusCode      = -1;
    var statusText      = "";
    var contentType     = 0;  
    var contentValue    = "";  
    var data            = null;    
    var list            = null;
    var count           = 0;
    var node            = null;
    
    // Parse the status
    list = xml.getElementsByTagName("Response");   
    
    if(list.length <= 0)
    {
        alert("Error:  The xml document doesn't contain a Response node");
        return null;
    }
    
    node = list[0];   
    
    statusCode = parseInt(node.getAttribute("Status"), 10);
    statusText = node.getAttribute("StatusText");
    
    // Parse the content type 
    contentValue = node.getAttribute("ContentType");
    
    if(contentValue.toUpperCase() == "TEXT")
    {
        contentType = CTYPE_TEXT;        
    }
    else if(contentValue.toUpperCase() == "XML")
    {
        contentType = CTYPE_XML;        
    }
    else
    {
        alert("Warning: Unknown content type");
        return null;
    }
    
    // Parse exception nodes
    list = xml.getElementsByTagName("Exception");
    count = list.length;
    
    if(count > 0)
    {
        for (var i=0; i<count; i++)
        {
            exceptions.push(list[i].text);
        }
    }
    
    // Read the data
    list = xml.getElementsByTagName("Data");
    node = list[0];
    
    if(contentType == CTYPE_TEXT)
    {
        data = node.text;        
    } 
    else if(contentType == CTYPE_XML)
    {
        data = node;
    }
    
    return {
        Status: {
            Code: statusCode,
            Text: statusText
        },
        Exceptions: exceptions,
        ContentType: contentType,
        Data: data
    };    
}

//-----------------------------------------------------------------------------------------
// Base64 class
// JavaScript Base64 encoding and decoding
//-----------------------------------------------------------------------------------------
var Base64 = {
    // private property
    _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

    // public method for encoding
    Encode : function (input)
    {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;

        input = Base64._utf8_encode(input);

        while (i < input.length)
        {
            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);

            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;

            if (isNaN(chr2))
            {
                enc3 = enc4 = 64;
            }
            else if (isNaN(chr3))
            {
                enc4 = 64;
            }

            output = output +
            this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
            this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
        }

        return output;
    },

    // public method for decoding
    Decode : function (input)
    {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;

        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

        while (i < input.length)
        {
            enc1 = this._keyStr.indexOf(input.charAt(i++));
            enc2 = this._keyStr.indexOf(input.charAt(i++));
            enc3 = this._keyStr.indexOf(input.charAt(i++));
            enc4 = this._keyStr.indexOf(input.charAt(i++));

            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;

            output = output + String.fromCharCode(chr1);

            if (enc3 != 64) {
                output = output + String.fromCharCode(chr2);
            }
            if (enc4 != 64) {
                output = output + String.fromCharCode(chr3);
            }
        }

        output = Base64._utf8_decode(output);

        return output;
    },
    // private method for UTF-8 encoding
    _utf8_encode : function (string)
    {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";

        for (var n = 0; n < string.length; n++)
        {
            var c = string.charCodeAt(n);

            if (c < 128)
            {
                utftext += String.fromCharCode(c);
            }
            else if((c > 127) && (c < 2048))
            {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else
            {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }
        }

        return utftext;
    },

    // private method for UTF-8 decoding
    _utf8_decode : function (utftext)
    {
        var string = "";
        var i = 0;
        var c = c1 = c2 = 0;

        while ( i < utftext.length )
        {
            c = utftext.charCodeAt(i);

            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            }
            else if((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i+1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else
            {
                c2 = utftext.charCodeAt(i+1);
                c3 = utftext.charCodeAt(i+2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }
        }
        return string;
    }
}


//-----------------------------------------------------------------------------------------
// Handle multiple asynchronous (non-blocking) processes.
//-----------------------------------------------------------------------------------------
function Async(
    fn,         // a reference to the function to execute
    args,       // args an array [] of objects
    fnComplete  // function that will be executed when the process has completed
    )
{    
    args = args || [];
    
    // Configure current process
    var process = {
        Id: Async.Queue.length,
        Exec: fn,
        Args: args,
        OnComplete: fnComplete           
    };     
    
    // Append process to queue
    Async.Queue.push(process);
    
    setTimeout("Async.Execute()", 1);     
} 

// Holds references to the async processes
// ...................................................................................  
Async.Queue = [];

// Execute the queued processes. 
// ...................................................................................        
Async.Execute = function ()
{
    var process = null;

    for(var i=0; i<Async.Queue.length; i++)
    {
        // execute
        process = Async.Queue[i];
        process.Exec(
            process.Args[0],
            process.Args[1],
            process.Args[2],
            process.Args[3],
            process.Args[4],
            process.Args[5],
            process.Args[6],
            process.Args[7],
            process.Args[8],
            process.Args[9]
            );
            
        if(process.OnComplete)
        {
            process.OnComplete();
        }
    }     
    
    Async.Queue = [];
};

//-----------------------------------------------------------------------------------------
// Allows delaying the execution of a function
//-----------------------------------------------------------------------------------------
var Timer = function ()
{
    var timer = {};        
    var _timerRef = -1;
    
    var Start = function (fn, delay, data)
    {
        Timer.Callback = fn;
        Timer.Data = data;
        _timerRef = setTimeout("Timer.Exec();", delay);
    };
    
    var Clear = function ()
    {
        clearTimeout(_timerRef);   
        Timer.Callback = null;
        Timer.Data = null;        
    }; 
    
    timer.Clear = Clear;
    timer.Start = Start;
    
    return timer;         
};

// Global references to the Data and Callback objects.
// ...................................................................................
Timer.Data = null;    
Timer.Callback = null;

// Executes the Callback object, passing the Data as the argument.
// ...................................................................................
Timer.Exec = function ()
{
    if(Timer.Callback != null)
    {
        Timer.Callback(Timer.Data);
    }        
    Timer.Callback = null;
    Timer.Data = null;
};

//-----------------------------------------------------------------------------------------
// Provides quick selection by id.
//-----------------------------------------------------------------------------------------
function $(id)
{
    return document.getElementById(id);
}

//-----------------------------------------------------------------------------------------
// Element - argument can be a DOM object, or an ID
//-----------------------------------------------------------------------------------------
Bash.Element = function (elm)
{
    if (typeof(elm) == 'string')
    {
        elm = document.getElementById(elm);
    }
    
    //--------------------------------------
    // Append a class to the current element
    //--------------------------------------
    var AppendClass = function (className)
    {
        className = className.toLowerCase();
        
        if (elm.className.indexOf(className) == -1)
        {
            elm.className += " " + className;
        }      
    }
    
    //--------------------------------------
    // Remove a class from the current element
    //--------------------------------------    
    var RemoveClass = function (className)
    {
        className = className.toLowerCase();
        
        if (elm.className.indexOf(" " + className) != -1)
        {
            elm.className = elm.className.Replace(className, '').Trim();
        }      
    }
    
    var InsertElementAt = function (newElm, index)
    {
        elm.insertBefore(newElm, elm.childNodes[index]);
    }
    
    elm.AppendClass = AppendClass;
    elm.RemoveClass = RemoveClass;
    elm.InsertElementAt = InsertElementAt;
    
    return elm;
}

//-----------------------------------------------------------------------------------------
// Returns an Elment object
//-----------------------------------------------------------------------------------------
function $$(elm)
{
    return (new Bash.Element(elm));
}