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 this.formatDate(date, dateFormat);
        }

        return day_diff == 0 && (
            diff < 60 && "just now" ||
            diff < 120 && "1 min. ago" ||
            diff < 3600 && Math.floor( diff / 60 ) + " min. 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 'transparent';
        }
        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 '';
        }
        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':
                var original = input;
                input = $.trim(input);
                var inLen = input.length > 7 ? 7 : input.length;
                if (input.substring(0, inLen) != 'http://'.substring(0, inLen) && input.substring(0, inLen) != 'feed://'.substring(0, inLen)) {
                    input = input.replace(/^.*?\:\/\//,'');
                    input = 'http://' + input;
                }
                if(original == input){
                    return false;
                }
                return input;
                break;
            case 'anchor':
                var url, label;
                
                var parts = input.split('|');
                var chk = this.verifyInput('http', parts[0]);
                url = label = chk ? chk : 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;
    },
    
    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;
    },
    
    // https://www.pivotaltracker.com/story/show/3092509
    // look into position.js for value structure
    // it is something like:
    // {
    //     horzOff: "0",
    //     horz: "fromLeft",
    //     horzWidth: "100",
    //     vertOff: "20",
    //     vert: "fromTop",
    //     vertHeight: "200",
    //     platform: ["android_800x480"]
    // }    
    applyPlatformResizeToPositionStructure: function(param, platformObj, diff, event) {
        for(var plat in platformObj){
            var value = platformObj[plat];

//            this.showPlatformValues('before', param, plat, value);

            // deal with x-axis
            if (value.horz == "resizeLeft") {
                value.horzWidth = (parseInt(value.horzWidth) + diff[plat].width).toString();
                if (isNaN(value.horzWidth) || value.horzWidth < 1) value.horzWidth = "1"; // sanitize
            }
            if (value.horz == "fromRight" || value.horz == "centerProportionately" || value.horz == "stretchProportionately") {
                value.horzOff = (parseInt(value.horzOff) + diff[plat].width).toString();
                if (isNaN(value.horzOff) || value.horzOff<0) value.horzOff = "0"; // sanitize
            }
            
            // deal with y-axis
            var isMenu = (event && (event.type == 'tabNew' || event.type == 'tabRemoveAfter'));
            if (isMenu) {
                var isPhone = (plat.indexOf('iphone') > -1);
                var isDroid = (plat.indexOf('android') > -1);
                var isNokia = (plat.indexOf('nokia') > -1);
                if (!isPhone && !isDroid && !isNokia) {
                    value.vertOff = (parseInt(value.vertOff) + diff[plat].height).toString();
                    if (isNaN(value.vertOff) || value.vertOff<0) value.vertOff = "0"; // sanitize
                } else if (isPhone || isDroid) {
                    if (value.vert == "resizeTop") {
                        value.vertHeight = (parseInt(value.vertHeight) + diff[plat].height).toString();
                        if (isNaN(value.vertHeight) || value.vertHeight<1) value.vertHeight = "1"; // sanitize
                    }
                    if (value.vert == "fromBottom" || value.vert == "stretchProportionately") {
                        value.vertOff = (parseInt(value.vertOff) + diff[plat].height).toString();
                        if (isNaN(value.vertOff) || value.vertOff<0) value.vertOff = "0"; // sanitize
                    }
                }
            } else {
                if (value.vert == "resizeTop") {
                    value.vertHeight = (parseInt(value.vertHeight) + diff[plat].height).toString();
                    if (isNaN(value.vertHeight) || value.vertHeight<1) value.vertHeight = "1"; // sanitize
                }
                if (value.vert == "fromBottom" || value.vert == "stretchProportionately") {
                    value.vertOff = (parseInt(value.vertOff) + diff[plat].height).toString();
                    if (isNaN(value.vertOff) || value.vertOff<0) value.vertOff = "0"; // sanitize
                }
            }
            
//            this.showPlatformValues('after', param, plat, value);
            
        }
    },
    
    showPlatformValues: function(label, param, plat, value){
        var txt = label + ': ' + param + ': ' + plat + ': ';
        for(var x in value){
            txt += x + ', ' + value[x] + ' / ';
        }
        console.log(txt);
    },
    
    nicePlatform: function(platform){
        var pieces = platform.split('_');
        var nicePlatform = pieces[0];
        switch (nicePlatform) {
            case 'facebook':
                nicePlatform = 'Facebook';
            break;
            case 'embed':
                nicePlatform = 'Microsite';
            break;
            case 'myspace':
                nicePlatform = 'MySpace';
            break;
            case 'iphone':
                nicePlatform = 'iPhone';
            break;
            case 'android':
                nicePlatform = 'Android';
            break;
            case 'nokia':
                nicePlatform = 'Nokia';
            break;
            case 'twitter':
                nicePlatform = 'Twitter';
            break;
        }
        
        if (nicePlatform == 'Nokia') {
            nicePlatform += ' - ' + pieces[1];
        } else if (nicePlatform == 'Android' || nicePlatform == 'iPhone') {
            var xy = pieces[1].split('x');
            //ipad
            if((nicePlatform == 'iPhone') && ((xy[0] == '768' && xy[1] == '1024') || (xy[0] == '1024' && xy[1] == '768'))){
                nicePlatform = 'iPad';
            }
            nicePlatform += (parseInt(xy[0], 10) < parseInt(xy[1], 10)) ? ' - Portrait' : ' - Landscape';
        }
        
        if (nicePlatform == 'MySpace') {
            if(pieces[1] == 'wide'){
                nicePlatform += ' - Profile';
            }
            else if(pieces[1] == 'canvas'){
                nicePlatform += ' - Canvas';
            }
        }
        
        return nicePlatform;
    },
    
    preloadedImages: [],
    
    preloadImage: function(){
        function getImage(url){
            var a = new Image();
            a.src = url;
            return a;
        }
        for(var x = 0; x < arguments.length; x++){
            this.preloadedImages.push(getImage(arguments[x]));
        }
    }
    
};