function box() {
  this.element = null;
  this.error = '';
  this.defaultRollOverProperties = { opacity: 0.3, backgroundColor: 'gray' };

  this.findBoxById = function(o, instanceID) {
    if (instanceID == null) return;
    var boxes = o.getElementsByTagName((ivy.browser.isIE ? 'BOX' : 'ivy:box'));
    var i = 0;
    while (i < boxes.length && boxes[i].getAttribute('ivy:instance-id') != instanceID) i++;
    if (i < boxes.length) return boxes[i];
  }

  this.findBoxByDescendant = function(o, instanceID) {
    while (o != null && o != document && ivy.dom.tagName(o) != 'BOX' && o.getElementsByTagName('ivy:box').length == 0) o = o.parentNode;
    if (o != null && o != document && ivy.dom.tagName(o) != 'BOX') return this.findBoxById(o, instanceID);
    return o;
  }

  this.preventScroll = function(e) {
    if (e == null) e = window.event;
    ivy.dom.cancelBubbling(e);
    return false;
  }

  this.createRolloverDiv = function(o, properties) {
    var div = document.createElement('DIV');
    if (properties.element != null) {
      o = properties.element;
    } else {
      if (o.firstChild && ivy.dom.tagName(o) == 'BOX' && ivy.dom.tagName(o.firstChild) == 'DIV' && (o.offsetWidth == 0 || o.offsetHeight != o.firstChild.offsetHeight)) o = o.firstChild;
    }

    var dim;
    if (o == document.body || ivy.dom.style(o, 'position') == 'relative') {
      var pl = ivy.dom.style(o, 'paddingLeft') || '0px';
      dim = { left: -1 * pl.px(), top: 0 };
    } else {
      dim = this.calculateTopLeft(o, false);
    }

    div.id = 'ajaxRollOver';
    var style = {
      zIndex: 10000,
      top: (o.parentNode != document ? dim.top + o.scrollTop : 0) + 'px',
      left: (o.parentNode != document ? dim.left + o.scrollLeft : 0) + 'px',
      // itt regen clientWidth volt, kene tudni, miert
      width: (o.parentNode != document || o.offsetWidth > o.scrollWidth ? o.offsetWidth : o.scrollWidth) + 'px',
      height: (o.parentNode != document || o.offsetHeight > o.scrollHeight ? o.offsetHeight : o.scrollHeight) + 'px',
      position: (properties.position ? properties.position : 'absolute')
    };
    ivy.dom.apply(div.style, style);

    var bg = document.createElement('DIV');
    var bgstyle = {
      width: style.width,
      height: style.height,
      position: style.position,
      opacity: properties.opacity || 1,
      filter: (properties.opacity ? 'alpha(opacity=' + (properties.opacity * 100) + ')' : ''),
      backgroundColor: properties.backgroundColor || 'transparent'
    };

    div.appendChild(bg);
    ivy.dom.apply(bg.style, bgstyle);

    if (properties.content) {
      var inner = document.createElement('DIV');
      var istyle = {
        // topleft biztos, hogy jo?!?!?!
        top: dim.top + o.scrollTop + 'px',
        left: dim.left + o.scrollLeft + 'px',
        width: o.clientWidth + 'px',
        height: o.clientHeight + 'px',
        position: style.position,
        textAlign: ((properties.align || 'left') != 'left' && (properties.valign || 'top') != 'top' ? properties.align : 'left')
      };
      div.appendChild(inner);
      ivy.dom.apply(inner.style, istyle);

      if ((properties.valign || 'top') != 'top') {
        inner.innerHTML = '<table cellspacing="0" cellpadding="0" border="0"><tr><td style="text-align: ' + properties.align + '!important; vertical-align:' + properties.valign + '"></td></tr></table>';
        inner.firstChild.style.width = istyle.width;
        inner.firstChild.style.height = istyle.height;
        inner = inner.firstChild.firstChild.firstChild.firstChild;
      }
      inner.innerHTML = properties.content;
    }

    o.appendChild(div);

    return dim;
  }

  this.removeRolloverDiv = function() {
    var roli = document.getElementById('ajaxRollOver');
    if (roli) roli.parentNode.removeChild(roli);
  }

  this.uriBuilder = function(args) {
    var uri = args.url;
    if (uri == null) uri = args.htmlElement.getAttribute('ivy:src');
    if (uri == null) uri = '/ivy/iem-shared/system/gems/print/show.xslt';

    var instance = args.htmlElement.getAttribute('ivy:instance-id');
    if (instance == null) instance = args.instanceID;

    uri += '?';

    if (location.href.indexOf('?') != -1) {
      var oldqs = location.href.substring(location.href.indexOf('?') + 1);
      if (oldqs.indexOf('#') != -1) oldqs = oldqs.substring(0, oldqs.indexOf('#'));
      var x = args.queryString.split('&');
      for (var i = 0; i < x.length; i++) x[i] = x[i].substring(0, x[i].indexOf('='));
      var y = oldqs.split('&');
      for (var i = 0; i < y.length; i++) {
        var j = 0;
        while (j < x.length && y[i].indexOf(x[j] + '=') == -1) j++;
        if (j == x.length) uri += y[i] + '&';
      }
    }

    uri += 'pageloader.no-cache=true';
    uri += '&request.instance-id=' + instance;
    uri += '&rnd=' + Math.random();
    if (args.queryString != null) uri += '&' + args.queryString;
    if (uri.indexOf('http://') == -1) {
      var r = new RegExp('http://.*?/', 'i');
      var m = r.exec(location.href);
      uri = m[0].substring(0, m[0].length - 1) + uri;
    }
    return uri;
  }

  this.validateRefreshArgs = function(args) {
    if (typeof args.fragment != 'undefined') args.fragment = args.fragment + '';
    if (args.htmlElement == null) {
      if (args.fragment == 'true') { this.error = 'Element is undefined.'; return; }
      args.htmlElement = this.findBoxById(document, args.instanceID);
      if (args.htmlElement == null) { this.error = 'Box element cannot be found.'; return; }
    }
    if (args.htmlElement == document) { this.error = 'Element was set to the document root.'; return; }
    if (args.queryString == null) args.queryString = '';
    if (args.instanceID == null) args.instanceID = args.htmlElement.getAttribute('instance-id');
    if (args.fragment != 'true') args.htmlElement = this.findBoxByDescendant(args.htmlElement, args.instanceID);
    if (args.htmlElement == null || args.htmlElement == document) { this.error = 'Box element cannot be found.'; return; }
    if (args.rollOver != null && args.rollOver + '' != 'false') {
      this.createRolloverDiv(args.htmlElement, (typeof args.rollOver != 'object' ? this.defaultRollOverProperties : args.rollOver));
    }
    if (args.callback == null) args.callback = this.refreshCallback;
    return args;
  }

  this.evalScripts = function(xml) {
    if (ivy.browser.isIE) xml.setProperty("SelectionLanguage", "XPath");
    var script = ivy.xml.xpath.eval(xml, "//script");
    var s;
    while (s = ivy.xml.xpath.iterate(script)) {
      try {
        var val = ivy.xml.xpath.valueOf(s);
        if (val == '' && s.firstChild) val = ivy.xml.xpath.valueOf(s.firstChild);
        eval(val);
      } catch (ex) {
        alert('JavaScript error:' + ex.description);
      }
    }
  }

  this.serialize = function(xml, o) {
    var sx = ivy.xml.serialize(xml);
    if (!ivy.browser.isIE && o != null) {
      var r1 = new RegExp('<(a|b|strong|u|i|div|span|font|h1|h2|h3)(\\s[^>]*?)?/>', 'gi'); // mozilla httprequest hack
      var r2 = new RegExp('<\\?xml(.*?)\\?>', 'gi'); // opera hack
      sx = sx.replace(r2, '').replace(r1, '<$1$2></$1>');
      if (ivy.browser.name != 'Opera' && o.getAttribute('moz-hacked') != 'true') { // ff refresh nem tartalmazos hiba hack
        var i = 0;
        var c = o.parentNode.childNodes;
        while (i < c.length && c[i] != o) i++;
        if (i < c.length - 1) {
          o.parentNode.removeChild(c[i + 1]);
          o.setAttribute('moz-hacked', 'true');
        }
      }
    }
    return sx;
  }

  this.refresh = function(args) {
    this.error = null;
    args = this.validateRefreshArgs(args);
    if (args == null || args.htmlElement == null) { alert('AJAX Error: ' + this.error); return; }
    this.element = args.htmlElement;
    var uri = this.uriBuilder(args);
    switch (args.method) {
      case 'post':
        ivy.http.post(uri, args.postData, args.callback, args.contentType);
        break;
      default:
        ivy.http.get(uri, args.callback);
        break;
    }
  }

  this.refreshCallback = function(wr) {
    var result = ivy.box.checkServerError(wr);
    if (!result.error) {
      var xml = result.xml;
      if (location.href.indexOf('debug=true') != -1) ivy.dom.copyToClipboard(ivy.xml.serialize(xml));
      var a;
      var automation = ivy.xml.xpath.eval(xml, '//automation/*');
      while (a = ivy.xml.xpath.iterate(automation)) {
        switch (a.nodeName) {
          case "redirect":
            location.href = ivy.xml.xpath.valueOf(a);
            return;
        }
      }
      if (ivy.box.element != null) {
        var sx = ivy.box.serialize(xml, ivy.box.element);
        ivy.box.removeRolloverDiv();
        ivy.box.element.innerHTML = sx;
        ivy.box.evalScripts(xml);
      }
    } else {
      ivy.box.element.innerHTML = '<div id="error">' + result.errorText + '</div>';
    }
  }

  this.checkServerError = function(wr) {
    var xml = wr.requester.responseXML;
    var result = {
      xml: xml,
      error: (xml == null)
    };
    if (xml == null) {
      var r = new RegExp('<body.*?>(.*?)</body>', 'gi');
      var m = r.exec(wr.requester.responseText.replace(new RegExp('\n|\r', 'gi'), ''));
      result.errorText = m[1];
    }
    return result;
  }

  this.submit = function(args) {
    ivy.dom.cancelBubbling(args.event);
    if (args.target == null) args.target = args.source;
    if (args.source.tagName != 'FORM') args.source = args.source.form;
    if (args.errorHandler != null) {
      var count = args.errorHandler(args.source);
      if (count != 0) return false;
    }
    switch (args.method) {
      case 'get':
        var data = this.queryStringBuilder(args.source);
        this.refresh(
        {
          htmlElement: args.target,
          instanceID: args.instanceID,
          queryString: data,
          method: 'get',
          rollOver: args.rollOver,
          fragment: args.fragment,
          callback: args.callback
        });
        break;
      case 'exml':
      case 'xml':
        var data = this.serializeForm(args.source, args.method);
        this.refresh(
        {
          htmlElement: args.target,
          instanceID: args.instanceID,
          queryString: 'pageloader.parsecontent=true' + (args.queryString ? '&' + args.queryString : ''),
          method: 'post',
          postData: data,
          rollOver: args.rollOver,
          fragment: args.fragment,
          callback: args.callback
        });
        break;
      default:
        var data = this.queryStringBuilder(args.source);
        this.refresh(
        {
          htmlElement: args.target,
          instanceID: args.instanceID,
          queryString: args.queryString,
          method: 'post',
          postData: data,
          contentType: 'application/x-www-form-urlencoded',
          rollOver: args.rollOver,
          fragment: args.fragment,
          callback: args.callback
        });
        break;
    }
    return false;
  }

  this.submitForm = function(e, o) {
    o.onsubmit({dummy: "x"});
  }

  this.pager = function(e, formID, page, buttonID) {
    var f = document.getElementById(formID);
    if (f == null) return;
    var i = 0;
    while (i < f.elements.length && f.elements[i].name != 'page') i++;
    if (i < f.elements.length) {
      f.elements[i].value = page;
      var b = document.getElementById(buttonID);
      if (b != null) {
        b.click();
      } else {
        this.submitForm(e, f);
      }
    }
  }

  this.serializeForm = function(o, method, chkgroup) {
    var e = o.elements;
    var sXml = '<form-action oID="' + o.id.replace('form-', '') + '">';
    var formkey = o.getAttribute('key');
    for (var i = 0; i < e.length; i++) {
      var value = this.inputValue(e[i], chkgroup);
      var name = e[i].name;
      if (formkey != null) {
        var r = new RegExp(formkey + '\.(.*?)\.ValueString', 'i');
        if (name.match(r)) name = name.replace(r, '$1');
      }
      if (this.validInput(e[i], value, chkgroup)) {
        value = value.replace(new RegExp('&', 'gi'), '&amp;');
        switch (method) {
          case 'exml':
            sXml += '<input name="' + name + '" value="' + value + '"/>';
            break;
          default:
            sXml += '<' + name + '>' + value + '</' + name + '>';
        }
      }
    }
    sXml += '</form-action>';
    return sXml;
  }

  this.queryStringBuilder = function(form) {
    var e = form.elements;
    var s = 'oID=' + form.id.replace('form-', '');
    var t = '';
    var u = '';
    for (var i = 0; i < e.length; i++) {
      var value = this.inputValue(e[i]);
      if (this.validInput(e[i], value)) {
        // mindenfele mocsok hack, hogy az elementek speciális sorrendben legyenek a postdataban
        // *** requested by Thomas
        if (e[i].type == 'text') {
          u = this.stringBuilder(u, e[i], value);
        } else if (e[i].type == 'checkbox' && value == 'true') {
          t = this.stringBuilder(t, e[i], value);
        } else {
          s = this.stringBuilder(s, e[i], value);
        }
      }
    }
    return s + (t != '' ? '&' : '') + t + (u != '' ? '&' : '') + u;
  }

  this.stringBuilder = function(s, o, v) {
    if (s != '') s += '&';
    s += o.name.replace(':', '%3A') + '=' + v;
    return s;
  }

  this.validInput = function(o, value, chkgroup) {
    if (!o.name) return false;
    if (o.getAttribute('suggestBox') == 'true') return value.length > 1;
    if (o.tagName != 'INPUT') return value != '';
    if (o.type != 'button' && o.type != 'submit' && o.type != 'image' && o.type != 'checkbox') return value != '';
    return (o.type == 'checkbox');
  }

  this.inputValue = function(o, chkgroup) {
    var value = '';
    switch (o.tagName) {
      case 'INPUT':
        switch (o.type) {
          case 'radio':
            if (o.checked) value = o.value;
            break;
          case 'checkbox':
            if (chkgroup) {
              if (o.checked) value = o.value;
            } else {
              value = (o.checked ? (o.value != null && o.value != '' && o.value != 'on' ? o.value : 'true') : 'false');
            }
            break;
          default:
            value = o.value;
        }
        break;
      case 'SELECT':
        value = o.value;
        break;
      case 'TEXTAREA':
        value = o.value;
        break;
    }
    return value;
  }

  this.removeRootElement = function(sx) {
    sx = sx.replace(new RegExp('\n|\r', 'g'), '');
    var r1 = new RegExp('^<.*?>', 'gi');
    var r2 = new RegExp('</.*?>.*?$', 'gi');
    return sx.replace(r1, '').replace(r2, '');
  }

  this.addToLog = function(result, htmlElement) {
    if (htmlElement == document) alert('!!!');
    result.log.push({
      tagName: htmlElement.tagName,
      id: htmlElement.id,
      position: ivy.dom.style(htmlElement, 'position'),
      left: ivy.dom.style(htmlElement, 'left'),
      top: ivy.dom.style(htmlElement, 'top'),
      offsetLeft: htmlElement.offsetLeft,
      offsetTop: htmlElement.offsetTop,
      offsetWidth: htmlElement.offsetWidth,
      offsetHeight: htmlElement.offsetHeight,
      resultLeft: result.left,
      resultTop: result.top
    });
  }

  this.calculateTopLeft = function(htmlElement, overall) {
    var stack = new Array();
    var result = { left: 0, top: 0, log: [] };
    if (!htmlElement.tagName) htmlElement = htmlElement.offsetParent;
    var p = ivy.dom.style(htmlElement, 'position');
    while (htmlElement != null && htmlElement != document) {
      if (p != "absolute") {
        result.top += htmlElement.offsetTop / 1;
        result.left += htmlElement.offsetLeft / 1;
      }
      this.addToLog(result, htmlElement);
      if (htmlElement.tagName == 'MARQUEE' && htmlElement.direction == 'left') result.left -= htmlElement.clientWidth;
      if (htmlElement.tagName == 'MARQUEE' && htmlElement.direction == 'up') result.top -= htmlElement.clientHeight;
      try {
        var op = htmlElement.offsetParent;
        while (htmlElement != null && htmlElement != op) {
          stack.push(htmlElement);
          htmlElement = htmlElement.parentNode;
          if (htmlElement != null && htmlElement != document && htmlElement != op) this.addToLog(result, htmlElement);
        }
      } catch (ex) {
        htmlElement = null;
      }
      if (htmlElement == null || htmlElement == document || ivy.dom.style(htmlElement, 'position') == 'relative' && overall != 'true') {
        if (htmlElement != null && ivy.dom.style(htmlElement, 'position') == 'relative' && ivy.browser.isIE) {
          var f = ivy.dom.firstChild(htmlElement);
          result.left -= f.offsetLeft;
          result.top -= f.offsetTop;
          var pt = ivy.dom.style(htmlElement, 'paddingTop');
          if (pt) result.top += pt.px();
        }
        return result;
      }
    }
    return result;
  }
}

ivy.box = new box();