// padding with zeros
function zlead(str, len){
    if(typeof str != 'string') str = !str ? '0' : str.toString();
    while(str.length < (len || 2)) str = '0' + str;
    return str;
}

function escapeHTML(str) {
    return str
        .replace(/<\/?[a-z][a-z0-9]*[^<>]*>/g, '');
}

// converting html entities
function proc_entity(str) {
    return str
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/\$/g, '&#36;');
}
function decode_entity(str){
    return str
        .replace(/&amp;/g, '&')
        .replace(/&#39;/g, "'")
        .replace(/&lt;/g, '<')
        .replace(/&gt;/g, '>')
        .replace(/&#36;/g, '$');
}

function proc_xss_entity(str){
    return str
        .replace(/<(\/)?script[^>]?>/ig, '');
}
function cutstr(str, len, sym){
    return ((str || '').length > len)
        ? str.substr(0, len) + (sym || '&hellip;')
        : str;
}
/**
 * sprintf() for JavaScript v.0.4
 *
 * Copyright (c) 2007 Alexandru Marasteanu <http://alexei.417.ro/>
 * Thanks to David Baird (unit test and patch).
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 */

function str_repeat(i, m) { for (var o = []; m > 0; o[--m] = i); return(o.join('')); }

function sprintf() {
    var i = 0, a, f = arguments[i++], o = [], m, p, c, x;
    while (f) {
        if (m = /^[^\x25]+/.exec(f)) o.push(m[0]);
            else if (m = /^\x25{2}/.exec(f)) o.push('%');
                else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:[\.](\d+))?([b-ftosuxX])/.exec(f)) {
                    if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) throw("Too few arguments.");
                    if (/[^s]/.test(m[7]) && (typeof(a) != 'number'))
                        throw("Expecting number but found " + typeof(a));
                    switch (m[7]) {
                        case 'b': a = a.toString(2); break;
                        case 'c': a = String.fromCharCode(a); break;
                        case 'd': a = parseInt(a); break;
                        case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
                        case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
                        case 't': 
                            var _f = parseInt(a, 10),
                                _b = (parseFloat(a) - _f).toFixed(m[6]) * 100;
                            a = zlead(_f, m[3]) + ':' + zlead(_b, m[6]);
                            break;
                        case 'o': a = a.toString(8); break;
                        case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
                        case 'u': a = Math.abs(a); break;
                        case 'x': a = a.toString(16); break;
                        case 'X': a = a.toString(16).toUpperCase(); break;
                    }
                    a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a);
                    c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
                    x = m[5] - String(a).length;
                    p = m[5] ? str_repeat(c, x) : '';
                    o.push(m[4] ? a + p : p + a);
                } else throw ("Huh ?!");
                f = f.substring(m[0].length);
            }
    return o.join('');
}

function money(d, sign){
    var tmp = d && (d = parseFloat(d).toFixed(2)) && d.toString() || '0.00',
        m = tmp.match(/^(\-)?(\d+)\.(\d+)$/),
        res = [],
        b = m && m[2].match(/^(\d{1,2})?((\d{3})+)$/) || null;
        if(b && b[2])
            for(var len = b[2].length / 3, i = 0; i < len; i++)
                res.push(b[2].substr(i * 3, 3));
        else res.push(m[2]);
    return (m[1] || '') + (sign || '') + (b && b[1] && b[1] + ',' || '') + res.join(',') + '.' + m[3];
}

function dump(v){
    var str = '';
    for(var i in v){
        str += i + '=' + v + '\n';
    }
    return str;
}
var utc = 0,
    uHOUR = 3600000,
    uMIN  = 600000,
    __futime_cache = null;
/**
 * reset and initialize futime cache
 */
function futime_cache_reset(){
    __futime_cache = {
        get: function(id){
            return this[id] || null;
        },
        put: function(id, value){
            this[id] = value;
        }
    };
}
function futime_cache(){
    return __futime_cache && true || false;
}

/**
 * format date&time with cache usage
 * paramets:
 * (mixed)time: the source to be formated. string/number/date object
 * (string)format: how the date&time should be formated
 * (number)tzoffset: timezone offset
 * (boolean)return_int: does the return had to be casted to integer
 * (boolean)use_cache: check cache for the value
 * return: formated string
 */
function futime(time, format, tzoffset, return_int, use_cache){
    var d = time === false ? new Date() : new Date().sparse(time),
        tm, mt, fmt, tmp;
    fmt = format || '%u';

    if(typeof(tzoffset) == 'string') tzoffset = parseInt(tzoffset, 10);
    
    if(use_cache && __futime_cache && (tmp = __futime_cache.get(time + '-' + format + '-' + tzoffset))) return tmp;
    
    if(time !== false && time != 'now'){ // if time === false then use currect time
        if(typeof(time) != 'string' && typeof(time) != undefined) time = time.toString(); else time = time || '';
        // general REST date 2008-02-12T09:11:23Z
        if(tm = time.match(/^(\d{4})\-?(\d{2})\-?(\d{2})T(\d{2}):?(\d{2}):?(\d{2})Z$/)){
            d.setUTCHours(tm[4], tm[5], tm[6]);
            d.setUTCFullYear(tm[1], tm[2] - 1, tm[3]);
        }
        // unix time stamp, minutes or seconds
        else if(tm = time.match(/^(\-?\d+)(u|m|s)$|^(\-?\d{13})$/)){
            switch(tm[2]){
                case 'm':
                    d.setTime(0);
                    d.setUTCMinutes(tm[1]);
                    break;
                case 's':
                    d.setTime(0);
                    d.setUTCSeconds(tm[1]);
                    break;
                default:
                    d.setTime(tm[1] || tm[0])
                    break;
            }
        }
        // unix time stamp w/o milliseconds
        else if(tm = time.match(/^\d{10}$/)){
            d.setTime(tm[0] + '000');
        }
        else if(tm = time.match(/^\d{10}\.\d{5}$/)){
            d.setTime(tm[0].replace(/\./, '').substr(0, 13));
        }
        // just time 09[:12[:23]]
        else if(tm = time.match(/^(\d{1,2}):?(\d{1,2}):?(\d{1,2})?$/)){
            d.setUTCHours(tm[1], tm[2] || 0, tm[3] || 0);
        }
        // just date
        else if(tm = time.match(/^(\d{4})\-?(\d{2})\-?(\d{2})$/)){
            d.setUTCFullYear(tm[1], tm[2] - 1, tm[3]);
            d.setUTCHours(0, 0, 0);
        }
    } else tm = true;
    // time zone offset apply
    d.setTime(d.getTime() + (tzoffset || 0));
    d.setUTCMilliseconds(0);
    if(tm)
        while(mt = fmt.match(/(%[AbBYymdDHahIMsSrRuTqlLwWfn])/)){
            var re, rp, tr = '', tmp;
            switch(mt[1]){
                case '%Y': // full year
                    tr = d.getUTCFullYear();
                    break;
                case '%y': // last 2 digs of the year
                    tr = d.getUTCFullYear().toString().substr(2);
                    break;
                case '%m': // month
                    tr = zlead(d.getUTCMonth()+1, 2);
                    break;
                case '%d': // date with zero lead
                    tr = zlead(d.getUTCDate(), 2);
                    break;
                case '%D': // date
                    tr = d.getUTCDate();
                    break;
                case '%A': // day of the week
                    tr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][d.getUTCDay()];
                    break;
                case '%b': // manth name
                    tr = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][d.getUTCMonth()];
                    break;
                case '%B': // short month name
                    tr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][d.getUTCMonth()];
                    break;
                case '%w': //day of the week
                    tr = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][d.getUTCDay()];
                    break;
                case '%W': //short day of the week
                    tr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][d.getUTCDay()];
                    break;
                case '%H': // 24h hour, zero lead
                    tr = zlead(d.getUTCHours(), 2);
                    break;
                case '%q': // hours from date X
                    tr = zlead(Math.floor(d.getTime() / uHOUR));
                    break;
                case '%a': // am / pm
                    tr = d.getUTCHours() > 11 ? 'pm' : 'am';
                    break;
                case '%h': // 12h hour
                    tr = (rp = d.getUTCHours()) > 12 ? rp - 12 : rp == 0 ? 12 : rp;
                    break;
                case '%I': // 12h hour, zero lead
                    tr = zlead((rp = d.getUTCHours()) > 12 ? rp - 12 : rp == 0 ? 12 : rp, 2);
                    break;
                case '%M': // minutes
                    tr = zlead(d.getUTCMinutes(), 2);
                    break;
                case '%s': // seconds
                    tr = zlead(d.getUTCSeconds(), 2);
                    break;
                case '%S': // unix time stamp
                    tr = d.getTime();
                    break;
                case '%r': // unix time stamp floor round to full hour
                    var b = new Date();
                    b.setTime(d.getTime());
                    b.setSeconds(0);
                    b.setMinutes(0);
                    tr = b.getTime();
                    break;
                case '%R': // unix time stamp round up to full hour
                    var b = new Date();
                    b.setTime(d.getTime());
                    b.setUTCHours(d.getUTCHours() + (d.getUTCMinutes() > 0 ? 1 : 0), 0, 0);
                    tr = b.getTime();
                    break;
                case '%T': // minutes round to 10th and return unix time stamp
                    var b = new Date();
                    b.setTime(d.getTime());
                    b.setUTCMinutes(Math.floor(b.getUTCMinutes() / 10) * 10);
                    b.setUTCSeconds(0);
                    tr = b.getTime();
                    break;
                case '%u': // iso time stamp 20081201T235500Z
                    tr = d.getUTCFullYear() + zlead(d.getUTCMonth()+1, 2) + zlead(d.getUTCDate(), 2) + 'T' + zlead(d.getUTCHours(), 2) + zlead(d.getUTCMinutes(), 2) + zlead(d.getUTCSeconds(), 2) + 'Z';
                    break;
                case '%l': // relative time in digit form
                    var diff, b = new Date();
                    if(tzoffset) b.setTime(b.getTime() + tzoffset)
                    diff = Math.floor(((b.getTime() - d.getTime()) / 1000) / 60);
                    if(diff < 1440){ // 24h
                        tr = futime(time, '%h:%M%a', tzoffset);
                    } else
                        if(diff <= 21600 || b.getUTCFullYear() == d.getUTCFullYear()) { // 15 days
                            tr = futime(time, '%B %d', tzoffset);
                        } else
                            tr = futime(time, '%m/%d/%Y', tzoffset);
                    break;
                case '%L': // relative time in words
                    var words, mins, diff, b = new Date();
                    if(tzoffset) b.setTime(b.getTime() + tzoffset)
                    mins = Math.floor((b.getTime() - d.getTime()) / 1000 / 60);
                    if(mins < 1440){
                        if(mins == 0) words = 'less than a minute ago'; else
                            if(mins == 1) words = 'a minute ago'; else
                                if(mins < 45) words = mins + ' minutes ago'; else
                                    if(mins < 90) words = '1 hour ago'; else
                                        if(mins < 1440) words = Math.floor(mins / 60) + ' hours ago';
                        tr = futime(time, '%h:%M%a (' + words + ')', tzoffset);
                    } else
                        if(mins <= 21600 && b.getUTCFullYear() == d.getUTCFullYear()){
                            if(mins < 2880) words = '1 day ago'; else
                                words = Math.floor(mins / 1440) + ' days ago';
                            tr = futime(time, '%B %d (' + words +')', tzoffset);
                        } else
                            if(b.getUTCFullYear() == d.getUTCFullYear()) tr = futime(time, '%B %d', tzoffset); else
                                tr = futime(time, '%m/%d/%Y', tzoffset);
                    break;
                case '%f': // date of monday (modificator)
                    d.setDate(d.getDate() - (d.getDay() + 6));
                    tr = '';
                    break;
                case '%n': // week number
                    var b = new Date();
                    b.setTime(d.getTime());
                    tr = b.getWeek(1); // give me iso8601 week number
                    break;
                default:
                    tr = '';
                    break;
            }
            re = new RegExp(mt[1]);
            fmt = fmt.replace(re, tr);
        }
        if(use_cache) __futime_cache.put(time + '-' + format + '-' + tzoffset, fmt);
    return return_int === true ? parseInt(fmt, 10) : fmt;
}

/**
 * format date&time with cache usage
 * paramets:
 * (mixed)time: the source to be formated. string/number/date object
 * (string)format: how the date&time should be formated
 * (number)tzoffset: timezone offset
 * return: formated string
 */
function dfcache(time, format, tzoffset){
    var d = time === false
            ? new Date()
            : new Date().sparse(time),
        tm, mt, fmt, tmp;
    fmt = format || '%u';

    if(typeof(tzoffset) == 'string') tzoffset = parseInt(tzoffset, 10);
    
    if(__futime_cache && (tmp = __futime_cache.get(d.getTime() + '-' + format + '-' + tzoffset))) return tmp;
    
    fmt = d.format(fmt, tzoffset);
    
    __futime_cache.put(d.getTime() + '-' + format + '-' + tzoffset, fmt);
    return fmt;
}

/* ------------------------ Date() object extend -------------------------------- */

/**
 * set date in UTC
 * (number)year: the year || the date in YYYYMMDD format
 * (number)month: the month, 0 - january
 * (number)date: date of the month
 * return: Date object
 */
Date.prototype.UTCDate = function(year, month, date){
    if(year && !month && !date){
        var tmp = year.toString().match(/^(\d{4})(\d{2})(\d{2})$/);
        year = tmp[1];
        month = parseInt(tmp[2], 10) - 1;
        date = tmp[3];
    }
    this.setUTCFullYear(year);
    this.setUTCMonth(month);
    this.setUTCDate(date);
    return this;
}
/**
 * set number of week
 * (number)wn: week number to be set
 * (number)year: the year
 * return: Date object
 */
Date.prototype.setWeek = function(wn, year){
    if(!year) year = this.getUTCFullYear();
    this.setTime(0);
    this.setUTCFullYear(year);
    this.setUTCMonth(0);
    this.setUTCDate(1);
    this.setUTCDate(7 * (wn - 1) - (this.getUTCDay() - 2));
    return this;
}

/**
 * return number of the week
 */
Date.prototype.getWeek = function(){
    var d = new Date(this.getTime());
    var DoW = d.getUTCDay();
    d.setUTCDate(d.getUTCDate() - (DoW + 6) % 7 + 3);
    var ms = d.valueOf();
    d.setUTCMonth(0);
    d.setUTCDate(4);
    return Math.round((ms - d.valueOf()) / 604800000) + 1; 
}

/**
 * set week day to monday
 * return: Date object
 */
Date.prototype.resetWeekDay = function(){
    this.setUTCDate(this.getUTCDate() - (this.getUTCDay() == 0 && 6 || this.getUTCDay() - 1));
    return this;
}

/**
 * set the date to 12:00am of the 1st January
 * return: Date object
 */
Date.prototype.resetMonth = function(){
    this.setUTCMonth(0);
    this.resetDate();
    return this;
}

/**
 * set the date to 12:00am of the 1st of the current month
 * return: Date object
 */
Date.prototype.resetDate = function(){
    this.setUTCDate(1);
    this.resetTime();
    return this;
}

/**
 * set the time to 12:00am
 * return: Date object
 */
Date.prototype.resetTime = function(){
    this.setUTCHours(0);
    this.resetMinutes();
    return this;
}

/**
 * set the time to round hour (12:20am -> 12:00am)
 * return: Date object
 */
Date.prototype.resetMinutes = function(){
    this.setUTCMinutes(0);
    this.setUTCSeconds(0);
    this.setUTCMilliseconds(0);
    return this;
}

/**
 * set the date to last date of the current month
 * return: Date object
 */
Date.prototype.setLastDateOfMonth = function(){
    this.setUTCMonth(this.getUTCMonth() + 1);
    this.setUTCDate(0);
    return this;
}

/**
 * increment/decrement the current date
 * (string)inc: the [inc|dec]rementor. +1w == increment current date by 1 week
 * return: Date object
 */
Date.prototype.inc = function(inc, reset){
    if(typeof(inc) != 'string') inc = inc.toString();
    var m = inc.match(/^(-|\+)?(\d+)([dwMyhtm])$/),
        p = m[1] != '-',
        inc = p ? +(parseInt(m[2], 10)) : -(parseInt(m[2], 10));
    switch(m[3]){
        case 'd': // increment date
            this.setUTCDate(this.getUTCDate() + inc);
            break;
        case 'w': // increment week
            this.setUTCDate(this.getUTCDate() + (7 * inc));
            break;
        case 'm': // increment month
            this.setUTCMonth(this.getUTCMonth() + inc);
            if(reset) this.resetDate();
            break;
        case 'y': // increment year
            this.setUTCFullYear(this.getUTCFullYear() + inc);
            if(reset) this.resetMonth(0);
            break;
        case 'h': // increment hourt
            this.setUTCHours(this.getUTCHours() + inc);
            if(reset) this.resetMinutes();
            break;
        case 'M': // increment minute
            this.setTime(this.getTime() + (inc * 60000));
            break;
    }
    return this;
}

/**
 * how much days in the month
 * (number)month:
 * (number)year:
 * return: (number) qty of days
 */
Date.prototype.daysInMonth = function(month, year){
    return new Date().UTCDate(year || this.getUTCFullYear(), (month || this.getUTCMonth()) + 1, 0).getUTCDate();
}

/**
 * setup timezone
 * (number)tz: offset in seconds
 * return: Date object
 */
Date.prototype.setTimezone = function(tz){
    this.tzoffset = tz;
    if(tz){
    // time zone offset apply
        this.setTime(this.getTime() + tz);
        this.setUTCMilliseconds(0);
    }
    return this;
}

/**
 * parse string for date&time
 * (string|Date object)str: the string to be parsed
 * return: Date object
 */
Date.prototype.sparse = function(str){
    if(typeof(str) == 'object' && str instanceof Date){
        this.setTime(str.getTime());
        if(str.tzoffset) this.tzoffset = str.tzoffset;
        return this;
    } else
        if(typeof(str) != 'string') str = str.toString();
    var tm;
    // general REST date 2008-02-12T09:11:23Z
    if(tm = str.match(/^(\d{4})\-?(\d{2})\-?(\d{2})T(\d{2}):?(\d{2}):?(\d{2})Z$/)){
        this.setUTCHours(tm[4], tm[5], tm[6]);
        this.setUTCFullYear(tm[1], tm[2] - 1, tm[3]);
    }
    // minutes, seconds or unix time stamp (in seconds)
    else if(tm = str.match(/^(\-?\d+)(u|m|s)$/)){
        this.setTime(0);
        switch(tm[2]){
            case 'm':
                this.setUTCMinutes(tm[1]);
                break;
            case 's':
                this.setUTCSeconds(tm[1]);
                break;
            case 'u':
                this.setTime(tm[1] + '000');
                break;
        }
    }
    // unix time stamp in milliseconds
    else if(tm = str.match(/^\-?\d+$/)){
            this.setTime(tm[0])
    }
    // just time 09[:12[:23]]
    else if(tm = str.match(/^(\d{1,2}):?(\d{2}):?(\d{2})?$/)){
        this.setUTCHours(tm[1], tm[2] || 0, tm[3] || 0);
    }
    // just date
    else if(tm = str.match(/^(\d{4}).(\d{2}).(\d{2})|(\d{4})(\d{2})(\d{2})d|(\d{2}).(\d{2}).(\d{4})$/)){
        this.setUTCFullYear(tm[1] || tm[4] || tm[9], (tm[2] || tm[5] || tm[7]) - 1, tm[3] || tm[6] || tm[8]);
        this.resetTime();
    }
    return this;
}

/**
 * Format timezone in GMT+01:00
 * (integer)tz: timezone offset in seconds
 * return: formated string
 */
function format_gmt(tz){
    var s = tz > 0 && '+' || ((tz = -(tz)) && '-'),
        h = Math.floor(tz / 1000 / 60 / 60),
        m = (tz / 1000 / 60) - (h * 60);
    return 'GMT' + s + zlead(h, 2) + (m && ':' + zlead(m, 2) || '');
}

/**
 * format object according format string
 * (string)fmt: format string
 * return: formated string
 */
Date.prototype.format = function(fmt, tz){
    var re, rp, tr = '', tmp, mt, store;
    if(tz) {
        store = {tz: this.tzoffset, time: this.getTime()};
        this.setTimezone(tz)
    }
    while(mt = fmt.match(/(%[AbBYymdDHahIMsSrRuTqlLwWfn])/)){
        switch(mt[1]){
            case '%Y': // full year
                tr = this.getUTCFullYear();
                break;
            case '%y': // last 2 digs of the year
                tr = this.getUTCFullYear().toString().substr(2);
                break;
            case '%m': // month
                tr = zlead(this.getUTCMonth() + 1, 2);
                break;
            case '%d': // date with zero lead
                tr = zlead(this.getUTCDate(), 2);
                break;
            case '%D': // date
                tr = this.getUTCDate();
                break;
            case '%A': // day of the week
                tr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][this.getUTCDay()];
                break;
            case '%b': // manth name
                tr = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][this.getUTCMonth()];
                break;
            case '%B': // short month name
                tr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][this.getUTCMonth()];
                break;
            case '%w': //day of the week
                tr = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][this.getUTCDay()];
                break;
            case '%W': //short day of the week
                tr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][this.getUTCDay()];
                break;
            case '%H': // 24h hour, zero lead
                tr = zlead(this.getUTCHours(), 2);
                break;
            case '%q': // hours from date X
                tr = zlead(Math.floor(this.getTime() / uHOUR));
                break;
            case '%a': // am / pm
                tr = this.getUTCHours() > 11 ? 'pm' : 'am';
                break;
            case '%h': // 12h hour
                tr = (rp = this.getUTCHours()) > 12 ? rp - 12 : rp == 0 ? 12 : rp;
                break;
            case '%I': // 12h hour, zero lead
                tr = zlead((rp = this.getUTCHours()) > 12 ? rp - 12 : rp == 0 ? 12 : rp, 2);
                break;
            case '%M': // minutes
                tr = zlead(this.getUTCMinutes(), 2);
                break;
            case '%s': // seconds
                tr = zlead(this.getUTCSeconds(), 2);
                break;
            case '%S': // unix time stamp
                tr = this.getTime();
                break;
            case '%r': // unix time stamp floor round to full hour
                var b = new Date();
                b.setTime(this.getTime());
                b.resetMinutes();
                tr = b.getTime();
                delete b;
                break;
            case '%R': // unix time stamp round up to full hour
                var b = new Date();
                b.setTime(this.getTime());
                b.setUTCHours(this.getUTCHours() + (this.getUTCMinutes() > 0 ? 1 : 0), 0, 0);
                tr = b.getTime();
                delete b;
                break;
            case '%T': // minutes round to 10th and return unix time stamp
                var b = new Date();
                b.setTime(this.getTime());
                b.setUTCMinutes(Math.floor(b.getUTCMinutes() / 10) * 10);
                b.setUTCSeconds(0);
                tr = b.getTime();
                delete b;
                break;
            case '%u': // iso time stamp 20081201T235500Z
                tr = this.getUTCFullYear()
                        + zlead(this.getUTCMonth() + 1, 2)
                        + zlead(this.getUTCDate(), 2)
                        + 'T' + zlead(this.getUTCHours(), 2)
                        + zlead(this.getUTCMinutes(), 2)
                        + zlead(this.getUTCSeconds(), 2)
                        + 'Z';
                break;
            case '%l': // relative time in digit form
                var diff, b = new Date();
                if(this.tzoffset) b.setTime(b.getTime() + this.tzoffset)
                diff = Math.floor(((b.getTime() - this.getTime()) / 1000) / 60);
                if(diff < 1440){ // 24h
                    tr = this.format('%h:%M%a');
                } else
                    if(diff <= 21600 || b.getUTCFullYear() == this.getUTCFullYear()) { // 15 days
                        tr = this.format('%B %d');
                    } else
                        tr = this.format('%m/%d/%Y');
                delete b;
                break;
            case '%L': // relative time in words
                var words, mins, diff, b = new Date(), tmp;
                if(this.tzoffset) b.setTime(b.getTime() + this.tzoffset)
                mins = Math.floor((b.getTime() - this.getTime()) / 1000 / 60);
                if(mins < 1440){
                    if(mins == 0) words = 'less than a minute ago'; else
                        if(mins == 1) words = 'a minute ago'; else
                            if(mins < 45) words = mins + ' minutes ago'; else
                                if(mins < 90) words = '1 hour ago'; else
                                    if(mins < 1440) words = (tmp = Math.floor(mins / 60)) + ' hour' + (tmp > 1 && 's' || '') + ' ago';
                    tr = this.format('%h:%M%a (' + words + ')');
                } else
                    if(mins <= 21600 && b.getUTCFullYear() == this.getUTCFullYear()){
                        if(mins < 2880) words = '1 day ago'; else
                            words = Math.floor(mins / 1440) + ' days ago';
                        tr = this.format('%B %d (' + words +')');
                    } else
                        if(b.getUTCFullYear() == this.getUTCFullYear()) tr = this.format('%B %d'); else
                            tr = this.format('%m/%d/%Y');
                delete b;
                break;
            case '%f': // date of monday (modificator)
                d.setDate(d.getDate() - (d.getDay() + 6));
                tr = '';
                break;
            case '%n': // week number
                tr = this.getWeek(1); // give me iso8601 week number
                break;
            default:
                tr = '';
                break;
        }
        re = new RegExp(mt[1]);
        fmt = fmt.replace(re, tr);
    }
    if(tz) {
        this.setTimezone(store.tz);
        this.setTime(store.time);
    }
    return fmt;
}
// timer processing
var timers = {
    pool: {},
    base: 0,
    // creating timer
    // timeout - idle time in milliseconds
    // func - callback function
    // arg - arguments for callback function
    // loop - should timer be looped
    // return timer ID
    add: function(timeout, func, arg, loop, pid){
        var id = pid ? pid : this.base++;
        this.pool[id] = {
            id: setTimeout("timers._done('" + id + "')", timeout),
            func: func,
            arg: arg,
            loop: loop || false,
            timeout: timeout
        };
        return id;
    },
    restart: function(id, new_callback, new_arg){
        if(this.pool[id]) {
            clearTimeout(this.pool[id].id);
            this.pool[id].id = setTimeout("timers._done('" + id + "')", this.pool[id].timeout);
            // if false - we going to remove existing callback so lets clear the existing one by null
            // null as argument will doesnt touch the function
            if(new_callback === false || new_callback) this.pool[id].func = new_callback || null;
            this.pool[id].arg = new_arg || null;
            return id;
        } return false;
    },
    // kill timer
    stop: function(id){
        if(this.pool[id]) clearTimeout(this.pool[id].id);
        this._remove(id);
    },
    // clean up the holder
    _remove:function(id){
        this.pool[id] = null;
    },
    // internal action for done timer
    _done:function(id){
        if(!this.pool[id]) return;
        this.pool[id].func(this.pool[id].arg);
        this.pool[id].loop ? this.pool[id].id = setTimeout("timers._done(" + id + ")", this.pool[id].timeout) : this._remove(id);
    }
}

var httpReq = {
    xhr: false,
    busy: false,
    stack: [],
    timeout: 60000,
    cur_req_method: null,
    _cb_params: false,
    _cb_func: false,
    _stopTO: function(){
//         timers.add(this.timeout, )
    },
    _startTO: function(){},
    _fireTO: function(){
    },
    _create: function(){
        if (window.XMLHttpRequest) {
            this.xhr = new XMLHttpRequest();
        } else if (window.ActiveXObject) {
            try {
                this.xhr = new ActiveXObject("Msxml2.XMLHTTP");
            } catch (e) {
                try {
                    this.xhr = new ActiveXObject("Microsoft.XMLHTTP");
                } catch (e) {}
            }
        }
        if (!this.xhr) {
            alert('Cannot create XMLHTTP instance');
            return false;
        }
        this.xhr.onreadystatechange = this._state;
    },
    _state: function(){
        if (httpReq.xhr.readyState == 4) {
            httpReq.cur_req_method = null;
            if(httpReq._cb_func){
                httpReq._cb_func(httpReq.xhr.status, httpReq.xhr.responseText, httpReq._cb_params);
                httpReq._cb_func = false;
                httpReq._cb_params = false;
            }
            httpReq.busy = false;
            httpReq._stopTO();
            if(httpReq.stack.length){
                var tmp = httpReq.stack.pop();
                httpReq.request(tmp.url, tmp.method, tmp.pars, tmp.cb_func, tmp.cb_pars, tmp.concurent);
            }
        }
    },
    request: function(url, method, req_params, cb_func, cb_params, concurent){
//         alert(this.request.caller);
        if(this.busy){
            if(concurent && this.cur_req_method == 'get'){
                this.xhr.abort();
            } else {
                this.stack.push({url: url, method: method, pars: req_params, cb_func: cb_func, cb_pars: cb_params, concurent: concurent});
                return;
            }
        }
        this._cb_func = cb_func;
        this._cb_params = cb_params;
        this._create();
        this._startTO();
        if(req_params && typeof(req_params)!='string' && typeof(req_params)!='boolean') {
            if(typeof(req_params) == 'object'){
                var result = new Array;
                for(var key in req_params) if(req_params[key] !== null && typeof(req_params[key]) != 'undefined') result.push(encodeURIComponent(key) + '=' + encodeURIComponent(req_params[key]));
                req_params = result;
            }
            req_params = req_params.join('&').replace(/%20/g, '+');
        } else if(!req_params) req_params = '';
        this.busy = true;
        method = method.toLowerCase();
        this.xhr.open(method, url + (method == 'get' && req_params != '' ? '?' + req_params : ''), true);
        this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
        switch(this.cur_req_method = method.toLowerCase()){
            case 'post':
                this.xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                this.xhr.setRequestHeader('Content-length', req_params.length);
                this.xhr.send(req_params);
                break;
            case 'get':
                this.xhr.send(null);
                break;
        }
    }
}

// validating input fields and return data as object
// IN
// ctrls: jQuery input fields collection
// err_txt: array where the key is field ID and value - error msg in case validation failed
// OUT
// return.data: object where key is field ID and value is input value
// return.error: array of string for fields failed validation
function check_form(ctrls, err_txt, skip_trim, defs){
    var post = defs || {}, error = [], tmp;
    if(typeof(ctrls) == 'string') ctrls = $(ctrls);
    for(var i = ctrls.length;--i>=0;){
        var val = skip_trim || false ? $(ctrls[i]).val() : $(ctrls[i]).val().replace(/^[\s\n]+|[\s\n]+$/g, '');
        if(tmp = $(ctrls[i]).attr('check')){
            if(val.match(tmp)) {
                post[(ctrls[i].name || ctrls[i].id)] = val;
                $(ctrls[i]).removeClass('error');
            } else {
                error.push(err_txt[(ctrls[i].name || ctrls[i].id)]);
                $(ctrls[i]).addClass('error');
            }
        } else post[(ctrls[i].name || ctrls[i].id)] = val;
    }
    return {
        error: error,
        data: post
    };
}

function fillup_form(ctrls, data){
    if(typeof(ctrls) == 'string') ctrls = $(ctrls);
    for(var name, i = ctrls.length; --i>=0;){
        if(name = (ctrls[i].name || ctrls[i].id)){
            switch(ctrls[i].tagName){
                case 'INPUT':
                    switch($(ctrls[i]).attr('type')){
                        case 'checkbox':
                            $(ctrls[i]).attr('checked', data[name]);
                            break;
                        default:
                            $(ctrls[i]).val(data[name]);
                            break;
                    }
                    break;
                default:
                    $(ctrls[i]).val(data[name]);
                    break;
            }
        }
    }
}

// processing dhtml templates
// IN
// tpl - the name of template or template
// src - object/array with data
//--------------------
// template tags:
// %key_name_in_object% - will replace tag with value of src.key_name_in_object
// %key_name_in_object.0.field_value% - same to previous but src.key_name_in_object[0].field_value which allow you to get value from any depth
// %key_name_in_object!value_true:value_false% - will evaluate the value of the src.key_name_in_object and replace tag with value_true if the result of evaluation is true and vise versa
// %@template_name% - will replace tag with result of template processing of the template "template_name"
// %key_name_in_object!@template_name_if_true:template_name_if_false% - same to conditional substitute but will replaced by the template
// %#variable_name% - will be replaced by value of public variable
// in conditional tags true and/or false value can be ommited: %var!true% or %var!:false%
function proc_tpl(tpl, src){
    this.get_value = function(key){
        var d = src, re, val = '', sub, ikey;
        // substitute, if the key is set put the sub instead of value
        // %key!true[:false]%
        if((ikey = key.split(/!/)).length > 1){
            sub = ikey[1];
            key = ikey[0];
        } else {
            sub = ikey = false;
        }
        // check if the key is the path in array key.key1 => key[key1]
        if((key = key.split(/\./)).length > 1){
            var j = 0;
            do {
                if(d && typeof(d[key[j]]) != 'undefined' && (typeof(d) == 'object' || typeof(d) == 'array')) d = d[key[j]];
                    else {
                        d = '';
                        break;
                    }
            } while(key.length > ++j);
            val = d;
        } else 
            if(key[0] && key[0].substr(0, 1) == '#'){
                try {
                    eval('val = ' + key[0].substr(1) + ';');
                } catch(e){
                    val = '';
                }
            } else val = typeof(src[key[0]]) != 'undefined' ? src[key[0]] : '';
       
        if(sub !== false) {
            if($.browser.msie && sub.match(/^:/)) { // i hate ie6...
                tmp = sub;
                sub = [];
                sub[0] = '';
                sub[1] = tmp.replace(/^:/, '');
            } else sub = sub.split(/:/);
            var ret = Boolean(
                (typeof(val) != 'object' && val.match)
                    ? val.match(/^\d+$/)
                        ? parseInt(val)
                        : val.match(/^true|false$/i)
                            ? val.match(/true/i)
                            : val
                    : val
            ) ? sub[0] : (sub[1] || '');
            if(ret.match && ret.match(/^\$|@/)){
                switch(ret.substr(0,1)){
                    case '$':
                        ret = ret == '$' ? val : this.get_value(ret.substr(1));
                        break;
                    case '@':
                        ret = this.process(this.get_tpl(ret.substr(1)), src);
                        break;
                }
            }
            return ret;
        }
        if(key[0].match && key[0].match(/^@/) && val == '')
            val = this.process(this.get_tpl(key[0].substr(1)), src);
        return val;
    };
    this.get_tpl = function(tpl){
        if(tpl.match(/^[\w\.]+$/)){
            var tmp = document.getElementById(tpl);//$('#' + tpl + '.template');
            switch((tmp.attributes.getNamedItem('type') && tmp.attributes.getNamedItem('type').nodeValue) || false){
                case 'text':
                    var i = -1;
                    while(tmp.childNodes.length > ++i)
                        if(tmp.childNodes[i].nodeName == '#comment'){
                            tpl = tmp.childNodes[i].data;
                            break;
                        }
                        break;
                default:
                    tpl = tmp.innerHTML;
                    break;
            }
        }
        return tpl;
    }
    this.process = function(tpl, src){
        var match;
        tpl = this.get_tpl(tpl);
        if(tpl && src && (match = tpl.match(/%[^%][\w\.\-:!\&;\$@#]+%/g))){
            for(var i = match.length;--i>=0;){
                var val = this.get_value(match[i].replace(/%/g, '')),
                re = new RegExp(match[i].replace(/\$/g, '\\$'), 'g');
                tpl = tpl.replace(re, val);
            }
        }
        return tpl;
    }
    return this.process(tpl, src).replace(/\$/g, '&#36;');
}

// converting error message
function http_status2str(status){
    var msg;
    switch(status){
        case 400:
            msg = 'Bad Request';
            break;
        case 415:
            msg = 'Unsupported media type';
            break;
        case 401:
            msg = 'Unauthorized';
            break;
        case 403:
            msg = 'Forbidden';
            break;
        case 404:
            msg = 'Not found';
            break;
        case 503:
            msg = 'Service Unavailable';
            break;
        case 500:
            msg = 'Internal Server Error';
            break;
        case 200:
            msg = 'OK';
            break;
        case 0:
            msg = 'request terminated';
            break;
        case -1:
            msg = 'Data processing error';
            break;
        case -2:
            msg = 'Request timeout'
            break;
    }
    return msg;
}

function fmtSize(size){
    if(typeof(size) != 'integer') size = parseInt(size);
    var k = 10485760,
        ret, suf;
    if(size > 1024 && size < k){
        ret = Math.round(size / 1024);
        suf = 'Kb';
    } else if(size >= k){
        ret = Math.round(size / 1048576);
        suf = 'Mb';
    } else {
        ret = size || 0;
        suf = 'b';
    }
    return ret + suf;
}

function paging(total, pp, cp, page_tpl){
    var total_pages = Math.upround(total / pp),
        page_list = [], sp;
    cp++;
    if(cp > 4 && total_pages > 7){
        page_list.push(proc_tpl(page_tpl, {page: 1}));
        page_list.push(proc_tpl(page_tpl, {page: 2}));
        page_list.push(proc_tpl(page_tpl, {blanks: true}));
    } 
    if(cp <= 4 || total_pages <= 7) sp = 1;
        else if(cp < total_pages - 3) sp = cp - 1;
            else if(cp >= total_pages - 3) sp = total_pages - 4;
                else sp = cp;
    for(var i = sp; (sp < sp + 3) && i <= (total_pages > 7 && cp < total_pages - 3 ? (cp < 4 ? 4 : cp + 1) : total_pages); i++) page_list.push(proc_tpl(page_tpl, {page:i, current:i == cp}));
    if(cp < total_pages - 3 && total_pages > 7){
        var tmp;
        page_list.push(proc_tpl(page_tpl, {blanks: true}));
        page_list.push(proc_tpl(page_tpl, {page: tmp = total_pages - 1, current: cp == tmp}));
        page_list.push(proc_tpl(page_tpl, {page: tmp = total_pages, current: cp == tmp}));
    }
    return {
        prev: cp > 1 ? cp - 1 : false,
        next: cp < total_pages ? cp + 1 : false,
        page_list: page_list.join('')
    }
}
Math.upround = function(val){
    var rval = Math.round(val);
    return val > rval ? rval + 1 : rval;
}
// format float to pDp after sign
Math.format_float = function (pFloat, pDp) {    
    return parseFloat(new Number(pFloat).toFixed(pDp));
}

// format to odesk_float
Math.odesk_float = function (pFloat) {
    if (typeof(pFloat) == 'undefined') return '0.00';

    var f = Math.format_float(pFloat, 2);

    if (!f) f = '0.00';
    var t = f.toString().split('.');
    if (!t[1]) f += '.00';
        else if (t[1].length == 1) f += '0';
   
    // requires NumberFormat154.js to be loaded
    return addSeparatorsNF(f, '.', '.', ',');
}


function toggle_navbar(id, sender, fake){
    if($('#tdnavbar').css('display') != 'none'){
            $('#tdnavbar').css('display', 'none');
        return false;
    } else {
        $('#tdnavbar').toggle();
        return true;
    }
}

btree_leaf.prototype = {
    leaf_left: null,
    leaf_right: null,
    value: null,
    key: null,
    get_tree: function(filter){
        var tree = [];
        if(this.leaf_left) tree = tree.concat(this.leaf_left.get_tree(filter));
        if(filter && this.key.toString().match(filter) || !filter) tree.push({key:this.key, value:this.value});
        if(this.leaf_right) tree = tree.concat(this.leaf_right.get_tree(filter));
        return tree;
    },
    get_rtree: function(filter){
        var tree = [];
        if(this.leaf_right) tree = tree.concat(this.leaf_right.get_rtree(filter));
        if(filter && this.key.toString().match(filter) || !filter) tree.push({key:this.key, value:this.value});
        if(this.leaf_left) tree = tree.concat(this.leaf_left.get_rtree(filter));
        return tree;
    }
}
function btree_leaf(key, value, root){
    if(!root){
        this.key = key;
        this.value = value;
        return this;
    } else {
        if (root.key >= key) root.leaf_left = new btree_leaf(key, value, root.leaf_left);
            else root.leaf_right = new btree_leaf(key, value, root.leaf_right);
        return root;
    }
}

btree.prototype = {
    root_node: null,
    get_tree: null,
    _init: function(revert){
        if(revert)
            this.get_tree = this._get_rtree;
        else
            this.get_tree = this._get_tree;
        return this;
    },
    add: function(key, value){
        this.root_node = new btree_leaf(key, value, this.root_node);
    },
    get_value: function(key){
        var node = this.root_node;
        while(node && node.key != key){
            if(node.key > key)
                node = node.leaf_left;
            else
                node = node.leaf_right;
        }
        if(node && typeof(node.value) != 'undefined') return node.value;
        return null;
    },
    _get_tree: function(arg){
        return this.root_node && this.root_node.get_tree(arg) || [];
    },
    _get_rtree: function(arg){
        return this.root_node && this.root_node.get_rtree(arg) || [];
    }
}
function btree(revert){return this._init(revert);}

function cthread(func, args){
    setTimeout(func, 1);
}

function winopen(url){
    window.open(url, '', 'toolbars=0,resizable=1,scrollbars=1');
}

function unescapeHTML(html) {
   var htmlNode = document.createElement("DIV");
   htmlNode.innerHTML = html;
   if(htmlNode.innerText)
      return htmlNode.innerText; // IE
   return htmlNode.textContent; // FF
}

var mc_unread_msg = {
    unread_sum: 0,
    timer: null,
    link_obj: null,
    link: '',
    data: {},
    title_limit: ('undefined' != typeof(MC_WIDGET_ITEM_LENGTH)) ? MC_WIDGET_ITEM_LENGTH : 30,
    msg_limit:   ('undefined' != typeof(MC_WIDGET_ITEMS)) ? MC_WIDGET_ITEMS: 3,
    inbox_exists: false,
    last_msg_id: 0,
    prev_cache_value: null,
    tray: 'index',
    trays: {inbox: '', alerts: '', updates: '', tickets: ''},
    c_name: (('undefined' != typeof ODESK_COOKIE_PREFIX) ? ODESK_COOKIE_PREFIX : '') + 'mc_unread' , // a cookie name for the icon cache
    init: function(tray, data) {
        if (this.is_initialized)return;
        this.is_initialized = true;
        if (1 > jQuery('#mc_unread_link').length) return;
        this.link_obj = jQuery('#mc_unread_link');
        if (tray) this.tray = tray;
        this.inbox_exists = (0 < jQuery('#mc_widget').length && 'none' != this.tray) ? true : false;
        this.data = ('object' == typeof(data)) ? data : {};
        if (true == this.inbox_exists) this.refresh_inbox();
        this._update_icon_data();
        this.prev_cache_value = null;
        this.refresh_icon(); 
        if (true == this.inbox_exists && 0 < this.data.current_tray.threads.length) {
            jQuery('#mc_widget').show();
            jQuery('#mc_widget h2.toggler > span.icon').toggle(function(){
                jQuery('#mc_widget .content').hide('fast');
                jQuery(this)
                    .removeClass('expanded')
                    .addClass('collapsed');
            }, function(){
                jQuery('#mc_widget .content').show('fast');
                jQuery(this)
                    .removeClass('collapsed')
                    .addClass('expanded');
            });
        }
        if (timers) this.timer = timers.add(MC_SHOW_NEW_TTL, this.update_status, null, true);
    },
    _set_status_data: function(data) {
        this.data.trays = data;
        mc_unread_msg._update_icon_data();
        mc_unread_msg.refresh_icon();
    },
    _get_sender_name: function(data, sender_id) {
        if (sender_id == USER_UID) return 'me';
        var ret = ""; 
        for(var i = 0; i<data.length; i++){
            if (data[i].username == sender_id ) {
                ret = (data[i].first_name) ? data[i].first_name : (data[i].last_name) ? data[i].last_name : sender_id;
                break;
            };
        }
        return ret;
    },
    _update_icon_data: function() {
        var unread_sum = 0,
            expires = MC_UNREAD_CACHE_TTL / 86400,
            c_value = '',
            data = ('undefined' != typeof this.data.trays) ? this.data.trays : [];

        for (var i=data.length; --i>=0;) {
            if (0 != data[i]['unread'] && 'undefined' != typeof this.trays[data[i]['type']]) {
                this.trays[data[i]['type']] = parseInt(data[i]['unread']);
                if ('inbox' == data[i]['type'] || 'tickets' == data[i]['type']) {
                    unread_sum += this.trays[data[i]['type']];
                }
                c_value += data[i]['type'] + '=' + this.trays[data[i]['type']] + ',';
            }
        }

        this.unread_sum = unread_sum;
        this.prev_cache_value = jQuery.cookie(this.c_name);
        if (0 == unread_sum) {
            expires = expires/2;
        }
        jQuery.cookie(this.c_name, (c_value) ? c_value : 0, {domain: ODESK_COOKIE_DOMAIN, path: '/', expires: expires});
    },
    get_status: function() {
        httpReq.request(MC_REST_URI + '/trays.json', 'get', null,  function(status, data){
            switch (status) {
                case 200:
                    try {
                        eval('mc_unread_msg.data = ' + data);
                        mc_unread_msg._update_icon_data();
                        mc_unread_msg.refresh_icon();
                    } catch(e){
                        return;
                    }
                    break;
                default:
                    break;
            } 
        });
    },
    get_inbox_data: function() {
        httpReq.request(MC_REST_URI + '/trays/'+ USER_UID +'/'+ this.tray +'.json?page=0;'+ this.msg_limit, 'get', null,  function(status, data){
            switch (status) {
                case 200:
                    try {
                        eval('mc_unread_msg.data = ' + data);
                        mc_unread_msg.refresh_inbox();
                        mc_unread_msg._update_icon_data();
                        mc_unread_msg.refresh_icon();
                    } catch(e){
                        return;
                    }
                    break;
                default:
                    break;
            } 
        });
    },
    refresh_icon: function() {
        var c_value = jQuery.cookie(this.c_name),
            icon_obj = this.link_obj.children('span.badge'),
            patt = /(\w+)=(\d+)/i,
            trays = [],
            tmp = '';

        if (c_value && this.prev_cache_value != c_value) {
            this.prev_cache_value = c_value;
            this.unread_sum = 0;
            trays = c_value.split(',');
            jQuery('#messages_dropdown li.menuitem a')
                //.css({ 'font-weight' : 'normal !important'}) // !!!it dosn't accept "!important" directive in this method under IE and FF
                .attr('style', 'font-weight: normal !important;')
                .each(function(){
                    jQuery(this).html(jQuery(this).html().replace(/\s+\(\d+\)/, ''));
                });
            for (var i=trays.length; --i>=0;) {
                if ((tmp = trays[i].match(patt)) && tmp[1] && tmp[2]) {
                    if ('inbox' == tmp[1] || 'tickets' == tmp[1]) {
                        this.unread_sum += parseInt(tmp[2]);
                    }
                    jQuery("#messages_dropdown li.menuitem a[href$='"+tmp[1]+"']")
                        .append(' (' + tmp[2]+')')
                        .attr('style', 'font-weight: bold !important;');
                }
            }
        }
        this.link_obj.attr('title', this.unread_sum + ' unread Inbox and Ticket messages');
        jQuery('#messages_dropdown [title]').Tooltip({ 
            track: true,
            delay: 500,
            showURL: false,
            showBody: " - ",
            fade: 250
        });
        if (0 < this.unread_sum) {
            this.link_obj.css('font-weight', 'bold');
            icon_obj.text(this.unread_sum).show();
        } else {
            this.link_obj.css('font-weight', 'normal');
            icon_obj.hide();
        }
    },
    check_icon_cache: function() {
        var c_value = jQuery.cookie(this.c_name);
        if (!c_value) {
            return false;
        } else {
            return true;
        }
    },
    refresh_inbox: function() {
        var threads_cnt = (this.data.current_tray.threads.length > this.msg_limit) ? this.msg_limit : this.data.current_tray.threads.length,
            tzoffset = ('undefined' != typeof(this.data.tzoffset)) ? (parseInt(this.data.tzoffset, 10) * 1000) : '';
        if (0 < threads_cnt) {
            jQuery('#mc_widget').show();
            for (var obj, msgs = '', i = 0; i < threads_cnt; i++) {
                obj = this.data.current_tray.threads[i];
                obj.sender = this._get_sender_name(obj.participants, obj.last_post_sender);
                obj.cr_time = futime(obj.created_ts, '%l', tzoffset);
                obj.preview = proc_xss_entity(obj.last_post_preview.substr(0, this.title_limit) + ((obj.last_post_preview.length > this.title_limit) ? '&hellip;':''));
                obj.tray = this.tray;
                msgs += proc_tpl('mc_widget_msg_tpl', obj);
            }
            var more_msg = (0 < (this.data.current_tray.lister.total_items - this.msg_limit)) ? this.data.current_tray.lister.total_items - this.msg_limit : 0,
                msg_sfx  = (1 < more_msg) ? 's' : '',
                total_unread = (0 < this.data.current_tray.unread) ? ('(' + this.data.current_tray.unread + ')'): '';
            jQuery('#mc_widget').html(proc_tpl('mc_widget_tpl', {total: total_unread, more_msg: more_msg, msg_sfx: msg_sfx, tray: this.tray}));
            jQuery('#mc_widget_msgs_list').html(msgs);
            jQuery('#mc_widget_msg_more').css('display', more_msg ? 'block' : 'none')
            wrap_word('.word_wrap');
        } else {
            jQuery('#mc_widget').hide();
        }
    },
    update_status: function(tray_data) {
        if (tray_data) {
            mc_unread_msg._set_status_data(tray_data);
        } else {
            if (false === mc_unread_msg.check_icon_cache()) {
                if (true == mc_unread_msg.inbox_exists) {
                    mc_unread_msg.get_inbox_data();
                } else {
                    mc_unread_msg.get_status();
                }
            } else {
                mc_unread_msg.refresh_icon();
            }
        }
    },
    stop: function(){
        timers.stop(this.timer);
    }
}

function loading_process(text, ismodal){
    var tmp;
    if(!text && (tmp = $('#page_loading, #page_loading_shield')).length){
        tmp.fadeOut(500, function(){
            $(this).remove();
            $('#loading_throbber').remove();
        });
    } else if(text){
        if((tmp = $('#page_loading')).length){
            tmp.html(text);
        } else {
            if(ismodal)
                $('<div id="page_loading_shield"></div>')
                    .appendTo('body');
            $('<div id="page_loading" class="' + (ismodal ? 'modal' : 'non-modal') + '">' + text + '</div>')
                .appendTo('body')
                .fadeIn(500);
        }
    }
}

function generate_feedback_tooltip(obj) {
    obj.comment = obj.comment != ''
        ? (obj.is_public
            ? obj.comment = obj.comment.replace(/&amp;/g,'&').replace(/\n/g, '<br />')
            : obj.comment = proc_tpl("comment_is_private_tpl")
        )
        : proc_tpl("no_comment_tpl");

    obj.detailed_rows = "";
    if ("undefined" != typeof(obj.scores))
        for(var i = 0; i < obj.scores.score.length; i++)
            obj.detailed_rows += proc_tpl("detailed_feedback_row_tpl", obj.scores.score[i]);
    return proc_tpl("detailed_feedback_tpl", obj);
}

// show floating box
// params:
// head - header text
// text - actual text message
// type - class name for alert (error/warning/success/etc)
// timeout - how long the box would be displayed, then fade out
function showbox(head, text, type, timeout){
    $('div#box_underlayer').remove();
    var box = $('<div class="alert dismissable box-float" id="showbox"></div>')
            .addClass(type = type || 'friendly')
        //    .append('<h2><span class="icon ' + type+ '" />' + head + '<span class="close" id="showbox_close">close</span></h2>')
            .append('<h2>' + head + '<span class="close" id="showbox_close">close</span></h2>')
            .append(
                $('<div class="content"></div>')
                    .html(text)
            );
    $('<div id="box_underlayer"></div>')
        .append(box)
        .append('<div class="shield"></div>')
        .appendTo('body');
    $(box).css('width', $(box)[0].offsetWidth + 'px');
    $('#box_underlayer')
        .css({
            left: Math.round(($('body')[0].offsetWidth - $('#box_underlayer')[0].offsetWidth) / 2),
            display: 'none'
        });
    if($.browser.msie && $.browser.version.substr(0,1) == '7'){
        $('#box_underlayer').show();
    } else {
        $('#box_underlayer').fadeIn('slow');
    }
    $('span#showbox_close')
        .click(function(){
            $('#box_underlayer')
                .fadeOut('slow', function(){
                    $(this).remove();
                })
        });
    if(timeout)
        timers.add(timeout, function(){
            $('#box_underlayer')
                .fadeOut(1000, function(){
                    $(this).remove();
                })
        })
}

var dtpl = {
    tpls: {},
    behaviors: {deps:[], code:[]},
    load: function(list, on_complete){
        if(typeof(list) == 'array') list = list.join(',');
        loading_process('Loading resources...');
        httpReq.request('/api/resources/v1.js', 'post', {list: list}, function(status, data, on_complete){
            switch(status){
                case 200:
                    try {
                        eval('/*templates*/' + data);
                    } catch(e){
                        logger.error('error templates loading:\n' + e);
                        return;
                    }
                    if(typeof(on_complete) == 'function') on_complete();
                    break;
                default:
                    logger.error('templates loding error:\n' + status + '/' + http_status2str(status) + '');
                    break;
            }
        }, on_complete);
    },
    proc: function(rule, tpl, data){
        if(typeof(tpl) != 'string'){
            data = tpl;
            tpl = rule;
            rule = null;
        }
        if(typeof(this.tpls[tpl]) != 'function') {
            alert('template ' + tpl + ' was not found!');
            return;
        }
        if(rule){
            try{
                var ret = this.proc(tpl, data);
            } catch(e){
                logger.error('error template(' + tpl + ') execution:\n' + (e.message || e));
            }
            var m = '';
            if(typeof(rule) == 'string'){
                m = (m = rule.match(/^(.)/)) && m[1];
                rule = rule.replace(/^[\+\-]/, '');
            }
            with($(rule))
                switch(m){
                    case '+': //append
                        append(ret);
                        break;
                    case '-': // replace
                        replaceWith(ret);
                        break;
                    default: // inner html
                        html(ret);
                        break;
                }
            this.behavior(tpl);
        } else {
            try {
                return this.tpls[tpl](data)
                    .replace(/<s>/g, ' ')
                    .replace(/<t>/g, '\t')
                    .replace(/<n>/g, '\n');
            } catch(e){
                logger.error('error template(' + tpl + ') execution:\n' + (e.message || e));
            }
        }
    },
    behavior: function(tpl){
        var deps;
        if(deps = this.behaviors.deps[tpl])
            for(var i = deps.length; --i>=0;) 
                if(deps[i] != tpl)
                    this.behavior(deps[i]);
        if(this.behaviors.code[tpl])
            try {
                this.behaviors.code[tpl].call(null);
            } catch(e){
                alert('catch error while behavior processing:\n---\ntemplate: ' + tpl + '\n---\n' + e);
            }
    }
}

function in_array(src, niddle){
    for(var i = src.length; --i>=0;)
        if(src[i] === niddle) return i;
    return false;
}

function join_mixed(src, char){
    var result;
    switch(typeof(src)){
        case 'object':
            if(src instanceof Array){
                result = src.join(char || ' ');
            } else {
                var ar = [];
                for(var i in src)
                    ar.push(i + '="' + src[i] + '"')
                result = ar.join(char || ' ');
            }
            break;
        case 'array':
            result = src.join(char || ' ');
            break;
        case 'undefined':
            result = '';
            break;
        default:
            result = src;
            break;
    }
    return result;
}

function add2mixed(mixed, data, delim){
    delim ? delim = ' ' : null;
    if(typeof(mixed) == 'string' || mixed instanceof String){
        if(mixed != '') mixed += delim + data;
            else mixed = data;
    } else
        if(typeof(mixed) == 'array' || mixed instanceof Array){
            mixed.push(data);
        } else
            if(typeof(mixed) == 'object' || mixed instanceof Object)
                mixed[data] = delim;
    return mixed;
}

function fire_callback(method, args, async, error_msg){
    var af = function(){
            try {
                if(method instanceof Array)
                    method[1].apply(method[0], args)
                else
                    if(method instanceof String)
                        eval(method + '.call(null, args)')
                    else
                        if(method instanceof Function)
                            method.call(null, args);
            } catch(e) {
                logger.error(sprintf(error_msg, e.message || e));
            }
        }
    if(async)
        setTimeout(af, 5)
    else
        af();
}

var dispatcher = {
    container:{},
    register: function(event_type, rule, handler){
        var id = event_type + '::' + rule;
        if(!this.container[id]) this.container[id] = [];
        this.container[event_type + '::' + rule].push(handler);
        logger.say('event registration:\ntype:' + event_type + '\nrule:' + rule)
        $(rule).live(event_type, this.__event_handler);
    },
    unregister: function(event_type, rule){
        var id = event_type + '::' + rule;
        logger.say('event deregistration:\ntype:' + event_type + '\nrule:' + rule)
        if(this.container[id]){
            $(rule).die(event_type, this.__event_handler);
            delete this.container[id];
        }
    },
    __event_handler: function(event, rule, data){
        var self = dispatcher,
            evid = $(this).attr('evid') || $(this).attr('id') || 'none',
            handlers = self.container[event.type + '::' + rule];
        logger.say('event processing:\ntype:' + event.type + '\nevid: ' + evid + '\nrule:' + rule);
        if($(this).hasClass('disabled') || $(this).parents('.disabled').length) return false;
        for(var i = handlers.length; --i>=0;)
            fire_callback(handlers[i], [evid, event, this, data || event.data], true, 'catch error while dispatch event:\n%s\n---\nevent id: ' + evid + ', event: ' + event.type);
    }
}

/**
* Long words wrapper
* 
* Description: 
* Wrap long words by inserting a break symbol
* 
* Params:
* obj_id: a long string | DOM objects array | string as DOM object id or class
* limit: integer (max symbols) | string (max pixel)
* wbr: a break symbol
*/
function wrap_word(obj_id, limit, wbr) {
    wbr = (wbr) ? wbr : ' ';
    var alpha_str = '1234567890abcdefghijklmnopqrstuvwxyz',
        cur_words = [],
        limit_width = 0,
        min_width = 0,
        mg = 'px';
    
    if ('string' == typeof(obj_id)) {
        if(limit && !isNaN(limit)) {
            // obj_id is a long string && the limit is a number of symbols
            return obj_id.replace(RegExp('(\\S{' + limit + '})(\\S)', 'g'), function(text, word, char){
                return word + wbr + char; 
            });
        } else {
            // obj_id is a string as DOM object id or as a class name 
            // && the limit is a width in px|cm|em|pt
            try {
                cur_words = jQuery(obj_id);
                if (1 > cur_words.length) return false;
            } catch(e) {
                return false;
            }
            if (limit && (res = limit.match(RegExp('^(\\d+)(\\w+)')))) {
                limit_width = res[1];
                mg = res[2];
            } else {
             // or trying to calculate the width
                var org_overflow = cur_words.css('overflow'),
                    org_display  = cur_words.css('display');
                cur_words.css({overflow: 'scroll', display: 'block'})
                limit_width = cur_words[0].clientWidth + 10;
                cur_words.css({overflow: org_overflow, display: org_display})
            }
            if (limit_width) {
                container = jQuery(obj_id + ':first').clone();
                jQuery(obj_id + ':first').after(container);
            } else {
                return false;
            }
        }
    } else if ('object' == typeof(obj_id)) {
        cur_words = obj_id;
        if (limit && (res = limit.match(RegExp('^(\\d+)(\\w+)')))) {
            limit_width = res[1];
            mg = res[2];
        }
        if (1 < cur_words.length) {
            var first_obj = jQuery(':first', cur_words).parent();
            container = first_obj.clone();
            first_obj.after(container);
            if (0 == limit_width) limit_width = first_obj.innerWidth();
        } else {
            container = cur_words.clone();
            cur_words.after(container);
            if (0 == limit_width) limit_width = cur_words.innerWidth();
        }
    }
    container.css({width: limit_width +mg, position: 'absolute', left: '-3000px', top: '-3000px'});
    container.html('<span id="tmp_container">' + alpha_str + '</span>');
    min_width = parseInt(limit_width * alpha_str.length / jQuery('#tmp_container').width()) - 1;
    container.remove();
    
    for (var k=0; k<cur_words.length; k++) {
        cur_words[k].innerHTML = cur_words[k].innerHTML.replace(RegExp('(\\S{' + min_width + '})(\\S)', 'g'), function(text, word, char){
           return word + wbr + char; 
        });
    }
}

function obj2arr(obj){
    return obj instanceof Array && obj || [obj];
}

function round_time(min){
    var tmp = Math.round(parseFloat(min) * 60),
        a = tmp % 10;
    return a > 4 && (tmp + (10 - a)) || tmp - a;
}

logger = {
    actual: null,
    stack: [],
    whole: null,
    disabled: false,
    start: function(msg){
        if(this.disabled) return;
        
        this.actual = {
            msg: msg,
            time: new Date().getTime()
        }
        if(!this.whole) this.whole = this.actual.time;
    },
    end: function(){
        if(this.disabled) return;
        if(!this.actual) return;
        this.actual.time = new Date().getTime() - this.actual.time;
        this.stack.push(this.actual);
        this.actual = null;
    },
    say: function(msg){
        console.say && console.say(msg) || console.log(msg);
    },
    time: function(time){
        if(time > 1000) time = sprintf('%0.3fs', time / 1000)
            else time = time + 'ms';
        return time;
    },
    show: function(head){
        if(this.disabled) return;
        console.log('---------' + head + '[' + this.time(new Date().getTime() - this.whole) + ']----------');
        this.whole = null;
        for(var gt = 0, obj, str = '', i = 0; i<this.stack.length; i++){
            obj = this.stack[i];
            console.log('[' + this.time(obj.time) + ']  ' + obj.msg);
            gt += obj.time;
        }
        console.log('----------')
        console.log('total time: ' + this.time(gt));
        this.stack = [];
    },
    error: function(msg){
        if(this.disabled) {
            alert(msg);
            return;
        }
        console.error(msg);
    }
}

if(typeof(console) == 'undefined'){
    console = {
        log: function(){},
        error: function(){},
        say: function(str){}
    }
}
