var scrollbarWidth = 17, scrollbarHeight = 17;

function testElements(baseid, callback)
{
  scrollbarWidth = scrollbarHeight = gcs($("scrollbox-test"), "width");

  var elements = $(baseid).getElementsByTagName("*");
  for (var t = 0; t < elements.length; t++) {
    var element = elements[t];
    testElement(element);
  }

  var nonappended = document.createElement("div");
  nonappended.id = "nonappended";
  nonappended.setAttribute("_offsetParent", "null");
  testElement(nonappended);

  checkScrolledElement($("scrollbox"), $("scrollchild"));

  var div = $("noscroll");
  div.scrollLeft = 10;
  div.scrollTop = 10;
  is(element.scrollLeft, 0, element.id + " scrollLeft after nonscroll");
  is(element.scrollTop, 0, element.id + " scrollTop after nonscroll");

  callback();
}

function toNearestAppunit(v)
{
  // 60 appunits per CSS pixel; round result to the nearest appunit
  return Math.round(v*60)/60;
}

function isEqualAppunits(a, b, msg)
{
  is(toNearestAppunit(a), toNearestAppunit(b), msg);
}

function testElement(element)
{
  var offsetParent = element.getAttribute("_offsetParent");
  offsetParent = $(offsetParent == "null" ? null: (offsetParent ? offsetParent : "body"));

  var borderLeft = gcs(element, "borderLeftWidth");
  var borderTop = gcs(element, "borderTopWidth");
  var borderRight = gcs(element, "borderRightWidth");
  var borderBottom = gcs(element, "borderBottomWidth");
  var paddingLeft = gcs(element, "paddingLeft");
  var paddingTop = gcs(element, "paddingTop");
  var paddingRight = gcs(element, "paddingRight");
  var paddingBottom = gcs(element, "paddingBottom");
  var width = gcs(element, "width");
  var height = gcs(element, "height");

  if (element instanceof HTMLElement)
    checkOffsetState(element, -10000, -10000,
                              borderLeft + paddingLeft + width + paddingRight + borderRight,
                              borderTop + paddingTop + height + paddingBottom + borderBottom,
                              offsetParent, element.id);

  var scrollWidth, scrollHeight, clientWidth, clientHeight;
  var doScrollCheck = true;
  if (element.id == "scrollbox") {
    var lastchild = $("lastline");
    scrollWidth = lastchild.getBoundingClientRect().width + paddingLeft + paddingRight;
    var top = element.firstChild.getBoundingClientRect().top;
    var bottom = element.lastChild.getBoundingClientRect().bottom;
    var contentsHeight = bottom - top;
    scrollHeight = contentsHeight + paddingTop + paddingBottom;
    clientWidth = paddingLeft + width + paddingRight - scrollbarWidth;
    clientHeight = paddingTop + height + paddingBottom - scrollbarHeight;
  } else {
    clientWidth = paddingLeft + width + paddingRight;
    clientHeight = paddingTop + height + paddingBottom;
    if (element.id == "overflow-visible") {
      scrollWidth = 200;
      scrollHeight = 201;
    } else if (element.scrollWidth > clientWidth ||
               element.scrollHeight > clientHeight) {
      // The element overflows. Don't check scrollWidth/scrollHeight since the
      // above calculation is not correct.
      doScrollCheck = false;
    } else {
      scrollWidth = clientWidth;
      scrollHeight = clientHeight;
    }
  }

  if (doScrollCheck) {
    if (element instanceof SVGElement)
      checkScrollState(element, 0, 0, 0, 0, element.id);
     else
      checkScrollState(element, 0, 0, scrollWidth, scrollHeight, element.id);
  }

  if (element instanceof SVGElement)
    checkClientState(element, 0, 0, 0, 0, element.id);
  else
    checkClientState(element, borderLeft, borderTop, clientWidth, clientHeight, element.id);

  var boundingrect = element.getBoundingClientRect();
  isEqualAppunits(boundingrect.width, borderLeft + paddingLeft + width + paddingRight + borderRight,
     element.id + " bounding rect width");
  isEqualAppunits(boundingrect.height, borderTop + paddingTop + height + paddingBottom + borderBottom,
     element.id + " bounding rect height");
  isEqualAppunits(boundingrect.right - boundingrect.left, boundingrect.width,
     element.id + " bounding rect right");
  isEqualAppunits(boundingrect.bottom - boundingrect.top, boundingrect.height,
     element.id + " bounding rect bottom");

  var rects = element.getClientRects();
  if (element.id == "div-displaynone" || element.id == "nonappended") {
    is(rects.length, 0, element.id + " getClientRects empty");
  }
  else {
    is(rects[0].left, boundingrect.left, element.id + " getClientRects left");
    is(rects[0].top, boundingrect.top, element.id + " getClientRects top");
    is(rects[0].right, boundingrect.right, element.id + " getClientRects right");
    is(rects[0].bottom, boundingrect.bottom, element.id + " getClientRects bottom");
  }
}

function checkScrolledElement(element, child)
{
  var elemrect = element.getBoundingClientRect();
  var childrect = child.getBoundingClientRect();

  var topdiff = childrect.top - elemrect.top;

  element.scrollTop = 20;
  is(element.scrollLeft, 0, element.id + " scrollLeft after vertical scroll");
  is(element.scrollTop, 20, element.id + " scrollTop after vertical scroll");
  // If the viewport has been transformed, then we might have scrolled to a subpixel value
  // that's slightly different from what we requested. After rounding, however, it should
  // be the same.
  is(Math.round(childrect.top - child.getBoundingClientRect().top), 20, "child position after vertical scroll");

  element.scrollTop = 0;
  is(element.scrollLeft, 0, element.id + " scrollLeft after vertical scroll reset");
  is(element.scrollTop, 0, element.id + " scrollTop after vertical scroll reset");
  // Scrolling back to the top should work precisely.
  is(child.getBoundingClientRect().top, childrect.top, "child position after vertical scroll reset");

  element.scrollTop = 10;
  element.scrollTop = -30;
  is(element.scrollLeft, 0, element.id + " scrollLeft after vertical scroll negative");
  is(element.scrollTop, 0, element.id + " scrollTop after vertical scroll negative");
  is(child.getBoundingClientRect().top, childrect.top, "child position after vertical scroll negative");

  element.scrollLeft = 18;
  is(element.scrollLeft, 18, element.id + " scrollLeft after horizontal scroll");
  is(element.scrollTop, 0, element.id + " scrollTop after horizontal scroll");
  is(Math.round(childrect.left - child.getBoundingClientRect().left), 18, "child position after horizontal scroll");

  element.scrollLeft = -30;
  is(element.scrollLeft, 0, element.id + " scrollLeft after horizontal scroll reset");
  is(element.scrollTop, 0, element.id + " scrollTop after horizontal scroll reset");
  is(child.getBoundingClientRect().left, childrect.left, "child position after horizontal scroll reset");
}

function checkOffsetState(element, left, top, width, height, parent, testname)
{
  checkCoords(element, "offset", left, top, width, height, testname);
  is(element.offsetParent, parent, testname + " offsetParent");
}

function checkScrollState(element, left, top, width, height, testname)
{
  checkCoords(element, "scroll", left, top, width, height, testname);
}

function checkClientState(element, left, top, width, height, testname)
{
  checkCoords(element, "client", left, top, width, height, testname);
}

function checkCoord(element, type, val, testname)
{
  if (val != -10000)
    is(element[type], Math.round(val), testname + " " + type);
}

function checkCoordFuzzy(element, type, val, fuzz, testname)
{
  if (val != -10000)
    ok(Math.abs(element[type] - Math.round(val)) <= fuzz, testname + " " + type);
}

function checkCoords(element, type, left, top, width, height, testname)
{
  checkCoord(element, type + "Left", left, testname);
  checkCoord(element, type + "Top", top, testname);

  if (type == "scroll") {
    // scrollWidth and scrollHeight can deviate by 1 pixel due to snapping.
    checkCoordFuzzy(element, type + "Width", width, 1, testname);
    checkCoordFuzzy(element, type + "Height", height, 1, testname);
  } else {
    checkCoord(element, type + "Width", width, testname);
    checkCoord(element, type + "Height", height, testname);
  }

  if (element instanceof SVGElement)
    return;

  if (element.id == "outerpopup" && !element.parentNode.open) // closed popup
    return;

  if (element.id == "div-displaynone" || element.id == "nonappended") // hidden elements
    ok(element[type + "Width"] == 0 && element[type + "Height"] == 0,
       element.id + " has zero " + type + " width and height");
}

function gcs(element, prop)
{
  var propVal = (element instanceof SVGElement && (prop == "width" || prop == "height")) ?
                   element.getAttribute(prop) : getComputedStyle(element, "")[prop];
  if (propVal == "auto")
    return 0;
  return parseFloat(propVal);
}