let noop = function() {};
Browser = {
  updateViewportSize: noop
    /*************************************************************
  function
    let browser = document.getElementById("googlenews");
    let cdoc = browser.contentDocument;

    // These might not exist yet depending on page load state
    var body = cdoc.body || {};
    var html = cdoc.documentElement || {};

    var w = Math.max(body.scrollWidth || 0, html.scrollWidth);
    var h = Math.max(body.scrollHeight || 0, html.scrollHeight);

    window.tileManager.viewportHandler(new wsRect(0, 0, w, h),
                                       window.innerWidth,
                                       new wsRect(0, 0, window.innerWidth, window.innerHeight),
                                       false);
  *************************************************************/
};
let ws = {
  beginUpdateBatch: noop,
  panTo: noop,
  endUpdateBatch: noop
};
let Ci = Components.interfaces;
let bv = null;
let endl = "\n";


function BrowserView() {
  this.init();
  bindAll(this);
}

BrowserView.prototype = {

  // --- PROPERTIES ---
  // public:
  //   init()
  //   getViewportInnerBoundsRect(dx, dy)
  //   tileManager
  //   scrollbox
  //
  // private:
  //   _scrollbox
  //   _leftbar
  //   _rightbar
  //   _topbar
  //   _browser
  //   _tileManager
  //   _viewportRect
  //   _viewportInnerBoundsRect
  //

  get tileManager() { return this._tileManager; },
  get scrollbox() { return this._scrollbox; },

  init: function init() {
    let scrollbox = document.getElementById("scrollbox")
			.boxObject
			.QueryInterface(Components.interfaces.nsIScrollBoxObject);
    this._scrollbox = scrollbox;

    let leftbar  = document.getElementById("left_sidebar");
    let rightbar = document.getElementById("right_sidebar");
    let topbar   = document.getElementById("top_urlbar");
    this._leftbar = leftbar;
    this._rightbar = rightbar;
    this._topbar = topbar;

    scrollbox.scrollTo(Math.round(leftbar.getBoundingClientRect().right), 0);

    let tileContainer = document.getElementById("tile_container");
    tileContainer.addEventListener("mousedown", onMouseDown, true);
    tileContainer.addEventListener("mouseup",   onMouseUp,   true);
    tileContainer.addEventListener("mousemove", onMouseMove, true);
    this._tileContainer = tileContainer;

    let tileManager = new TileManager(this.appendTile, this.removeTile, window.innerWidth);
    this._tileManager = tileManager;

    let browser = document.getElementById("googlenews");
    this.setCurrentBrowser(browser, false);    // sets this._browser

    let cdoc = browser.contentDocument;

    // These might not exist yet depending on page load state
    let body = cdoc.body || {};
    let html = cdoc.documentElement || {};

    let w = Math.max(body.scrollWidth || 0, html.scrollWidth);
    let h = Math.max(body.scrollHeight || 0, html.scrollHeight);

    let viewportRect = new wsRect(0, 0, w, h);
    this._viewportRect = viewportRect;

    let viewportInnerBoundsRect = this.getViewportInnerBoundsRect();
    this._viewportInnerBoundsRect = viewportInnerBoundsRect;

    tileManager.viewportHandler(viewportRect,
				window.innerWidth,
				viewportInnerBoundsRect,
				true);
  },

  resizeTileContainer: function resizeTileContainer() {

  },

  scrollboxToViewportRect: function scrollboxToViewportRect(rect, clip) {
    let leftbar  = this._leftbar.getBoundingClientRect();
    let rightbar = this._rightbar.getBoundingClientRect();
    let topbar   = this._topbar.getBoundingClientRect();

    let xtrans = -leftbar.width;
    let ytrans = -topbar.height;
    let x = rect.x + xtrans;
    let y = rect.y + ytrans;

    // XXX we're cheating --- this is not really a clip, but its the only
    // way this function is used
    rect.x = (clip) ? Math.max(x, 0) : x;
    rect.y = (clip) ? Math.max(y, 0) : y;

    return rect;
  },

  getScrollboxPosition: function getScrollboxPosition() {
    let x = {};
    let y = {};
    this._scrollbox.getPosition(x, y);
    return [x.value, y.value];
  },

  getViewportInnerBoundsRect: function getViewportInnerBoundsRect(dx, dy) {
    if (!dx) dx = 0;
    if (!dy) dy = 0;

    let w = window.innerWidth;
    let h = window.innerHeight;

    let leftbar  = this._leftbar.getBoundingClientRect();
    let rightbar = this._rightbar.getBoundingClientRect();
    let topbar   = this._topbar.getBoundingClientRect();

    let leftinner  = Math.max(leftbar.right - dx, 0);
    let rightinner = Math.min(rightbar.left - dx, w);
    let topinner   = Math.max(topbar.bottom - dy, 0);

    let [x, y] = this.getScrollboxPosition();

    return this.scrollboxToViewportRect(new wsRect(x + dx, y + dy, rightinner - leftinner, h - topinner),
				        true);
  },

  appendTile: function appendTile(tile) {
    let canvas = tile.contentImage;

    canvas.style.position = "absolute";
    canvas.style.left = tile.x + "px";
    canvas.style.top  = tile.y + "px";

    let tileContainer = document.getElementById("tile_container");
    tileContainer.appendChild(canvas);

    dump('++ ' + tile.toString() + endl);
  },

  removeTile: function removeTile(tile) {
    let canvas = tile.contentImage;

    let tileContainer = document.getElementById("tile_container");
    tileContainer.removeChild(canvas);

    dump('-- ' + tile.toString() + endl);
  },

  scrollBy: function scrollBy(dx, dy) {
    // TODO
    this.onBeforeScroll();
    this.onAfterScroll();
  },

  // x: current x
  // y: current y
  // dx: delta to get to x from current x
  // dy: delta to get to y from current y
  onBeforeScroll: function onBeforeScroll(x, y, dx, dy) {
    this.tileManager.onBeforeScroll(this.getViewportInnerBoundsRect(dx, dy));

    // shouldn't update margin if it doesn't need to be changed
    let sidebars = document.getElementsByClassName("sidebar");
    for (let i = 0; i < sidebars.length; i++) {
      let sidebar = sidebars[i];
      sidebar.style.margin = (y + dy) + "px 0px 0px 0px";
    }

    let urlbar = document.getElementById("top_urlbar");
    urlbar.style.margin = "0px 0px 0px " + (x + dx) + "px";
  },

  onAfterScroll: function onAfterScroll(x, y, dx, dy) {
    this.tileManager.onAfterScroll(this.getViewportInnerBoundsRect());
  },

  setCurrentBrowser: function setCurrentBrowser(browser, skipZoom) {
    let currentBrowser = this._browser;
    if (currentBrowser) {
      // backup state
      currentBrowser.mZoomLevel = this.zoomLevel;
      currentBrowser.mPanX = ws._viewingRect.x;
      currentBrowser.mPanY = ws._viewingRect.y;

      // stop monitor paint events for this browser
      currentBrowser.removeEventListener("MozAfterPaint", this.handleMozAfterPaint, false);
      currentBrowser.setAttribute("type", "content");
      currentBrowser.docShell.isOffScreenBrowser = false;
    }

    browser.setAttribute("type", "content-primary");
    if (!skipZoom)
      browser.docShell.isOffScreenBrowser = true;

    // start monitoring paint events for this browser
    browser.addEventListener("MozAfterPaint", this.handleMozAfterPaint, false);

    this._browser = browser;

    // endLoading(and startLoading in most cases) calls zoom anyway
    if (!skipZoom) {
      this.zoomToPage();
    }

    if ("mZoomLevel" in browser) {
      // restore last state
      ws.beginUpdateBatch();
      ws.panTo(browser.mPanX, browser.mPanY);
      this.zoomLevel = browser.mZoomLevel;
      ws.endUpdateBatch(true);

      // drop the cache
      delete browser.mZoomLevel;
      delete browser.mPanX;
      delete browser.mPanY;
    }

    this.tileManager.browser = browser;
  },

  handleMozAfterPaint: function handleMozAfterPaint(ev) {
    this.tileManager.handleMozAfterPaint(ev);
  },

  zoomToPage: function zoomToPage() {
    /********************************************************
    let needToPanToTop = this._needToPanToTop;
    // Ensure pages are panned at the top before zooming/painting
    // combine the initial pan + zoom into a transaction
    if (needToPanToTop) {
      ws.beginUpdateBatch();
      this._needToPanToTop = false;
      ws.panTo(0, -BrowserUI.toolbarH);
    }
    // Adjust the zoomLevel to fit the page contents in our window width
    let [contentW, ] = this._contentAreaDimensions;
    let fakeW = this._fakeWidth;

    if (contentW > fakeW)
      this.zoomLevel = fakeW / contentW;

    if (needToPanToTop)
      ws.endUpdateBatch();
    ********************************************************/
  }

};


function onResize(e) {
  let browser = document.getElementById("googlenews");
  let cdoc = browser.contentDocument;

  // These might not exist yet depending on page load state
  var body = cdoc.body || {};
  var html = cdoc.documentElement || {};

  var w = Math.max(body.scrollWidth || 0, html.scrollWidth);
  var h = Math.max(body.scrollHeight || 0, html.scrollHeight);

  if (bv)
    bv.tileManager.viewportHandler(new wsRect(0, 0, w, h),
				   window.innerWidth,
				   bv.getViewportInnerBoundsRect(),
				   true);
}

function onMouseDown(e) {
  window._isDragging = true;
  window._dragStart = {x: e.clientX, y: e.clientY};

  bv.tileManager.startPanning();
}

function onMouseUp() {
  window._isDragging = false;

  bv.tileManager.endPanning();
}

function onMouseMove(e) {
  if (window._isDragging) {
    let scrollbox = bv.scrollbox;

    let x = {};
    let y = {};
    let w = {};
    let h = {};
    scrollbox.getPosition(x, y);
    scrollbox.getScrolledSize(w, h);

    let dx = window._dragStart.x - e.clientX;
    let dy = window._dragStart.y - e.clientY;

    // XXX if max(x, 0) > scrollwidth we shouldn't do anything (same for y/height)
    let newX = Math.max(x.value + dx, 0);
    let newY = Math.max(y.value + dy, 0);

    if (newX < w.value || newY < h.value) {
      // clip dx and dy to prevent us from going below 0
      dx = Math.max(dx, -x.value);
      dy = Math.max(dy, -y.value);

      bv.onBeforeScroll(x.value, y.value, dx, dy);

      /*dump("==========scroll==========" + endl);
      dump("delta: " + dx + "," + dy + endl);
      let xx = {};
      let yy = {};
      scrollbox.getPosition(xx, yy);
      dump(xx.value + "," + yy.value + endl);*/

      scrollbox.scrollBy(dx, dy);

      /*scrollbox.getPosition(xx, yy);
      dump(xx.value + "," + yy.value + endl);
      dump("==========================" + endl);*/

      bv.onAfterScroll();
    }
  }

  window._dragStart = {x: e.clientX, y: e.clientY};
}

function onLoad() {
  bv = new BrowserView();
}
