/*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */

/*
 * Configurable variables. You may need to tweak these to be compatible with
 * the server-side, but the defaults work in most cases.
 */
var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */

/*
 * These are the functions you'll usually want to call
 * They take string arguments and return either hex or base-64 encoded strings
 */
function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }

/*
 * Perform a simple self-test to see if the VM is working
 */
function md5_vm_test()
{
  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
}

/*
 * Calculate the MD5 of an array of little-endian words, and a bit length
 */
function core_md5(x, len)
{
  /* append padding */
  x[len >> 5] |= 0x80 << ((len) % 32);
  x[(((len + 64) >>> 9) << 4) + 14] = len;

  var a =  1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d =  271733878;

  for(var i = 0; i < x.length; i += 16)
  {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;

    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);

    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
  }
  return Array(a, b, c, d);

}

/*
 * These functions implement the four basic operations the algorithm uses.
 */
function md5_cmn(q, a, b, x, s, t)
{
  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}

/*
 * Calculate the HMAC-MD5, of a key and some data
 */
function core_hmac_md5(key, data)
{
  var bkey = str2binl(key);
  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);

  var ipad = Array(16), opad = Array(16);
  for(var i = 0; i < 16; i++)
  {
    ipad[i] = bkey[i] ^ 0x36363636;
    opad[i] = bkey[i] ^ 0x5C5C5C5C;
  }

  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
  return core_md5(opad.concat(hash), 512 + 128);
}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */
function safe_add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left.
 */
function bit_rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}

/*
 * Convert a string to an array of little-endian words
 * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
 */
function str2binl(str)
{
  var bin = Array();
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < str.length * chrsz; i += chrsz)
    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
  return bin;
}

/*
 * Convert an array of little-endian words to a string
 */
function binl2str(bin)
{
  var str = "";
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < bin.length * 32; i += chrsz)
    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
  return str;
}

/*
 * Convert an array of little-endian words to a hex string.
 */
function binl2hex(binarray)
{
  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i++)
  {
    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
  }
  return str;
}

/*
 * Convert an array of little-endian words to a base-64 string
 */
function binl2b64(binarray)
{
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i += 3)
  {
    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
    for(var j = 0; j < 4; j++)
    {
      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
    }
  }
  return str;
}

// OneCode Application Framework | Copyright (C) 2005-2009 Radek Tetik | www.onecode.cz

Prototype.Browser.IE6 = Prototype.Browser.IE && parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("MSIE")+5)) == 6;
Prototype.Browser.IE7 = Prototype.Browser.IE && parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("MSIE")+5)) == 7;
Prototype.Browser.IE8 = Prototype.Browser.IE && !Prototype.Browser.IE6 && !Prototype.Browser.IE7;

/**
* Inheritance, traits, dynamic loading etc.
*/
var Wsf =
{
    /**
    * From the book Pro Javascript Design Patterns, page 43.
    */
    extend: function (subClass, superClass)
    {
        var F = function() {};
        F.prototype = superClass.prototype;
        subClass.prototype = new F();
        subClass.prototype.constructor = subClass;
    },

    use: function (class_, trait)
    {
        for (o in trait) class_.prototype[o] = trait[o];
    },

    requireClass: function (className)
    {
        if (eval("typeof "+className) != "undefined") return;

        var url = className.replace(/\_/g, '/');
        var code = null;

        Wsf.requireScript("/"+url.substr(1)+".js");
    },

    requireScript: function (file)
    {
        var code = false;

        var r = new Ajax.Request(file, {
            method: "get",
            asynchronous: false,
            onSuccess: function(transport) { code = transport.responseText; }
        });

        if (!code) { alert('Error loading class. Please restart the application by refreshing the page (F5).'); return; }

        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.text = code;
        document.body.appendChild(script);
    },

    requireStylesheet: function (file)
    {
        var code = false;

        var r = new Ajax.Request(file, {
            method: "get",
            asynchronous: false,
            onSuccess: function(transport) { code = transport.responseText; }
        });

        if (!code) { alert('Error loading stylesheet. Please restart the application by refreshing the page (F5).'); return; }

        var e = document.createElement('style');
        e.setAttribute('type', 'text/css');

        if (e.styleSheet)
            e.styleSheet.cssText = code;
        else
            e.appendChild(document.createTextNode(code));

        document.getElementsByTagName("head")[0].appendChild(e);
    },

    sprintf: function ( ) {
        // Return a formatted string
        //
        // version: 909.322
        // discuss at: http://phpjs.org/functions/sprintf
        // +   original by: Ash Searle (http://hexmen.com/blog/)
        // + namespaced by: Michael White (http://getsprink.com)
        // +    tweaked by: Jack
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +      input by: Paulo Ricardo F. Santos
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +      input by: Brett Zamir (http://brett-zamir.me)
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // *     example 1: sprintf("%01.2f", 123.1);
        // *     returns 1: 123.10
        // *     example 2: sprintf("[%10s]", 'monkey');
        // *     returns 2: '[    monkey]'
        // *     example 3: sprintf("[%'#10s]", 'monkey');
        // *     returns 3: '[####monkey]'
        var regex = /%%|%(\d+\$)?([-+\'#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuidfegEG])/g;
        var a = arguments, i = 0, format = a[i++];

        // pad()
        var pad = function (str, len, chr, leftJustify) {
            if (!chr) {chr = ' ';}
            var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
            return leftJustify ? str + padding : padding + str;
        };

        // justify()
        var justify = function (value, prefix, leftJustify, minWidth, zeroPad, customPadChar) {
            var diff = minWidth - value.length;
            if (diff > 0) {
                if (leftJustify || !zeroPad) {
                    value = pad(value, minWidth, customPadChar, leftJustify);
                } else {
                    value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
                }
            }
            return value;
        };

        // formatBaseX()
        var formatBaseX = function (value, base, prefix, leftJustify, minWidth, precision, zeroPad) {
            // Note: casts negative numbers to positive ones
            var number = value >>> 0;
            prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || '';
            value = prefix + pad(number.toString(base), precision || 0, '0', false);
            return justify(value, prefix, leftJustify, minWidth, zeroPad);
        };

        // formatString()
        var formatString = function (value, leftJustify, minWidth, precision, zeroPad, customPadChar) {
            if (precision != null) {
                value = value.slice(0, precision);
            }
            return justify(value, '', leftJustify, minWidth, zeroPad, customPadChar);
        };

        // doFormat()
        var doFormat = function (substring, valueIndex, flags, minWidth, _, precision, type) {
            var number;
            var prefix;
            var method;
            var textTransform;
            var value;

            if (substring == '%%') {return '%';}

            // parse flags
            var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false, customPadChar = ' ';
            var flagsl = flags.length;
            for (var j = 0; flags && j < flagsl; j++) {
                switch (flags.charAt(j)) {
                    case ' ': positivePrefix = ' '; break;
                    case '+': positivePrefix = '+'; break;
                    case '-': leftJustify = true; break;
                    case "'": customPadChar = flags.charAt(j+1); break;
                    case '0': zeroPad = true; break;
                    case '#': prefixBaseX = true; break;
                }
            }

            // parameters may be null, undefined, empty-string or real valued
            // we want to ignore null, undefined and empty-string values
            if (!minWidth) {
                minWidth = 0;
            } else if (minWidth == '*') {
                minWidth = +a[i++];
            } else if (minWidth.charAt(0) == '*') {
                minWidth = +a[minWidth.slice(1, -1)];
            } else {
                minWidth = +minWidth;
            }

            // Note: undocumented perl feature:
            if (minWidth < 0) {
                minWidth = -minWidth;
                leftJustify = true;
            }

            if (!isFinite(minWidth)) {
                throw new Error('sprintf: (minimum-)width must be finite');
            }

            if (!precision) {
                precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : undefined;
            } else if (precision == '*') {
                precision = +a[i++];
            } else if (precision.charAt(0) == '*') {
                precision = +a[precision.slice(1, -1)];
            } else {
                precision = +precision;
            }

            // grab value using valueIndex if required?
            value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];

            switch (type) {
                case 's': return formatString(String(value), leftJustify, minWidth, precision, zeroPad, customPadChar);
                case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad);
                case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
                case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
                case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
                case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad).toUpperCase();
                case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
                case 'i':
                case 'd':
                    number = parseInt(+value, 10);
                    prefix = number < 0 ? '-' : positivePrefix;
                    value = prefix + pad(String(Math.abs(number)), precision, '0', false);
                    return justify(value, prefix, leftJustify, minWidth, zeroPad);
                case 'e':
                case 'E':
                case 'f':
                case 'F':
                case 'g':
                case 'G':
                    number = +value;
                    prefix = number < 0 ? '-' : positivePrefix;
                    method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
                    textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
                    value = prefix + Math.abs(number)[method](precision);
                    return justify(value, prefix, leftJustify, minWidth, zeroPad)[textTransform]();
                default: return substring;
            }
        };

        return format.replace(regex, doFormat);
    }
}

/**
* DOM element extensions
*/
var WsfElement =
{
    /**
    * Sets the position and optionally sets the content size.
    */
    move: function(element,x,y,w,h) {
        element.style.left = x + "px";
        element.style.top = y + "px";
        if (w !== null) element.style.width = w + "px";
        if (h !== null) element.style.height = h + "px";
    },

    /**
    * Sets the content size (=not including padding and border)
    */
    setContentSize: function(element,w,h) {
        if (w !== null) element.style.width = w + "px";
        if (h !== null) element.style.height = h + "px";
    },

    getTextContent: function (element) {
        return Prototype.Browser.IE ? element.innerText : element.textContent;
    },

    setTextContent: function (element, text) {
        if (Prototype.Browser.IE)
            element.innerText = text;
        else
            element.textContent = text;
    }
}

Element.addMethods(WsfElement);

/**
* Events trait
*/
var Wsf_EventsTrait = {
    attachEventHandler:  function (eventName, fnCallback)
    {
        if (this._events[eventName] == undefined) { this._events[eventName] = []; }
        this._events[eventName].push(fnCallback);
    },

    /**
    * @param eventData    an object { sender } plus custom properties
    */
    raiseEvent: function (eventName, eventData)
    {
        if (this._events[eventName] == undefined) return;
        if (!eventData) eventData = {};
        eventData.sender = this;

        for (var i=this._events[eventName].length; i--;) {
            this._events[eventName][i](eventData);
        }
    }
};

/*****************************************************************************/
function Wsf_App(state)
{
    this._ctrls = {};
    this._rootCtrl = null;
    this.lang = state.lang;
    this._deletedServerCtrls = [];
    this._nextCtrlId = state._nextCtrlId;
    this._nextServerCtrlId = this._nextCtrlId;

    // RSH

    if (window.dhtmlHistory) {
        window.dhtmlHistory.create({
            toJSON: function(o) { return Object.toJSON(o); },
            fromJSON: function(s) { return s.evalJSON(); }
        });
        window.dhtmlHistory.initialize();
    }

    // ...

    document.observe('dom:loaded', this.onDomReady.bind(this));
}

/*****************************************************************************/
Wsf_App.prototype.onDomReady = function()
{
    if (Prototype.Browser.IE6) $(document.body).addClassName('ie6');

    // Init controls

    var ctrls = this._CreateCtrlTree();

    for (var id in ctrls) {
        this._ctrls[id] = ctrls[id][0];
    }

    for (var id in ctrls) {
        this._ctrls[id].createFromServerState(ctrls[id][1]);
    }

    this._rootCtrl._makeStateSnapshot();
    this._rootCtrl.initFromMarkup();
    this._rootCtrl.onCreate();

    // Resize

    Event.observe(window, 'resize', this._onResize.bindAsEventListener(this));
    this._onResize();

    // In chrome and IE CSS is not ready in the domready event, so update the the layout in the load event

    Event.observe(window, 'load', this._onResize.bind(this));

    // Start automatic UI update

    this._rootCtrl.updateUI();
    this._startUpdateUI();
}

/*****************************************************************************/
Wsf_App.prototype._startUpdateUI = function()
{
    if (!this._updateUIExecuter) {
        this._updateUIExecuter = new PeriodicalExecuter(this._rootCtrl.updateUI.bind(this._rootCtrl), .25);
    }
}

/*****************************************************************************/
Wsf_App.prototype._stopUpdateUI = function()
{
    if (this._updateUIExecuter) {
        this._updateUIExecuter.stop();
        this._updateUIExecuter = null;
    }
}

/**
* Sets a cookie.
* The valus is encoded using encodeURIComponent(). You must use urldecode() in PHP.
* @param expireDate       Date object or null for session duration
*/
Wsf_App.prototype.setCookie = function(name, value, expireDate, isGlobal)
{
    var s = name + "=" + encodeURIComponent(value);
    if (expireDate !== null) s += "; expires=" + expireDate.toGMTString();
    if (isGlobal) s += "; path" + "=/";      // to make HTTRACK dizzy ;-)
    document.cookie = s;
}

/*****************************************************************************/
Wsf_App.prototype.getCookie = function (name, defValue)
{
    // cookies are separated by semicolons
    // IE bug: cookies in total can have up-to 4096KB
    var cookies = document.cookie.split("; ");

    for (var i=0; i < cookies.length; i++) {
        // a name/value pair (a crumb) is separated by an equal sign
        var aCrumb = cookies[i].split("=");
        if (name == aCrumb[0]) return unescape(aCrumb[1]);
    }

    // a cookie with the requested name does not exist
    return defValue;
}

/*****************************************************************************/
Wsf_App.prototype.getCookieInt = function (name, defValue)
{
    return parseInt(this.getCookie(name, defValue));
}

/*****************************************************************************/
Wsf_App.prototype.refresh = function ()
{
    window.location.reload();
}

/*****************************************************************************/
Wsf_App.prototype.Navigate = function (url)
{
    window.location.href = url;
}

/*******************************************************************************/
/*
/* CONTROLS
/*
/*******************************************************************************/

/*****************************************************************************/
Wsf_App.prototype.getRootCtrl = function()
{
    return this._rootCtrl;
}

/*****************************************************************************/
Wsf_App.prototype._registerCtrl = function (id, ctrl)
{
    this._ctrls[id] = ctrl;
}

/*****************************************************************************/
Wsf_App.prototype._deregisterCtrl = function (ctrl)
{
    var id = ctrl.getId();
    delete this._ctrls[id];

    if (id.substr(1) < this._nextServerCtrlId) {
        this._deletedServerCtrls.push(id);
    }
}

/*****************************************************************************/
Wsf_App.prototype.getCtrlById = function (id)
{
    return id ? this._ctrls[id] : null;
}

/*******************************************************************************/
/*
/* CMDS
/*
/*******************************************************************************/

/*******************************************************************************/
Wsf_App.prototype.updateUI = function ()
{
    this._rootCtrl.updateUI();
}

/*******************************************************************************/
Wsf_App.prototype.invokeUpdateCmd = function (sourceCtrl, cmdId, CmdUI)
{
    var method = "onUpdate" + cmdId;

    var a = sourceCtrl.getCmdTarget();

    for (; a; a=a.getParent()) {
        if (a[method] != undefined) {
            a[method](CmdUI);
            break;
        }
        else if (a.onUpdateCmdUI(cmdId, CmdUI))
            break;
    }
}

/*******************************************************************************/
Wsf_App.prototype.invokeCmd = function (sourceCtrl, cmdId, params)
{
    // Ensure cmd is enabled

    var cmdUI = {
        isEnabled: true,
        enable: function (isEnabled) { this.isEnabled = isEnabled; },
        check: function (isChecked) { }
    };

    this.invokeUpdateCmd(sourceCtrl, cmdId, cmdUI);
    if (!cmdUI.isEnabled) return;

    // Handle on client
    // Try concrete OnXYZ() handler and then generic OnCommand() handler

    var method = "on" + cmdId;
    var cmdTarget = sourceCtrl.getCmdTarget();
    params.sender = sourceCtrl;
    params.cmdId = cmdId;

    for (var a=cmdTarget; a; a=a.getParent()) {
        if (a[method] != undefined) {
            if (a[method](params) !== false) return;
            break;
        }
        else if (a['onCommand'] != undefined) {
            if (a.onCommand(params) !== false) return;
        }
    }

    // Handle on server

    delete params.sender;
    delete params.cmdId;

    Wsf_Web_CallServerMethodHandler.invoke("Wsf_Web_MVC_CtrlManager", "_HandleCommand",
        { cmdId: cmdId, cmdParams: params, srcCtrlId: cmdTarget.getId() },
        null, null,
        false);
}

/*******************************************************************************/
/*
/* EVENTS
/*
/*******************************************************************************/

Wsf_App.prototype._onResize = function (ev)
{
    if (this._inResize) return;   // IE calls resize multiple times
    this._inResize = true;
    this._rootCtrl.onSize();
    this._inResize = false;
}


var Wsf_Web_MVC_ControlStateCoder = {

   /*******************************************************************************/
   DecodeStateVariable : function (value)
   {
      if (value === null) {
         return value;
      }
      else if (typeof(value) == "string") {
         return value.charAt(0) == '*' ? value.substr(1) : g_app.getCtrlById(value);
      }
      else if (typeof(value) == "object") {
         if (Object.isArray(value)) {
            for (var i=0; i < value.length; ++i) {
               value[i] = this.DecodeStateVariable(value[i]);
            }      
            return value;
         }
         else {
            for (var name in value) {
               value[name] = this.DecodeStateVariable(value[name]);
            }      
            return value;
         }
      }
      else {
         return value;
      }
   },
  
   /*******************************************************************************/
   encodeVariable : function (value)
   {
      if (value === null)
      {
         return value;
      }
      else if (typeof(value) == "object")
      {
         if (value.isA)
         {
            return value.getId();
         }
         else if (Object.isArray(value))
         {
            var newArray = [];
            
            for (var i=0; i < value.length; ++i)
            {
               newArray[i] = this.encodeVariable(value[i]);
            }
            
            return newArray;
         }
         else
         {
            var newObj = {};
            
            for (var name in value)
            {
               newObj[name] = this.encodeVariable(value[name]);
            }
            
            return newObj;
         }
      }
      else if (typeof(value) == "string")
      {
         return "*"+value;
      }
      else 
      {
         return value;
      }
   }
};


/**
* Command handling:
* - define concrete handler OnXYZ(params)
*   Return TRUE or nothing if handled or FALSE to handle on the server
* - define generic handler OnCommand(eventData), where eventData = { cmdId, sender, params1, param2, etc. }
*   Return TRUE if handled or FALSE to continue routing.
*/

/*******************************************************************************/
function Wsf_Ctrls_Control()
{
   this._classes = ['Wsf_Ctrls_Control'];
   this._offsetWidth = null;
   this._offsetHeight = null;
   this._clientWidth = null;
   this._clientHeight = null;
   this._events = {};
   this._children = [];
   this._serverState = {};
}

Wsf_Ctrls_Control.EVENT_AFTER_DELETE =  100001;

Wsf.use(Wsf_Ctrls_Control, Wsf_EventsTrait);

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.createFromServerState = function (serverState)
{
   for (var name in serverState) this._serverState[name] = null;
   this._loadState(serverState);
   
   if (this._parent) 
      this._parent._children.push(this);
   else 
      g_app._rootCtrl = this;  
}

/**
* Init control from markup. May be called multiple times during the life of the control.
*/
Wsf_Ctrls_Control.prototype.initFromMarkup = function ()
{
   this.elem = $(this._id);
   if (!this.elem) console.log(this._id, " not found");
   this.elem.WsfCtrl = this;     // Got error? The control has not been rendered
   
   this._layout = this._childrenLayout ? eval('new '+this._childrenLayout+'(this);') : null;
   if (!this._enabled) this._enableInternal(false);
   
   if (this._layout) this.elem.style.overflow = "hidden";
   
   for (var i=0,n=this._children.length ; i < n; ++i) this._children[i].initFromMarkup();
}

/**
* Called after all controls have been properly inited. 
* Overload to init the control, you may do RPC here.
*/
Wsf_Ctrls_Control.prototype.onCreate = function ()
{
   if (this._parent) this._parent.onChildAdded(this);

   for (var i=0,n=this._children.length ; i < n; ++i) this._children[i].onCreate();
}

/**
* Called in ajax, when state is updated.
*/
Wsf_Ctrls_Control.prototype.onStateChanged = function ()
{
   this.show(this._visible);
}

Wsf_Ctrls_Control.prototype.onChildAdded = function (child)
{
}

Wsf_Ctrls_Control.prototype.onChildDeleted = function (child)
{
}

/*******************************************************************************
*
* STATE
*
*******************************************************************************/

/**
* Load state from the server. 
*/
Wsf_Ctrls_Control.prototype._loadState = function (state)
{
   for (var name in state) 
   {
      this[name] = Wsf_Web_MVC_ControlStateCoder.DecodeStateVariable(state[name]);
   }
}
 
/*******************************************************************************/
Wsf_Ctrls_Control.prototype.saveState = function (ctrlsState)
{
   var state = this._getControlState();
   // save only if non-empty
   for (var name in state) { ctrlsState[this.getId()] = state; break; } 
   
   for (var i=0; i < this._children.length; ++i)
   {
      this._children[i].saveState(ctrlsState);
   }
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._makeStateSnapshot = function ()
{
   for (var name in this._serverState) 
   {
      this._serverState[name] = Object.toJSON(Wsf_Web_MVC_ControlStateCoder.encodeVariable(this[name]));
   }
   
   for (var i=0; i < this._children.length; ++i) this._children[i]._makeStateSnapshot();
}

/**
* Returns a state object to send to the server.
* You can overload it to add custom state to be passed to the server.
*/
Wsf_Ctrls_Control.prototype._getControlState = function ()
{
   if (!this._serverState) return {};

   var state = { };
   
   this._visible = this.elem.visible();

   for (var name in this._serverState) 
   {
      var newValue = Wsf_Web_MVC_ControlStateCoder.encodeVariable(this[name]);
      if (Object.toJSON(newValue) !== this._serverState[name]) state[name] = newValue;
   }

   return state;
}

/*******************************************************************************/
/*
/* ...
/*
/*******************************************************************************/

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.onPreDelete = function ()
{
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.deleteCtrl = function ()
{
   // Already deleted? View cannot be deleted so this test is ok.
   if (!this._parent) return;
   
   this._parent._children = this._parent._children.without(this);
   this.elem.parentNode.removeChild(this.elem);  // removes the whole HTML subtree
   
   this._deleteInternal();
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._deleteInternal = function ()
{
   this.onPreDelete();

   this.elem = null;
   var parent = this._parent;
   this._parent = null;   
   
   this._children.each(function (ctrl) { ctrl._deleteInternal(); });
   this._children = [];

   g_app._deregisterCtrl(this);
   
   parent.onChildDeleted(this);
   this.raiseEvent(Wsf_Ctrls_Control.EVENT_AFTER_DELETE, { sender: this });
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getElem = function ()
{
   return this.elem;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._getChildrenContainerElem = function ()
{
   return this.elem;
}

/**
* Register a class name to be able to RTTI (e.g. isA())
*/
Wsf_Ctrls_Control.prototype._addClass = function (className)
{
   this._classes.push(className);
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.isA = function (className)
{
   return this._classes.indexOf(className) != -1;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getAncestorByClass = function (className)
{
   for (var o=this; o; o=o._parent) {
      if (o.isA(className)) return o;
   }
   return null;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getView = function ()
{
   return Wsf_Ctrls_ViewHtml.prototype._instance;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getTitle = function ()
{
   return this._title;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getId = function ()
{
   return this._id;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getParent = function ()
{
   return this._parent;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getChildByIdx = function (idx)
{
   return this._children[idx];
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getChildrenCount = function ()
{
   return this._children.length;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getChildren = function ()
{
   return this._children;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.updateUI = function ()
{
   for (var i=0; i < this._children.length; ++i)
   {
      this._children[i].updateUI();    
   }
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getForm = function ()
{
   for (var p = this; p && !p.isA("Wsf_Forms_CtrlForm"); p=p._parent) /* dummy */;
   
   return p;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getWindow = function ()
{
   for (var p = this; p && !p.isA("Wsf_Ctrls_Window"); p=p._parent) /* dummy */;
   
   return p;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.focus = function ()
{
   if (!this.isVisible() || !this.isEnabled()) return false;
   
   this.elem.focus();   
   return true;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.hasFocus = function ()
{
   return this.getView().getFocus() === this;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.enable = function (enable)
{
   if (enable == this._enabled) return;
   this._enableInternal(enable);
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._enableInternal = function (enable)
{
   if (enable) 
      this.elem.removeClassName('disabled'); 
   else 
      this.elem.addClassName('disabled'); 
   
   this.elem.disabled = !enable;
   this._enabled = enable;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.isEnabled = function ()
{
   return this._enabled;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.isEnabled = function ()
{
   return this._enabled;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.isReadOnly = function ()
{
   return this._readOnly;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getStyle = function (style, defValue)
{
   return this._styles && this._styles[style] ? this._styles[style] : defValue;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.setTitle = function (title, isHtml)
{
   if (isHtml) {
      this._titleHtml = title;
      this._title = null;
   }
   else {
      this._titleHtml = null;
      this._title = title;
   }
   
   return this;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.callServerMethod = function (method, params, fnOnSuccess, fnOnFailure)
{
   return Wsf_Web_CallServerMethodHandler.invoke(this, method, params, fnOnSuccess, fnOnFailure); 
}


/*******************************************************************************/
/* 
/* VISIBILITY
/*
/*******************************************************************************/

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.isVisible = function ()
{
   return this.elem.visible();
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.show = function (show)
{
   if (show || show == undefined) 
      this.elem.show();
   else 
      this.elem.hide();

   if (this._parent) this._parent.onSize();
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.hide = function ()
{
   this.show(false);
}

/*******************************************************************************/
/* 
/* POSITION & SIZE - this is slow
/*
/*******************************************************************************/

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._cacheComputedStyles = function ()
{
//   this._margin = [0,0,0,0];
//   this._border = [0,0,0,0];
//   this._padding = [0,0,0,0];
//   return;

   var s = window.getComputedStyle ? window.getComputedStyle(this.elem, null) : this.elem.currentStyle;

   var a = [parseInt(s.paddingTop), parseInt(s.paddingRight), parseInt(s.paddingBottom), parseInt(s.paddingLeft) ];
   if (isNaN(a[0])) a[0] = 0;
   if (isNaN(a[1])) a[1] = 0;
   if (isNaN(a[2])) a[2] = 0;
   if (isNaN(a[3])) a[3] = 0;
   this._padding = a;

   a = [parseInt(s.marginTop), parseInt(s.marginRight), parseInt(s.marginBottom), parseInt(s.marginLeft)];
   if (isNaN(a[0])) a[0] = 0;
   if (isNaN(a[1])) a[1] = 0;
   if (isNaN(a[2])) a[2] = 0;
   if (isNaN(a[3])) a[3] = 0;
   this._margin = a;
                 
   a = [parseInt(s.borderTopWidth), parseInt(s.borderRightWidth), parseInt(s.borderBottomWidth), parseInt(s.borderLeftWidth)];
   if (isNaN(a[0])) a[0] = 0;
   if (isNaN(a[1])) a[1] = 0;
   if (isNaN(a[2])) a[2] = 0;
   if (isNaN(a[3])) a[3] = 0;
   this._border = a;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._clearSizeCache = function ()
{
   this._offsetWidth = null;
   this._offsetHeight = null;
   this._clientWidth = null;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._cacheOffsetWidth = function ()
{
   this._offsetWidth = this.elem.offsetWidth;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._cacheOffsetHeight = function ()
{
   this._offsetHeight = this.elem.offsetHeight;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype._cacheClientSize = function ()
{
   this._clientWidth = this.elem.clientWidth;
   this._clientHeight = this.elem.clientHeight;
}

/**
* content+padding+border
*/
Wsf_Ctrls_Control.prototype.getOffsetSize = function ()
{
   if (this._offsetWidth === null) this._cacheOffsetWidth();
   if (this._offsetHeight === null) this._cacheOffsetHeight();
   return { width: this._offsetWidth, height: this._offsetHeight };
}

/**
* content+padding+border
*/
Wsf_Ctrls_Control.prototype.getOffsetWidth = function () 
{
   if (this._offsetWidth === null) this._cacheOffsetWidth();
   return this._offsetWidth;
}

/**
* content+padding+border
*/
Wsf_Ctrls_Control.prototype.getOffsetHeight = function () 
{
   if (this._offsetHeight === null) this._cacheOffsetHeight();
   return this._offsetHeight;
}

/**
* content+padding 
*/
Wsf_Ctrls_Control.prototype.getClientSize = function ()
{
   if (this._clientWidth === null || this._clientHeight === null) this._cacheClientSize();
   return { width: this._clientWidth, height: this._clientHeight };
}

/**
* Slow!
*/
Wsf_Ctrls_Control.prototype.getMargin = function() 
{
   if (!this._margin) this._cacheComputedStyles();
   return this._margin;
}

/**
* Slow!
*/
Wsf_Ctrls_Control.prototype.getPadding = function() 
{
   if (!this._padding) this._cacheComputedStyles();
   return this._padding;
}

/**
* Slow!
*/
Wsf_Ctrls_Control.prototype._getMarginBorderPadding = function() 
{
   if (!this._margin) this._cacheComputedStyles();
   
   return [this._margin[0]+this._border[0]+this._padding[0],
      this._margin[1]+this._border[1]+this._padding[1],
      this._margin[2]+this._border[2]+this._padding[2],
      this._margin[3]+this._border[3]+this._padding[3]
      ];
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.move = function (x,y,w,h)
{
   var s = this.elem.style;
   
   if (x < 0) x = 0;
   if (y < 0) y = 0;
   
   if (s.position != "fixed") s.position = "absolute";

   s.left = x+"px";
   s.top = y+"px";
   
   if (w !== null || h !==null)
   {
      this.setSize(w, h);
   }
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.move2 = function (x1,y1,x2,y2,w,h)
{
   var s = this.elem.style;
   
   if (s.position != "fixed") s.position = "absolute";

   if (x1 !== null) s.left = x1+"px";
   if (y1 !== null) s.top = y1+"px";
   if (x2 !== null) s.right = x2+"px";
   if (y2 !== null) s.bottom = y2+"px";
   
   if ((x1 !== null && x2 !== null) || (y1 !== null && y2 !== null)) this._clearSizeCache();

   if (w !== null || h !== null)
   {
      this.setSize(w, h);
   }
   else this.onSize();
}
    
/*******************************************************************************/
Wsf_Ctrls_Control.prototype.setSize = function (w, h)
{
   var s = this.elem.style;
   var b = this._getMarginBorderPadding();
   
   if (w !== null) 
   {
      this._offsetWidth = w;
      w -= b[1] + b[3];
      if (w < 0) w = 0;
      s.width = w + "px";
      this._offsetWidth = w + b[1] + b[3];
   }
   
   if (h !== null) 
   {
      this._offsetHeight = h;
      h -= b[0] + b[2];
      if (h < 0) h = 0;
      s.height = h  + "px";
      this._offsetHeight = h + b[0] + b[2];
   }

   this.onSize();
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.onSize = function ()
{
   if (!this.isVisible()) return;
   
   if (this._layout) 
   {
      for (var i=0,n=this._children.length; i < n; ++i)
      {
         this._children[i]._clearSizeCache();
      }   
      this._layout.layoutChildren(this);
   }
   else
   {
      // Default CSS layout of children
      for (var i=0,n=this._children.length; i < n; ++i)
      {
         var o = this._children[i];
         o._clearSizeCache();
         o.onSize();
      }   
   }   
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.setZIndex = function (zIndex)
{
   this.elem.style.zIndex = zIndex;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.moveBelow = function (ctrl)
{
   this.elem.style.zIndex = ctrl.elem.style.zIndex - 1;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.setOpacity = function (opacityFloat)
{
   this.elem.setOpacity(opacityFloat);
}

/*******************************************************************************/
/* 
/* COMMANDS
/*
/*******************************************************************************/

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.getCmdTarget = function ()
{
   return this._cmdTarget ? this._cmdTarget : this;
}

/*******************************************************************************/
Wsf_Ctrls_Control.prototype.onUpdateCmdUI = function (cmdId, cmdui)
{
   return false;
}


/*******************************************************************************/
function Wsf_Ctrls_LayoutStack(ctrl)
{
   ctrl.elem.style.overflow = "auto";
}

/*******************************************************************************/
Wsf_Ctrls_LayoutStack.prototype.layoutChildren = function (ctrl)
{  
   // Get the height of all fixed controls and # of stretch controls
   
   var fixedH = 0;
   var stretchCount = 0;
   
   for (var i=ctrl._children.length; i--;)
   {
      var o = ctrl._children[i];
      if (!o.isVisible()) continue;
      
      if (o.getStyle('stretch',false))
      {
         ++stretchCount;
         o.elem.style.overflow = "auto";
      }
      else {                           
         var m = o.getMargin();
         fixedH += o.getOffsetHeight() + m[0] + m[2];
      }
   }
   
   // Layout

   var padding = ctrl.getPadding();
   var a = ctrl.getClientSize();
   var w = a['width'] - padding[1] - padding[3];
   var h = a['height'] - padding[0] - padding[2];
//   var x = padding[3];
   var y = padding[0];
   var stretchH = Math.max(h - fixedH, 0);
   
   if (stretchCount > 0)
   {
      var avgStretchH = Math.floor(stretchH / stretchCount);
      var lastStretchH = h - avgStretchH*(stretchCount-1) - fixedH;
   }
   
   for (var i=0,n=ctrl._children.length; i < n; ++i)
   {
      var o = ctrl._children[i];
      if (!o.isVisible()) continue;

      // e.g. input[text] does not stretch to 100% by default -> we must set also width
      
      if (o.getStyle('stretch',false))
      {
         h = (--stretchCount ? avgStretchH : lastStretchH);
//         o.move(x, y, w, h);
         o.setSize(w,h);
//         o.elem.style.position = "relative";
      }
      else
      {
         o.setSize(w,null);
//         var m = o.getMargin();
//         h = o.getOffsetHeight() + m[0] + m[2];
//         o.move(x, y, w, null);
//         o.elem.style.position = "relative";
      }
      
//      y += h;
   }   
}


/*******************************************************************************/
function Wsf_Ctrls_LayoutFullsize(ctrl)
{
}

/*******************************************************************************/
Wsf_Ctrls_LayoutFullsize.prototype.layoutChildren = function (ctrl)
{  
   for (var i=0,n=ctrl.getChildrenCount(); i<n; ++i)
   {
      var t = ctrl.getChildByIdx(i);

      if (t.isVisible())
      {
         var p = ctrl.getPadding();
         t.move2(p[3],p[0],p[1],p[2],null,null);
         break;
      }
   }
}


/*****************************************************************************/
var Wsf_Web_Vocabulary = { 
   
   // Vocabularies are added as new classes are loaded via AJAX
   _vocabularies : [],
   
   add : function (vocabulary)
   {
      this._vocabularies.push(vocabulary);
   },
   
   getWord : function (id)
   {
      a = id.split('.');
   
      for (var i=0,n=this._vocabularies.length; i < n; ++i)
      {
         var ns = this._vocabularies[i][a[0]];
         
         if (ns)
         {
            var w = ns[a[1]];
            if (w) return w;
         }
      }
      
      return "[***"+id+"***]";
   }
};

/*****************************************************************************/
function W(id)
{
   return Wsf_Web_Vocabulary.getWord(id);
}

/*******************************************************************************/
function Wsf_Web_CallServerMethodHandler(ctrl, method, params, fnOnSuccess, fnOnFailure, async)
{
    if (async === undefined) async = true;

    // Busy info

    var div = $(document.createElement('div'));
    div.id = 'wsfAjaxStatus';
    div.update(W('Wsf_Ctrls.LOADING')+"...");
    document.body.appendChild(div);

    // Overlay is needed to disable clicking 

    var div = $(document.createElement('div'));
    div.id = 'wsfDisableUiOverlay';
    document.body.appendChild(div);

    // DTO

    var postParams = { 
      wsfState: {
         async: async, 
         object: Wsf_Web_MVC_ControlStateCoder.encodeVariable(ctrl),
         method: method, 
         params: params === undefined ? {} : Wsf_Web_MVC_ControlStateCoder.encodeVariable(params), 
         ctrlsState: {},
         deletedServerCtrls: g_app._deletedServerCtrls
      }
    };

    g_app.getRootCtrl().saveState(postParams.wsfState.ctrlsState);
    postParams.wsfState = Object.toJSON(postParams.wsfState);

    // GO!

    if (async)
    {
        var a = $$('input','select','textarea');
        var filePresent = false;

        for (var i=0,n=a.length; i < n; ++i)
        {
            var elem = a[i];
            var name = elem.name;
            if (name == 'wsfState') continue;

            if (elem.type == 'file') 
            {
                if (!elem.files.length) continue;
                postParams[name] = { filename: elem.files[0].fileName, fileObject: elem.files[0] };
                filePresent = true;
            }
            else
            {         
                if (elem.type == 'checkbox' && !elem.checked) continue;
                var value = elem.value;

                if (postParams[name] === undefined)
                    postParams[name] = value;
                else {
                    if (!Object.isArray(postParams[name])) postParams[name] = [postParams[name]];
                    postParams[name].push(value);
                }
            }
      }

      if (filePresent)
      {
          var boundary = "wsfAjax--------------" + (new Date).getTime();      
          new Ajax.Request(document.location.href, {
                asynchronous: true,
                method: 'post',
                encoding: null, // default is UTF-8 which as appended to contentType, but this is an error for multipart
                contentType: "multipart/form-data; boundary=" + boundary,
                postBody: this._buildMessage(postParams, boundary), 
                wsfSendAsBinary: true,
                onSuccess: this.onSuccess.bind(this, fnOnSuccess, fnOnFailure),
                onFailure: this.onFailure.bind(this, fnOnFailure)
             });
      }
      else {      
          new Ajax.Request(document.location.href, {
                asynchronous: true,
                method: 'post',
                parameters: postParams,
                onSuccess: this.onSuccess.bind(this, fnOnSuccess, fnOnFailure),
                onFailure: this.onFailure.bind(this, fnOnFailure)
             });
      }
    }
    else
    {
      $('wsfState').value = postParams.wsfState; 
      $('wsfForm').submit();   
      this.done();      
    }
}
                         
/*******************************************************************************/
Wsf_Web_CallServerMethodHandler._instance = null;
Wsf_Web_CallServerMethodHandler._queue = [];

/**
* From http://www.webtoolkit.info/javascript-utf8.html
*/
Wsf_Web_CallServerMethodHandler._encodeUtf8 = function (string) 
{
//    string = string.replace(/\r\n/g,"\n");
    var utftext = "";

    for (var n=0, m=string.length; n < m; n++) 
    {
        var c = string.charCodeAt(n);

        if (c < 128) {
            utftext += String.fromCharCode(c);
        }
        else if((c > 127) && (c < 2048)) {
            utftext += String.fromCharCode((c >> 6) | 192);
            utftext += String.fromCharCode((c & 63) | 128);
        }
        else {
            utftext += String.fromCharCode((c >> 12) | 224);
            utftext += String.fromCharCode(((c >> 6) & 63) | 128);
            utftext += String.fromCharCode((c & 63) | 128);
        }

    }

    return utftext;
}

/*******************************************************************************/
Wsf_Web_CallServerMethodHandler.prototype._buildMessage = function(postParams, boundary) 
{
    var parts = [];

    for (var name in postParams)
    {
        var part  = "";
        var param = postParams[name];
        
        if (typeof(param) == "object")
        {
            if (Object.isArray(param)) {
                alert("Wsf_Web_CallServerMethodHandler.prototype._buildMessage: NOT IMPLEMENTED");
            }
            else {
                part += 'Content-Disposition: form-data; ';
                part += 'name="' + name + '"; ';
                part += 'filename="'+ Wsf_Web_CallServerMethodHandler._encodeUtf8(param.filename) + '"' + "\r\n";
                part += "Content-Type: application/octet-stream" + "\r\n\r\n";
                part += param.fileObject.getAsBinary() + "\r\n";
            }
        } else {
            part += 'Content-Disposition: form-data; ';
            part += 'name="' + name + '"' + "\r\n\r\n";
            //part += "Content-Type: text/plain; charset=UTF-8" + "\r\n\r\n";
            part += Wsf_Web_CallServerMethodHandler._encodeUtf8(param) + "\r\n";
        }

        parts.push(part);
    };

    var request = "--" + boundary + "\r\n";
    request += parts.join("--" + boundary + "\r\n");
    request += "--" + boundary + "--" + "\r\n";

    return request;
}
 
/**
* Parameters can be passed as an object of named parameters or an array of unnamed parameters.
* NOTE: You may not alter the control tree until method call has finished.
*/
Wsf_Web_CallServerMethodHandler.invoke = function (ctrl, method, params, fnOnSuccess, fnOnFailure, async)
{
   if (Wsf_Web_CallServerMethodHandler._instance)
   {
      Wsf_Web_CallServerMethodHandler._queue.push([ctrl, method, params, fnOnSuccess, fnOnFailure, async]);
      return;
   }
   
   Wsf_Web_CallServerMethodHandler._instance = new Wsf_Web_CallServerMethodHandler(ctrl, method, params, fnOnSuccess, fnOnFailure, async);
   return true; 
}

/*******************************************************************************/
Wsf_Web_CallServerMethodHandler.prototype.done = function ()
{
   var e = $('wsfAjaxStatus');
   e.parentNode.removeChild(e);
   e = $('wsfDisableUiOverlay');
   e.parentNode.removeChild(e);

   Wsf_Web_CallServerMethodHandler._instance = null;
   
   if (Wsf_Web_CallServerMethodHandler._queue.length) 
   {
      var p = Wsf_Web_CallServerMethodHandler._queue.shift();
      new Wsf_Web_CallServerMethodHandler(p[0], p[1], p[2], p[3], p[4], p[5]);
   }
}

/*******************************************************************************/
Wsf_Web_CallServerMethodHandler.prototype.onFailure = function (fnOnFailure, transport)
{
   alert(W('Wsf_Web.AJAX_SERVER_ERROR') + (DEBUG ? "\n\n"+transport.responseText : ''));
   if (fnOnFailure) fnOnFailure();
   this.done();   
}   
   
/*******************************************************************************/
Wsf_Web_CallServerMethodHandler.prototype.onSuccess = function (fnOnSuccess, fnOnFailure, transport)
{
   try {
      // If PHP failed, transport.responseText is invalid and usually contains a PHP error message (in debug mode).
      var response = transport.responseText.evalJSON();
   }
   catch (e) {
      this.onFailure(fnOnFailure, transport);
      return;
   }
   
   try
   {
      g_app._stopUpdateUI();
      
      g_app._nextCtrlId = response.nextCtrlId;
      g_app._nextServerCtrlId = response.nextCtrlId;
      g_app._deletedServerCtrls = [];
      
      // Load style sheets
      
      if (response.styleSheet) Wsf.requireStylesheet(response.styleSheet);
      
      // Load scripts

      for (var i=0,n=response.scripts.length; i < n; ++i)
      {
         var s = response.scripts[i];
         if (s.charAt(0) == '/' || s.substr(0,4) == 'http')
            Wsf.requireScript(s);
         else
            eval(s);
      }
      
      // Deleted controls
      
      for (var i=0,n=response.deletedCtrls.length; i < n; ++i) {
         var c = g_app.getCtrlById(response.deletedCtrls[i]);
         if (c) c.deleteCtrl();
      }  

      // 1. Add/update markup to the DOM
      
      if (response.renderedSubtrees !== undefined)
      {
         for (var id in response.renderedSubtrees)
         {
            var s = response.renderedSubtrees[id];
            var o = g_app.getCtrlById(id);
            
            if (o)
               o.getElem().replace(s.html);
            else {
               // TODO: insert before the right child
               var p = g_app.getCtrlById(s.pid);
               p._getChildrenContainerElem().insert(s.html);
            }
         }
      }
      
      // 2a) Create new controls
      
      if (response.newCtrls !== undefined)
      {
         eval('var ctrls = ' + response.newCtrls);

         for (var id in ctrls) {           
            g_app._registerCtrl(id, ctrls[id][0]);
         }
         
         for (var id in ctrls) {           
            g_app.getCtrlById(id).createFromServerState(ctrls[id][1]);
         }
      }
      
      // 2b) Load state of updated controls
      // Must be called after all new controls have been created

      if (response.ctrlsState !== undefined)
      {
         for (var id in response.ctrlsState) {
            var o = g_app.getCtrlById(id);
            o._loadState(response.ctrlsState[id]);
         }
      }

      // 4) MakeStateSnapshot() for all controls
      
      g_app.getRootCtrl()._makeStateSnapshot();
      
      // 5) .initFromMarkup(), OnCreate() for all rendered controls

      if (response.renderedSubtrees !== undefined)
      {
         for (var id in response.renderedSubtrees) {
            var c = g_app.getCtrlById(id);
            c.initFromMarkup();
         }
         for (var id in response.renderedSubtrees) {
            g_app.getCtrlById(id).onCreate();
         }
      }
      
      // 6) OnStateChanged() for updated controls
      
      if (response.ctrlsState !== undefined)
      {
         for (var id in response.ctrlsState) {
            var o = g_app.getCtrlById(id);
            o.onStateChanged();
         }
      }

      // Done
      
      g_app.getRootCtrl().onSize();  // force reflow
      if (fnOnSuccess) fnOnSuccess(Wsf_Web_MVC_ControlStateCoder.DecodeStateVariable(response.returnValue));
      g_app._startUpdateUI();
      this.done();   
   }
   
   catch (e) {
      alert(W('Wsf_Web.AJAX_FATAL_ERROR') + (DEBUG ? "\r\n\r\n"+e+"\r\n\r\n"+e.stack : ""));
      g_app.refresh();
   }
}

function Wsf_Ctrls_View(p) { Wsf_Ctrls_Control.call(this, p); } Wsf.extend(Wsf_Ctrls_View, Wsf_Ctrls_Control);


/*******************************************************************************/
function Wsf_Ctrls_ViewHtml(serverState)
{
   Wsf_Ctrls_View.call(this, serverState);
   
   // TODO: BUGGY
   Event.observe(document.body, 'activate', this._OnFocus.bindAsEventListener(this));
   
   Wsf_Ctrls_ViewHtml.prototype._instance = this;
}

Wsf.extend(Wsf_Ctrls_ViewHtml, Wsf_Ctrls_View);

/*******************************************************************************/
Wsf_Ctrls_ViewHtml.prototype.onCreate = function ()
{
   Wsf_Ctrls_View.prototype.onCreate.call(this);

   if (this._focusedCtrl) 
   {   
      if (!this._focusedCtrl.focus()) this._focusedCtrl = null;
   }
}

Wsf_Ctrls_ViewHtml.prototype.set_focusedCtrl = function (value)
{
   if (this._focusedCtrl = value) 
   {   
      if (!this._focusedCtrl.focus()) this._focusedCtrl = null;
   }
}

/*******************************************************************************/
Wsf_Ctrls_ViewHtml.prototype._getChildrenContainerElem = function ()
{
   return this.elem.down('form');
}

/*******************************************************************************/
Wsf_Ctrls_ViewHtml.prototype._OnFocus = function (ev)
{
   // BUGGY
   
   // NOTE: IE: Do not pass newly focused controls back to improve focus-stealing resistance :-)
   // Optimize when we know how
   if (Prototype.Browser.IE) this._focusedCtrl = g_app.getCtrlById(ev.element().id);
}

/*******************************************************************************/
Wsf_Ctrls_ViewHtml.prototype.getFocus = function()
{
   return this._focusedCtrl;
}

/*******************************************************************************/
function Wsf_Ctrls_Menu(state)
{
   Wsf_Ctrls_Control.call(this, state);

   this.aItemElems = [];
   this._isAnyItemEnabled = false;
   this.isForceUpdateUIWhenInvisible = false;
}

Wsf.extend(Wsf_Ctrls_Menu, Wsf_Ctrls_Control);

Wsf_Ctrls_Menu.EVENT_ITEM_CLICKED = 1;

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype.initFromMarkup = function ()
{
   Wsf_Ctrls_Control.prototype.initFromMarkup.call(this);  
   this.elem.descendants().each(this._decorateElem.bind(this));
}

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype._decorateElem = function (e)
{
   if (e.tagName != "LI") return;

   this.aItemElems.push(e);

   var a = e.id.split('.');
   e.sCmdId = a.length == 2 ? a[1] : null;

   if (this._isClientExpandMode)
   {
      if (e.sCmdId != '' && e.hasClassName('clickable'))
      {
         e.observe('click', this.onClickItem.bindAsEventListener(this, e));
      } 
      else if (e.hasClassName('expandable'))
      {
         e.observe('click', this.onExpandItem.bindAsEventListener(this, e));
      }

      e.observe('mouseover', this.LI_OnMouseOver.bindAsEventListener(this, e));
      e.observe('mouseout', this.LI_OnMouseOut.bindAsEventListener(this, e));   
   }
}

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype._getControlState = function ()
{
   var a = Wsf_Ctrls_Control.prototype._getControlState.apply(this);

   a.aSelIds = [];
   
   for (var i=this.aItemElems.length; i--;)
   {
      var e = this.aItemElems[i];
      if (e.hasClassName('sel')) a.aSelIds.push(e.id.split('.')[1]);
   }
   
   return a;
}

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype.updateUI = function ()
{
   if (!this._isCmdMode || (!this.isForceUpdateUIWhenInvisible && !this.isVisible())) return;

   var CmdUI = 
   { 
      isEnabled : true,
      isChecked : false,
      enable: function (isEnabled) { this.isEnabled = isEnabled; },
      check: function (isChecked) { this.isChecked = isChecked; }
   };
   
   this._isAnyItemEnabled = false;
                                          
   for (var i=this.aItemElems.length; i--;)
   {
      var e = this.aItemElems[i];
      var sCmdId = e.id.split('.')[1];
      if (!sCmdId) continue;
      CmdUI.isEnabled = true;
      CmdUI.isChecked = false;
      
      g_app.invokeUpdateCmd(this.getCmdTarget(), sCmdId, CmdUI);

      if (CmdUI.isEnabled) {
         this._isAnyItemEnabled = true;
         e.removeClassName('disabled'); 
      }
      else
         e.addClassName('disabled'); 
         
      if (CmdUI.isChecked)
         e.addClassName('checked'); 
      else
         e.removeClassName('checked');          
   }
}

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype.isAnyItemEnabled = function()
{
   return this._isAnyItemEnabled;
}

/*****************************************************************************/
/*
/* SELECTION
/*
/*****************************************************************************/

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.selectItem = function (id, isSelect)
{
   for (var i=this.aItemElems.length; i--;)
   {
      var e = this.aItemElems[i];
      
      if (id == e.id.split('.')[1])
      {
         if (isSelect)
            e.addClassName('sel');
         else
            e.removeClassName('sel');
         break;
      }
   }
}

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.getSelId = function ()
{
   for (var i=this.aItemElems.length; i--;)
   {
      var e = this.aItemElems[i];
      
      if (e.hasClassName('sel')) return e.id.split('.')[1];
   }
   
   return null;
}

/*****************************************************************************/
/*
/* EVENTS
/*
/*****************************************************************************/

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.onClickItem = function (ev, LI)
{
   Event.stop(ev);
   
   if ($(LI).hasClassName("disabled")) return;
   if ($(LI).hasClassName("expandable")) return;
   
   if (this.isContextMenu) this.hideContextMenu();
   
   // Notify

   this.raiseEvent(Wsf_Ctrls_Menu.EVENT_ITEM_CLICKED, { sender: this, id: LI.sCmdId });
   
   if (this._isCmdMode) g_app.invokeCmd(this.getCmdTarget(), LI.sCmdId, {});
}

/*****************************************************************************/
/*
/* CONTEXT MENU
/*
/*****************************************************************************/

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype.trackContextMenu = function(nX, nY)
{
   this.isContextMenu = true;
   this.isWithin = false;
   
   this.elem.style.left = nX + "px";
   this.elem.style.top = nY + "px";
   this.show(true);
   this.focus();

   this.fnTCMOnClickDoc = this.TcmOnClickDoc.bindAsEventListener(this);
   this.fnTCMOnClickMenu = this.TcmOnClickMenu.bindAsEventListener(this);
   this.fnTCMOnKeyDown = this._TcmOnKeyDown.bindAsEventListener(this);
   
   Event.observe(document, "mousedown", this.fnTCMOnClickDoc);
   Event.observe(this.elem, "mousedown", this.fnTCMOnClickMenu);         
   Event.observe(this.elem, "keydown", this.fnTCMOnKeyDown);         
   
   this.updateUI();
}

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype.hideContextMenu = function()
{
   Event.stopObserving(document, "mousedown", this.fnTCMOnClickDoc);
   Event.stopObserving(this.elem, "mousedown", this.fnTCMOnClickMenu);
   Event.stopObserving(this.elem, "keydown", this.fnTCMOnKeyDown);
   this.show(false);
   this.isContextMenu = false;
}

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.TcmOnClickDoc = function (event)
{
   if (!this.isWithin) this.hideContextMenu();
   this.isWithin = false;
}

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.TcmOnClickMenu = function (event)
{
   this.isWithin = true;
}

/*******************************************************************************/
Wsf_Ctrls_Menu.prototype._TcmOnKeyDown = function (ev)
{
   if (ev.keyCode == Event.KEY_ESC)
   {
      this.hideContextMenu();
   }
}

/*******************************************************************************/
/*
/* Expanding subitems
/*
/*******************************************************************************/

/*****************************************************************************/
function WsfCtrlMenu_DelayedHide(LI)
{
   var subItemsUl = $(LI).down().next('ul');
   var TitleDIV = $(LI).down();
   TitleDIV.removeClassName("exp");
   subItemsUl.hide();

   window.clearTimeout(subItemsUl.nTimeID);
   subItemsUl.nTimeID = null;
}

/*****************************************************************************/
function WsfCtrlMenu_DelayedShow(LI)
{
   var subItemsUl = $(LI).down().next('ul');
   var TitleDIV = $(LI).down();
   TitleDIV.addClassName("exp");
   subItemsUl.show();

   window.clearTimeout(subItemsUl.nTimeID);
   subItemsUl.nTimeID = null;
}

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.LI_OnMouseOver = function (ev, LI)
{
   if ((!LI.hasClassName("clickable") && !LI.hasClassName("expandable")) || LI.hasClassName("disabled")) return;

   var subItemsUl = LI.down().next('ul');
   if (!subItemsUl) return;
      
   if (subItemsUl.nTimeID)
   {
      window.clearTimeout(subItemsUl.nTimeID);
      subItemsUl.nTimeID = null;
   }   
   else
   {
      subItemsUl.nTimeID = window.setTimeout(function() { WsfCtrlMenu_DelayedShow(LI); }, 150);
   }
}

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.onExpandItem = function (ev, li)
{
   var subItemsUl = li.down().next();
   if (subItemsUl.nTimeID)
   {
      window.clearTimeout(subItemsUl.nTimeID);
      subItemsUl.nTimeID = null;
   }   

   WsfCtrlMenu_DelayedShow(li);
}

/*****************************************************************************/
Wsf_Ctrls_Menu.prototype.LI_OnMouseOut = function (ev, LI)
{
   if (LI.hasClassName("disabled")) return;

   var subItemsUl = LI.down().next('ul');
   if (!subItemsUl) return;
   
   if (subItemsUl.nTimeID)
   {
      window.clearTimeout(subItemsUl.nTimeID);
      subItemsUl.nTimeID = null;
   }   
   else
   {
      subItemsUl.nTimeID = window.setTimeout(function() { WsfCtrlMenu_DelayedHide(LI); }, 500);
   }
}

Wsf_Web_Vocabulary.add({"Web" : {"REFERENCE_TITLE" : "Název akce","REFERENCE_PLACE" : "Lokalita","REFERENCE_INVESTOR" : "Investor","REFERENCE_PHOTO" : "Fotografie"},"Wsf_Ctrls" : {"WSF_ROWS_PER_PAGE" : "Řádků na stránce","WSF_PAGE" : "Stránka","WSF_PREV_PAGE" : "« Předchozí","WSF_NEXT_PAGE" : "Další »","WSF_THUMBS" : "Náhledy","SORT_BY" : "Seřadit podle","WSF_LOCATION" : "Umístění","RESULTS" : "Výsledky","OF" : "z","CLOSE_WINDOW" : "Zavřít okno","LOADING" : "Pracuji"},"Wsf" : {"MONTH_1" : "leden","MONTH_2" : "únor","MONTH_3" : "březen","MONTH_4" : "duben","MONTH_5" : "květen","MONTH_6" : "červen","MONTH_7" : "červenec","MONTH_8" : "srpen","MONTH_9" : "září","MONTH_10" : "říjen","MONTH_11" : "listopad","MONTH_12" : "prosinec","MONTH_3LTRS_1" : "Led","MONTH_3LTRS_2" : "Úno","MONTH_3LTRS_3" : "Bře","MONTH_3LTRS_4" : "Dub","MONTH_3LTRS_5" : "Kvě","MONTH_3LTRS_6" : "Čer","MONTH_3LTRS_7" : "Čev","MONTH_3LTRS_8" : "Srp","MONTH_3LTRS_9" : "Zář","MONTH_3LTRS_10" : "Říj","MONTH_3LTRS_11" : "Lis","MONTH_3LTRS_12" : "Pro","DAY2_0" : "Po","DAY2_1" : "Út","DAY2_2" : "St","DAY2_3" : "Čt","DAY2_4" : "Pá","DAY2_5" : "So","DAY2_6" : "Ne","CANCEL" : "Storno","APPLY" : "Použít","INVALID_ARGUMENT" : "Neplatný parametr","INVALID_OPERATION" : "Neplatná operace: v aktuálním stavu nelze operaci provést.","OPERATION_FAILED" : "Akce se nezdařila. Zkuste jí zopakovat.","YES" : "Ano","NO" : "Ne"},"Wsf_Web" : {"OLD_IE_NOTICE" : "Používáte <strong>zastaralý prohlížeč</strong> Internet Explorer %s. Zobrazená <strong>stránka nemusí fungovat správně</strong>.<br/>Požádejte vašeho administrátora o aktualizaci prohlížeče nebo si ji <a href=\"http://www.microsoft.com/cze/windows/internet-explorer/default.aspx\">stáhněte sami</a>.","OLD_FF_NOTICE" : "Používáte <strong>zastaralý prohlížeč</strong> Mozilla Firefox %s. Zobrazená <strong>stránka nemusí fungovat správně</strong>.<br/>Požádejte vašeho administrátora o aktualizaci prohlížeče nebo si ji <a href=\"http://www.getfirefox.com\">stáhněte sami</a>.","AJAX_SERVER_ERROR" : "Chyba při komunikaci se serverem. Zkuste akci opakovat nebo obnovit stránku (klávesa F5).","AJAX_FATAL_ERROR" : "Chyba webové aplikace. Aplikace bude znovu načtena."}});


