var psd = {
    config: {
        debug:false
    },
    
    util: {
        /*
            Calculates the colors between color1 and color2
            @param { String } Hex value for the starting color
            @param { String } Hex value for the end color
            @return { Array } The range of colors
        */
        createColorPath: function(color1,color2) {
            function setColorHue(originColor,opacityPercent,maskRGB) {
		        var returnColor=[];
		        for(var w = 0; w < originColor.length; w++) returnColor[w] = Math.round(originColor[w]*opacityPercent) + Math.round(maskRGB[w]*(1.0-opacityPercent));
		        return returnColor;
	        }

	        function longHexToDec(longHex) {
		        var r = parseInt(longHex.substring(0,2),16);
		        var g = parseInt(longHex.substring(2,4),16);
		        var b = parseInt(longHex.substring(4,6),16);
		        return [r,g,b];	
	        }
	        
	        var colorPath = [];
	        var colorPercent = 1;
	        do {
		        colorPath.push(setColorHue(longHexToDec(color1),colorPercent,longHexToDec(color2)));
		        colorPercent-= 0.01;
	        } while(colorPercent>0);
	        
	        return colorPath;
        },
         /**
            Writes arbitrary debug information to the console or a div if console is not present
            @param { String } str Data to log
            @param { Boolean } critical If true, will throw an error, not write to the console
        */
        debug: function(str,critical) {
            if(!psd.config.debug) return;
            if(typeof window.console == "undefined") {
                return;
                window.console = {
                    log: function(str) {
                        var db = document.getElementById("_debugger");
                        if(!db) {
                            db = document.createElement("div");
                            db.setAttribute("id","_debugger");
                            document.body.appendChild(db);
                        }
                        db.innerHTML += "<p> (" + new Date() + "): " + str + "</p>";
                    }
                };
            }
            if(!critical) {
                console.log("(" + new Date() + "): " + str);
            } else {
                throw new Error(str);
            }
        },

        /**
         * Builds a url object with various helper methods
         * @param url
         * @constructor
         */
        url: function(url) {
            this.url = url;
            /**
             * Gets the value of a param
             * @param param String
             * @return String the value of the param. null if not found
             */
            psd.util.url.prototype.getParameter = function(param) {
                var url = this.url.substring(this.url.indexOf("?")+1,this.url.length);
                var params = url.split("&");
                for(var i = 0, len = params.length; i < len; i++) {
                    var tmp = params[i].split("=");
                    if(param == tmp[0]) return tmp[1];
                }
                return null;
            };
            /**
             * Set a parameter on a url
             * @param key  parameter name
             * @param value  parameter value
             */
            psd.util.url.prototype.setParameter = function(key,value) {
                if(this.url.indexOf("?") > -1) {
                    this.url += "&" + key + "=" + value;
                } else {
                    this.url += "?" + key + "=" + value;
                }
            };
        },

        dom: {
            /**
                Used to attach DOM level event handlers
                @param { DOM Element } ele The element to attach the event to
                @param { String } evt The event. Don't include "on", i.e. -  "load", not "onload"
                @param { Function } handler The function to attach to the event
            */
            attachEvent: function(ele,evt,handler) {
                if(ele.attachEvent) {
                    ele.attachEvent("on" + evt,handler);
                } else {
                    ele.addEventListener(evt,handler,false);
                }
            },
            /**
                Appends a class to a DOM element
                @param { DOM Element } obj The element to append the class to
                @param { String } className Class to append
            */
            addClass: function(obj,className) {
                if(obj.className.trim() === "") {
                    obj.className = className;
                } else {
                    obj.className += " " + className;
                }
            },
            /**
                @param { DOMElement } parent 
                @param { Array } children
                
                Append multiple children to a parent element
            */           
            append: function(parent,children) {
                var len = children.length;
                for(var i = 0; i < len; i++) {
                    parent.appendChild(children[i]);
                }
            },

            /**
             *  constains an image's dimensions. should be called as the onload handler for the image
             * and only for IE. other browsers will contrain based on max-width
             */
            constrainImage: function() {
              if(!document.all) {
                    return;
                }
                var w = this.width;
                var h = this.height;
                var maxWidth = 100;
                var maxHeight = 180;
                if(h > maxHeight || w > maxWidth) {
                    if(w > maxWidth) {
                        w = maxWidth;
                        h = h * (maxWidth / w);
                    } else if ( h > maxHeight) {
                        h = maxHeight;
                        w = w * (maxHeight / h);
                    }
                }
                this.width = w;
                this.height = h;  
            },
            /**
                Copies all the nodes of an element that are NOT dependencies of the CMS application and returns them on a document fragment
                @param { DOM Element } The element to copy
                @return { DocumentFragment }
            */
            copyElementContents: function(ele) {
                var children = ele.childNodes, len = children.length;
                var frag = document.createDocumentFragment();
                for(var i = 0; i < len; i++) {
                    if(psd.util.dom.hasClass(children[i],"cms-dependency")) continue;
                    var clone = children[i].cloneNode(true);
                    frag.appendChild(clone);
                }
                return frag;
            },
            /**
                @param { Object } obj JSON representing the element to create
                @param { Boolean } noDependency Is the element being created a dependency of the CMS
                Creates and returns a DOM Element
            */
            
            createElement: function(obj,noDependency) {
                var ele = document.createElement(obj.kind);
                for(var i in obj) {
                    if(i != "kind" && i != "innerHTML") {
                        if(i == "class") {
                            ele.className = obj[i];
                        } else {
                            ele.setAttribute(i,obj[i]);
                        }
                    }
                    if(i == "innerHTML") ele.innerHTML = obj.innerHTML;
                }
                if(!noDependency) psd.util.dom.addClass(ele,"cms-dependency");
                return ele;
            },
            /**
                Removes all nodes of an element that are NOT dependencies of the CMS application.
                @param { DOM Element } ele The element to destroy the contents of. 
            */
            destroyElementContents: function(ele) {
                var children = ele.childNodes, len = children.length;
                var remove = [];
                for(var i = 0; i < len; i++) {
                    if(psd.util.dom.hasClass(children[i],"cms-dependency")) continue;
                    remove.push(children[i]);
                }
                for(var j = 0; j < remove.length; j++) ele.removeChild(remove[j]);
            },
            
            disableLinks: function() {
                var a = document.getElementsByTagName("a"), len = a.length;
                var fn = function() { return false; };
                for(var i = 0; i < a.length; i++) {
                    //a[i].setAttribute("_href",a[i].getAttribute("href"));
                   // a[i].removeAttribute("href");
                   a[i].onclick = fn;
                   //psd.util.dom.attachEvent(a[i],function() { return false; });
                }
                
            },
            
            enableLinks: function() {
                var a = document.getElementsByTagName("a"), len = a.length;
                for(var i = 0; i < a.length; i++) {
                    //a[i].setAttribute("_href",a[i].getAttribute("href"));
                   // a[i].removeAttribute("href");
                   a[i].onclick = null;
                   //psd.util.dom.attachEvent(a[i],function() { return false; });
                } 
            },
            /**
                Returns all elements decending from obj who's class attribute contains className
                @param { DOM Element } obj Element to search
                @param { String } className Class to search for
                @return { Array } All the matching elements
                
                TODO: the non-native won't return on multiple class names!
            */
            getElementsByClassName: function(obj,className) {
                if(document.getElementsByClassName) {
                    return obj.getElementsByClassName(className);
                } else {
                    var ele = obj.getElementsByTagName("*"), len = ele.length;
                    var ret = [];
                    for(var i = 0; i < len; i++) {
                        if(psd.util.dom.hasClass(ele[i],className)) {
                            ret.push(ele[i]);
                        }
                    }
                    return ret;
                }
            },
            
            /**
                Returns a collection of elements who's pointer attribute matches obj. Elements must decend from a CMS window
                @private
                @param { DOM Element} obj The element who will match another elements pointer attribute
                @param { String } The class of the element to search for.
                @return { Array } All elements decended from a cms-window who's pointer attribute is obj
            */
            _getElementsByPointer: function(obj,cls) {
                var win = psd.util.dom.getElementsByClassName(document,"cms-window")[0];
                var ele = psd.util.dom.getElementsByClassName(win,cls);
                var len = ele.length;
                var arr = [];
                for(var i = 0; i < len; i++) {
                    if(ele[i].pointer == obj) arr.push(ele[i]);
                }
                return arr;
            },
            
            /*
                Traverses siblings in a given direction until it finds an element node sibling
                @param { DOM Element} ele The element for who's sibling you want
                @param { Boolean } dir True for next, false for previous
            */
            getElementSibling: function(ele,dir) {
                var who = dir ? "nextSibling" : "previousSibling";
                if(!ele[who]) return ele;
                ele = ele[who];
                try {
                    // I have no idea why IE wants != 1, and frankly I'm a little to afraid to delve into it
                    while(ele[who] && (document.all ? ele[who].nodeType != 1 : ele[who].nodeType == 1)) {
                        ele = ele[who];
                    }
                } catch(err) {
                    return ele;
                }
                return ele;
            },
            
            /**
                @param { DOM Element } child The child whos parent you want
                @return { DOM Element } The module the element decends from. Null if not found
                
                Walks backwards up the dom until it finds an element with module as a class. Useful for separating nested modules
            */
            getModuleElement: function(child) {
                //while(child.parentNode.className.indexOf("module") == -1) {
                while(!psd.util.dom.hasClass(child.parentNode,"module")) {
                    child = child.parentNode;
                }
                return child.parentNode;
            },
            
            getScrollOffset: function() {
                var offsets = {
                    x:0,y:0
                };
                if(document.body.scrollTop !== 0) offsets.y = document.body.scrollTop;
                if(document.documentElement.scrollTop !== 0) offsets.y = document.documentElement.scrollTop;
                if(document.body.scrollLeft !== 0) offsets.x =  document.body.scrollLeft;
                if(document.documentElement.scrollLeft !== 0) offsets.x = document.documentElement.scrollLeft;
	            return offsets;
            },
            /**
                @param { Object } obj Element to extract text from
                @return { String } The text content of the element passed to it.
            */
            getText: function(obj) {
                if(obj.textContent) return obj.textContent.trim();
                if(obj.innerText) return obj.innerText;
	            if (obj.nodeType == 3) return obj.data.trim();
	            var txt = [], i = 0;
	            while(obj.childNodes[i]) {
		            txt[txt.length] = psd.util.dom.getText(obj.childNodes[i]);
		            i++;
	            }
                return txt.join("").trim();
            },
            
            /**
                Returns the dimensions of the window
                @return { Object } object with width and height properties
            */
            getWindowDimensions: function() {
                return {
                    "width" : window.innerWidth || document.documentElement.offsetWidth,
                    "height": window.innerHeight || document.documentElement.offsetHeight
                };
            },
            
            hasAttribute: function(ele,attr) {
                if(ele.hasAttribute) {
                    return ele.hasAttribute(attr);
                } else {
                    return ele.getAttribute(attr);
                }
            },
            
            /**
                Determines if a DOM Element contains a given class
                @param { DOM Element } obj The element to check
                @param { String } className The class to check for
                @return { Boolean }
            */
            hasClass: function(obj,className) {
                if(!obj) {
                    throw new Error("psd.util.dom.hasClass: missing required parameter obj");
                }
                if(obj.nodeType != 1) {
                    return false;
                }
                var c = className.split(" "), len = c.length;
                var matches = 0;
                for(var i = 0; i < len; i++) {
                    var re = new RegExp("(?:^|\\s+)" + c[i] + "(?:\\s+|$)", "g");
                    if(obj.className.match(re)) matches++;
                }
                return matches == c.length ? true : false;
            },
            
            /**
                @param { String } src URI for the script to import
                Creates a script node and appends it to the head. Also ensures that the script is unique - if you need to import
                additional scripts that point at the same URI, you'll want to append a timestamp on the src. This will prevent caching too, since most
                likely its a transaction.
            */
            importScript: function(src) {
                var s = document.getElementsByTagName("script"), i = s.length;
                while(i--) {
                    if(s[i].getAttribute("src") == src) return;
                }
                var script = psd.util.dom.createElement({"kind":"script","async":true,"src":src});// + "?" + Date.parse(new Date())});
                document.getElementsByTagName("head")[0].appendChild(script);
                return script;
            },
            
            /**
                Imports a CSS file
                @param { String } src The url of the css file
                @returns { DOM Element } The created link
            */
            importCSS: function(src) {
                var link = psd.util.dom.createElement({"kind":"link","rel":"stylesheet","href":src});
                document.getElementsByTagName("head")[0].appendChild(link);
                return link;
            },
            
            /**
                Removes a class from an element
                @param { DOM Element } obj Element to remove the class from
                @param { String } className The class to remove
            */
            removeClass: function(obj,className) {
                if(!obj) throw new Error("psd.util.dom.removeClass: object wasn't passed!");
                var re = new RegExp("(?:^|\\s+)" + className + "(?:\\s+|$)", "g");
		        obj.className = obj.className.replace(re," ");
		        obj.className = obj.className.trim();
            },
            
            /**
                Helper method for replacing one node with another
                @param { DOM Element } replacement The new node
                @param { DOM Element } the node that is being replaced
            */
            replaceChild: function(replacement,original) {
                if(document.replaceChild) {
                    original.parentNode.replaceChild(replacement,original);
                    /*
                    if(document.all) {
                        document.createElement("nav");
                        document.createElement("header");
                        replacement.style.display = "none";
                        var fn = function() {
                            replacement.style.display = "block";
                        }
                        setTimeout(fn,1000);
                    }*/
                } else {
                    original.parentNode.replaceNode(replacement,original);
                }
            }
            
        },
        
        /**
            Helper method to get Date.now or getTime() depending on browser support
        */
        rightNow: function() {
            return Date.now ? Date.now() : new Date().getTime();
        },
        
        /**
            Animates the background color of a DOM Element. Called from Element.hightlight(color1,color2)
            If the object has a backgroundAlpha property defined, the second color is ignored and the initial color is faded to 0% opacity
            @param { DOM Element } The DOM element to animate
            @private
        */
        transitionBackgroundColor: function(obj) {
            if(document.all) return;
            var fn;
            if(obj.backgroundAlpha != null) {
                obj.backgroundAlpha -= 0.03;
                obj.style.backgroundColor = "rgba(" + obj.colorPath[0][0] + "," + obj.colorPath[0][1] + "," + obj.colorPath[0][2] + "," + obj.backgroundAlpha + ")";
                if(obj.backgroundAlpha <= 0) {
                    obj.backgroundAlpha = 1;
                } else {
                    fn = function() {
                        psd.util.transitionBackgroundColor(obj);
                    };
                    setTimeout(fn,15);
                }
            } else {
                obj.style.backgroundColor = "rgba(" + obj.colorPath[obj.colorTransitionIndex][0] + "," + obj.colorPath[obj.colorTransitionIndex][1] + "," + obj.colorPath[obj.colorTransitionIndex][2] + ",.75)";
                obj.colorTransitionIndex+=2;
                if(obj.colorTransitionIndex >= obj.colorPath.length) {
                    obj.colorTransitionIndex = 0;
                } else {
                    fn = function() {
                        psd.util.transitionBackgroundColor(obj);
                    };
                    setTimeout(fn,15);
                }
            }
        },

        xhr: function(uri,params,method,callback) {
            var req = new XMLHttpRequest();
            if(callback) req.callback = callback;
            req.open(method,uri,true);
            try {
                req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");  // do we need to set this if method is GET?
                req.setRequestHeader("Content-length", params.length);
                req.setRequestHeader("Keep-Alive","timeout=15, max=100");
                req.setRequestHeader("Connection", "keep-alive");
            } catch(err) { }
            req.onreadystatechange = function() {
               if(this.readyState == 4) {
                    if(this.status == 200) {
                        //var growl = new Growl(cms.lang[cms.config.lang].growl.SAVED);
                        if(this.callback){
                            if(typeof this.callback == "string") {
                                eval(this.callback + "(" + this.responseText + ")");   
                            } else {
                                this.callback(this.responseText);
                            }
                        }
                    } else {
                         var args = {
                            "title":"Oh, dear ...",
                            "body":"We encountered a problem. Please try again later.",
                            "callback":cms.foo,
                            "hideCancel":true
                          };
                        var confirm = cms.component.confirm(args);
                    }
               }
            }
            req.send(params);
        }
    }
};

// Native object prototypes
if(!String.trim) String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ""); };

String.prototype.toBoolean = function() {
    return this == "true" ? true : false;
};

Array.prototype._removeNullValues = function() {
    var tmp = [];
    for(var i = 0, len = this.length; i < len; i++) {
        if(this[i] !== null) tmp.push(this[i]);    
    }
    return tmp;
};

Array.prototype._removeValue = function(value) {
    for(var i = 0, len = this.length; i < len; i++) {
        if(this[i] == value) this[i] = null;
    }
    return this._removeNullValues();
};

Array.prototype._sortBy = function(key) {
    tmpArray = [];
    var i = this.length;
    while(i-->0) tmpArray.push(this[i][key]);
    tmpArray = tmpArray._unique();
    tmpArray = tmpArray.sort();
    var newArray = [];
    var i = tmpArray.length;

    while(i-->0) newArray.push(findValue(this,key,tmpArray[i]));

    return newArray;

    function findValue(_array,_key,_val) {
        var j = _array.length;
        while(j-->0) {
            if(_array[j] == null || _array[j][_key] != _val) continue;
            if(_array[j][_key] == _val) {
                var ret = _array[j];
                return ret;
                break;
            }
        }
    }
};

Array.prototype._unique = function () {
    var r = [];
    o:for(var i = 0, n = this.length; i < n; i++)   {
        for(var x = 0, y = r.length; x < y; x++) if(r[x]==this[i]) continue o;
        r.push(this[i]);
    }
    return r;
};

Array.prototype._indexOf = function(val) {
    for(var i = 0, len = this.length; i < len; i++) {
        if(this[i] == val) return i;
    }
    return -1;
};

Array.prototype._removeObjectByValue = function(value) {
    for(var i = 0, len = this.length; i < len; i++) {
        for(var j in this[i]) {
            if(this[i][j] == value) this[i] = null;
        }
    }
    return this._removeNullValues();
};

try {
    window.Element.prototype.highlight = function(sColor,eColor,useAlpha) {
        this.colorPath = psd.util.createColorPath(sColor,eColor);
        this.colorTransitionIndex = 0;
        if(useAlpha) this.backgroundAlpha = 1;
        psd.util.transitionBackgroundColor(this);
    };
} catch(err) {

}

