/*namespace 'general' contains general functionality not related to maps*/

var general; // acts as namespace for everything in this file

if (!general) {
    general = {};
}

// aliases for common ops
function $id (id) {
    return document.getElementById(id);
}

function $createEl (el, parent, content) {
    var element = document.createElement(el);
    if (parent) {
        parent.appendChild(element);
    }
    if (content) {
        element.innerHTML = content;
    }
    return element;
}


/* special treatment of 'table': automatically adds 'tbody' to table and uses tbody to which to attach rows */
function $C (type) {
    var el = document.createElement(type);
    
    var childStartIdx = 1;    
    var styles = arguments[1];
    
    if (styles === null) {
        childStartIdx++;
    } else if (styles && ! (styles.getAttribute) ) { // use existence of getAttribute to determine if this is an HTMLElement (in IE, instanceof causes an error)
        childStartIdx++;
        for (var s in styles) {
            el.style[s] = styles[s]
        }
    }
    
    var attributes = arguments[2];
    if (attributes === null) {
        childStartIdx++;
    }
    else if (attributes && ! (attributes.getAttribute) ) {
        childStartIdx++;
        if (attributes.html) {
            el.innerHTML = attributes.html;
            delete attributes.html;
        }
        if (attributes.classes) {
            el.className = attributes.classes;
            delete attributes.classes;
        }
        for (var a in attributes) {
            el.setAttribute(a, attributes[a]);
        }
    }
    
    if (type === 'table') {
        var table = el;
        var el = document.createElement('tbody');
        table.appendChild(el);
    }
    
    // append children 
    for (var i = childStartIdx; i < arguments.length; i++) {
        el.appendChild(arguments[i]);
    }
    
    if (table) {
        el = table;
    }

    return el;
}

/* return true if 'className' is a class on element 'ele'*/
function $isClass (ele, className) {
    var classes = ele.className;
    if (!classes) {
        return false;
    }
    if (classes === className) {
        return true;
    }    
    return ele.className.search("\\b" + className + "\\b") !== -1;
}

function $addClass (ele, classNames) {
    for (var i = 0; i < classNames.length; i++) {
        var className = classNames[i];
        if ($isClass(ele, className)) {
            return;
        }
        if (ele.className) {
            className = " " + className; // whitespace separator
        }
        ele.className += className;
    }
}


function $removeClass (ele, classNames) {
    for (var i = 0; i < classNames.length; i++) {
        var className = classNames[i];
        ele.className = ele.className.replace(new RegExp("\\b" + className + "\\b\\s*", "g"), "");
    }
}



// namespace created in anonymous function so only the namespace itself is added to global namespace
(function() {

    // configuration
    if (typeof (YAHOO) !== "undefined") {
        // this is undocumented behaviour, but it sets the correct Content-Type
        YAHOO.util.Connect.setDefaultPostHeader('application/json; charset=utf-8');
    }

    // copied from yui
    general.namespace = function() {
        var a = arguments, o = null, i, j, d;
        for (i = 0; i < a.length; i = i + 1) {
            d = a[i].split(".");
            o = window;

            // window is implied, so it is ignored if it is included
            for (j = (d[0] == "window") ? 1 : 0; j < d.length; j = j + 1) {
                o[d[j]] = o[d[j]] || {};
                o = o[d[j]];
            }
        }

        return o;
    };

    //use js to move around window.
    general.moveWindow = function(anchorname) {
        window.scrollBy(0, 800);
    };

    general.alert = function() {
        // TODO do nothing now; alert user of issue in future
    };

    general.rand = function() {
        return (Math.random() * 100000000000000000) + '' + new Date().getTime();
    };


    var errors = general.errors = {};

    /* when errors have occured since last interval, report error to our log  */
    window.setInterval(function() {
        for (var errorMsg in errors) {
            //stop logging messages for bad calls to absoluteclr (content feeder)
            //todo: remove after converting content to run from ssa.wetsand.com            
            var error = errors[errorMsg];
            if (error.sendMe && errorMsg.indexOf("wetsandsurfshop") > 0) {
                error.exception.count = error.count;
                YAHOO.util.Connect.asyncRequest(
                        'POST',
                        '/WebServices/ClientError.asmx/LogError?r=' + general.rand(),
                        {}, // no handlers
                        JSON.encode({ errorMsg: errorMsg, exception: JSON.encode(error.exception) })
                    );
                error.sendMe = false;
            }
        }
    },
        100000    // 100 seconds
    );

    /* update errors to be logged */
    general.reportClientError = function(errorMsg, exception) {
        if (!exception) exception = {};
        var error = errors[errorMsg];
        if (error) {
            error.count++;
            error.exception = exception;
            error.sendMe = true;
        }
        else {
            errors[errorMsg] = {
                count: 1,
                exception: exception,
                sendMe: false
            };
            exception.count = 1;
            YAHOO.util.Connect.asyncRequest(
                'POST',
                '/WebServices/ClientError.asmx/LogError?r=' + general.rand(),
                {}, // no handlers
                JSON.encode({ errorMsg: errorMsg, exception: JSON.encode(exception) })
            );
        }
    };

    /* can't pass in functions "arguments" prop to args, it will always be empty */
    general.handleException = function(func, funcName, ex, message) {
        if (!ex.stack) {
            var stack = [];
            var limit = 20;  // timeout for recursion

            var c = 0;
            while (func && limit > 0) {
                stack[c] = {};
                if (func.name) {
                    stack[c].____FUNCTION_NAME____ = func.name;
                }
                else {
                    stack[c].____FUNCTION_BODY____ = func.toString();
                }
                stack[c].____FUNCTION_ARGS____ = [];
                for (var i = 0; i < func.arguments.length; i++) {

                    try {
                        stack[c].____FUNCTION_ARGS____[i] = JSON.encode(func.arguments[i]);
                    }
                    catch (xxxx) {
                        stack[c].____FUNCTION_ARGS____[i] = "non encodeable arg";
                    }
                }
                func = func.caller;
                limit--;
                c++;
            }
            ex.stack = stack;
            ex.message = message;
        }
        general.reportClientError(funcName, ex);
    };


    /* some code to fire the postback when you hit enter with the login control */
    general.WebForm_FireDefaultButton = function(event, target) {
        var element = event.target || event.srcElement;

        var ENTER_KEYCODE = 13;

        if (!__defaultFired && event.keyCode === ENTER_KEYCODE &&
                !(element && (element.tagName.toLowerCase() === "textarea"))) {

            var defaultButton;

            if (__nonMSDOMBrowser) {
                defaultButton = document.getElementById(target);
            }
            else {
                defaultButton = document.all[target];
            }

            if (defaultButton && typeof (defaultButton.click) !== "undefined") {
                __defaultFired = true;
                defaultButton.click();
                event.cancelBubble = true;
                if (event.stopPropagation) {
                    event.stopPropagation();
                }
                return false;
            }
        }
        return true;
    };

    /**/
    general.evalXmlResult = function(results) {

        try {
            var jsonString = results.documentElement.firstChild.text || results.firstChild.textContent; // IE or Firefox/et.al.

            // for Safari 2
            if (!jsonString) {
                var nodes = results.documentElement.childNodes;
                nodeList = new Array(nodes.length);
                for (var i = 0; i < nodes.length; i++) {
                    nodeList[i] = nodes[i].data;
                }
                jsonString = nodeList.join('');
            }
            if (!jsonString) {
                general.reportClientError('general.evalXmlResult: jsonString is empty');
            }
            return eval(jsonString);
            //jsonString = general.stripSurroundingSpaces(jsonString);
            //return YAHOO.lang.JSON.parse(jsonString);
        }
        catch (ex) {
            general.handleException(general.evalXmlResult, 'general.evalXmlResult', ex, 'jsonString: ' + jsonString);
        }
    };

    /**/
    general.disableScrolling = function(div) {
        // add do-nothing mousewheel handler on map container so page doesn't scroll when you use mouse wheel on the map (Can't use general.addListener because of the special handler name in Firefox)
        function preventScrollMousewheelHandler(evt) {
            evt = evt || window.event;
            if (evt.preventDefault) {
                evt.preventDefault();
            }
            evt.returnValue = false;
        }
        if (YAHOO.env.ua.gecko !== 0) {  // Firefox
            div.addEventListener('DOMMouseScroll', preventScrollMousewheelHandler, false);
        }
        else {  // IE, Safari, and Opera
            div.onmousewheel = preventScrollMousewheelHandler;
        }
    };

    /**/
    general.makeEnum = function(names /* array of value names*/) {
        var o = {};
        for (var i in names) {
            o[names[i]] = i;
        }
        return o;
    };

    /* creates and returns a link element */
    general.makeLink = function(text /*displayed text*/, href /* target */, onclick /* handler */) {
        var link = document.createElement("a");
        link.appendChild(document.createTextNode(text));
        link.href = href;
        link.onclick = onclick;
        return link;
    };

    /* replaces all children of 'node' with nodes in array 'newChildren' */
    general.replaceAllChildren = function(node, newChildren) {
        general.removeAllChildren(node);
        general.appendNewChildren(node, newChildren);

    };

    general.removeAllChildren = function(node) {
        // itterate backwards because removeChild shrinks array from end
        if (node.childNodes) {
            for (var i = node.childNodes.length - 1; i >= 0; i--) {
                node.removeChild(node.childNodes[i]);
            }
        }
    };

    general.appendNewChildren = function(node, newChildren) {
        for (n in newChildren) {
            node.appendChild(newChildren[n]);
        }
    };

    /* test whether array contains element */
    general.contains = function(array, element) {
        for (var i = 0; i < array.length; i++) {
            if (array[i] === element) {
                return true;
            }
        }
        return false;
    };

    /* test whether object/assoc-array contains a value in one of its props */
    general.objectContainsValue = function(obj, value) {
        for (var i in obj) {
            if (obj[i] == value) { // don't change to ===
                return true;
            }
        }
        return false;
    };

    /* test whether object/assoc-array contains at least one prop */
    general.objectHasProps = function(obj) {
        for (var i in obj) {
            return true;
        }
        return false;
    };

    /*toggles the visibility of a div based on its current display attribute.*/
    general.toggle = function(id) {
        var div = $id(id);
        if ("none" == div.style.display) {
            div.style.display = "block";
        } else {
            div.style.display = "none";
        }
    };

    /* */
    general.padWithLeadingZeroes = function(num, totalDigits) {
        num = num.toString();

        var diff = totalDigits - num.length;

        var s = '';
        for (var i = 0; i < diff; i++) {
            s += '0';
        }

        return s + num;
    };

    /**/
    general.numberOfMembers = function(object) {
        var i = 0;
        for (var j in object) {
            i++;
        }
        return i;
    };

    /**/
    general.addMembers = function(targetObj, sourceObj) {
        for (var i in sourceObj) {
            targetObj[i] = sourceObj[i];
        }
    };

    /* Cross-browser implementation of element.addEventListener() */
    general.addListener = function(element, eventtype /* string w/o 'on' prefix */, func, capture /* bool */) {
        if (typeof element === 'string') {
            element = document.getElementById(element);
        }

        capture = capture || false;

        if (window.addEventListener) { // Standard
            element.addEventListener(eventtype, func, capture);
            return true;
        } else if (window.attachEvent) { // IE
            element.attachEvent('on' + eventtype, func);
            return true;
        } else {
            return false; // mystery browser that doesn't support either event model
        }
    };

    var error;
    general.error = error; //EXPORT

    /**/
    general.setError = function(string) {
        general.error.innerHTML = 'Error: ' + string;
    };


    /* determing if 'node' is a child of 'parent'*/
    general.isChildOf = function(node, parent) {

        var n = node;

        while (n.parentNode) {

            if (n.parentNode === parent) {
                return true;
            }
            n = n.parentNode;
        }

        return false;

    };


    /* return true if node is the same as parent or a child of it; stop looking once you reach limit */
    general.isNodeOrChildOf = function(node, parent, /*opt*/limit) {
        if (!node) {
            return false;
        }

        while (true) {
            if (node === parent) {
                return true;
            } else if (node === limit) {
                return false;
            } else if (node.parentNode) {
                node = node.parentNode;
            } else {
                return false;
            }
        }

    };


    /* textbox character counter */

    general.txtshow = function(txt2show, txtmsg) {
        var viewer = document.getElementById(txtmsg);
        viewer.style.display = "block";
        viewer.innerHTML = txt2show;
    };


    general.keyup = function(what, txtmsg, maxKeys) {
        var str = new String(what.value);
        var len = str.length;
        var showstr = len + " characters of " + maxKeys + " entered";
        if (len > maxKeys) {
            showstr += '<br>Some information will be lost, please revise your entry';
        }
        general.txtshow(showstr, txtmsg);
    };


    var timestamps = {};

    /* delay execution of function; provide string for tag, which is used by clearDelay */
    general.addDelay = function(milliseconds, tag, func) {
        timestamps[tag] = window.setTimeout(func, milliseconds);
    };

    general.clearDelay = function(tag) {
        if (timestamps[tag]) {
            window.clearTimeout(timestamps[tag]);
        }
    };


    /* frame functions */
    general.getFrameDocument = function(frameOrFrameName, doc) {
        if (!doc) {
            doc = document;
        }
        if ((typeof (frameOrFrameName) === "object") && (frameOrFrameName.contentDocument)) {
            return frameOrFrameName.contentDocument;
        }
        else {
            return general.getFrameWindow(frameOrFrameName, doc).document;
        }
    };


    /*  */
    general.getFrameWindow = function(frameOrFrameName, doc) {
        if (!doc) {
            doc = document;
        }

        var frameWindow;
        if (typeof (frameOrFrameName) === "object") {
            if (frameOrFrameName.contentWindow) {
                frameWindow = frameOrFrameName.contentWindow;
            }
            else if (frameOrFrameName.window) {
                frameWindow = frameOrFrameName.window; // untested - this may work for safari
            }
        }
        else {
            if (doc.all) {
                frameWindow = doc.frames[frameOrFrameName];
            }
            else {
                frameWindow = doc.getElementById(frameOrFrameName).contentWindow;
            }
        }
        return frameWindow;
    };

    /* a modal dialogue that overlays everything, greying out the background */
    general.createOverlayWindow = function(name, body, sizeX, sizeY, context) {
        var overlay = new YAHOO.widget.Panel(
            name,
            {
                context: context || null,
                width: (typeof sizeX === 'undefined' || sizeX === null) ? null : sizeX + "px",
                height: (typeof sizeY === 'undefined' || sizeY === null) ? null : sizeY + "px",
                close: true,
                draggable: false,
                modal: true,
                fixedcenter: (context) ? false : true,
                zindex: 5005,
                visible: false,
                monitorresize: false,
                iframe: false
            }
        );

        overlay.setBody(body);
        overlay.render(document.body);

        // make the onclick of the mask close the panel
        YAHOO.util.Event.on(name + "_mask", "click",
            function() {
                overlay.hide();
            }
        );

        return overlay;
    };



    /* element creation */
    general.createIFrame = function(id, width, height, src) {
        var iFrame = document.createElement("iframe");
        iFrame.style.width = width + "px";
        iFrame.style.height = height + "px";
        iFrame.frameBorder = 0;
        iFrame.marginHeight = 0;
        iFrame.marginWidth = 0;
        iFrame.scrolling = "no";
        iFrame.src = src;
        iFrame.id = id;
        return iFrame;
    };

    /**
    * deserialize a JSON string
    * TODO use yui json parser to validate?
    */
    general.deserializeJSON = function(json, noDates) {
        // this is from .NET's ajax framework Sys.Serialization.JavaScriptSerializer.deserialize
        // it is to handle Date "literals" sent from .NET ScriptService methods
        try {
            if (noDates) {
                var exp = json;
            }
            else {
                exp = json.replace(new RegExp('(^|[^\\\\])\\"\\\\/Date\\((-?[0-9]+)\\)\\\\/\\"', 'g'), "$1new Date($2)");
            }
            // TODO YUI parser causing some kind of error
            //exp = general.stripSurroundingSpaces(exp);
            //return YAHOO.lang.JSON.parse(exp);

            return eval('(' + exp + ')');
        }
        catch (ex) {
            general.handleException(general.deserializeJSON, 'general.deserializeJSON', ex, json);
        }
    };

    /**
    * generic caching object
    */
    general.Cache = function() {
        this.data = {};
    };
    general.Cache.prototype.constructor = general.Cache;


    /**
    * returns the value for the cached key(s)
    *
    * @param array  array of keys
    *
    * @return mixed
    */
    general.Cache.prototype.get = function(keys) {
        var key = keys.join("_");
        if (this.data[key]) return this.data[key];
        else return null;
    };


    /**
    * returns the value for the cached key(s)
    *
    * @param array  array of keys
    * @param mixed  something to save in cache
    *
    * @return void
    */
    general.Cache.prototype.save = function(keys, value) {
        var key = keys.join("_");
        this.data[key] = value;
    };

    /**
    * see if the users browser is up to snuff
    *
    * @return void
    */
    general.isBrowserCompatibleForSW = function() {
        if (YAHOO.env.ua.opera > 0) return false;
        if (YAHOO.env.ua.gecko > 0 && YAHOO.env.ua.gecko < 1.8) return false;
        if (YAHOO.env.ua.ie > 0 && YAHOO.env.ua.ie < 6) return false;
        if (parseFloat(YAHOO.env.ua.webkit) > 0 && parseFloat(YAHOO.env.ua.webkit) < 522) return false;
        // google browser check is last because it needs make it through the previous tests first
        return GBrowserIsCompatible();
    };

    /** 
    * returns the user's date, unless in dev mode (variables below are written out in page if in dev mode)
    * DEV_MODE_DATE_YEAR = 2008;
    * DEV_MODE_DATE_MONTH = 3;
    * DEV_MODE_DATE_DAY = 1;
    *
    * @return Date
    */
    general.getDate = function() {
        var now = new Date();
        if ((typeof DEV_MODE_DATE_YEAR != "undefined") && DEV_MODE_DATE_YEAR) now.setFullYear(DEV_MODE_DATE_YEAR, DEV_MODE_DATE_MONTH - 1, DEV_MODE_DATE_DAY);
        return now;
    };

    /*
    Date Format 1.1
    (c) 2007 Steven Levithan <stevenlevithan.com>
    MIT license
    With code by Scott Trenda (Z and o flags, and enhanced brevity)
    http://blog.stevenlevithan.com/archives/date-time-format
    */

    /*** dateFormat
    Accepts a date, a mask, or a date and a mask.
    Returns a formatted version of the given date.
    The date defaults to the current date/time.
    The mask defaults ``"ddd mmm d yyyy HH:MM:ss"``.
    */
    general.dateFormat = function() {
        var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloZ]|"[^"]*"|'[^']*'/g,
		    timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
		    timezoneClip = /[^-+\dA-Z]/g,
		    pad = function(value, length) {
		        value = String(value);
		        length = parseInt(length) || 2;
		        while (value.length < length)
		            value = "0" + value;
		        return value;
		    };

        // Regexes and supporting functions are cached through closure
        return function(date, mask) {
            // Treat the first argument as a mask if it doesn't contain any numbers
            if (
			    arguments.length == 1 &&
			    (typeof date == "string" || date instanceof String) &&
			    !/\d/.test(date)
		    ) {
                mask = date;
                date = undefined;
            }

            date = date ? new Date(date) : new Date();
            if (isNaN(date))
                throw "invalid date";

            var dF = general.dateFormat;
            mask = String(dF.masks[mask] || mask || dF.masks["default"]);

            var d = date.getDate(),
			    D = date.getDay(),
			    m = date.getMonth(),
			    y = date.getFullYear(),
			    H = date.getHours(),
			    M = date.getMinutes(),
			    s = date.getSeconds(),
			    L = date.getMilliseconds(),
			    o = date.getTimezoneOffset(),
			    flags = {
			        d: d,
			        dd: pad(d),
			        ddd: dF.i18n.dayNames[D],
			        dddd: dF.i18n.dayNames[D + 7],
			        m: m + 1,
			        mm: pad(m + 1),
			        mmm: dF.i18n.monthNames[m],
			        mmmm: dF.i18n.monthNames[m + 12],
			        yy: String(y).slice(2),
			        yyyy: y,
			        h: H % 12 || 12,
			        hh: pad(H % 12 || 12),
			        H: H,
			        HH: pad(H),
			        M: M,
			        MM: pad(M),
			        s: s,
			        ss: pad(s),
			        l: pad(L, 3),
			        L: pad(L > 99 ? Math.round(L / 10) : L),
			        t: H < 12 ? "a" : "p",
			        tt: H < 12 ? "am" : "pm",
			        T: H < 12 ? "A" : "P",
			        TT: H < 12 ? "AM" : "PM",
			        Z: (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
			        o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4)
			    };

            return mask.replace(token, function($0) {
                return ($0 in flags) ? flags[$0] : $0.slice(1, $0.length - 1);
            });
        };
    } ();

    // Some common format strings
    general.dateFormat.masks = {
        "default": "ddd mmm d yyyy HH:MM:ss",
        shortDate: "m/d/yy",
        mediumDate: "mmm d, yyyy",
        longDate: "mmmm d, yyyy",
        fullDate: "dddd, mmmm d, yyyy",
        shortTime: "h:MM TT",
        mediumTime: "h:MM:ss TT",
        longTime: "h:MM:ss TT Z",
        isoDate: "yyyy-mm-dd",
        isoTime: "HH:MM:ss",
        isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
        isoFullDateTime: "yyyy-mm-dd'T'HH:MM:ss.lo"
    };

    // Internationalization strings
    general.dateFormat.i18n = {
        dayNames: [
		    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
		    "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
	    ],
        monthNames: [
		    "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
		    "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
	    ]
    };

    /*
    wsDate is an object like {Year:2008, Month:3, Day:19, Hour:18, Minute:0}
    */
    general.webServiceDateFormat = function(wsDate, format) {
        var date = new Date(wsDate.Year, wsDate.Month - 1, wsDate.Day, wsDate.Hour, wsDate.Minute, 0, 0);
        return general.dateFormat(date, format);
    };

    general.stripSurroundingSpaces = function(str) {
        for (var i = 0; i < str.length; i++) {
            if (str[i] !== '(') break;
        }
        return str.slice(i, str.length - i);
    };




})();


$handler = general.addListener;