function addEvent(obj, type, fn) {
  if (obj.addEventListener) {
    if (type == 'mousewheel') {
      type = 'DOMMouseScroll';
    };

    obj.addEventListener( type, fn, false );
  } else if (obj.attachEvent) {
    obj["e"+type+fn] = fn;
    obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
    obj.attachEvent( "on"+type, obj[type+fn] );
  }
}

function removeEvent(obj, type, fn) {
  if (obj.removeEventListener) {
    obj.removeEventListener( type, fn, false );
  } else if (obj.detachEvent) {
    obj.detachEvent( "on"+type, obj[type+fn] );
    obj[type+fn] = null;
    obj["e"+type+fn] = null;
  }
}
function getPositionParent(element) {
  var parent = element.parentNode;
 
  while (parent && parent.style.position == "static") {
    parent = parent.parentNode;
  };

  if (parent == null) {
    return document.body;
  };

  return parent;
}

function getWindowHeight() {
  return window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
}

function getWindowWidth() {
  return window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
}

function getLeft(item) {
  var parent = item.offsetParent;
  if (parent) {
    return item.offsetLeft + getLeft(parent);
  } { 
    return item.offsetLeft;
  };
}

function getTop(item) {
  var parent = item.offsetParent;
  if (parent) {
    return item.offsetTop + getTop(parent);
  } { 
    return item.offsetTop;
  };
}

function getBottom(item) {
  if (item.tagName == "SELECT") {
    return getTop(item) + item.scrollHeight;
  } else {
    return getTop(item) + item.clientHeight;
  };
}

function getRight(item) {
  return getLeft(item) + item.clientWidth;
}function textBoxSelect(item, from, to) {
  if (item.setSelectionRange) {
    // Mozilla/Firefox
    item.setSelectionRange(from, to);
  } else if (item.createTextRange) {
    // IE
    var range = item.createTextRange();
    range.moveStart("character", from);
    range.moveEnd("character", to - item.value.length);
    range.select();
  };
};
// #REQUIRE: events/core.js
// #REQUIRE: gui/geometry.js
// #REQUIRE: gui/select.js

var Validators = {
  POPUP_BOTTOM: 0,
  POPUP_TOP: 1,
  POPUP_LEFT: 2,
  POPUP_RIGHT: 3,
  errorPopup: null,
  hideCallback: null
};

var error_offset_x = 3;
var error_offset_y = 3;
var error_timeout  = 3000;

function getErrorPopup() {
  if (Validators.errorPopup == null) {
     Validators.errorPopup = document.createElement('div');
     Validators.errorPopup.className = 'errorpopup';
     document.body.appendChild(Validators.errorPopup);

     addEvent(Validators.errorPopup, 'click', hideErrorPopup);
  };

  return Validators.errorPopup;
}

function hideErrorPopup() {
  if (Validators.hideCallback != null) {
    clearTimeout(Validators.hideCallback);
    Validators.hideCallback = null;
  };

  console.log("Hide error popup called");

  getErrorPopup().style.visibility = 'hidden';
}

function showErrorPopup(element, text, onTop, popupBase) {
  element.focus();
  textBoxSelect(element, 0, element.value.length);

  showErrorPopupNoSelect(element, text, onTop, popupBase);
}

function showErrorPopupNoSelect(element, text, popupPosition, popupBase) { 
  // Hide previously displayed popup
  hideErrorPopup();

  // By default, the validated element server as a base for error message positioning

  if (!popupBase) {
    popupBase = element;
  };

  // By default, error popup is placed below validated element
  
  if (!popupPosition) {
    popupPosition = Validators.POPUP_BOTTOM;
  };

  element.focus();

  var error_item = getErrorPopup();
  error_item.innerHTML = text + "<div class='comment'>"+_("Click to dismiss popup")+"</div>";

  var top;
  var left;
  switch (popupPosition) {
    case Validators.POPUP_TOP:
      left = getLeft(popupBase) + error_offset_x;
      top = (getTop(popupBase) - error_item.clientHeight - error_offset_y);
      break;
    case Validators.POPUP_BOTTOM:
      left = getLeft(popupBase) + error_offset_x;
      top = getBottom(popupBase)  + error_offset_y;
      break;
    case Validators.POPUP_LEFT:
      top  = getTop(popupBase);
      left = getLeft(popupBase) - 150 - error_offset_x;
      break;
    case Validators.POPUP_RIGHT:
      top  = getTop(popupBase);
      left = getRight(popupBase) + error_offset_x;
      break;
  };

  if (top < 0) { top = 0; };

  // Check if this popup will hang over the right edge of the window

  if (left + 150 > document.body.clientWidth) {
    left = document.body.clientWidth - 150;
  };
  if (left < 0) { left = 0; };

  console.log("Show error popup: popup=%s, left=%s, top=%s", error_item, left, top);

  error_item.style.left = (left) + 'px';
  error_item.style.top  = (top) + 'px';

  error_item.style.visibility = 'visible';
  error_item.style.position = 'absolute';

  console.log("Error popup display: %s", error_item.style.display);

  Validators.hideCallback = setTimeout(function() { Validators.hideCallback = null; hideErrorPopup(); }, error_timeout);
};
/* Function printf(format_string,arguments...)
 * Javascript emulation of the C printf function (modifiers and argument types 
 *    "p" and "n" are not supported due to language restrictions)
 *
 * Copyright 2003 K&L Productions. All rights reserved
 * http://www.klproductions.com 
 *
 * Terms of use: This function can be used free of charge IF this header is not
 *               modified and remains with the function code.
 * 
 * Legal: Use this code at your own risk. K&L Productions assumes NO resposibility
 *        for anything.
 ********************************************************************************/
function sprintf(fstring)
  { var pad = function(str,ch,len)
      { var ps='';
        for(var i=0; i<Math.abs(len); i++) ps+=ch;
        return len>0?str+ps:ps+str;
      }
    var processFlags = function(flags,width,rs,arg)
      { var pn = function(flags,arg,rs)
          { if(arg>=0)
              { if(flags.indexOf(' ')>=0) rs = ' ' + rs;
                else if(flags.indexOf('+')>=0) rs = '+' + rs;
              }
            else
                rs = '-' + rs;
            return rs;
          }
        var iWidth = parseInt(width,10);
        if(width.charAt(0) == '0')
          { var ec=0;
            if(flags.indexOf(' ')>=0 || flags.indexOf('+')>=0) ec++;
            if(rs.length<(iWidth-ec)) rs = pad(rs,'0',rs.length-(iWidth-ec));
            return pn(flags,arg,rs);
          }
        rs = pn(flags,arg,rs);
        if(rs.length<iWidth)
          { if(flags.indexOf('-')<0) rs = pad(rs,' ',rs.length-iWidth);
            else rs = pad(rs,' ',iWidth - rs.length);
          }    
        return rs;
      }
    var converters = new Array();
    converters['c'] = function(flags,width,precision,arg)
      { if(typeof(arg) == 'number') return String.fromCharCode(arg);
        if(typeof(arg) == 'string') return arg.charAt(0);
        return '';
      }
    converters['d'] = function(flags,width,precision,arg)
      { return converters['i'](flags,width,precision,arg); 
      }
    converters['u'] = function(flags,width,precision,arg)
      { return converters['i'](flags,width,precision,Math.abs(arg)); 
      }
    converters['i'] =  function(flags,width,precision,arg)
      { var iPrecision=parseInt(precision);
        var rs = ((Math.abs(arg)).toString().split('.'))[0];
        if(rs.length<iPrecision) rs=pad(rs,' ',iPrecision - rs.length);
        return processFlags(flags,width,rs,arg); 
      }
    converters['E'] = function(flags,width,precision,arg) 
      { return (converters['e'](flags,width,precision,arg)).toUpperCase();
      }
    converters['e'] =  function(flags,width,precision,arg)
      { iPrecision = parseInt(precision);
        if(isNaN(iPrecision)) iPrecision = 6;
        rs = (Math.abs(arg)).toExponential(iPrecision);
        if(rs.indexOf('.')<0 && flags.indexOf('#')>=0) rs = rs.replace(/^(.*)(e.*)$/,'$1.$2');
        return processFlags(flags,width,rs,arg);        
      }
    converters['f'] = function(flags,width,precision,arg)
      { iPrecision = parseInt(precision);
        if(isNaN(iPrecision)) iPrecision = 6;
        rs = (Math.abs(arg)).toFixed(iPrecision);
        if(rs.indexOf('.')<0 && flags.indexOf('#')>=0) rs = rs + '.';
        return processFlags(flags,width,rs,arg);
      }
    converters['G'] = function(flags,width,precision,arg)
      { return (converters['g'](flags,width,precision,arg)).toUpperCase();
      }
    converters['g'] = function(flags,width,precision,arg)
      { iPrecision = parseInt(precision);
        absArg = Math.abs(arg);
        rse = absArg.toExponential();
        rsf = absArg.toFixed(6);
        if(!isNaN(iPrecision))
          { rsep = absArg.toExponential(iPrecision);
            rse = rsep.length < rse.length ? rsep : rse;
            rsfp = absArg.toFixed(iPrecision);
            rsf = rsfp.length < rsf.length ? rsfp : rsf;
          }
        if(rse.indexOf('.')<0 && flags.indexOf('#')>=0) rse = rse.replace(/^(.*)(e.*)$/,'$1.$2');
        if(rsf.indexOf('.')<0 && flags.indexOf('#')>=0) rsf = rsf + '.';
        rs = rse.length<rsf.length ? rse : rsf;
        return processFlags(flags,width,rs,arg);        
      }  
    converters['o'] = function(flags,width,precision,arg)
      { var iPrecision=parseInt(precision);
        var rs = Math.round(Math.abs(arg)).toString(8);
        if(rs.length<iPrecision) rs=pad(rs,' ',iPrecision - rs.length);
        if(flags.indexOf('#')>=0) rs='0'+rs;
        return processFlags(flags,width,rs,arg); 
      }
    converters['X'] = function(flags,width,precision,arg)
      { return (converters['x'](flags,width,precision,arg)).toUpperCase();
      }
    converters['x'] = function(flags,width,precision,arg)
      { var iPrecision=parseInt(precision);
        arg = Math.abs(arg);
        var rs = Math.round(arg).toString(16);
        if(rs.length<iPrecision) rs=pad(rs,' ',iPrecision - rs.length);
        if(flags.indexOf('#')>=0) rs='0x'+rs;
        return processFlags(flags,width,rs,arg); 
      }
    converters['s'] = function(flags,width,precision,arg)
      { var iPrecision=parseInt(precision);
        var rs = arg;
        if(rs.length > iPrecision) rs = rs.substring(0,iPrecision);
        return processFlags(flags,width,rs,0);
      }
    farr = fstring.split('%');
    retstr = farr[0];
    fpRE = /^([-+ #]*)(\d*)\.?(\d*)([cdieEfFgGosuxX])(.*)$/;
    for(var i=1; i<farr.length; i++)
      { fps=fpRE.exec(farr[i]);
        if(!fps) continue;
        if(arguments[i]!=null) retstr+=converters[fps[4]](fps[1],fps[2],fps[3],arguments[i]);
        retstr += fps[5];
      }
    return retstr;
  }
/* Function printf() END */function _(string) {
  return string;
}// #REQUIRE: input/validators/core.js
// #REQUIRE: strings/sprintf.js
// #REQUIRE: i18n/core.js

Validators.string_length = function(element, minlength, maxlength, fieldname) {
  if (minlength != null && element.value.length < minlength) {
    showErrorPopup(element, sprintf(_("This field should contain %s symbols at least"), minlength.toString()));
    return false;
  }

  if (maxlength != null && element.value.length > maxlength) {
    showErrorPopup(element, sprintf(_("This field should contain %s symbols at most"), maxlength.toString()));
    return false;
  };

  return true;
};function $(id) {
  return document.getElementById(id);
}

function $T(id, tag) {
  return $(id).getElementsByTagName(tag);
}

function ac(element, cls) {
  if (!element.className.match(cls)) {
    element.className += " " + cls;
  };
}

function rc(element, cls) {
  element.className = element.className.replace(new RegExp('\s*'+cls+'\s*'),"");
}
try {
  HTMLSelectElement.prototype.selectOption = HTMLSelectElement__selectOption;
} catch (err) {
}

function HTMLSelectElement__selectOption(value) {
  return HTMLSelectElement___selectOption(this, value);
}

function HTMLSelectElement___selectOption(obj, value) {
  for (var i=0; i<obj.options.length; i++) {
    var option = obj.options[i];
    if (option.value == value) {
      option.selected = true;
    };
  };
}
// #REQUIRE: html/tags/select.js

var AJAX = new Object;

AJAX.loadHTML = function(object, fetcher) {
  fetcher(function(response) { 
    object.innerHTML = response.param;
    object.disabled = (response.param == "");
  });
};

AJAX.loadSelectOptions = function(object, fetcher) {
  fetcher(function(response) { 
    object.options.length = 0;

    var options = response.param;

    for (var i=0; i <options.length; i++) {
      var option = options[i];
      object.options[i] = new Option(option.text, option.value);
    };
    object.disabled = (options.length == 1); // Only one (empty) option
  });
};function createXMLHttpRequest() {
  var xmlhttp;

  try {
    xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
  } catch (e) {
    try {
      xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (E) {
      xmlhttp = new XMLHttpRequest();
    };
  };

  return {
    xmlhttp: xmlhttp,
    readyState: 0,
    responseXML: null,
    responseText: "",
    onsuccess: null,
    url: "",
    sent: "",
    async: 1,
    onsuccesscalled: false,

    onreadystatechange: function() {
    },

    open: function(method, url, mode) { 
      var obj = this;

      this.xmlhttp.onreadystatechange = function () {
        obj.readyState = obj.xmlhttp.readyState;

        if (obj.xmlhttp.readyState == 4) {
          obj.responseXML  = obj.xmlhttp.responseXML;
          obj.responseText = obj.xmlhttp.responseText;

          if (obj.onsuccess) { 
            obj.onsuccesscalled = true;
            obj.onsuccess(); 
          };
        };

        obj.onreadystatechange();
      };

      var ts = new Date;
      if (url.indexOf('?') == -1) {
        url = url + "?_ts=" + ts.getTime();
      } else {
        url = url + "&_ts=" + ts.getTime();
      };

      this.url = url;
      this.async = mode;

      this.xmlhttp.open(method, url, mode);
      this.xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    },

    send: function(data) {
      this.sent = data;

      this.xmlhttp.send(data);

      // There's a difference in FF and IE behavior:
      // IE calls onreadystatechange even foe non-async requests,
      // while FF doesn't; thus, the following condition a 
      // a workaround for FF behavior

      if (!this.async && !this.onsuccesscalled && this.xmlhttp.readyState == 4) {
        this.responseXML  = this.xmlhttp.responseXML;
        this.responseText = this.xmlhttp.responseText;
        this.onsuccesscalled = true;
        if (this.onsuccess) {
          this.onsuccess();
        };
      };
    },
    
    handleError: function(callbacks) { 
      // FireFox bug workaround;
      // sometimes FF (up to 1.5.0.1) XMLHTTPRequest object 
      // returns empty response body whatever the actual response was 
      // (when calling from the FCKEditor event handler).

      if (this.xmlhttp.responseText == "" && this.xmlhttp.responseXML == null) {
        return false;
      };

      if (!this.xmlhttp.responseXML) {
        alert("Error; server responded with '"+this.xmlhttp.responseText+"'");
        return true;
      };

      if (!this.xmlhttp.responseXML.documentElement) {
        alert("Error; server responded with '"+this.xmlhttp.responseText+"'");
        return true;
      };

      if (this.xmlhttp.responseXML.documentElement.tagName == "parsererror") {
        alert("Error; server responded with '"+this.xmlhttp.responseText+"'");
        return true;
      };

      var root = this.xmlhttp.responseXML.documentElement;
      if (root.tagName == 'error') {
        var handler = null;
        var code = root.getAttribute('code');
        var text = root.text ? root.text : root.textContent;

        if (callbacks != null) {
          handler = callbacks[code];
        };

        if (handler) {
          return handler(code, text);
        } else {
          var query_text = this.sent.replace(/&/g, "\n");
          alert("ERROR: " + code + " " + text + "\n" + query_text);
          return true;
        };
      };

      return false;
    }
  } 
};
function getInnerText (node) {
  if (typeof node.textContent != 'undefined') {
    return node.textContent;
  } else if (typeof node.innerText != 'undefined') {
    return node.innerText;
  } else if (typeof node.text != 'undefined') {
    return node.text;
  } else {
    switch (node.nodeType) {
      case 3:
      case 4:
        return node.nodeValue;
        break;
      case 1:
      case 11:
        var innerText = '';
        for (var i = 0; i < node.childNodes.length; i++) {
          innerText += getInnerText(node.childNodes[i]);
        }
        return innerText;
        break;
      default:
        return '';
    }
  }
}// #REQUIRE: ajax/request.js
// #REQUIRE: xml/core.js

XMLRPCResponse.prototype = new Object;
XMLRPCResponse.prototype.isError = XMLRPCResponse_isError;

function XMLRPCResponse(param) {
  this.param = param;
}

function XMLRPCResponse_isError() {
  return false;
}

/* ---------- */

XMLRPCError.prototype = new Object;
XMLRPCError.prototype.isError = XMLRPCError_isError;
XMLRPCError.prototype.getMessage = XMLRPCError_getMessage;

function XMLRPCError(code, message) {
  this._message = message;
  this._code    = code;
}

function XMLRPCError_getMessage() {
  return this._message;
}

function XMLRPCError_isError() {
  return true;
}

/* ---------- */

var XMLRPC = new Object;
XMLRPC.prepare = function(name, params) {
  var methodName = "<methodName>"+name+"</methodName>\n";

  var paramsText = "<params>\n";
  for (var i=0; i<params.length; i++) {
    var paramText = "<param><value><string>"+params[i]+"</string></value></param>\n";
    paramsText += this._prepareParam(params[i]);
  };
  paramsText += "</params>\n";

  return "<methodCall>\n"+methodName+paramsText+"</methodCall>\n";
};

XMLRPC._prepareParam = function(value) {
   var paramText;
   paramText  = "<param>";
   paramText += this._prepareValue(value);
   paramText += "</param>\n";
   return paramText;
}

XMLRPC._prepareValue = function(value) {
  var paramText = "<value>";
  switch (typeof value) {
    case "object":
      paramText += this._prepareObject(value);
      break;
    default:
      paramText += this._prepareString(value);
      break;
  };
  paramText += "</value>";
  return paramText;
}

XMLRPC._prepareObject = function(value) {
  var valueText = "";
  for (var key in value) {
    valueText += "<member><name>"+key+"</name>"+this._prepareValue(value[key])+"</member>\n"
  };
  return "<struct>"+valueText+"</struct>";
}

XMLRPC._prepareString = function(value) {
  return "<string>"+value+"</string>";
}

XMLRPC.parseResponse = function(responseXML) {
  // Check if it is the error response
  var faultElement = responseXML.getElementsByTagName('fault')[0];

  // TODO: make more reliable parsing utility
  if (faultElement) {
    var code    = getInnerText(faultElement.getElementsByTagName('int')[0]);
    var message = getInnerText(faultElement.getElementsByTagName('string')[0]);

    var response = new XMLRPCError(code, message);
    return response;
  };

  // Normal response
  //
  // The body of the response is a single XML structure, a <methodResponse>, 
  // which _can_ contain a single <params> which contains a single <param> which contains a single <value>.
  //
  var paramXML = responseXML.getElementsByTagName('param')[0];
  if (paramXML) {
    return new XMLRPCResponse(this._parseResponseParam(paramXML));
  } else {
    return new XMLRPCResponse(null);
  };
};

XMLRPC._parseResponseParam = function(element) {
  for (var i=0; i<element.childNodes.length; i++) {
    var node = element.childNodes[i];
    if (node.tagName == "value") {
      return this._parseValue(node);
    };
  };
}

XMLRPC._parseValue = function(element) {
  for (var i=0; i<element.childNodes.length; i++) {
    var node = element.childNodes[i];
    switch (node.tagName) {
      case "int":
      case "i4":
        return this._parseInt(node);
      case "boolean":
        return this._parseBoolean(node);
      case "string":
        return this._parseString(node);
      case "double":
        return this._parseDouble(node);
      case "dateTime.iso8601":
        return this._parseDateTime(node);
      case "base64":
        return this._parseBase64(node);
      case "struct":
        return this._parseStruct(node);
      case "array":
        return this._parseArray(node);
    };
  };  
}

XMLRPC._parseInt = function(element) {
  return parseInt(getInnerText(element));
}

XMLRPC._parseBoolean = function(element) {
  return (getInnerText(element)) ? true : false;
}

XMLRPC._parseString = function(element) {
  return getInnerText(element);
}

XMLRPC._parseDouble = function(element) {
  return parseFloat(getInnerText(element));
}

XMLRPC._parseDateTime = function(element) {
  // @TODO
  return getInnerText(element);
}

XMLRPC._parseBase64 = function(element) {
  return getInnerText(element);
}

XMLRPC._parseStruct = function(element) {
  var struct = new Object;

  for (var i=0; i<element.childNodes.length; i++) {
    var node = element.childNodes[i];
    if (node.tagName == "member") {
      var member = this._parseMember(node);
      struct[member.name] = member.value;
    };
  };

  return struct;  
}

XMLRPC._parseMember = function(element) {
  var member = new Array();

  for (var i=0; i<element.childNodes.length; i++) {
    var node = element.childNodes[i];
    switch (node.tagName) {
      case "name":
        member.name = this._parseName(node);
        break;
      case "value":
        member.value = this._parseValue(node); 
        break;
    };
  };

  return member;
}

XMLRPC._parseName = function(element) {
  return getInnerText(element);
}

XMLRPC._parseArray = function(element) {
  for (var i=0; i<element.childNodes.length; i++) {
    var node = element.childNodes[i];
    if (node.tagName == "data") {
      return this._parseData(node);
    };
  };
}

XMLRPC._parseData = function(element) {
  var data = new Array();
  for (var i=0; i<element.childNodes.length; i++) {
    var node = element.childNodes[i];
    if (node.tagName == "value") {
      data.push(this._parseValue(node));
    };
  };
  return data;
}

XMLRPC.call = function(url, callback, name, params) {
  var request = createXMLHttpRequest();  
  request.onsuccess = function() { 
    if (request.handleError()) { return false; }; 
    var response = XMLRPC.parseResponse(request.xmlhttp.responseXML);
    callback(response); 
  };

  // request.xmlhttp.setRequestHeader("Content-Type", "text/xml");
  request.open('POST',url,1);

  var query = this.prepare(name, params);
  request.send(query);
};// #REQUIRE: ajax/core.js
// #REQUIRE: xmlrpc/core.js
