/*****************

  (c) 2006 Q42 B.V.

  The contents of this file, partially or in whole, may not be reproduced
  without prior written permission by Q42 B.V.
  
  Leak-free javascript closures, by Laurens van den Oever from Q42
  http://laurens.vd.oever.nl/weblog/items2005/closures

*****************/

Function.prototype.closure = function closure(obj)
{
  // Init object storage.
  if (!window.objs)
  {
    window.objs = [];
    window.funs = [];
  }

  // For symmetry and clarity.
  var fun = this;

  // Make sure the object has an id and is stored in the object store.
  var objId = obj.objId;
  if (!objId)
    objs[objId = obj.objId = objs.length] = obj;

  // Make sure the function has an id and is stored in the function store.
  var funId = fun.funId;
  if (!funId)
    funs[funId = fun.funId = funs.length] = fun;

  // Init closure storage.
  if (!obj.closures)
    obj.closures = [];

  // See if we previously created a closure for this object/function pair.
  var closure = obj.closures[funId];
  if (closure)
    return closure;

  // Clear references to keep them out of the closure scope.
  obj = null;
  fun = null;

  // Create the closure, store in cache and return result.
  return objs[objId].closures[funId] = function closure()
  {
    return funs[funId].apply(objs[objId], arguments);
  };
};

/**
 * Cross browser user agent
 * (c) 2006 by Q42
 */
function Agent()
{
  this.IE = (navigator.appName == "Microsoft Internet Explorer");
  this.FF = (navigator.appName == "Netscape");
}
Agent.prototype =
{
  addEventListener : function(el, strEventName, funcListener)
  {
    if (this.IE)
    {
      el.attachEvent("on" + strEventName, funcListener);
    }
    else
    {
      el.addEventListener(strEventName, funcListener, true);
    }
  },
  removeEventListener : function(el, strEventName, funcListener)
  {
    if (this.IE)
    {
      el.detachEvent("on" + strEventName, funcListener);
    }
    else
    {
      el.removeEventListener(strEventName, funcListener, true);
    }
  }
}

function Dragger(dragElement, 
                 dragHandleElement, 
                 dragStartHandler, 
                 dragHandler, 
                 dragEndHandler)
{
  this._NOT_DRAGGING = 0;
  this._START_DRAGGING = 1;
  this._DRAGGING = 2;
  
  this._state = this._NOT_DRAGGING;
  this._el = dragElement;
  this._handle = dragHandleElement;

  if (dragStartHandler)
    this._dragStartHandler = dragStartHandler;
  if (dragHandler)
    this._dragHandler = dragHandler;
  if (dragEndHandler)
    this._dragEndHandler = dragEndHandler;
  
  this._mouseDownStub = this._mouseDownListener.closure(this);
  this._mouseMoveStub = this._mouseMoveListener.closure(this);
  this._mouseUpStub = this._mouseUpListener.closure(this);

  agent.addEventListener(this._handle, "mousedown", this._mouseDownStub);
  this._handle.setAttribute("unselectable", "on");
};

Dragger.prototype =
{
  _mouseDownListener : function(evt)
  {
    this._state = this._START_DRAGGING;
    var doc = this._el.ownerDocument;
    agent.addEventListener(doc, "mousemove", this._mouseMoveStub);
    agent.addEventListener(doc, "mouseup", this._mouseUpStub);
    
    var mt = parseInt(this._el.offsetTop);
    var ml = parseInt(this._el.offsetLeft);    
    
    this._mouseX = evt.clientX - (!isNaN(ml)? ml : 0);
    this._mouseY = evt.clientY - (!isNaN(mt)? mt : 0);
  },
  _mouseUpListener : function(evt)
  {
    this._state = this._NOT_DRAGGING;
    var doc = this._el.ownerDocument;
    agent.removeEventListener(doc, "mousemove", this._mouseMoveStub);
    agent.removeEventListener(doc, "mouseup", this._mouseUpStub);
    if (this._dragEndHandler)
      this._dragEndHandler(evt);
  },
  _mouseMoveListener : function(evt)
  {
    evt.leftButton = (!evt.which && evt.button == 1) || evt.which == 1;
    if (!evt.leftButton)
    {
      this._mouseUpListener(evt);
      return;
    }
    switch(this._state)
    {
      case this._START_DRAGGING:
        if (this._dragStartHandler)
          this._dragStartHandler(evt);
        this._state = this._DRAGGING;
        break;
      case this._DRAGGING:
        this._el.style.top = (evt.clientY - this._mouseY) + "px";
        this._el.style.left= (evt.clientX - this._mouseX) + "px";
        if (this._dragHandler)
          this._dragHandler(evt);
        break;
      default:
        this._state = this._NOT_DRAGGING;
    }
  },
  doIFrameMouseMove: function(w, evt)
  {
    if (this._state != this._DRAGGING)
      return;
      
    var screenTop = document.getElementById('zoom-box').style.top;
    var screenLeft = document.getElementById('zoom-box').style.left;
      
    this._el.style.top = (evt.clientY + parseInt(screenTop) - this._mouseY) + "px";
    this._el.style.left= (evt.clientX + parseInt(screenLeft) - this._mouseX) + "px";
    if (this._dragHandler)
      this._dragHandler(evt);
  }
};

var agent = new Agent();
