/* 
  Prototype Extension Library 
  Extends Prototype 1.3.1
  Based on prototype-1.3.1.js - Prototype JavaScript framework, version 1.3.1 (c) 2005 Sam Stephenson <sam@conio.net>
  Lots of thanks also go to Peter-Paul Koch for his incredibly invaluable resource at http://quirksmode.com/.  
  (c) Copyright 2005-2006, e-numera, Inc.
  http://e-numera.com/ http://ajax-web2.com/
*/

/* Requires ptototype-1.3.1.js */

/* class String enhancements */
String.prototype.inject = function() {
  var result = this;
  for (var i = 0; i < arguments.length; i++) {
    result = result.replace('{' + i + '}', arguments[i]);
  }
  return result;
}

/* class Object enhancements */
Object.copy = function (srcObj, destObj, filter) {
  var regex;
  if (filter) regex = new RegExp(filter);
  for (var prop in srcObj) 
    try {
      if (!regex || regex.test(prop) == false) 
        destObj[prop] = srcObj[prop];
    }
    catch (ex) {}
  return destObj;
}
Object.copyMapped = function (srcObj, destObj, mapping, contextObj) { // contextObj is optional
  // Mapping = array of strings qualifying properties (or aggregated properties) in src and dest
  // Dest properties are evaluated using srcObj.eval() so simple transforms are possible
  // (e.g. ['src', 'src', 'style.left', 'style.left+1', 'id', 'id+"_copy"'])
  // If contextObj is supplied, evaluations are done in it's context.  This allows nearly uinlimited
  // transformations.  (e.g. if contextObj = window: ['src', 'myObj.myFunc(event.clientX, 'foobar')'])
  if (!contextObj) contextObj = srcObj;
  for (var i = 0; i < mapping.length; i = i + 2) {
    var dest = destObj, destAgg = mapping[i].split('.');
    for (var d = 0; d < destAgg.length - 1; p++) dest = dest[d];
    dest[destAgg[destAgg.length]] = Object.findPropValue(contextObj, mapping[i + 1]); 
  }
  return destObj;
}
// finds the value of a property that may be several objects deep. e.g.: this.style.left
Object.findPropValue = function (obj, expr) {
  try {
    var val = '';
    if (obj.eval) // mozilla
      val = obj.eval(expr);
    else { // IE only supports eval at window scope (ignores eval.call())
      var derefs = expr.split('.'); // note: assumes no array syntax!
      var prop = obj[derefs[0]];
      for (var i = 1; i < derefs.length; i++) 
        prop = prop[derefs[i]];
      val = prop;
    }
    if (val)
      return val;
    else
      return null;
  }
  catch (ex) {
    return null;
  }
}
// calls a function in a separate thread in the context of another function / thread
// useful for executing code immediately *after* the current code / event
Object.execAsync = function(asyncFunc, contextFunc, delay) { 
  if (!delay) delay = 10;
  if (contextFunc)
    setTimeout(asyncFunc.bind(contextFunc), delay);
  else
    setTimeout(asyncFunc, delay);
};

/* class Array enhancements */
Array.prototype.indexOf = function (value) {
  for (var i = 0; i < this.length; i++)
    if (this[i] == value) return i;
  return null;
}

/* class Element enhancements */
Object.extend(Element, {
  newElement: function (tagName, id, name) { // id and name are optional
    var el = document.createElement(tagName);
    if (id) el.id = id;
    if (name) el.name = name;
    return el;
  },
  disposeElement: function (element) {
    var el = $(element);
    if (el && el.parentNode) el.parentNode.removeChild(el);
  },
  setOpacity: function (element, alpha) {
    element.style.MozOpacity = alpha;
    element.style.opacity = alpha;
    if (alpha == 1.0) // fix for IE font rendering bug
      this.removeIEFilter(element, 'Alpha');
    else
      this.setIEFilter(element, 'Alpha(opacity=' + parseInt(100 * alpha) + ')');
  },
  setIEFilter: function (element, filterTerm) {
    this.removeIEFilter(element, filterTerm);
    element.style.filter += ' ' + filterTerm;
  },
  removeIEFilter: function (element, filterTerm) {
    var filter = element.style.filter || '', p = filterTerm.indexOf('('), 
      filterName = p >= 0 ? filterTerm.substr(0, p) : filter;
    p = filter.indexOf(filterName);
    if (p >= 0) {
      var start = filter.lastIndexOf(' ', p) + 1, end = filter.indexOf(')', p);
      if (end > start) { // if not, something is screwed up
        filter = filter.substr(0, start) + filter.substr(end + 1);
        element.style.filter = filter;
      }
    }
  },
  setCursor: function (element, cursor) {
    if (Browser.isIE5 && cursor == 'pointer')
      element.style.cursor = 'hand';
    else
      element.style.cursor = cursor;
  },
  switchParent: function (element, newParent) {
    newParent.appendChild(element);
  },
  intToPx: function (i) {
    return parseInt(i).toString() + 'px';
  },
  pxToInt: function (px) {
    return parseInt(px);
  },
  findStyleProperty: function (element, property, pseudoElement) {
    var value = element.style[property]; // try inline property first
    if (!value) { // if not found
      if (element.currentStyle) { //  IE 5-6
        if (property.indexOf('-') >= 0)
          value = element.currentStyle[this._textToJsStyle(property)];
        else
          value = element.currentStyle[property];
      }
      else if (window.getComputedStyle) // W3C
        value = window.getComputedStyle(element, pseudoElement).getPropertyValue(property);
    }
    return value;
  },
  _textToJsStyle: function (text) {
    text = text.toLowerCase();
    var p = text.indexOf('-');
    while (p >= 0) {
      text = text.substr(0, p) + text.substr(p + 1, 1).toUpperCase() + text.substr(p + 2);
      p = text.indexOf('-');
    }
    return text;
  },
  resetStyleProperty: function (element, property, psuedoElement) {
    element.style[property] = '';
    element.style[property] = this.findStyleProperty(element, property, psuedoElement);
  },
  getSize: function (element) {
    // TO DO: this only works for border-box model, not content-box (borders and margins interfere)
    var w = this.findStyleProperty(element, 'width');
    var h = this.findStyleProperty(element, 'height');
    return new Size(this.pxToInt(w), this.pxToInt(h));
  },
  setSize: function (element, size) {
    element.style.width = this.intToPx(size.width);
    element.style.height = this.intToPx(size.height);
  },
  overlap: function (e1, e2) {
    var rect1 = new Rect(this.getClientPos(e1), this.getSize(e1)),
      rect2 = new Rect(this.getClientPos(e2), this.getSize(e2));
    return rect1.intersectsWith(rect2);
  }
});

/* class Floater */
var Floater = new Object();
Object.extend(Floater, Element);
Object.extend(Floater, {
  isFloating: function (element) {
    return element.style.position == 'absolute';
  },
  makeFloating: function () {
    for (var i = 0; i < arguments.length; i++)
      arguments[i].style.position = 'absolute';
  },
  switchParent: function (element, newParent, keepInPlace) {
    var pt;
    if (keepInPlace) {
      pt = this.getClientPos(element);
    }
    Element.switchParent(element, newParent);
    if (keepInPlace) {
      var parentPt = Floater.getClientPos(newParent);
      element.style.left = this.intToPx(pt.X - parentPt.X);
      element.style.top = this.intToPx(pt.Y - parentPt.Y);
    }
  },
  getSize: function (element) {
    // TO DO: this only works for border-box model, not content-box (borders and margins interfere)
    var w = element.offsetWidth;
    var h = element.offsetHeight;
    return new Size(this.pxToInt(w), this.pxToInt(h));
  },
  setSize: function (element, size) {
    element.style.width = this.intToPx(size.width);
    element.style.height = this.intToPx(size.height);
  },
  getClientPos: function (element) {
    // TO DO: this only works for border-box model, not content-box (borders and margins interfere)
    var el = element;
    var parent = el.offsetParent;
    var pos = new Point(0, 0);
    while (el && parent){// && (parent.tagName != 'HTML')) { 
      var pt = this.getOffset(el);
      pos.X += pt.X - (parent.scrollLeft || 0);
      pos.Y += pt.Y - (parent.scrollTop || 0);
      el = parent; 
      parent = el.offsetParent; 
    }
    return pos;
  },
  getOffset: function (element) {
    return new Point(element.offsetLeft, element.offsetTop);
  },
  setOffset: function (element, left, top) {
    element.style.left = Element.intToPx(left);
    element.style.top = Element.intToPx(top);
  },
  getClientTop: function (element) {
    return this.getClientPos(element).Y;
  },
  getClientLeft: function (element) {
    return this.getClientPos(element).X;
  },
  setClientTop: function (element, top) { // element must be floating to work
    element.style.top = this.intToPx(this.getClientTop(element.parentNode) + top);
  },
  setClientLeft: function (element, left) { // element must be floating to work
    element.style.left = this.intToPx(this.getClientLeft(element.parentNode) + left);
  },
  show: function() { // overrides Element.show from base library
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      element.style.visibility = 'visible';
    }
  },
  hide: function() { // overrides Element.hide from base library
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      element.style.visibility = 'hidden';
    }
  },
  overlap: function (e1, e2) {
    var rect1 = new Rect(this.getClientPos(e1), this.getSize(e1)),
      rect2 = new Rect(this.getClientPos(e2), this.getSize(e2));
    return rect1.intersectsWith(rect2);
  }
});

/* class PropertyBag  */
// defines a list of properties and values
function PropertyBag(sourceObject) {
  if (sourceObject)
    Object.copy(sourceObject, this, 'extend');  // don't copy extend function
  this.extend = null; // good enough for now (see next line)
  //delete this.extend; // TO DO: why doesn't ths remove?
};
PropertyBag.prototype = {extend: null}; // do not add a constructor or it will end up in the property bag as a property
// TO DO: why doesn't this remove?
//delete PropertyBag.prototype.extend;  // remove extend function

/* class Point */
var Point = Class.create();
Point.prototype = {
  initialize: function (x, y) {
   this.X = isNaN(parseInt(x)) ? 0 : parseInt(x);
   this.Y = isNaN(parseInt(y)) ? 0 : parseInt(y);
  },
  offset: function (dxOrPt, dy) { // pass a Point, or an x and y delta pair
    if (dxOrPt.constructor == Point) {
      this.X += dxOrPt.X;
      this.Y += dxOrPt.Y;
    }
    else {
      this.X += dxOrPt;
      this.Y += dy;
    }
    return this;
  },
  mult: function (factor) {
    this.X *= factor;
    this.Y *= factor;
    return this;
  }
}
Point.prototype.constructor = Point;

/* class Size */
var Size = Class.create();
Size.prototype.extend({
  initialize: function (width, height) {
    this.width = isNaN(parseInt(width)) ? 0 : parseInt(width);
    this.height = isNaN(parseInt(height)) ? 0 : parseInt(height);
  },
  inc: function (dxOrSize, dy) {
    if (dxOrSize.constructor == Size) {
      this.width += dxOrPt.width;
      this.height += dxOrPt.height;
    }
    else {
      this.width += dxOrSize;
      this.height += dy;
    }
    return this;
  },
  mult: function (factor) {
    var pt = new Point(this.width, this.height);
    pt.mult(factor);
    this.width = pt.X, this.height = pt.Y;
    return this;
  }
});
Size.prototype.constructor = Size;

/* class Rect */
var Rect = Class.create();
Rect.prototype.extend({
  initialize: function (pt, size) {
    this.left = pt ? pt.X : 0;
    this.top = pt ? pt.Y : 0;
    this.width = size ? size.width : 0;
    this.height = size ? size.height : 0;
  },
  getRight: function () {
    return this.left + this.width - 1;
  },
  getBottom: function () {
    return this.top + this.height - 1;
  },
  intersectsWith: function (rect) {
    return (((this.left <= rect.getRight()) && (this.left >= rect.left)) // left edge intersects
      || ((this.getRight() >= rect.left) && (this.getRight() <= rect.getRight()))) // right edge intersects
      && (((this.top <= rect.getBottom()) && (this.top >= rect.top)) // top edge intersects
      || ((this.getBottom() >= rect.top) && (this.getBottom() <= rect.getBottom()))); // bottom edge intersects
  },
  getOrigin: function () {
    return new Point(this.left, this.top);
  },
  getSize: function () {
    return new Point(this.width, this.height);
  }
});
Rect.prototype.constructor = Rect;

/* class Page */
var Page = new Object();
Object.extend(Page, {
  getClientHeight: function() {
    return getClientSize.height;
  },
  getClientWidth: function() {
    return getClientSize.width;
  },
  getClientSize: function() {
    var x = 0, y = 0;
    // all except Explorer
    if (self.innerHeight) {
      x = self.innerWidth;
      y = self.innerHeight;
    }
    // Explorer 6 Strict Mode
    else if (document.documentElement && document.documentElement.clientHeight) {
      x = document.documentElement.clientWidth;
      y = document.documentElement.clientHeight;
    }
    // other Explorers
    else if (document.body) {
      x = document.body.clientWidth;
      y = document.body.clientHeight;
    }
    return new Size(x, y);
  },
  getRootElement: function () {
    if (document.getElementsByTagName)
      return document.getElementsByTagName('BODY')[0];
    else if (document.body)
      return document.body;
    else
      return null;
  },
  makeUrl: function () { // arg 1 = base url, other arg pairs are parameters
    if (arguments.length == 0) return "";
    var base = arguments[0], url = base;
    var sep = (base == "" ? "" : base.indexOf('?') <= 0 ? '?' : '&');
    for (var i = 1; i < arguments.length; i = i + 2) {
      url += sep + arguments[i].toString() + (arguments[i + 1] && arguments[i + 1] != "" ? '=' + encodeURIComponent(arguments[i + 1].toString()) : '');
      if (sep != '&') sep = '&';
    }
    return url;
  },
  makeParamList: function () {
    var params = arguments[0] + (arguments[1] && arguments[1] != "" ? '=' + encodeURIComponent(arguments[1].toString()) : '');
    for (var i = 2; i < arguments.length; i = i + 2) {
      params += '&' + arguments[i] + (arguments[i + 1] && arguments[i + 1] != "" ? '=' + encodeURIComponent(arguments[i + 1].toString()) : '');
    }
    return params;
  }
});

/* base class DraggableElement */
var DraggableElement = Class.create();
DraggableElement.DragStates = {NOT_DRAGGING: 0, READY: 1, DRAGGING: 2};
DraggableElement.prototype = {
  initialize: function (element) {
    this.draggedElement = element;
    this.draggedElement.onmousedown = this._element_onmousedown.bind(this);
  },
  _element_onmousedown: function (e) {
    this.wasMoved = false;
    var event = (e || window.event);
    var mousekey = (event.which || event.button);
    if (mousekey == 1) {
      this._stopEventPropagation(event);
      this._startDrag(
        event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft), 
        event.clientY + (document.documentElement.scrollTop || document.body.scrollTop));
    }
  },
  _element_onmousemove: function (e) {
    var event = (e || window.event);
    var delta = new Point(
      event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft) - this.dragClientPt.X, 
      event.clientY + (document.documentElement.scrollTop || document.body.scrollTop) - this.dragClientPt.Y);
    if (this.dragState == DraggableElement.DragStates.READY) {
      if (Math.abs(delta.X) > 1 || Math.abs(delta.Y) > 1) { // moved far enough to be a drag instead of a click
        this.dragState = DraggableElement.DragStates.DRAGGING;
        this.wasMoved = true;
        if (this.onChangedDragState) 
          this.onChangedDragState(this, Floater.getOffset(this.draggedElement), this.dragState);
      }
    }
    if (this.dragState == DraggableElement.DragStates.DRAGGING) {
      // TO DO: prevent a user from placing icons off-parent (don't do modifiedCallback if not moved at all)
      this._moveElementTo(delta.X + this.dragOffsetPt.X, delta.Y + this.dragOffsetPt.Y);
    }
  },
  _element_onmouseup: function (e) {
    this._stopDrag();
  },
  _stopEventPropagation: function (event) {
    if (event.preventDefault) {
      event.preventDefault(); 
      event.stopPropagation();
    }
    else 
      window.event.returnValue = false;
  },
  _captureElementEvents: function () {
    if (this.draggedElement.setCapture) this.draggedElement.setCapture();
    this.prev_onmousemove = this.draggedElement.onmousemove;
    this.prev_onmouseup = this.draggedElement.onmouseup;
    this.prev_ondragstart = this.draggedElement.ondragstart;
    this.draggedElement.onmousemove = this._element_onmousemove.bind(this);
    this.draggedElement.onmouseup = this._element_onmouseup.bind(this);
    this.draggedElement.ondragstart = function() { return false; } // cancel IE's built-in (lame) functionality
  },
  _releaseElementEvents: function () {
    if (this.draggedElement.releaseCapture) this.draggedElement.releaseCapture();
    this.draggedElement.onmousemove = this.prev_onmousemove;
    this.draggedElement.onmouseup = this.prev_onmouseup;
    this.draggedElement.ondragstart = this.prev_ondragstart;
  },
  _captureDocumentEvents: function () {
    this.prev_document_ondragstart = document.ondragstart;
    this.prev_document_onmousemove = document.onmousemove;
    this.prev_document_onmouseup = document.onmouseup;
    this.prev_document_onselectstart = document.onselectstart;
    this.prev_document_ondragstart = document.ondragstart;
    document.onmousemove = this._element_onmousemove.bind(this);
    document.onmouseup = this._element_onmouseup.bind(this);
    document.onselectstart = function() {return false; }
    document.ondragstart = function() { return false; } // cancel IE's built-in (lame) functionality
  },
  _releaseDocumentEvents: function () {
    document.ondragstart = this.prev_document_ondragstart;
    document.onmousemove = this.prev_document_onmousemove;
    document.onmouseup = this.prev_document_onmouseup;
    document.onselectstart = this.prev_document_onselectstart;
    document.ondragstart = this.prev_document_ondragstart;
  },
  _moveElementTo: function (x, y) {
    Floater.setOffset(this.draggedElement, x, y);
  },
  _startDrag: function (x, y) {
    this.dragClientPt = new Point(x, y);
    this.dragOffsetPt = Floater.getOffset(this.draggedElement);
    this._captureElementEvents();
    this._captureDocumentEvents();
    this.dragState = DraggableElement.DragStates.READY;
    if (this.onChangedDragState) 
      this.onChangedDragState(this, this.dragOffsetPt, this.dragState);
  },
  _stopDrag: function () {
    this._releaseDocumentEvents();
    this._releaseElementEvents();
    this.dragState = DraggableElement.DragStates.NOT_DRAGGING;
    if (this.onChangedDragState) 
      this.onChangedDragState(this, Floater.getOffset(this.draggedElement), this.dragState);
  }
}
DraggableElement.prototype.constructor = DraggableElement;

/* class HtmlDialog */
var HtmlDialog = Class.create();
HtmlDialog.prototype.extend({
  onSubmit: null,
  onCancel: null,
  initialize: function (topElement, titleElement, submitElements, cancelElements, shadowElement) {
    this.topElement = topElement;
    if (titleElement) this.titleElement = titleElement;
    if (submitElements) this.submitElements = submitElements;
    if (cancelElements) this.cancelElements = cancelElements;
    if (shadowElement) this.shadowElement = shadowElement;
  },
  show: function () {
    if (this.submitElements && this.submitElements.length > 0) 
      for (var i = 0; i < this.submitElements.length; i++)
        this.submitElements[i].onclick = (function (e) { this.hide(); if (this.onSubmit) this.onSubmit(this, e); }).bind(this);
    if (this.cancelElements && this.cancelElements.length > 0) 
      for (var i = 0; i < this.cancelElements.length; i++)
        this.cancelElements[i].onclick = (function (e) { this.hide(); if (this.onCancel) this.onCancel(this, e); }).bind(this);
    this._setTitle(this.title);
    Floater.show(this.topElement);
    if (this.shadowElement) {
      this._sizeShadow();
    }
    var forms = this._getForms();
    if (forms.length > 0)
      Form.focusFirstElement(forms[0]);
    if (this.onShow)
      this.onShow(this);
  },
  hide: function () {
    Floater.hide(this.topElement);
    if (this.shadowElement) 
      Floater.hide(this.shadowElement);
    if (this.onHide)
      this.onHide(this);
  },
  clearForm: function (formIndex) {
    var forms = this._getForms();
    if (forms.length > 0)
      forms[formIndex || 0].clear();
  },
  setFormValues: function (propertyBag, formIndex) {
    var forms = this._getForms();
    var index = (formIndex || 0);
    if (forms.length > index) {
      for (var prop in propertyBag) {
      var el = forms[index][prop] || document.getElementById(prop);
        if (el && el.type)
          switch (el.type.toLowerCase()) {
            case 'checkbox':  
            case 'radio':
              el.checked = propertyBag[prop] == el.value;
              break;
            case 'select-one':
            case 'select-many':
              this._loadSelect(el, propertyBag[prop]);
              break;
            default:
              el.value = propertyBag[prop];
              break;
          } // switch
      } // for 
    } // if 
  },
  getFormValues: function (propertyBag, formIndex) {
    var forms = this._getForms();
    var index = (formIndex || 0);
    if (forms.length > index) {
      for (var prop in propertyBag) {
        var el = forms[index][prop] || document.getElementById(prop);
        propertyBag[prop] = null; // set default for checkboxes and radio buttons
        if (el && el.type) 
          switch (el.type.toLowerCase()) {
            case 'checkbox':  
            case 'radio':
              if (el.checked) propertyBag[prop] = el.value;
              break;
            case 'select-one':
              propertyBag[prop] = el.selectedIndex >= 0 ? el.options[el.selectedIndex].value : null;
              break;
            case 'select-many':
              this._unloadSelect(el, propertyBag[prop]);
              break;
            default:
              propertyBag[prop] = el.value;
              break;
          } // switch
      } // for 
    } // if 
  },
  _loadSelect: function (element, propertyBag) {
    element.options.length = 0;
    for (var prop in propertyBag) {
      if (propertyBag[prop] != null) {
        var opt = new Option(propertyBag[prop], prop);
        element.options[element.options.length] = opt;
      }
    }
  },
  _unloadSelect: function (element, propertyBag) { // for select-many / multi-selects
    for (var i = 0; i < element.options.length; i++)
      if (element.options[i].selected)
        propertyBag[element.options[i].value] = element.options[i].text;
  },
  _setTitle: function (title) {
    if (this.titleElement && (typeof title != 'undefined')) this.titleElement.innerHTML = title;
  },
  _getForms: function () {
    return this.topElement.getElementsByTagName('form');
  },
  _sizeShadow: function () {
    Floater.setSize(this.shadowElement, Floater.getSize(this.topElement));
    Floater.show(this.shadowElement);
  }
});
HtmlDialog.prototype.constructor = HtmlDialog;

/* class DraggableDialog */
var DraggableDialog = Class.create();
DraggableDialog.prototype.extend(new DraggableElement()).extend(new HtmlDialog()).extend({
  initialize: function (draggedElement, grabElement, titleElement, submitElements, cancelElements, shadowElement) { // grabElement = element that user clicks to drag
    if (!grabElement) grabElement = draggedElement;
    HtmlDialog.prototype.initialize.call(this, draggedElement, titleElement, submitElements, cancelElements, shadowElement);
    this.grabElement = grabElement;
    this.draggedElement = draggedElement;
    this.grabElement.onmousedown = this._element_onmousedown.bind(this);
  }
});
DraggableDialog.prototype.constructor = DraggableDialog;

/* class DynamicDialog */
ReusableDialog = Class.create();
ReusableDialog.prototype.extend(new HtmlDialog()).extend({
  initialize: function (containerElement, grabElement, placeholder, content, titleElement, submitElements, cancelElements, shadowElement) { // content is inserted into placeholder
    HtmlDialog.prototype.initialize.call(this, content, titleElement, submitElements, cancelElements, shadowElement);
    this.placeholder = placeholder;
    this.content = content;
    this.containerElement = containerElement;
  },
  show: function () {
    if (this.placeholder.hasChildNodes()) { // while loop hangs sometimes????  use for
      for (var i = 0 ; i < this.placeholder.childNodes.length; i++)
        this.placeholder.removeChild(this.placeholder.childNodes[i]);
    }
    this.placeholder.appendChild(this.content);
    Floater.show(this.content);
    Floater.show(this.containerElement);
    HtmlDialog.prototype.show.call(this);
  },
  hide: function () {
    Floater.hide(this.containerElement);
    Floater.hide(this.content);
  },
  _sizeShadow: function () {
    Floater.setSize(this.shadowElement, Floater.getSize(this.containerElement));
    Floater.show(this.shadowElement);
  }
});
ReusableDialog.prototype.constructor = ReusableDialog;

/* class ModalDialog -- must inherit! */
var ModalDialog = Class.create();
ModalDialog.prototype.extend(new HtmlDialog()).extend({
  initialize: function (modalElement, titleElement, opacity, bgcolor, submitElements, cancelElements, shadowElement) {
    HtmlDialog.prototype.initialize.call(this, modalElement, titleElement, submitElements, cancelElements, shadowElement);
    this.opacity = opacity;
    this.bgcolor = bgcolor;
    this.modalElement = modalElement;
  },
  show: function () {
    HtmlDialog.prototype.show.call(this);
    var block = Element.newElement('Div');
    this.blockingElement = block;
    document.body.appendChild(block);
    Element.setOpacity(block, Number("0" + this.opacity));
    if (this.bgcolor) block.style.backgroundColor = this.bgcolor;
    Floater.makeFloating(block);
    this._sizeBlock();
    this._prev_window_onresize = window.onresize;
    window.onresize = this._sizeBlock.bind(this);
    block.style.zIndex = Element.findStyleProperty(this.modalElement, 'z-index') - 1; // assumes dialog is already at top of z-order
  },
  hide: function () {
    window.onresize = this._prev_window_onresize;
    Element.disposeElement(this.blockingElement);
    HtmlDialog.prototype.hide.call(this);
  },
  _sizeBlock: function () {
    var area = Page.getClientSize();    
    this.blockingElement.style.top = '0px';
    this.blockingElement.style.left = '0px';
    this.blockingElement.style.width = area.width + 'px';
    this.blockingElement.style.height = area.height + 'px';
  }
});
ModalDialog.prototype.constructor = ModalDialog;

/* class UltraDialog */
var UltraDialog = Class.create();
UltraDialog.prototype.extend(new ModalDialog()).extend(new DraggableDialog()).extend(new ReusableDialog()).extend({
  initialize: function (containerElement, submitElements, cancelElements, grabElement, placeholder, content, titleElement, modalOpacity, modalBgcolor, shadowElement) {
    DraggableDialog.prototype.initialize.call(this, containerElement, grabElement);
    ReusableDialog.prototype.initialize.call(this, containerElement, grabElement, placeholder, content);
    ModalDialog.prototype.initialize.call(this, containerElement, null, modalOpacity, modalBgcolor);
    // HtmlDialog must be last to override the setting of topElement (should be = content)
    HtmlDialog.prototype.initialize.call(this, content, titleElement, submitElements, cancelElements, shadowElement);
  },
  show: function () {
    ReusableDialog.prototype.show.call(this);
    ModalDialog.prototype.show.call(this);
  },
  hide: function () {
    ReusableDialog.prototype.hide.call(this);
    ModalDialog.prototype.hide.call(this);
  }
});
UltraDialog.prototype.constructor = UltraDialog;

var MacZoomDialog = Class.create();
MacZoomDialog.prototype.extend(new HtmlDialog()).extend({
  initialize: function (topElement, titleElement, submitElements, cancelElements, srcElement, transparentImageFile, shadowElement) {
    this.srcElement = srcElement;
    this.transparentImageFile = transparentImageFile;
    this.image = new Image();
    this.image.src = this.transparentImageFile; // pre-load
    this.image.style.zIndex = 4000; // TO DO: don't hard-code
    Floater.makeFloating(this.image);
    Floater.hide(this.image);
    this.image.style.border = '1px dotted black';
    Page.getRootElement().appendChild(this.image);
    this._isZooming = false;
    this.isShowing = false;
    this._animTime = 360; // msec
    this.isReusable = false; // set to true if this dialog will service multple srcElement targets
    HtmlDialog.prototype.initialize.call(this, topElement, titleElement, submitElements, cancelElements, shadowElement);
  },
  show: function () {
    if (this._currSrc && (this._currSrc != this.srcEleemnt) && this.isReusable) { // zoom back
      this._startZooming(-1, this._currSrc);
      HtmlDialog.prototype.hide.call(this);
    }
    else
      this._startZooming(1, this.srcElement);
  },
  hide: function () {
    this._startZooming(-1, this.srcElement);
    HtmlDialog.prototype.hide.call(this);
    this.isShowing = false;
  },
  _startZooming: function (direction, source) {
    this._currSrc = source;
    if (!this._isZooming) {
      this._isZooming = true;
      this._startPt = Floater.getClientPos(source);
      this._startSize = Floater.getSize(source);
    }
    else { // keep going at current state (may be opposite direction, though)
      this._startPt = this._currPt;
      this._startSize = this._currSize;
    }
    this._stopPt = Floater.getClientPos(this.topElement);
    // TO DO: this won't work: need to know coordinates before showing!
    this._stopSize = Floater.getSize(this.topElement);
    if (direction < 0) { // switch start and stop
      var tmp = this._startPt;
      this._startPt = this._stopPt;
      this._stopPt = tmp;
      tmp = this._startSize;
      this._startSize = this._stopSize;
      this._stopSize = tmp;
    }
    this._leftRate = (this._stopPt.X - this._startPt.X) / this._animTime;
    this._topRate = 1.0 * (this._stopPt.Y - this._startPt.Y) / this._animTime;
    this._widthRate = 1.0 * (this._stopSize.width - this._startSize.width) / this._animTime;
    this._heightRate = 1.0 * (this._stopSize.height - this._startSize.height) / this._animTime;
    this._direction = direction;
    this._startTime = new Date();
    clearInterval(this._animThread);
    this._animThread = setInterval(this._doZoom.bind(this), 20);
    this._placeImage(this._startPt, this._startSize);
    Floater.show(this.image);
  },
  _stopZooming: function () {
    clearInterval(this._animThread);
    Floater.hide(this.image);
    this._isZooming = false;
    if (this._direction > 0) // zoomed out
      this.showDialog();
    else {
      if (this._currSrc != this.srcElement) // needed to zoom back before zooming out to new source
        this._startZooming(1, this.srcElement);
      else
        this._currSrc = null;
    }
  },
  showDialog: function () {
    HtmlDialog.prototype.show.call(this);
    this.isShowing = true;
  },
  _doZoom: function () {
    var delta = (new Date()).getTime() - this._startTime.getTime();
    if (delta > this._animTime)
      this._stopZooming();
    else {
      this._currPt = new Point(this._startPt.X + delta * this._leftRate, this._startPt.Y + delta * this._topRate);
      this._currSize = new Size(this._startSize.width + delta * this._widthRate, this._startSize.height + delta * this._heightRate);
      this._placeImage(this._currPt, this._currSize);
    }
  },
  _placeImage: function (pt, size) {
    this.image.style.left = Element.intToPx(pt.X);
    this.image.style.top = Element.intToPx(pt.Y);
    this.image.style.width = Element.intToPx(size.width);
    this.image.style.height = Element.intToPx(size.height);
  }
});
MacZoomDialog.prototype.constructor = MacZoomDialog;

/* class ShadowDecorator */
var ShadowDecorator = Class.create();
ShadowDecorator.prototype = {
  initialize: function (mainElement, intensity, width, hideInitially) {
    // mainElement must be able to accept child elements (DIV, SPAN, LI, LABEL, P, etc...) and must have overflow set to visible
    this.element = mainElement;
    this._i = intensity || 0.5;
    this._w = width;
    if (!hideInitially)
      this.show();
  },
  show: function () {
    this._prepare();
    this._show();
  },
  hide: function () {
    this._prepare();
    this._hide();
  },
  _createDiv: function () {
    var div = Element.newElement('DIV');
    with (div.style) {
      display = 'none';
      position = 'absolute';
      backgroundColor = 'black';
      top = Element.intToPx(this._w);
      left = Element.intToPx(this._w);
      width = '100%';
      height = '100%'; // IE doesn't work if parent's height is 'auto'
      zIndex = '-10000';
    }
    Element.setOpacity(div, this._i);
    Element.switchParent(div, this.element);
    return div;
  },
  _prepare: function () {
    if (!this._prepared) {
      this._s = this._createDiv();
      this._prepared = true;
    }
  },
  _show: function () {
    if (this._prepared) {
      this._setSize();
      this._s.style.display = 'block';
    }
  },
  _setSize: function () {
    with (this._s.style) {
      var msize = Floater.getSize(this.element), ssize = Floater.getSize(this._s);
      if (msize.width != ssize.width)
        width = Element.intToPx(msize.width);
      if (msize.height != ssize.height)
        height = Element.intToPx(msize.height);
    }
  },
  _hide: function () {
    if (this._prepared) 
      this._s.style.display = 'none';
  },
  dispose: function () {
    if (this._prepared) 
      Element.disposeElement(this._s);
  }
};
ShadowDecorator.prototype.constructor = ShadowDecorator;