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);
};function $(id) {
  return document.getElementById(id);
}

function $T(id, tag) {
  return $(id).getElementsByTagName(tag);
}
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;
  }
}
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;
    };
  };
}
