/*
    Class: DLSupport
        JavaScript framework containing common functionalities.

        This is used when we cannot use Prototype.
*/
var DLSupport = {
    /*
        Group: Data

        Object: Browser
            Contains browser information.

            The stucture of this object will look something similar to the
            following:
            (code)
            Object(
                Gecko: {
                    (boolean) Whether or not the current browser is Gecko based
                    like Firefox.
                },
                IE: {
                    (boolean) Whether or not the current browser is Internet
                    Explorer.
                },
                MobileSafari: {
                    (boolean) Whether or not the current browser is Mobile
                    Safari like the iPhone.
                },
                Opera: {
                    (boolean) Whether or not the current browser is Opera.
                },
                WebKit: {
                    (boolean) Whether or not the current browser is WebKit based
                    like Safari and Google Chrome.
                }
            )
            (end)
    */
    Browser: {},

    /*
        Array: ErrorLog
            (array) Contains a log of all the errors during the script
            execution.
    */
    ErrorLog: [],

    /*
        Integer: IdCounter
            (int) Counter used for generating unqiue IDs.
    */
    IdCounter: 1,

    /*
        String: ModuleUrl
            (string) The URL to the directory containing this module. This will
            be set dynamically after the script loads.
    */
    ModuleUrl: '',

    /*
        Group: Methods

        Method: addClassName
            Add the given class name ot the given DOM element.

        Parameters:
            obj - (object) The DOM element that the class name will be added to
                if it does not already have it.
            class_name - (string) The class name to be added.
    */
    addClassName: function(obj, class_name) {
        try {
            if(!DLSupport.hasClassName(obj, class_name)) {
                obj.className += (obj.className ? ' ' : '') + class_name;
            }
        } catch(e) {
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.addClassName', [obj, class_name], e.message);
        }
    },

    /*
        Method: addErrorLog
            Add a message to the error log.

        Parameters:
            method - (string) The name of the method that generated the error.
            parameters - (array) The parameters that was given to the method
                that generated the error.
            message - (string) The error message.
    */
    addErrorLog: function(method, parameters, message) {
        var item = {
            method: method,
            parameters: parameters,
            message: message
        };

        DLSupport.ErrorLog.push(item);
    },

    /*
        Method: checboxSelectAllToggle
            Check or uncheck all input checkboxes with the same class name as
            the given target based on the current status of the given element.

        Parameters:
            element - (object) The DOM element in which if it is checked, all
                input boxes with the given class name, will be checked and vice
                versa.
            target - (string) The class name of all input checkboxes that will
                be checked or unchecked.
    */
    checkboxSelectAllToggle: function(element, target) {
        // Get all input check boxes
        var check_boxes = document.getElementsByTagName('input');

        // Now go through list of input boxes
        var class_name;
        for(var i = 0; i < check_boxes.length; ++i) {
            class_name = check_boxes[i].className;

            // Set element's status if input has given class name
            if(class_name.indexOf(target) >= 0) {
                check_boxes[i].checked = element.checked;
            }
        }
    },

    /*
        Method: childElements
            Retrieves an array of all direct child DOM elements of the given
            element.

        Parameters:
            parent - (object) The DOM element that will serve as the parent.

        Returns:
            (array) An array of DOM elements that is the child of the given DOM
            element.
    */
    childElements: function(parent) {
        try {
            var result = [];

            for(var i = 0; i < parent.childNodes.length; ++i) {
                // Make sure it is a DOM element
                if(parent.childNodes[i].nodeType == 1) {
                    result.push(parent.childNodes[i]);
                }
            }

            return result;
        } catch(e) {
            DLSupport.addErrorLog('DLSupport.childElements', [parent], e.message);

            return [];
        }
    },

    /*
        Method: cloneArray
            Create a copy of the given array.

        Parameters:
            data - (array) The array to be cloned.

        Returns:
            (array) A clone of the given array.
    */
    cloneArray: function(data) {
        var result = [].concat(data);

        return result;
    },

    /*
        Method: combineArray
            Create an associative array (object) by using the first given array
            as the keys and the second given array as the value.

            It is required that both arrays be of the same length.

        Parameters:
            keys - (array) The array to be used as the keys.
            values - (array) The array to be used as the values.

        Returns:
            (object) An object with the given keys as the property names and the
            given values as its corresponding value.
    */
    combineArray: function(keys, values) {
        // Create an empty object
        var result = {};

        // Combine the arrays
        for(var i = 0; (i < keys.length) && (i < values.length); ++i) {
            result[keys[i]] = values[i];
        }

        return result;
    },

    /*
        Method: curry
            Currify the given function.

            Don't know what currify means? Google it.

        Parameters:
            func - (function) The function to currify.
            scope - (object) The scope of the function, namely, the object that
                will serve as the "this".

        Returns:
            (function) A function that wraps the given function with some of its
            parameters preset.
    */
    curry: function(func, scope) {
        // Make sure the scope is given, otherwise, default to window
        scope = scope || window;

        // Get the arguments
        var args = [].slice.call(arguments,2);

        // Create the function
        return function() {
            return func.apply(scope, args.concat([].slice.call(arguments,0)));
        };
    },

    /*
        Method: defaultValues
            Using the given base values object, copy values from the source
            object if they have the same property name.

        Parameters:
            source - (object) The object being used to copy values over from.
            base - (object) The object that contains the default values.

        Returns:
            (object) The object containing values from both the source and base
            object with the source object's value replacing the base object's
            value if they have the same property name.
    */
    defaultValues: function(source, base) {
        // Create an empty object
        var result = {};

        // Get all the property names of base
        var property_names = DLSupport.propertyNames(base);

        // Traverse the property names
        var property;
        for(var i = 0; i < property_names.length; ++i) {
            property = property_names[i];

            // If source has a value, use it, otherwise use base's
            if(source[property]) {
                result[property] = source[property];
            } else {
                result[property] = base[property];
            }
        }

        return result;
    },

    /*
        Method: delayFunction
            Delay the execution of the given function by the given number of
            seconds.

        Parameters:
            func - (function) The function whose execution will be delayed.
            delay - (int) The number of seconds to wait before the given
                function is executed.
    */
    delayFunction: function(func, delay) {
        // No need to delay the function if it is 0 seconds
        if(delay == 0) {
            func.apply(window);
        } else {
            // Convert the given delay to milliseconds
            delay = delay * 1000;

            window.setTimeout(func, delay);
        }
    },

    /*
        Method: getCookie
            Get a value from the cookie.

        Parameters:
            key - (string) The key under which the cookie was stored.

        Returns:
            (mixed) Returns the value of the cookie of the given key or null if
            it does not exist.
    */
    getCookie: function(key) {
        var nameEQ = key+'=';
        var ca = document.cookie.split(';');

        for(var i = 0; i < ca.length; ++i) {
            var c = ca[i];

            while(c.charAt(0) == ' ') {
                c = c.substring(1, c.length);
            }

            if(c.indexOf(nameEQ) === 0) {
                return c.substring(nameEQ.length, c.length);
            }
        }

        return null;
    },

    /*
        Method: getElement
            If given is a string, this will try to get the object with the same
            ID as the string. Otherwise, this just returns back the given data
            if it is anything else.

        Parameters:
            data - (mixed) A string representing the ID of a DOM element or
                anything else.

        Returns:
            (mixed) The DOM element with the given ID or whatever was given.
    */
    getElement: function(data) {
        if(typeof(data) == 'string') {
            data = document.getElementById(data);
        }

        return data;
    },

    /*
        Method: hasClassName
            Determine whether or not the given DOM element has the given class
            name.

        Parameters:
            obj - (object) The DOM element that will be tested.
            class_name - (string) The class name to look for.

        Returns:
            (boolean) Whether or not the given class name exist in the given DOM
            element.
    */
    hasClassName: function(obj, class_name) {
        try {
            // First, make sure the element actually has any class names
            var result = obj.className.length > 0;

            // If it does but doesn't match the given class
            if(result && (obj.className != class_name)) {
                // Try regular expression since it can have multiple class
                var regex = new RegExp('(^|\\s)'+class_name+'(\\s|$)');

                result = regex.test(obj.className);
            }

            return result;
        } catch(e) {
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.hasClassName', [obj, class_name], e.message);

            return false;
        }
    },

    /*
        Method: hideLoadingImage
            Hide the loading image.
    */
    hideLoadingImage: function() {
        var loading_overlay = DLSupport.getElement('DLSupport_loading_overlay');
        var loading_div = DLSupport.getElement('DLSupport_loading_div');

        // If the elements exist, hide it
        if(loading_overlay) {
            loading_overlay.style.display = 'none';
        }
        if(loading_div) {
            loading_div.style.display = 'none';
        }
    },

    /*
        Method: humanReadable
            Retrieves the given data into human readable format. Credits goes to
            Michael White & Ben Bryan.

        Parameters:
            data - (mixed) The data to be parsed and returned.

        Returns:
            (string) The string representation of the given data.
    */
    humanReadable: function(data) {
        var result = '';
        var pad_char = ' ';
        var pad_val = 4;

        var formatArray = function(obj, cur_depth, pad_val, pad_char) {
            if(cur_depth > 0) {
                ++cur_depth;
            }

            var base_pad = repeat_char(pad_val*cur_depth, pad_char);
            var thick_pad = repeat_char(pad_val*(cur_depth+1), pad_char);
            var str = '';

            if((obj instanceof Array) || (obj instanceof Object)) {
                str += "Array\n" + base_pad + "(\n";
                var property_names = DLSupport.propertyNames(obj);
                var key;
                for(var i = 0; i < property_names.length; ++i) {
                    key = property_names[i];

                    if(DLSupport.isArray(obj[key])) {
                        str += thick_pad + "["+key+"] => "+formatArray(obj[key], cur_depth+1, pad_val, pad_char);
                    } else {
                        str += thick_pad + "["+key+"] => " + obj[key] + "\n";
                    }
                }
                str += base_pad + ")\n";
            } else {
                str = obj.toString();
            }

            return str;
        };

        var repeat_char = function(len, pad_char) {
            var str = '';
            for(var i=0; i < len; ++i) {
                str += pad_char;
            };

            return str;
        };

        result = formatArray(data, 0, pad_val, pad_char);

        return result;
    },

    /*
        Method: identify
            Retrieve the ID of the given object, creating one if it does not
            exist.

        Parameters:
            obj - (object) The object to retrieve the ID from.

        Returns:
            (string) The ID of the given object. This will return an empty
            string if the object does not exist.
    */
    identify: function(obj) {
        try {
            // If object doesn't have an ID, create one
            if(obj.id.length < 1) {
                while(DLSupport.getElement('dlsupport_id_'+DLSupport.IdCounter)) {
                    ++DLSupport.IdCounter;
                }

                obj.id = 'dlsupport_id_'+IdCounter;
            }

            return obj.id;
        } catch(e) {
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.identify', [obj], e.message);

            return '';
        }
    },

    /*
        Method: inArray
            Find out whether or not the given item is in the given array.

        Parameters:
            item - (mixed) The item to look for.
            haystack - (array) The array to used for the search.
            strict - (boolean) This is optional and defaults to true. If this is
                true, then the search will use === when comparing the data,
                otherwise, it will just use ==.

        Returns:
            (boolean) Returns true if the given item is in the given array,
            false otherwise.
    */
    inArray: function(item, haystack) {
        // Get the strict parameter
        var strict = arguments[2] || true;

        // Traverse the array and look for the item
        var result = false;
        for(var i = 0; (i < haystack.length) && !result; ++i) {
            if(strict) {
                result = item === haystack[i];
            } else {
                result = item == haystack[i];
            }
        }

        return result;
    },

    /*
        Method: isArray
            Determine whether or not the given item is an array.

        Parameters:
            item - (mixed) The item to check.

        Returns:
            (boolean) Returns true if the given item is an array, false
            otherwise.
    */
    isArray: function(item) {
        var result = (item instanceof Array);

        return result;
    },

    /*
        Method: isNumeric
            Determine whether or not the given item is numeric.

        Parameters:
            item - (mixed) The item to check.

        Returns:
            (boolean) Returns true if the given item is a number, false
            otherwise.
    */
    isNumeric: function(item) {
        var result = !isNaN(item);

        return result;
    },

    /*
        Method: newEvent
            Attach a new event to the given element.

        Parameters:
            element - (object) The element in which the event should be attached
                to.
            event - (string) The event by which the given function should be
                executed. This should not include the text "on".
            func - (function) The function to append to the list.
            delay - (int) The time, in seconds, to delay the execution after the
                page has loaded. This is optional & defaults to 0.

        Returns:
            (boolean) Returns true if successful, otherwise, false.
    */
    newEvent: function(element, event, func, delay) {
        // See if we have a delay param
        var delay = arguments[3] || 0;

        // Delay the function execution
        var delayed_func = DLSupport.curry(DLSupport.delayFunction, window, func, delay);

        if(element.addEventListener) {       // W3C compliant browsers
            element.addEventListener(event, delayed_func, false);
        } else if(element.attachEvent) {     // IE
            element.attachEvent('on'+event, delayed_func);
        } else {                            // Give up
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.newEvent', [element, event, func, delay], e.message);

            return false;
        }

        return true;
    },

    /*
        Method: PropertyNames
            Get the property names of the given object.

        Parameters:
            item - (object) The item to retrieve the property names.

        Returns:
            (array) An array containing all the properties belonging to the
            given object. Note that this will only contain properties that the
            given object actually own.
    */
    propertyNames: function(item) {
        // Create an empty array
        var result = new Array();

        for(var key in item) {
            if(item.hasOwnProperty(key)) {
                result.push(key);
            }
        }

        return result;
    },

    /*
        Method: removeClassName
            Remove the given class name from the given DOM element.

        Parameters:
            obj - (object) The DOM element that will have the given class name
                removed.
            class_name - (string) The class name to remove.
    */
    removeClassName: function(obj, class_name) {
        try {
            var regex = new RegExp('(^|\\s+)'+class_name+'(\\s+|$)');

            obj.className = obj.className.replace(regex, ' ');
        } catch(e) {
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.removeClassName', [obj, class_name], e.message);
        }
    },

    /*
        Method: removeCookie
            Wrapper method around the method <setCookie>. This method will set
            the expire time to the current time.

        Parameters:
            key - (string) The key under which the cookie was stored.
    */
    removeCookie: function(key) {
        DLSupport.setCookie(key, '', 0);
    },

    /*
        Method: removeElement
            This will remove the given element from the DOM structure of the web
            site.

            Be careful when using this method because it is a mutation of the
            DOM structure. Removing the given element will also remove all of
            its children.

        Parameters:
            element - (object) The DOM element to remove.

        Returns:
            (boolean) Returns true if successful, false, otherwise.
    */
    removeElement: function(element) {
        try {
            element.parentNode.removeChild(element);

            return true;
        } catch(e) {
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.removeElement', [element], e.message);

            return false;
        }
    },

    /*
        Method: reverseArray
            Get the array that has the reverse order of the given array.

            This differs from the pre-defined reverse array function in that
            this can be set to be done recursively.

        Parameters:
            data - (array) The array to have it values reversed.
            recursive - (boolean) Optional & defaults to false. If true, recur
                into an inner arrays.

        Returns:
            (array) The given array but its order in reverse.
    */
    reverseArray: function(data) {
        // Get the recursive parameter
        var recursive = arguments[1] || false;

        // Traverse the array in descending order and add back the items
        var result = new Array();
        for(var i = data.length-1; i >= 0; --i) {
            if(recursive && DLSupport.isArray(data[i])) {
                result.push(DLSupport.reverseArray(data[i]));
            } else {
                result.push(data[i]);
            }
        }

        return result;
    },

    /*
        Method: setCookie
            Store the given value to cookie under the given key for the given
            number of days.

        Parameters:
            key - (string) The key under which the cookie's value is stored.
            value - (string) The value to be stored in the cookie.
            days - (int) The number of days until the cookie expires from the
                current date.
    */
    setCookie: function(key, value, days) {
        // Get the current date
        var date = new Date();

        // Incremenet the date by the given number of days
        date.setTime(date.getTime() + (days*24*60*60*1000));

        // Set the cookie
        document.cookie = key+'='+value+'; expires='+date.toGMTString()+
                          '; domain='+window.location.hostname+'; path=/';
    },

    /*
        Method: showLoadingImage
            Fade out the entire page and show the loading image.

        Parameters:
            color - (string) The color of the loading image. The possible values
                are: "black", "blue", "brown", "gold", "gray", "green", "navy",
                "orange", "purple", "red", "white", and "yellow".
    */
    showLoadingImage: function(color) {
        var loading_overlay = DLSupport.getElement('DLSupport_loading_overlay');
        var loading_div = DLSupport.getElement('DLSupport_loading_div');
        var loading_image = DLSupport.getElement('DLSupport_loading_image');

        // Create the divs if they don't exists
        if(!loading_overlay) {
            loading_overlay = document.createElement('div');
            loading_overlay.id = 'DLSupport_loading_overlay';
            loading_overlay.style.display = 'none';
            loading_overlay.style.position = 'absolute';
            document.body.appendChild(loading_overlay);
        }
        if(!loading_div) {
            loading_div = document.createElement('div');
            loading_div.id = 'DLSupport_loading_div';
            loading_div.style.display = 'none';
            loading_div.style.position = 'absolute';

            var loading_image = document.createElement('img');
            loading_image.id = 'DLSupport_loading_image';
            loading_image.alt = 'Loading...';

            document.body.appendChild(loading_div);
            loading_div.appendChild(loading_image);
        }

        // Set the color
        loading_image.src = DLSupport.ModuleUrl+'/images/loading_images/'+color+'.gif';

        // Get the screen dimension
        var screen_dimension = DLSupport.Viewport.getDimensions();

        // Get page offset
        var offsets = DLSupport.Viewport.getOffsets();

        // Determine position of loading image
        var offset_left = offsets.left + Math.floor((screen_dimension.width/2) - 50);
        var offset_top = offsets.top + Math.floor((screen_dimension.height/2) - 50);

        // Set the loading overlay postion and dimension
        loading_overlay.style.left = offsets.left+'px';
        loading_overlay.style.top = offsets.top+'px';
        loading_overlay.style.height = screen_dimension.height+'px';
        loading_overlay.style.width = screen_dimension.width+'px';

        // Set the loading div position
        loading_div.style.left = offset_left+'px';
        loading_div.style.top = offset_top+'px';

        // Show the divs
        loading_overlay.style.display = '';
        loading_div.style.display = '';
    },

    /*
        Method: toggleDisplay
            Toggle the display of the given DOM element. If it is currently is
            hidden, then show it. If it is currently visibile, then hdie it.

        Parameters:
            element - (object) The DOM element whose display will be toggled.
    */
    toggleDisplay: function(element) {
        if(element.style.display == 'none') {
            // Show the given element
            element.style.display = '';
        } else {
            // Hide the given element
            element.style.display = 'none';
        }
    },

    /*
        Method: toQueryString
            Convert the given object to a query string.

        Parameters:
            data - (object) The object to be converted.

        Returns:
            (string) The given data as a query string.
    */
    toQueryString: function(data) {
        // Get all the property names for the given data
        var property_names = DLSupport.propertyNames(data);

        // Traverse all the property names and create query string
        var result = new Array(), property, extra;
        for(var i = 0, j; i < property_names.length; ++i) {
            property = property_names[i];

            // If the property value is an array, combine them together first
            if(DLSupport.isArray(data[property])) {
                extra = new Array();
                for(j = 0; j < data[property].length; ++j) {
                    extra[j] = encodeURIComponent(property)+'='+encodeURIComponent(data[property][j]);
                }

                result[i] = extra.join('&');
            } else {
                result[i] = encodeURIComponent(property)+'='+encodeURIComponent(data[property]);
            }
        }

        return result.join('&');
    },

    /*
        Method: uniqueArray
            Get the array that has only the unique values of the given array.

        Parameters:
            data - (array) The array to use to create a unique array.
            strict - (boolean) This is optional and defaults to true. If this is
                true, then the search will use === when comparing the data,
                otherwise, it will just use ==.

        Returns:
            (array) Returns the given array with repeated items removed.
    */
    uniqueArray: function(data) {
        // Get the strict parameter
        var strict = arguments[1] || true;

        // Traverse the array and add only unique items
        var result = new Array();
        for(var i = 0; i < data.length; ++i) {
            if(!DLSupport.inArray(data[i], result, strict)) {
                result.push(data[i]);
            }
        }

        return result;
    },

    /*
        Method: windowOnload
            This will add a new event to the event listener that will run the
            given function after the window has finish loading (basically when
            all of the DOM structure has finish loading).

            Since this is adding to the list, you do not have to worry about
            overwriting someone else's events or theirs overwriting yours. The
            second parameter can futher delay the execution so you can pretty
            much sort the order of the execution of the function. It also allows
            you to wait for other javascript functions to finish running before
            yours can go. At worse case, it uses window.setTimeout.

        Parameters:
            func - (function) The function to append to the list.
            delay - (int) The time, in seconds, to delay the execution after the
                page has loaded. This is optional & defaults to 0.

        Returns:
            (boolean) Returns true if it was successful in adding the onload to
                the list. Returns false if it has to default to using
                window.setTimeout.
    */
    windowOnload: function(func) {
        // See if we have a delay param
        var delay = arguments[1] || 0;

        // Try to add the event
        if(DLSupport.newEvent(window, 'load', func, delay)) {
            return true;
        } else {
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.windowOnload', [func], 'Failed to add event, using window.setTimeout');

            // Delay the function execution
            var delayed_func = DLSupport.curry(DLSupport.delayFunction, window, func, delay);

            // Delay it even further by 5 seconds so hopefully the page has
            // loaded
            window.setTimeout(delayed_func, 5000);

            return false;
        }
    }
};