if (!window.wb) 
    window.wb = {};

window.wb.util = {

    /*
     * JavaScript Pretty Date
     * Copyright (c) 2008 John Resig (jquery.com)
     * Licensed under the MIT license.
     */
    // Takes an ISO time and returns a string representing how
    // long ago the date represents.
    
    // time can be:
    // - integer (UTC milliseconds timestamp) 
    // - string (ISO8601)
    // - javascript Date object 
    //
    // daysBack and dateFormat are optional
    // when specified, dates older than daysBack days will be printed using formatDate function with given date format
    //
    prettyDate: function(time, daysBack, dateFormat) { 
        var date = time;
        if (typeof date == "string") {
            date = this.parseISO8601(time);
        } else if (typeof date == "number") {
            date = new Date(time);
        }
        var diff = (((new Date()).getTime() - date.getTime()) / 1000);
        // don't travel back to the future!
        // date may be provided by the server and user's local time may be shifted slightly
        // https://www.pivotaltracker.com/story/show/2265150
        if (diff<0) diff = 0;
        var day_diff = Math.floor(diff / 86400);
            
        if (daysBack && day_diff>daysBack || day_diff>=31) {
            if (!dateFormat) dateFormat = "M j, Y"; // default date format
            return "on "+this.formatDate(date, dateFormat);
        }

        return day_diff == 0 && (
            diff < 60 && "just now" ||
            diff < 120 && "1 minute ago" ||
            diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
            diff < 7200 && "1 hour ago" ||
            diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
            day_diff == 1 && "Yesterday" ||
            day_diff < 7 && day_diff + " days ago" ||
            day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
    },
    
    detectClientTimezone: function() {
        // see discussion http://stackoverflow.com/questions/1091372/getting-the-clients-timezone-in-javascript
        var offset = new Date().getTimezoneOffset();
        offset = -Math.round(offset/60);
        if (offset<-11) offset+=12;
        if (offset>14) offset-=12;
        return offset;
    },
    
    // simulates PHP's date function: http://php.net/manual/en/function.date.php
    // taken from http://jacwright.com/projects/javascript/date_format
    formatDate: function(date, format) {
        var returnStr = '';
        var replace = {
            shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
            longMonths: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
            shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
            longDays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],

            // Day
            d: function() { return (this.getDate() < 10 ? '0' : '') + this.getDate(); },
            D: function() { return replace.shortDays[this.getDay()]; },
            j: function() { return this.getDate(); },
            l: function() { return replace.longDays[this.getDay()]; },
            N: function() { return this.getDay() + 1; },
            S: function() { return (this.getDate() % 10 == 1 && this.getDate() != 11 ? 'st' : (this.getDate() % 10 == 2 && this.getDate() != 12 ? 'nd' : (this.getDate() % 10 == 3 && this.getDate() != 13 ? 'rd' : 'th'))); },
            w: function() { return this.getDay(); },
            z: function() { return "Not Yet Supported"; },
            // Week
            W: function() { return "Not Yet Supported"; },
            // Month
            F: function() { return replace.longMonths[this.getMonth()]; },
            m: function() { return (this.getMonth() < 9 ? '0' : '') + (this.getMonth() + 1); },
            M: function() { return replace.shortMonths[this.getMonth()]; },
            n: function() { return this.getMonth() + 1; },
            t: function() { return "Not Yet Supported"; },
            // Year
            L: function() { return (((this.getFullYear()%4==0)&&(this.getFullYear()%100 != 0)) || (this.getFullYear()%400==0)) ? '1' : '0'; },
            o: function() { return "Not Supported"; },
            Y: function() { return this.getFullYear(); },
            y: function() { return ('' + this.getFullYear()).substr(2); },
            // Time
            a: function() { return this.getHours() < 12 ? 'am' : 'pm'; },
            A: function() { return this.getHours() < 12 ? 'AM' : 'PM'; },
            B: function() { return "Not Yet Supported"; },
            g: function() { return this.getHours() % 12 || 12; },
            G: function() { return this.getHours(); },
            h: function() { return ((this.getHours() % 12 || 12) < 10 ? '0' : '') + (this.getHours() % 12 || 12); },
            H: function() { return (this.getHours() < 10 ? '0' : '') + this.getHours(); },
            i: function() { return (this.getMinutes() < 10 ? '0' : '') + this.getMinutes(); },
            s: function() { return (this.getSeconds() < 10 ? '0' : '') + this.getSeconds(); },
            // Timezone
            e: function() { return "Not Yet Supported"; },
            I: function() { return "Not Supported"; },
            O: function() { return (-this.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(this.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(this.getTimezoneOffset() / 60)) + '00'; },
            P: function() { return (-this.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(this.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(this.getTimezoneOffset() / 60)) + ':' + (Math.abs(this.getTimezoneOffset() % 60) < 10 ? '0' : '') + (Math.abs(this.getTimezoneOffset() % 60)); },
            T: function() { var m = this.getMonth(); this.setMonth(0); var result = this.toTimeString().replace(/^.+ \(?([^\)]+)\)?$/, '$1'); this.setMonth(m); return result;},
            Z: function() { return -this.getTimezoneOffset() * 60; },
            // Full Date/Time
            c: function() { return this.format("Y-m-d") + "T" + this.format("H:i:sP"); },
            r: function() { return this.toString(); },
            U: function() { return this.getTime() / 1000; }
        };
        for (var i = 0; i < format.length; i++) {
            var curChar = format.charAt(i);
            if (replace[curChar]) {
                returnStr += replace[curChar].call(date);
            } else {
                returnStr += curChar;
            }
        }
        return returnStr;
    },
    
    // source: http://anentropic.wordpress.com/2009/06/25/javascript-iso8601-parser-and-pretty-dates/
    parseISO8601: function(str) {
        // we assume str is a UTC date ending in 'Z'

        var parts = str.split('T'),
        dateParts = parts[0].split('-'),
        timeParts = parts[1].split('Z'),
        timeSubParts = timeParts[0].split(':'),
        timeSecParts = timeSubParts[2].split('.'),
        timeHours = Number(timeSubParts[0]),
        _date = new Date;

        _date.setUTCFullYear(Number(dateParts[0]));
        _date.setUTCMonth(Number(dateParts[1])-1);
        _date.setUTCDate(Number(dateParts[2]));
        _date.setUTCHours(Number(timeHours));
        _date.setUTCMinutes(Number(timeSubParts[1]));
        _date.setUTCSeconds(Number(timeSecParts[0]));
        if (timeSecParts[1]) _date.setUTCMilliseconds(Number(timeSecParts[1]));

        // by using setUTC methods the date has already been converted to local time(?)
        return _date;
    },
    
    createTimestamp: function(){
        var ts = new Date();
        return ts.getTime();
    },

    inheritObj: function(o){
        function F(){
        }
        F.prototype = o;
        return new F();
    },
    
    checkImage: function(src, func, err){
        // life is not perfect: http://stackoverflow.com/questions/198892/img-onload-doesnt-work-well-in-ie7
        //   
        // even timestamping does not work on IE7:
        // var timestamp = +new Date();
        // if (src.indexOf('?')==-1) {
        //     src += '?_=' + timestamp;
        // } else {
        //     src += '&_=' + timestamp;
        // }
        // var a = new Image();
        // a.onload = func;
        // a.onerror = err;
        // a.src = src;
        //
        // TODO: when you get old and talking to your grandsons about your work, don't forget to note this story
        // the new generation will not believe you anyway ... ;)
        
        // let's use John's image measuring service for the 21st century
        //
        // I just wanted to let you know that the image measuring service is done and committed.  It takes only one parameter, the URL of the image.  Here is an example:
        // http://john.transpond.com/transponder/image/measure?url=http://cvcl.mit.edu/hybrid/cat2.jpg
        // 
        // -John
        //
        $.ajax({
            dataType: "json",
            type: "GET",
            url: '/transponder/image/measure?url='+encodeURIComponent(src),
            success: function(data) {
                if (data && data.width && data.height) { // John returns {"height":null,"width":null} for invalid image URL
                    if (func) func(data);
                } else {
                    if (err) err();
                }
            },
            error: function(response) { // this gets called when our service is having troubles, treat it as image is not found
                if (err) err();
            }
        });        
    },
    
    generateId: function(){
        return 'id' + parseInt(Math.random() * 1000000, 10);
    },
    
    ajaxCall: function(options){
        var params = $.extend({
            dataType: 'json',
            data: {},
            success: function(){
                return;
            },
            error: function(){
                console.log('Problem communicating with server');
            },
            type: 'GET'
        }, options);
        
        params.data.ts = this.createTimestamp();
        
        $.ajax(params);
    },
    
    rgbColor: function(color_string){
        if (!color_string) {
            return color_string;
        }
        
        if (color_string == 'transparent') {
            return '';
        }
        color_string = color_string.replace(/ /g, '');
        color_string = color_string.toLowerCase();
        var re = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/;
        var process = function(bits){
            return [parseInt(bits[1], 10), parseInt(bits[2], 10), parseInt(bits[3], 10)];
        };
        var bits = re.exec(color_string);
        if (bits) {
            var channels = process(bits);
            var r = channels[0];
            var g = channels[1];
            var b = channels[2];
            
            // validate/cleanup values
            r = (r < 0 || isNaN(r)) ? 0 : ((r > 255) ? 255 : r);
            g = (g < 0 || isNaN(g)) ? 0 : ((g > 255) ? 255 : g);
            b = (b < 0 || isNaN(b)) ? 0 : ((b > 255) ? 255 : b);
            
            r = r.toString(16);
            g = g.toString(16);
            b = b.toString(16);
            if (r.length == 1) {
                r = '0' + r;
            }
            if (g.length == 1) {
                g = '0' + g;
            }
            if (b.length == 1) {
                b = '0' + b;
            }
            
            return '#' + r + g + b;
        }
        else {
            return color_string;
        }
    },
    
    verifyInput: function(type, input){
        //don't verify empty input
        if (input === undefined || input.length == 0) {
            return null;
        }
        switch (type) {
            case 'color':
                if (input.substr(0, 1) != '#') {
                    input = '#' + input;
                }
                var regmatch = input.match(/^#([a-f\d]{3}){1,2}$/i);
                if (regmatch !== null) {
                    // for consistancy we'll turn #abc into #aabbcc
                    if (input.length == 4) {
                        var pos1 = input.substr(1, 1);
                        var pos2 = input.substr(2, 1);
                        var pos3 = input.substr(3, 1);
                        
                        input = "#" + pos1 + pos1 + pos2 + pos2 + pos3 + pos3;
                    }
                    return input;
                }
                else {
                    return false;
                }
                break;
            case 'integer':
                if (isNaN(input) || input.length === 0) {
                    return false;
                }
                else {
                    return parseInt(input, 10);
                }
                break;
            case 'text':
                return input;
            case 'width_height':
                if ((isNaN(input) && input != 'auto') || input.length === 0) {
                    return false;
                }
                else {
                    return parseInt(input, 10);
                }
                break;
            case 'http':
                input = $.trim(input);
                if (input.substr(0, 4) != 'http' && input.substr(0, 7) != 'feed://') {
                    input = input.replace(/^.*?\:\/\//,'');
                    input = 'http://' + input;
                }
                return input;
                break;
            case 'anchor':
                var url, label;
                
                var parts = input.split('|');
                url = label = this.verifyInput('http', parts[0]);
                if (parts.length == 2) {
                    label = $.trim(parts[1]);
                }
                var anchor = '<a href="' + url + '" target="_blank">' + label + '</a>';
                return anchor;
                break;
        }
    },
    
    capitalize: function(s){
        return s.charAt(0).toUpperCase() + s.substr(1);
    },
    
    trim: function(text){
        //return (text || "").replace( /^\s+|\s+$/g, "" );  original jQuery trim
        return (text || "").replace( /^\W+|\W+$/g, "" );
    },
    
    trimFront: function(text){
        return (text || "").replace( /^\s+/g, "" );
    },
    
    extractKeys: function(o) {
        var a = [];
        for (var k in o) {
            a.push(k);
        }
        return a;
    },
    
    arrayFromObj: function(o){
        var result = [];
        for (var x in o) {
            result.push(o[x]);
        }
        return result;
    },
    
    sameObjects: function(objA, objB, diff){ // diff is optional
        if (!diff) diff = {}; // this will be thrown away, but it is here just for code compatibility
        if (typeof objA != "object" || typeof objB != "object") {
            // one or both are not object at all? 
            diff['reason'] = 'objA and objB are not both objects';
            return false;
        }
        // collect all properties of both objects in props 
        var props = {};
        var prop;
        for (prop in objA) {
            var val = objA[prop];
            if (val!==undefined && !$.isFunction(val)) 
                props[prop] = 1;
        }
        for (prop in objB) {
            var val = objB[prop];
            if (val!==undefined && !$.isFunction(val)) 
                props[prop] = 1;
        }
        // now loop through all collected properties of props 
        for (prop in props) {
            var res = (function(name) { // need to fix prop name
                if (typeof objA[name] == "object" && typeof objB[name] == "object") {
                    diff[name] = {};
                    if (!wb.util.sameObjects(objA[name], objB[name], diff[name])) {
                        return false;
                    }
                    delete diff[name];
                } else {
                    if (objA[name] !== objB[name]) {
                        diff[name] = [objA[name], objB[name], "different (atom) values"];
                        return false;
                    }
                }
            })(prop);
            if (res===false) return false;
        }
        return true;
    },
    
    findModel: function(type){
        var pages = wb.wizard.model.pages;
        for(var x = 0; x < pages.length; x++){
            if(pages[x].type == type){
                return pages[x];
            }
        }
        return false;
    },
    
    /*
     Copyright (c) 2002 JSON.org
     
     Permission is hereby granted, free of charge, to any person obtaining a copy
     of this software and associated documentation files (the "Software"), to deal
     in the Software without restriction, including without limitation the rights
     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     copies of the Software, and to permit persons to whom the Software is
     furnished to do so, subject to the following conditions:
     
     The above copyright notice and this permission notice shall be included in all
     copies or substantial portions of the Software.
     
     The Software shall be used for Good, not Evil.
     */
    toJsonString: function(arg){
        return this.toJsonStringArray(arg).join('');
    },
    
    toJsonStringArray: function(arg, out){
        out = out || [];
        var u; // undefined
        var i;
        switch (typeof arg) {
            case 'object':
                if (arg) {
                    if (arg.constructor == Array) {
                        out.push('[');
                        for (i = 0; i < arg.length; ++i) {
                            if (i > 0) {
                                out.push(',\n');
                            }
                            this.toJsonStringArray(arg[i], out);
                        }
                        out.push(']');
                        return out;
                    }
                    else 
                        if (typeof arg.toString != 'undefined') {
                            out.push('{');
                            var first = true;
                            for (i in arg) {
                                var curr = out.length; // Record position to allow undo when arg[i] is undefined.
                                if (!first) {
                                    out.push(',\n');
                                }
                                this.toJsonStringArray(i, out);
                                out.push(':');
                                this.toJsonStringArray(arg[i], out);
                                if (out[out.length - 1] == u) {
                                    out.splice(curr, out.length - curr);
                                }
                                else {
                                    first = false;
                                }
                            }
                            out.push('}');
                            return out;
                        }
                    return out;
                }
                out.push('null');
                return out;
            case 'unknown':
            case 'undefined':
            case 'function':
                out.push(u);
                return out;
            case 'string':
                out.push('"');
                out.push(arg.replace(/(["\\])/g, '\\$1').replace(/\r/g, '').replace(/\n/g, '\\n'));
                out.push('"');
                return out;
            default:
                out.push(String(arg));
                return out;
        }
    },
    
    fontStyleSizes: {
        "Arial": {
            "8": 9,
            "9": 10,
            "10": 11,
            "11": 12,
            "12": 14,
            "14": 16,
            "16": 18,
            "18": 21,
            "20": 23,
            "22": 26,
            "24": 28,
            "26": 31,
            "28": 32,
            "36": 42,
            "48": 55,
            "72": 82
        },
        "Georgia":{
            "8":9,
            "9":10,
            "10":11,
            "11":12,
            "12":14,
            "14":16,
            "16":19,
            "18":21,
            "20":22,
            "22":25,
            "24":27,
            "26":30,
            "28":32,
            "36":41,
            "48":55,
            "72":82
        },
        "Helvetica":{
            "8":9,
            "9":10,
            "10":12,
            "11":13,
            "12":14,
            "14":16,
            "16":18,
            "18":21,
            "20":23,
            "22":25,
            "24":28,
            "26":30,
            "28":32,
            "36":41,
            "48":55,
            "72":83
        },
        "Lucida Grande":{
            "8":10,
            "9":11,
            "10":12,
            "11":13,
            "12":15,
            "14":17,
            "16":18,
            "18":21,
            "20":23,
            "22":26,
            "24":28,
            "26":30,
            "28":33,
            "36":43,
            "48":56,
            "72":170
        },
        "Sans Serif":{
            "8":9,
            "9":10,
            "10":13,
            "11":13,
            "12":14,
            "14":17,
            "16":18,
            "18":22,
            "20":23,
            "22":26,
            "24":28,
            "26":31,
            "28":32,
            "36":41,
            "48":55,
            "72":83
        },
        "Times New Roman":{
            "8":9,
            "9":10,
            "10":11,
            "11":12,
            "12":15,
            "14":16,
            "16":18,
            "18":21,
            "20":23,
            "22":26,
            "24":27,
            "26":30,
            "28":32,
            "36":42,
            "48":55,
            "72":83
        },
        "Verdana":{
            "8":10,
            "9":11,
            "10":12,
            "11":13,
            "12":15,
            "14":17,
            "16":19,
            "18":22,
            "20":24,
            "22":27,
            "24":29,
            "26":31,
            "28":34,
            "36":44,
            "48":58,
            "72":174
        }
},
    
    bind: function(type, data, func){
        $(this).bind(type, data, func);
    },
    
    trigger: function(event, data){
        $(this).trigger(event, data);
    },
    
    inDevMode: function() {
        var l = document.location+"";
        return !(l.match('http://transpond.com') || l.match('http://www.transpond.com'));
    },

    isAntoninsLocalMachine: function() {
        var l = document.location+"";
        return !!l.match('http://antonin.transpond.com');
    },

    isQAMachine: function() {
        var l = document.location+"";
        return !!l.match('http://qa.transpond.com');
    },
    
    // returns identifier of the environment app is currently running
    environment: function() {
        if (!this.inDevMode()) return 'production';
        if (this.isQAMachine()) return 'qa';
        if (this.isAntoninsLocalMachine()) return 'antonin';
        // TODO: you may add your machine here
        return 'dev';
    },
    
    prepareQueryParams: function(params) {
        var paramNames = [];
        var name, value;
        for (name in params) {
            if (params.hasOwnProperty(name)) {
                paramNames.push(name);
            }
        }
        paramNames.sort(function(a, b) {
            // special sorting rule to move cache buster param "_" to the end of the URL 
            if (a=='_') return true;
            if (b=='_') return false;
            return b<a;
        });
        var fragments = [];
        for (var i=0; i < paramNames.length; i++) {
            name = paramNames[i];
            value = params[name];
            if (value) fragments.push(name+"="+encodeURIComponent(value));
        }
        return fragments.join('&');
    },
    
    // usage: 
    //   >>> wb.util.formatURL('/wizard/', { type: "some type", value: "some nasty value %$#$@$!" })
    //   -> "/wizard/?type=some%20type&value=some%20nasty%20value%20%25%24%23%24%40%24!"
    //
    //   >>> wb.util.formatURL('/wizard/', { p: "this_url_has_cache_buster" }, true)
    //   -> "/wizard/?p=this_url_has_cache_buster&_=1260828536286"
    formatURL: function(url, params, nocache) {
        if (!params) params = {};
        if (nocache) {
            params._ = +new Date; // cache buster
        }
        var paramsQuery = this.prepareQueryParams(params);
        if (!paramsQuery) return url || "";
        return url+"?"+paramsQuery;
    },

    // aim is to absolutize relative url to reuse current domain
    absolutizeURL: function(url, params, nocache) {
        return location.protocol + '//' + location.host + this.formatURL.apply(this, arguments);
    },
    
    //find intersection of array values
    arrayIntersection: function(setA, setB){
        var setA_seen = {};
        var setB_seen = {};
        for (var i = 0; i < setB.length; i++) {
            setB_seen[setB[i]] = true;
        }
        var intersection = [];
        for (var i = 0; i < setA.length; i++) {
            if (!setA_seen[setA[i]]) {
                setA_seen[setA[i]] = true;
                if (setB_seen[setA[i]]) {
                    intersection.push(setA[i]);
                }
            }
        }
        return intersection;
    }
    
};