
/*  Prototype JavaScript framework, version 1.4.0
 *  (c) 2005 Sam Stephenson <sam@conio.net>
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://prototype.conio.net/
 *
/*--------------------------------------------------------------------------*/

var Prototype = {
  Version: '1.4.0',
  ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',

  emptyFunction: function() {},
  K: function(x) {return x}
}

var Class = {
  create: function() {
    return function() {
      this.initialize.apply(this, arguments);
    }
  }
}

var Abstract = new Object();

Object.extend = function(destination, source) {
  for (property in source) {
    destination[property] = source[property];
  }
  return destination;
}

Object.inspect = function(object) {
  try {
    if (object == undefined) return 'undefined';
    if (object == null) return 'null';
    return object.inspect ? object.inspect() : object.toString();
  } catch (e) {
    if (e instanceof RangeError) return '...';
    throw e;
  }
}

Function.prototype.bind = function() {
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
}

Function.prototype.bindAsEventListener = function(object) {
  var __method = this;
  return function(event) {
    return __method.call(object, event || window.event);
  }
}

Object.extend(Number.prototype, {
  toColorPart: function() {
    var digits = this.toString(16);
    if (this < 16) return '0' + digits;
    return digits;
  },

  succ: function() {
    return this + 1;
  },

  times: function(iterator) {
    $R(0, this, true).each(iterator);
    return this;
  }
});

var Try = {
  these: function() {
    var returnValue;

    for (var i = 0; i < arguments.length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) {}
    }

    return returnValue;
  }
}

/*--------------------------------------------------------------------------*/

var PeriodicalExecuter = Class.create();
PeriodicalExecuter.prototype = {
  initialize: function(callback, frequency) {
    this.callback = callback;
    this.frequency = frequency;
    this.currentlyExecuting = false;

    this.registerCallback();
  },

  registerCallback: function() {
    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.callback();
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
}

/*--------------------------------------------------------------------------*/

function $() {
  var elements = new Array();

  for (var i = 0; i < arguments.length; i++) {
    var element = arguments[i];
    if (typeof element == 'string')
      element = document.getElementById(element);

    if (arguments.length == 1)
      return element;

    elements.push(element);
  }

  return elements;
}
Object.extend(String.prototype, {
  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

  stripScripts: function() {
    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
  },

  extractScripts: function() {
    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
    return (this.match(matchAll) || []).map(function(scriptTag) {
      return (scriptTag.match(matchOne) || ['', ''])[1];
    });
  },

  evalScripts: function() {
    return this.extractScripts().map(eval);
  },

  escapeHTML: function() {
    var div = document.createElement('div');
    var text = document.createTextNode(this);
    div.appendChild(text);
    return div.innerHTML;
  },

  unescapeHTML: function() {
    var div = document.createElement('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
  },

  toQueryParams: function() {
    var pairs = this.match(/^\??(.*)$/)[1].split('&');
    return pairs.inject({}, function(params, pairString) {
      var pair = pairString.split('=');
      params[pair[0]] = pair[1];
      return params;
    });
  },

  toArray: function() {
    return this.split('');
  },

  camelize: function() {
    var oStringList = this.split('-');
    if (oStringList.length == 1) return oStringList[0];

    var camelizedString = this.indexOf('-') == 0
      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
      : oStringList[0];

    for (var i = 1, len = oStringList.length; i < len; i++) {
      var s = oStringList[i];
      camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
    }

    return camelizedString;
  },

  inspect: function() {
    return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
  }
});

String.prototype.parseQuery = String.prototype.toQueryParams;

var $break    = new Object();
var $continue = new Object();

var Enumerable = {
  each: function(iterator) {
    var index = 0;
    try {
      this._each(function(value) {
        try {
          iterator(value, index++);
        } catch (e) {
          if (e != $continue) throw e;
        }
      });
    } catch (e) {
      if (e != $break) throw e;
    }
  },

  all: function(iterator) {
    var result = true;
    this.each(function(value, index) {
      result = result && !!(iterator || Prototype.K)(value, index);
      if (!result) throw $break;
    });
    return result;
  },

  any: function(iterator) {
    var result = true;
    this.each(function(value, index) {
      if (result = !!(iterator || Prototype.K)(value, index))
        throw $break;
    });
    return result;
  },

  collect: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      results.push(iterator(value, index));
    });
    return results;
  },

  detect: function (iterator) {
    var result;
    this.each(function(value, index) {
      if (iterator(value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  },

  findAll: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      if (iterator(value, index))
        results.push(value);
    });
    return results;
  },

  grep: function(pattern, iterator) {
    var results = [];
    this.each(function(value, index) {
      var stringValue = value.toString();
      if (stringValue.match(pattern))
        results.push((iterator || Prototype.K)(value, index));
    })
    return results;
  },

  include: function(object) {
    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  },

  inject: function(memo, iterator) {
    this.each(function(value, index) {
      memo = iterator(memo, value, index);
    });
    return memo;
  },

  invoke: function(method) {
    var args = $A(arguments).slice(1);
    return this.collect(function(value) {
      return value[method].apply(value, args);
    });
  },

  max: function(iterator) {
    var result;
    this.each(function(value, index) {
      value = (iterator || Prototype.K)(value, index);
      if (value >= (result || value))
        result = value;
    });
    return result;
  },

  min: function(iterator) {
    var result;
    this.each(function(value, index) {
      value = (iterator || Prototype.K)(value, index);
      if (value <= (result || value))
        result = value;
    });
    return result;
  },

  partition: function(iterator) {
    var trues = [], falses = [];
    this.each(function(value, index) {
      ((iterator || Prototype.K)(value, index) ?
        trues : falses).push(value);
    });
    return [trues, falses];
  },

  pluck: function(property) {
    var results = [];
    this.each(function(value, index) {
      results.push(value[property]);
    });
    return results;
  },

  reject: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      if (!iterator(value, index))
        results.push(value);
    });
    return results;
  },

  sortBy: function(iterator) {
    return this.collect(function(value, index) {
      return {value: value, criteria: iterator(value, index)};
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }).pluck('value');
  },

  toArray: function() {
    return this.collect(Prototype.K);
  },

  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (typeof args.last() == 'function')
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      iterator(value = collections.pluck(index));
      return value;
    });
  },

  inspect: function() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }
}

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray
});
var $A = Array.from = function(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) {
    return iterable.toArray();
  } else {
    var results = [];
    for (var i = 0; i < iterable.length; i++)
      results.push(iterable[i]);
    return results;
  }
}

Object.extend(Array.prototype, Enumerable);

Array.prototype._reverse = Array.prototype.reverse;

Object.extend(Array.prototype, {
  _each: function(iterator) {
    for (var i = 0; i < this.length; i++)
      iterator(this[i]);
  },

  clear: function() {
    this.length = 0;
    return this;
  },

  first: function() {
    return this[0];
  },

  last: function() {
    return this[this.length - 1];
  },

  compact: function() {
    return this.select(function(value) {
      return value != undefined || value != null;
    });
  },

  flatten: function() {
    return this.inject([], function(array, value) {
      return array.concat(value.constructor == Array ?
        value.flatten() : [value]);
    });
  },

  without: function() {
    var values = $A(arguments);
    return this.select(function(value) {
      return !values.include(value);
    });
  },

  indexOf: function(object) {
    for (var i = 0; i < this.length; i++)
      if (this[i] == object) return i;
    return -1;
  },

  reverse: function(inline) {
    return (inline !== false ? this : this.toArray())._reverse();
  },

  shift: function() {
    var result = this[0];
    for (var i = 0; i < this.length - 1; i++)
      this[i] = this[i + 1];
    this.length--;
    return result;
  },

  inspect: function() {
    return '[' + this.map(Object.inspect).join(', ') + ']';
  }
});
var Hash = {
  _each: function(iterator) {
    for (key in this) {
      var value = this[key];
      if (typeof value == 'function') continue;

      var pair = [key, value];
      pair.key = key;
      pair.value = value;
      iterator(pair);
    }
  },

  keys: function() {
    return this.pluck('key');
  },

  values: function() {
    return this.pluck('value');
  },

  merge: function(hash) {
    return $H(hash).inject($H(this), function(mergedHash, pair) {
      mergedHash[pair.key] = pair.value;
      return mergedHash;
    });
  },

  toQueryString: function() {
    return this.map(function(pair) {
      return pair.map(encodeURIComponent).join('=');
    }).join('&');
  },

  inspect: function() {
    return '#<Hash:{' + this.map(function(pair) {
      return pair.map(Object.inspect).join(': ');
    }).join(', ') + '}>';
  }
}

function $H(object) {
  var hash = Object.extend({}, object || {});
  Object.extend(hash, Enumerable);
  Object.extend(hash, Hash);
  return hash;
}
ObjectRange = Class.create();
Object.extend(ObjectRange.prototype, Enumerable);
Object.extend(ObjectRange.prototype, {
  initialize: function(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  },

  _each: function(iterator) {
    var value = this.start;
    do {
      iterator(value);
      value = value.succ();
    } while (this.include(value));
  },

  include: function(value) {
    if (value < this.start)
      return false;
    if (this.exclusive)
      return value < this.end;
    return value <= this.end;
  }
});

var $R = function(start, end, exclusive) {
  return new ObjectRange(start, end, exclusive);
}

var Ajax = {
  getTransport: function() {
    return Try.these(
      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
      function() {return new ActiveXObject('Microsoft.XMLHTTP')},
      function() {return new XMLHttpRequest()}
    ) || false;
  },

  activeRequestCount: 0
}

Ajax.Responders = {
  responders: [],

  _each: function(iterator) {
    this.responders._each(iterator);
  },

  register: function(responderToAdd) {
    if (!this.include(responderToAdd))
      this.responders.push(responderToAdd);
  },

  unregister: function(responderToRemove) {
    this.responders = this.responders.without(responderToRemove);
  },

  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (responder[callback] && typeof responder[callback] == 'function') {
        try {
          responder[callback].apply(responder, [request, transport, json]);
        } catch (e) {}
      }
    });
  }
};

Object.extend(Ajax.Responders, Enumerable);

Ajax.Responders.register({
  onCreate: function() {
    Ajax.activeRequestCount++;
  },

  onComplete: function() {
    Ajax.activeRequestCount--;
  }
});

Ajax.Base = function() {};
Ajax.Base.prototype = {
  setOptions: function(options) {
    this.options = {
      method:       'post',
      asynchronous: true,
      parameters:   ''
    }
    Object.extend(this.options, options || {});
  },

  responseIsSuccess: function() {
    return this.transport.status == undefined
        || this.transport.status == 0
        || (this.transport.status >= 200 && this.transport.status < 300);
  },

  responseIsFailure: function() {
    return !this.responseIsSuccess();
  }
}

Ajax.Request = Class.create();
Ajax.Request.Events =
  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
  initialize: function(url, options) {
    this.transport = Ajax.getTransport();
    this.setOptions(options);
    this.request(url);
  },

  request: function(url) {
    var parameters = this.options.parameters || '';
    if (parameters.length > 0) parameters += '&_=';

    try {
      this.url = url;
      if (this.options.method == 'get' && parameters.length > 0)
        this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;

      Ajax.Responders.dispatch('onCreate', this, this.transport);

      this.transport.open(this.options.method, this.url,
        this.options.asynchronous);

      if (this.options.asynchronous) {
        this.transport.onreadystatechange = this.onStateChange.bind(this);
        setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
      }

      this.setRequestHeaders();

      var body = this.options.postBody ? this.options.postBody : parameters;
      this.transport.send(this.options.method == 'post' ? body : null);

    } catch (e) {
      this.dispatchException(e);
    }
  },

  setRequestHeaders: function() {
    var requestHeaders =
      ['X-Requested-With', 'XMLHttpRequest',
       'X-Prototype-Version', Prototype.Version];

    if (this.options.method == 'post') {
      requestHeaders.push('Content-type',
        'application/x-www-form-urlencoded');

      /* Force "Connection: close" for Mozilla browsers to work around
       * a bug where XMLHttpReqeuest sends an incorrect Content-length
       * header. See Mozilla Bugzilla #246651.
       */
      if (this.transport.overrideMimeType)
        requestHeaders.push('Connection', 'close');
    }

    if (this.options.requestHeaders)
      requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);

    for (var i = 0; i < requestHeaders.length; i += 2)
      this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
  },

  onStateChange: function() {
    var readyState = this.transport.readyState;
    if (readyState != 1)
      this.respondToReadyState(this.transport.readyState);
  },

  header: function(name) {
    try {
      return this.transport.getResponseHeader(name);
    } catch (e) {}
  },

  evalJSON: function() {
    try {
      return eval(this.header('X-JSON'));
    } catch (e) {}
  },

  evalResponse: function() {
    try {
      return eval(this.transport.responseText);
    } catch (e) {
      this.dispatchException(e);
    }
  },

  respondToReadyState: function(readyState) {
    var event = Ajax.Request.Events[readyState];
    var transport = this.transport, json = this.evalJSON();

    if (event == 'Complete') {
      try {
        (this.options['on' + this.transport.status]
         || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
         || Prototype.emptyFunction)(transport, json);
      } catch (e) {
        this.dispatchException(e);
      }

      if ((this.header('Content-type') || '').match(/^text\/javascript/i))
        this.evalResponse();
    }

    try {
      (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
      Ajax.Responders.dispatch('on' + event, this, transport, json);
    } catch (e) {
      this.dispatchException(e);
    }

    /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
    if (event == 'Complete')
      this.transport.onreadystatechange = Prototype.emptyFunction;
  },

  dispatchException: function(exception) {
    (this.options.onException || Prototype.emptyFunction)(this, exception);
    Ajax.Responders.dispatch('onException', this, exception);
  }
});

Ajax.Updater = Class.create();

Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
  initialize: function(container, url, options) {
    this.containers = {
      success: container.success ? $(container.success) : $(container),
      failure: container.failure ? $(container.failure) :
        (container.success ? null : $(container))
    }

    this.transport = Ajax.getTransport();
    this.setOptions(options);

    var onComplete = this.options.onComplete || Prototype.emptyFunction;
    this.options.onComplete = (function(transport, object) {
      this.updateContent();
      onComplete(transport, object);
    }).bind(this);

    this.request(url);
  },

  updateContent: function() {
    var receiver = this.responseIsSuccess() ?
      this.containers.success : this.containers.failure;
    var response = this.transport.responseText;

    if (!this.options.evalScripts)
      response = response.stripScripts();

    if (receiver) {
      if (this.options.insertion) {
        new this.options.insertion(receiver, response);
      } else {
        Element.update(receiver, response);
      }
    }

    if (this.responseIsSuccess()) {
      if (this.onComplete)
        setTimeout(this.onComplete.bind(this), 10);
    }
  }
});

Ajax.PeriodicalUpdater = Class.create();
Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
  initialize: function(container, url, options) {
    this.setOptions(options);
    this.onComplete = this.options.onComplete;

    this.frequency = (this.options.frequency || 2);
    this.decay = (this.options.decay || 1);

    this.updater = {};
    this.container = container;
    this.url = url;

    this.start();
  },

  start: function() {
    this.options.onComplete = this.updateComplete.bind(this);
    this.onTimerEvent();
  },

  stop: function() {
    this.updater.onComplete = undefined;
    clearTimeout(this.timer);
    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  },

  updateComplete: function(request) {
    if (this.options.decay) {
      this.decay = (request.responseText == this.lastText ?
        this.decay * this.options.decay : 1);

      this.lastText = request.responseText;
    }
    this.timer = setTimeout(this.onTimerEvent.bind(this),
      this.decay * this.frequency * 1000);
  },

  onTimerEvent: function() {
    this.updater = new Ajax.Updater(this.container, this.url, this.options);
  }
});
document.getElementsByClassName = function(className, parentElement) {
  var children = ($(parentElement) || document.body).getElementsByTagName('*');
  return $A(children).inject([], function(elements, child) {
    if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
      elements.push(child);
    return elements;
  });
}

/*--------------------------------------------------------------------------*/

if (!window.Element) {
  var Element = new Object();
}

Object.extend(Element, {
  visible: function(element) {
    return $(element).style.display != 'none';
  },

  toggle: function() {
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      Element[Element.visible(element) ? 'hide' : 'show'](element);
    }
  },

  hide: function() {
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      element.style.display = 'none';
    }
  },

  show: function() {
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      element.style.display = '';
    }
  },

  remove: function(element) {
    element = $(element);
    element.parentNode.removeChild(element);
  },

  update: function(element, html) {
    $(element).innerHTML = html.stripScripts();
    setTimeout(function() {html.evalScripts()}, 10);
  },

  getHeight: function(element) {
    element = $(element);
    return element.offsetHeight;
  },

  classNames: function(element) {
    return new Element.ClassNames(element);
  },

  hasClassName: function(element, className) {
    if (!(element = $(element))) return;
    return Element.classNames(element).include(className);
  },

  addClassName: function(element, className) {
    if (!(element = $(element))) return;
    return Element.classNames(element).add(className);
  },

  removeClassName: function(element, className) {
    if (!(element = $(element))) return;
    return Element.classNames(element).remove(className);
  },

  // removes whitespace-only text node children
  cleanWhitespace: function(element) {
    element = $(element);
    for (var i = 0; i < element.childNodes.length; i++) {
      var node = element.childNodes[i];
      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
        Element.remove(node);
    }
  },

  empty: function(element) {
    return $(element).innerHTML.match(/^\s*$/);
  },

  scrollTo: function(element) {
    element = $(element);
    var x = element.x ? element.x : element.offsetLeft,
        y = element.y ? element.y : element.offsetTop;
    window.scrollTo(x, y);
  },

  getStyle: function(element, style) {
    element = $(element);
    var value = element.style[style.camelize()];
    if (!value) {
      if (document.defaultView && document.defaultView.getComputedStyle) {
        var css = document.defaultView.getComputedStyle(element, null);
        value = css ? css.getPropertyValue(style) : null;
      } else if (element.currentStyle) {
        value = element.currentStyle[style.camelize()];
      }
    }

    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
      if (Element.getStyle(element, 'position') == 'static') value = 'auto';

    return value == 'auto' ? null : value;
  },

  setStyle: function(element, style) {
    element = $(element);
    for (name in style)
      element.style[name.camelize()] = style[name];
  },

  getDimensions: function(element) {
    element = $(element);
    if (Element.getStyle(element, 'display') != 'none')
      return {width: element.offsetWidth, height: element.offsetHeight};

    // All *Width and *Height properties give 0 on elements with display none,
    // so enable the element temporarily
    var els = element.style;
    var originalVisibility = els.visibility;
    var originalPosition = els.position;
    els.visibility = 'hidden';
    els.position = 'absolute';
    els.display = '';
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = 'none';
    els.position = originalPosition;
    els.visibility = originalVisibility;
    return {width: originalWidth, height: originalHeight};
  },

  makePositioned: function(element) {
    element = $(element);
    var pos = Element.getStyle(element, 'position');
    if (pos == 'static' || !pos) {
      element._madePositioned = true;
      element.style.position = 'relative';
      // Opera returns the offset relative to the positioning context, when an
      // element is position relative but top and left have not been defined
      if (window.opera) {
        element.style.top = 0;
        element.style.left = 0;
      }
    }
  },

  undoPositioned: function(element) {
    element = $(element);
    if (element._madePositioned) {
      element._madePositioned = undefined;
      element.style.position =
        element.style.top =
        element.style.left =
        element.style.bottom =
        element.style.right = '';
    }
  },

  makeClipping: function(element) {
    element = $(element);
    if (element._overflow) return;
    element._overflow = element.style.overflow;
    if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
      element.style.overflow = 'hidden';
  },

  undoClipping: function(element) {
    element = $(element);
    if (element._overflow) return;
    element.style.overflow = element._overflow;
    element._overflow = undefined;
  }
});

var Toggle = new Object();
Toggle.display = Element.toggle;

/*--------------------------------------------------------------------------*/

Abstract.Insertion = function(adjacency) {
  this.adjacency = adjacency;
}

Abstract.Insertion.prototype = {
  initialize: function(element, content) {
    this.element = $(element);
    this.content = content.stripScripts();

    if (this.adjacency && this.element.insertAdjacentHTML) {
      try {
        this.element.insertAdjacentHTML(this.adjacency, this.content);
      } catch (e) {
        if (this.element.tagName.toLowerCase() == 'tbody') {
          this.insertContent(this.contentFromAnonymousTable());
        } else {
          throw e;
        }
      }
    } else {
      this.range = this.element.ownerDocument.createRange();
      if (this.initializeRange) this.initializeRange();
      this.insertContent([this.range.createContextualFragment(this.content)]);
    }

    setTimeout(function() {content.evalScripts()}, 10);
  },

  contentFromAnonymousTable: function() {
    var div = document.createElement('div');
    div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
    return $A(div.childNodes[0].childNodes[0].childNodes);
  }
}

var Insertion = new Object();

Insertion.Before = Class.create();
Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
  initializeRange: function() {
    this.range.setStartBefore(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.parentNode.insertBefore(fragment, this.element);
    }).bind(this));
  }
});

Insertion.Top = Class.create();
Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
  initializeRange: function() {
    this.range.selectNodeContents(this.element);
    this.range.collapse(true);
  },

  insertContent: function(fragments) {
    fragments.reverse(false).each((function(fragment) {
      this.element.insertBefore(fragment, this.element.firstChild);
    }).bind(this));
  }
});

Insertion.Bottom = Class.create();
Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
  initializeRange: function() {
    this.range.selectNodeContents(this.element);
    this.range.collapse(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.appendChild(fragment);
    }).bind(this));
  }
});

Insertion.After = Class.create();
Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
  initializeRange: function() {
    this.range.setStartAfter(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.parentNode.insertBefore(fragment,
        this.element.nextSibling);
    }).bind(this));
  }
});

/*--------------------------------------------------------------------------*/

Element.ClassNames = Class.create();
Element.ClassNames.prototype = {
  initialize: function(element) {
    this.element = $(element);
  },

  _each: function(iterator) {
    this.element.className.split(/\s+/).select(function(name) {
      return name.length > 0;
    })._each(iterator);
  },

  set: function(className) {
    this.element.className = className;
  },

  add: function(classNameToAdd) {
    if (this.include(classNameToAdd)) return;
    this.set(this.toArray().concat(classNameToAdd).join(' '));
  },

  remove: function(classNameToRemove) {
    if (!this.include(classNameToRemove)) return;
    this.set(this.select(function(className) {
      return className != classNameToRemove;
    }).join(' '));
  },

  toString: function() {
    return this.toArray().join(' ');
  }
}

Object.extend(Element.ClassNames.prototype, Enumerable);
var Field = {
  clear: function() {
    for (var i = 0; i < arguments.length; i++)
      $(arguments[i]).value = '';
  },

  focus: function(element) {
    $(element).focus();
  },

  present: function() {
    for (var i = 0; i < arguments.length; i++)
      if ($(arguments[i]).value == '') return false;
    return true;
  },

  select: function(element) {
    $(element).select();
  },

  activate: function(element) {
    element = $(element);
    element.focus();
    if (element.select)
      element.select();
  }
}

/*--------------------------------------------------------------------------*/

var Form = {
  serialize: function(form) {
    var elements = Form.getElements($(form));
    var queryComponents = new Array();

    for (var i = 0; i < elements.length; i++) {
      var queryComponent = Form.Element.serialize(elements[i]);
      if (queryComponent)
        queryComponents.push(queryComponent);
    }

    return queryComponents.join('&');
  },

  getElements: function(form) {
    form = $(form);
    var elements = new Array();

    for (tagName in Form.Element.Serializers) {
      var tagElements = form.getElementsByTagName(tagName);
      for (var j = 0; j < tagElements.length; j++)
        elements.push(tagElements[j]);
    }
    return elements;
  },

  getInputs: function(form, typeName, name) {
    form = $(form);
    var inputs = form.getElementsByTagName('input');

    if (!typeName && !name)
      return inputs;

    var matchingInputs = new Array();
    for (var i = 0; i < inputs.length; i++) {
      var input = inputs[i];
      if ((typeName && input.type != typeName) ||
          (name && input.name != name))
        continue;
      matchingInputs.push(input);
    }

    return matchingInputs;
  },

  disable: function(form) {
    var elements = Form.getElements(form);
    for (var i = 0; i < elements.length; i++) {
      var element = elements[i];
      element.blur();
      element.disabled = 'true';
    }
  },

  enable: function(form) {
    var elements = Form.getElements(form);
    for (var i = 0; i < elements.length; i++) {
      var element = elements[i];
      element.disabled = '';
    }
  },

  findFirstElement: function(form) {
    return Form.getElements(form).find(function(element) {
      return element.type != 'hidden' && !element.disabled &&
        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
    });
  },

  focusFirstElement: function(form) {
    Field.activate(Form.findFirstElement(form));
  },

  reset: function(form) {
    $(form).reset();
  }
}

Form.Element = {
  serialize: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    var parameter = Form.Element.Serializers[method](element);

    if (parameter) {
      var key = encodeURIComponent(parameter[0]);
      if (key.length == 0) return;

      if (parameter[1].constructor != Array)
        parameter[1] = [parameter[1]];

      return parameter[1].map(function(value) {
        return key + '=' + encodeURIComponent(value);
      }).join('&');
    }
  },

  getValue: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    var parameter = Form.Element.Serializers[method](element);

    if (parameter)
      return parameter[1];
  }
}

Form.Element.Serializers = {
  input: function(element) {
    switch (element.type.toLowerCase()) {
      case 'submit':
      case 'hidden':
      case 'password':
      case 'text':
        return Form.Element.Serializers.textarea(element);
      case 'checkbox':
      case 'radio':
        return Form.Element.Serializers.inputSelector(element);
    }
    return false;
  },

  inputSelector: function(element) {
    if (element.checked)
      return [element.name, element.value];
  },

  textarea: function(element) {
    return [element.name, element.value];
  },

  select: function(element) {
    return Form.Element.Serializers[element.type == 'select-one' ?
      'selectOne' : 'selectMany'](element);
  },

  selectOne: function(element) {
    var value = '', opt, index = element.selectedIndex;
    if (index >= 0) {
      opt = element.options[index];
      value = opt.value;
      if (!value && !('value' in opt))
        value = opt.text;
    }
    return [element.name, value];
  },

  selectMany: function(element) {
    var value = new Array();
    for (var i = 0; i < element.length; i++) {
      var opt = element.options[i];
      if (opt.selected) {
        var optValue = opt.value;
        if (!optValue && !('value' in opt))
          optValue = opt.text;
        value.push(optValue);
      }
    }
    return [element.name, value];
  }
}

/*--------------------------------------------------------------------------*/

var $F = Form.Element.getValue;

/*--------------------------------------------------------------------------*/

Abstract.TimedObserver = function() {}
Abstract.TimedObserver.prototype = {
  initialize: function(element, frequency, callback) {
    this.frequency = frequency;
    this.element   = $(element);
    this.callback  = callback;

    this.lastValue = this.getValue();
    this.registerCallback();
  },

  registerCallback: function() {
    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  onTimerEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  }
}

Form.Element.Observer = Class.create();
Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.Observer = Class.create();
Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  getValue: function() {
    return Form.serialize(this.element);
  }
});

/*--------------------------------------------------------------------------*/

Abstract.EventObserver = function() {}
Abstract.EventObserver.prototype = {
  initialize: function(element, callback) {
    this.element  = $(element);
    this.callback = callback;

    this.lastValue = this.getValue();
    if (this.element.tagName.toLowerCase() == 'form')
      this.registerFormCallbacks();
    else
      this.registerCallback(this.element);
  },

  onElementEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  },

  registerFormCallbacks: function() {
    var elements = Form.getElements(this.element);
    for (var i = 0; i < elements.length; i++)
      this.registerCallback(elements[i]);
  },

  registerCallback: function(element) {
    if (element.type) {
      switch (element.type.toLowerCase()) {
        case 'checkbox':
        case 'radio':
          Event.observe(element, 'click', this.onElementEvent.bind(this));
          break;
        case 'password':
        case 'text':
        case 'textarea':
        case 'select-one':
        case 'select-multiple':
          Event.observe(element, 'change', this.onElementEvent.bind(this));
          break;
      }
    }
  }
}

Form.Element.EventObserver = Class.create();
Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.EventObserver = Class.create();
Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  getValue: function() {
    return Form.serialize(this.element);
  }
});
if (!window.Event) {
  var Event = new Object();
}

Object.extend(Event, {
  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,

  element: function(event) {
    return event.target || event.srcElement;
  },

  isLeftClick: function(event) {
    return (((event.which) && (event.which == 1)) ||
            ((event.button) && (event.button == 1)));
  },

  pointerX: function(event) {
    return event.pageX || (event.clientX +
      (document.documentElement.scrollLeft || document.body.scrollLeft));
  },

  pointerY: function(event) {
    return event.pageY || (event.clientY +
      (document.documentElement.scrollTop || document.body.scrollTop));
  },

  stop: function(event) {
    if (event.preventDefault) {
      event.preventDefault();
      event.stopPropagation();
    } else {
      event.returnValue = false;
      event.cancelBubble = true;
    }
  },

  // find the first node with the given tagName, starting from the
  // node the event was triggered on; traverses the DOM upwards
  findElement: function(event, tagName) {
    var element = Event.element(event);
    while (element.parentNode && (!element.tagName ||
        (element.tagName.toUpperCase() != tagName.toUpperCase())))
      element = element.parentNode;
    return element;
  },

  observers: false,

  _observeAndCache: function(element, name, observer, useCapture) {
    if (!this.observers) this.observers = [];
    if (element.addEventListener) {
      this.observers.push([element, name, observer, useCapture]);
      element.addEventListener(name, observer, useCapture);
    } else if (element.attachEvent) {
      this.observers.push([element, name, observer, useCapture]);
      element.attachEvent('on' + name, observer);
    }
  },

  unloadCache: function() {
    if (!Event.observers) return;
    for (var i = 0; i < Event.observers.length; i++) {
      Event.stopObserving.apply(this, Event.observers[i]);
      Event.observers[i][0] = null;
    }
    Event.observers = false;
  },

  observe: function(element, name, observer, useCapture) {
    var element = $(element);
    useCapture = useCapture || false;

    if (name == 'keypress' &&
        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
        || element.attachEvent))
      name = 'keydown';

    this._observeAndCache(element, name, observer, useCapture);
  },

  stopObserving: function(element, name, observer, useCapture) {
    var element = $(element);
    useCapture = useCapture || false;

    if (name == 'keypress' &&
        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
        || element.detachEvent))
      name = 'keydown';

    if (element.removeEventListener) {
      element.removeEventListener(name, observer, useCapture);
    } else if (element.detachEvent) {
      element.detachEvent('on' + name, observer);
    }
  }
});

/* prevent memory leaks in IE */
Event.observe(window, 'unload', Event.unloadCache, false);
var Position = {
  // set to true if needed, warning: firefox performance problems
  // NOT neeeded for page scrolling, only if draggable contained in
  // scrollable elements
  includeScrollOffsets: false,

  // must be called before calling withinIncludingScrolloffset, every time the
  // page is scrolled
  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

  realOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0;
      element = element.parentNode;
    } while (element);
    return [valueL, valueT];
  },

  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
    } while (element);
    return [valueL, valueT];
  },

  positionedOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        p = Element.getStyle(element, 'position');
        if (p == 'relative' || p == 'absolute') break;
      }
    } while (element);
    return [valueL, valueT];
  },

  offsetParent: function(element) {
    if (element.offsetParent) return element.offsetParent;
    if (element == document.body) return element;

    while ((element = element.parentNode) && element != document.body)
      if (Element.getStyle(element, 'position') != 'static')
        return element;

    return document.body;
  },

  // caches x/y coordinate pair to use with overlap
  within: function(element, x, y) {
    if (this.includeScrollOffsets)
      return this.withinIncludingScrolloffsets(element, x, y);
    this.xcomp = x;
    this.ycomp = y;
    this.offset = this.cumulativeOffset(element);

    return (y >= this.offset[1] &&
            y <  this.offset[1] + element.offsetHeight &&
            x >= this.offset[0] &&
            x <  this.offset[0] + element.offsetWidth);
  },

  withinIncludingScrolloffsets: function(element, x, y) {
    var offsetcache = this.realOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = this.cumulativeOffset(element);

    return (this.ycomp >= this.offset[1] &&
            this.ycomp <  this.offset[1] + element.offsetHeight &&
            this.xcomp >= this.offset[0] &&
            this.xcomp <  this.offset[0] + element.offsetWidth);
  },

  // within must be called directly before
  overlap: function(mode, element) {
    if (!mode) return 0;
    if (mode == 'vertical')
      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
        element.offsetHeight;
    if (mode == 'horizontal')
      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
        element.offsetWidth;
  },

  clone: function(source, target) {
    source = $(source);
    target = $(target);
    target.style.position = 'absolute';
    var offsets = this.cumulativeOffset(source);
    target.style.top    = offsets[1] + 'px';
    target.style.left   = offsets[0] + 'px';
    target.style.width  = source.offsetWidth + 'px';
    target.style.height = source.offsetHeight + 'px';
  },

  page: function(forElement) {
    var valueT = 0, valueL = 0;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;

      // Safari fix
      if (element.offsetParent==document.body)
        if (Element.getStyle(element,'position')=='absolute') break;

    } while (element = element.offsetParent);

    element = forElement;
    do {
      valueT -= element.scrollTop  || 0;
      valueL -= element.scrollLeft || 0;
    } while (element = element.parentNode);

    return [valueL, valueT];
  },

  clone: function(source, target) {
    var options = Object.extend({
      setLeft:    true,
      setTop:     true,
      setWidth:   true,
      setHeight:  true,
      offsetTop:  0,
      offsetLeft: 0
    }, arguments[2] || {})

    // find page position of source
    source = $(source);
    var p = Position.page(source);

    // find coordinate system to use
    target = $(target);
    var delta = [0, 0];
    var parent = null;
    // delta [0,0] will do fine with position: fixed elements,
    // position:absolute needs offsetParent deltas
    if (Element.getStyle(target,'position') == 'absolute') {
      parent = Position.offsetParent(target);
      delta = Position.page(parent);
    }

    // correct by body offsets (fixes Safari)
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

    // set position
    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
    if(options.setHeight) target.style.height = source.offsetHeight + 'px';
  },

  absolutize: function(element) {
    element = $(element);
    if (element.style.position == 'absolute') return;
    Position.prepare();

    var offsets = Position.positionedOffset(element);
    var top     = offsets[1];
    var left    = offsets[0];
    var width   = element.clientWidth;
    var height  = element.clientHeight;

    element._originalLeft   = left - parseFloat(element.style.left  || 0);
    element._originalTop    = top  - parseFloat(element.style.top || 0);
    element._originalWidth  = element.style.width;
    element._originalHeight = element.style.height;

    element.style.position = 'absolute';
    element.style.top    = top + 'px';;
    element.style.left   = left + 'px';;
    element.style.width  = width + 'px';;
    element.style.height = height + 'px';;
  },

  relativize: function(element) {
    element = $(element);
    if (element.style.position == 'relative') return;
    Position.prepare();

    element.style.position = 'relative';
    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.height = element._originalHeight;
    element.style.width  = element._originalWidth;
  }
}

// Safari returns margins on body which is incorrect if the child is absolutely
// positioned.  For performance reasons, redefine Position.cumulativeOffset for
// KHTML/WebKit only.
if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
  Position.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == document.body)
        if (Element.getStyle(element, 'position') == 'absolute') break;

      element = element.offsetParent;
    } while (element);

    return [valueL, valueT];
  }
}
/* JSXML XML Tools - REXML Lite
http://jsxml.homestead.com/
Ver 1.2 Jun 18 2001
Copyright 2000 Peter Tracey */

function REXMLLite(XML) {
	this.XML = XML;

	this.rootElement = null;

	this.parse = _p;
	if (this.XML && this.XML != "") this.parse();
}

	function _p() {
		var rT = new RegExp("<([^>/ ]*)([^>]*)>","g");
		var rTT = new RegExp("<([^>/ ]*)([^>]*)>([^<]*)","g");
		var t = "";
		var txt = "";
		var a = "";
		var e = 0;
		var eL = null;
		if (this.XML.length == 0) return;
		var aEU = this.XML.match(rT);
		var aEUT = this.XML.match(rTT);
		for (var i=0; i<aEU.length; i++) {
			t = aEU[i].replace(rT,"$1");
			a = aEU[i].replace(rT,"$2");
			txt = aEUT[i].replace(rTT,"$3").replace(/[\r\n\t ]+/g, " ");
			if (aEU[i].substring(1,2) != "/") {
				if (e == 0) {
					eL = this.rootElement = new _XMLElement(t,a,null,txt);
					e++;
				} else if (aEU[i].substring(aEU[i].length-2,aEU[i].length-1) != "/") {
					eL = eL.childElements[eL.childElements.length] = new _XMLElement(t,a,eL,txt);
					e++;
				} else eL.childElements[eL.childElements.length] = new _XMLElement(t,a,eL,txt);
			} else {
				eL = eL.parentElement;
				e--;
				if (eL) eL.text += txt;
			}
		}
	}

	function _XMLElement(n, a, p, t) {
		this.type = "element";
		this.name = n;
		this.aStr = a;
		this.attributes = null;
		this.childElements = new Array();
		this.parentElement = p;
		this.text = t;

		this.childElement = _c;
		this.attribute = _a;
	}

		function _c(n) {
			for (var i=0; i<this.childElements.length; i++) if (this.childElements[i].name == n) return this.childElements[i];
			return null;
		}

		function _a(n) {
			if (!this.attributes) {
				var ra = new RegExp(" ([^= ]*)=","g");
				if (this.aStr.match(ra) && this.aStr.match(ra).length) {
					var as = this.aStr.match(ra);
					if (!as.length) as = null;
					else for (var j=0; j<as.length; j++) {
						as[j] = new Array(
							(as[j]+"").replace(/[= ]/g,""),
							PA(this.aStr, (as[j]+"").replace(/[= ]/g,""))
										);
					}
					this.attributes = as;
				}
			}
			if (this.attributes) for (var i=0; i<this.attributes.length; i++) if (this.attributes[i][0] == n) return this.attributes[i][1];
			return "";
		}

function PA(s,n) {
	var s = s +  ">";
	if (s.indexOf(n + "='")>-1) var a = new RegExp(".*" + n + "='([^']*)'.*>");
	else if (s.indexOf(n + '="')>-1) var a = new RegExp(".*" + n + '="([^"]*)".*>');
	return s.replace(a, "$1");
}
/*
 * Map: a dictionary data structure based on a hash table
 * $Id: map.js 1932 2004-04-20 05:04:18Z scott $
 * Copyright (C) 2001-2003 Scott Martin (http://www.coffeeblack.org/contact/)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, it is available from the Free Software
 * Foundation, Inc. at http://www.gnu.org/copyleft/lesser.html or in writing at
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
 
/*
 * Creates a new map. This method simply initializes the internal hash table
 * backing this map. The optional argument 'size' refers to the number
 * of buckets that will be used in this map's internal hash table. If
 * this argument is unspecified, not a number, or less than one, a
 * default value of 10 buckets is used.
 *
 * A map stores a series of elements. Each element is a key and its
 * corresponding value. Once an element is added using put(), the key can then
 * be used to retrieve its corresponding value using get(). Keys must be
 * defined and non-null, and a map does not allow duplicate keys. An attempt to
 * add an element when its key is already present results in the current
 * element's value being replaced by the new one. The values themselves must be
 * defined, but can be null. Multiple duplicate values are permitted.
 *
 * Internally, a map uses a hash table to store elements inserted into it.
 * This hash table uses several buckets as storage, and the number of
 * buckets can be specified at construction. Each bucket is a chain of 
 * elements, and the appropriate bucket location of any given element is
 * determined by computing a hash code based on the element's key. The hash 
 * table implementation used by this map uses chaining to resolve collisions.
 */
function Map(size)
{	
	this.buckets = new Array();

	if(isNaN(size) || size < 1)
		size = 10;

	for(var i = 0; i < size; i++)
		this.buckets[i] = new Bucket();
}

/*
 * Stores the computed hash code for later reference.
 */
Object.prototype.hashCode = -1;

/*
 * Computes a hash code for an object.
 */
Object.prototype.hash = function()
{	
	with(this)
	{	
		if(hashCode == -1)
		{	
			hashCode = 0;
			var str = toString();
	
			for(var i = 0; i < str.length; i++)
				hashCode ^= str.charCodeAt(i);
		}

		return hashCode;
	}
}

/*
 * Finds the appropriate bucket for a given object using its hash code.
 */
Map.prototype.bucketFor = function(obj)
{	
	with(this)
	{	
		return buckets[obj.hash() % buckets.length];
	}
}

/*
 * Gets the size of the map, the number of elements it contains.
 */
Map.prototype.size = function()
{	
	var sz = 0;

	with(this)
	{	
		for(var i = 0; i < buckets.length; i++)
			sz += buckets[i].depth;
	}

	return sz;
}

/*
 * Tests whether this map is currently empty (has no elements).
 */ 
Map.prototype.isEmpty = function()
{	
	with(this)
	{	
		for(var i = 0; i < buckets.length; i++)
			if(buckets[i].depth > 0)
				return false;		
	}	

	return true;	
}

/*
 * Gets an array of the keys in this map.
 */
Map.prototype.keys = function()
{	
	var a = new Array();
	
	with(this)
	{	
		var bucket, e;
		for(var i = 0; i < buckets.length; i++)
		{	
			bucket = buckets[i];
			
			for(e = bucket.first; e != null; e = e.next)
				a[a.length] = e.key;
		}
	}
		
	return a;
}

/*
 * Gets an array of the values in this map.
 */
Map.prototype.values = function()
{	
	var a = new Array();
	
	with(this)
	{	
		var bucket, e;
		for(var i = 0; i < buckets.length; i++)
		{	
			bucket = buckets[i];
			
			for(e = bucket.first; e != null; e = e.next)
				a[a.length] = e.value;
		}
	}
		
	return a;
}

/*
 * Tests whether this map contains the specified key.
 */
Map.prototype.containsKey = function(key)
{	
	if(key && key != null)
	{	
		with(this)
		{	
			var bucket = bucketFor(key);

			for(var e = bucket.first; e != null; e = e.next)
				if(e.key == key)
					return true;
		}
	}
			
	return false;
}

/*
 * Tests whether this map contains the specified value.
 */
Map.prototype.containsValue = function(value)
{	
	if(value)
	{	
		with(this)
		{	
			var bucket, e;

			for(var i = 0; i < buckets.length; i++)
			{	
				bucket = buckets[i];

				for(e = bucket.first; e != null; e = e.next)
					if(e.value == value)
						return true;
			}
		}
	}
	
	return false;
}

/*
 * Adds an element to this map. The key must be defined
 * and non-null. The value must be defined, but can be null.
 *
 * If a key is currently contained in this map that is the same
 * as the key to be added, the new element is inserted in place of
 * the old one and the old element's value is then returned. If the new
 * key is not already present, this method returns null.
 */
Map.prototype.put = function(key, value)
{	
	with(this)
	{	
		return (key && key != null && value)
			? bucketFor(key).add(key, value) : null;
	}
}

/*
 * Gets the value associated with the specified key, if any.
 * Otherwise, this method returns null.
 */
Map.prototype.get = function(key)
{	
	if(key && key != null)
	{	
		with(this)
		{	
			var bucket = bucketFor(key);

			for(var e = bucket.first; e != null; e = e.next)
				if(e.key == key)
					return e.value;
		}
	}	
	
	return null;
}

/*
 * Removes the element with the specified key from this map, if any
 * such key is present. The removed element is then returned. If no such
 * key is present in this map, this method returns null. If an element is
 * successfully removed, ths size of this map decreases by 1.
 */
Map.prototype.remove = function(key)
{	
	if(key && key != null)
	{	
		with(this)
		{	
			return bucketFor(key).remove(key);
		}		
	}
	
	return null;
}


/*
 * Clears this map. This method reduces the size of the map to 0.
 */
Map.prototype.clear = function()
{	
	with(this)
	{	
		for(var i = 0; i < buckets.length; i++)
			buckets[i].clear();
	}
}

/*
 * Gets a string representation of this map.
 */
Map.prototype.toString = function()
{	
	return "[object Map]";
}

/*
 * A hash table bucket that holds entries.
 */
function Bucket()
{	// stores the number of elements contained
	this.depth = 0;
	// the first element in the chain
	this.first = null;
}

/*
 * Adds an entry to this bucket. If an entry already existed with the same
 * key, its value is replaced and the old value is then returned.
 */
Bucket.prototype.add = function(key, value)
{	
	with(this)
	{	
		if(first != null) // look for the key
		{	
			for(var e = first; e != null; e = e.next)
			{	
				if(e.key == key) // same key already present
				{	
					var old = e.value;
					e.value = value;
					return old;
				}
			}			
		}

		// bucket empty or key not present, add a new entry
		first = new Entry(key, value, first);
		depth++;
	}

	return null;
}

/*
 * Removes an entry from this bucket. If an entry was removed,
 * its value is returned and the bucket's depth is decreased by one.
 */
Bucket.prototype.remove = function(key)
{	
	with(this)
	{	
		if(first != null)
		{	
			for(var e = first, prev = null; e != null; prev = e, e = e.next)
			{	
				if(e.key == key)
				{	
					if(prev == null) // it's the first entry
						first = e.next;
					// otherwise, link previous to next
					else prev.next = e.next;

					depth--;
					return e.value;
				}
			}
		}		
	}
	
	return null;
}

/*
 * Removes all entries from this bucket.
 */
Bucket.prototype.clear = function()
{	
	with(this)
	{	
		first = null;
		depth = 0;
	}
}

/*
 * Gets a string representation of this bucket.
 */
Bucket.prototype.toString = function()
{	
	return "[object Bucket]";
}

/*
 * A map entry. This object simply ties a key to its associated value.
 * An entry also stores a reference to the next object in the chain of
 * entries in the bucket they belong to.
 */
function Entry(k, val, nxt)
{	
	this.key = k;
	this.value = val;
	this.next = nxt ? nxt : null;
}

/*
 * Gets a string representation of this entry.
 */
Entry.prototype.toString = function()
{	
	return "[object Entry]";
}


Registry = Class.create();
Registry.prototype = {
    initialize: function() {
    },

    getImageUrlGenerator: function() {
        return this.imageUrlGenerator;
    },

    getLinkUrlGenerator: function() {
        return this.linkUrlGenerator;
    },

    getSystem: function() {
        return this.system;
    },

    getEventHandler: function() {
        return this.eventHandler;
    },

    getElementDimensions: function() {
        return this.elementDimensions;
    },

    getRenderScheduler: function() {
        return this.renderScheduler;
    },

    getPageNavigator: function() {
        return this.pageNavigator;
    },

    getElementIdGenerator: function() {
        return this.elementIdGenerator;
    },

    getDiveTime: function() {
        return this.diveTime;
    },

    getHudElementSequencerPulse: function() {
        return this.hudElementSequencerPulse;
    },

    getAjaxRequester: function() {
        return this.ajaxRequester;
    },

    getCatchNotifier: function() {
        return this.catchNotifier;
    },

    getUpdater: function() {
        return this.updater;
    }
};
LiveRegistry = Class.create();
LiveRegistry.prototype = Object.extend(Object.extend({}, Registry.prototype), {
    initialize: function(refServerTime, encodedBackUrl) {
        this.imageUrlGenerator = new ImageUrlGenerator("Images");
        this.linkUrlGenerator = new LinkUrlGenerator(encodedBackUrl);
        this.system = new System();
        this.eventHandler = new EventHandler();
        this.elementDimensions = new ElementDimensions();
        this.renderScheduler = new RenderScheduler(this.getSystem());
        this.pageNavigator = new PageNavigator();
        this.elementIdGenerator = new ElementIdGenerator();
        this.diveTime = new DiveTime(refServerTime);
        this.hudElementSequencerPulse = new HudElementSequencerPulse(this.system);
        this.ajaxRequester = new AjaxRequester();
        this.catchNotifier = new CatchNotifier();
        this.updater = new Updater(this.getSystem());
    }
});

function ImageUrlGenerator(urlPrefix) {
    this.urlPrefix = urlPrefix;
}

ImageUrlGenerator.prototype.generateUrl = function(imageName) {
    var url;
    if (IE){
        url = this.urlPrefix+"/"+imageName;
    } else {
        url = "url("+this.urlPrefix+"/"+imageName+")";
    }
    return url;
}
LinkUrlGenerator = Class.create();
LinkUrlGenerator.prototype = {

    initialize: function(encodedBackUrl) {
        this.encodedBackUrl = encodedBackUrl;
    },

    getErrorPage: function(message) {
      return "Error.zd?errorMessageString=" + message;
    },

    getViewPage: function(listingId) {
      return "View.zd?listingId=" + listingId;
    },

    getAffiliateViewPage: function(affiliateId, listingId) {
      return "NoOp.zd?affiliateId="+affiliateId+
             "&affiliateUrl="+
             "View.zd%3FlistingId=" + listingId + "%26showHowItWorks=true"
    },

    getImageUrl: function(imageId) {
      return "PictureStream.zd?pictureDataId="+imageId;
    },

    getProcessCatchPage: function() {
      return "ProcessCatch.zd";
    },

    appendBackUrl: function(url) {
        var generatedUrl = url;
        if (this.encodedBackUrl != null) {
            generatedUrl += "&encodedBackUrl="+this.encodedBackUrl;
        }
        return generatedUrl;
    },

    appendCatchPrice: function(url, catchPrice) {
        var generatedUrl = url+"&validatedCatch.price="+catchPrice;
        return generatedUrl;
    }
};
//System
function System() {
}

System.prototype.setTimeout = function(expression, duration) {
	setTimeout(expression, duration);
}

System.prototype.getCurrentTime = function() {
	return new Date();
}
EventHandler = Class.create();
EventHandler.prototype = {
    initialize: function() {

    },

    addEvent: function(element, eventStr, func) {
        Event.observe(element, eventStr, func, false);
    },

    removeEvent: function(element, eventStr, func) {
        Event.stopObserving(element, eventStr, func, false);
    },

    element: function(event) {
        return Event.element(event);
    }
};

ElementDimensions = Class.create();
ElementDimensions.prototype = {
    initialize: function() {
    },

    getTop: function(anElement) {
        var result = 0;
        while (anElement != null) {
            result += anElement.offsetTop;
            anElement = anElement.offsetParent;
        }
        return result;
    },

    getLeft: function(anElement) {
        var result = 0;
        while (anElement != null) {
            result += anElement.offsetLeft;
            anElement = anElement.offsetParent;
        }
        return result;
    },

    getHeight: function(anElement) {
        return Element.getDimensions(anElement)['height'];
    },

    getWidth: function(anElement) {
        return Element.getDimensions(anElement)['width'];
    }
};
RenderScheduler = Class.create();
RenderScheduler.prototype = {
    initialize: function(system) {
        this.system = system;
        this.renderables = new Array();
        this.renderLoop();
    },

    registerRenderable: function(renderable, groupName) {
        var count = this.renderables.length;

        this.renderables[count] = {
            object: renderable,
            needsUpdate: false,
            groupName: groupName};
        return count;
	},


    requestRender: function(renderableId) {
        this.renderables[renderableId].needsUpdate = true;
    },

    requestRenderGroup: function(groupName) {
        for (var i=0; i<this.renderables.length; i++) {
            if (this.renderables[i].groupName == groupName) {
                this.renderables[i].needsUpdate = true;
            }
        }
    },

    renderLoop: function() {
        for (var i=0; i<this.renderables.length; i++) {
            if (this.renderables[i].needsUpdate) {
                if (this.renderables[i].object.prepareForRendering) {
                    this.renderables[i].object.prepareForRendering();
                }
            }
        }

        for (var i=0; i<this.renderables.length; i++) {
            if (this.renderables[i].needsUpdate) {
                this.renderables[i].needsUpdate = false;
                this.renderables[i].object.render();
            }
        }
        this.system.setTimeout(this.renderLoop.bind(this), 1);
    }
};

PageNavigator = Class.create();
PageNavigator.prototype = {
    initialize: function() {
    },

    goToPage: function(url) {
        window.top.location = url;
    }
};

ElementIdGenerator = Class.create();
ElementIdGenerator.prototype = {
    initialize: function() {
        this.elementId = 0;
    },

    generateId: function() {
        var id = "id_"+this.elementId;
        this.elementId++;
        return id; 
    }
};
DiveTime = Class.create();
DiveTime.prototype = {
    initialize: function(refServerTime) {
        this.setTime(refServerTime);
    },

    getTime: function() {
        var nowOnClient = new Date();
        nowOnClient.setTime(nowOnClient.getTime() + this.serverClientDeltaMillis);
        return nowOnClient;
    },

    setTime: function(refServerTime) {
        var refClientTime = new Date();

        this.serverClientDeltaMillis =
            refServerTime - refClientTime.getTime();
    }
 };


var _sequencerRefreshInterval = 50;

function HudElementSequencerPulse(system) {
    this.dynamicElementRefreshTag = new DynamicElementRefreshTag(_sequencerRefreshInterval, 100);
    this.system = system;
    this.setTimeout();
}

HudElementSequencerPulse.prototype.startNewHudElement = function(element, pulseType) {
    this.dynamicElementRefreshTag.animateElement(element, pulseType);
}

HudElementSequencerPulse.prototype.update = function() {
    this.dynamicElementRefreshTag.update();
    this.setTimeout();
}

HudElementSequencerPulse.prototype.setTimeout = function() {
    this.system.setTimeout(this.update.bind(this), _sequencerRefreshInterval);
}
var HUD_ELEMENT_SEQUENCE_PULSE_TYPE_POSITIVE = 0;
var HUD_ELEMENT_SEQUENCE_PULSE_TYPE_NEGATIVE = 1;
AjaxRequester = Class.create();
AjaxRequester.prototype = {
    initialize: function() {
    },

    makeRequest: function(url, options) {
        var request = new Ajax.Request(url,options);
        return request;
    }
}
CatchNotifier = Class.create();
CatchNotifier.prototype = {
    initialize: function() {
        this.observers = new Array();
    },

    registerObserver: function(observer) {
        var count = this.observers.length;
        this.observers[count] = observer;
    },

    notify: function(price) {
        for (var i = 0; i < this.observers.length; i++){
            this.observers[i].setCatch(price);
        }
    }
};

Updater = Class.create();
Updater.prototype = {
    initialize: function(system) {
        this.updateables = new Array();
        this.system = system;
    },

    addUpdateable: function(updateable) {
        var count = this.updateables.length;
        this.updateables[count] = updateable;
    },

    startUpdateables: function() {
        this.periodicUpdate();
    },

    updateUpdateables: function() {
        for (var i = 0; i < this.updateables.length; i++) {
            this.updateables[i].update();
        }
    },

    periodicUpdate: function() {
        this.updateUpdateables();
        this.system.setTimeout(this.periodicUpdate.bind(this), 1000);
    }
};
var _fadeDuration = 1000;
var _dynamicElementRefreshTagColors = [
            //HUD_ELEMENT_SEQUENCE_PULSE_TYPE_POSITIVE
            [ [255,245,236],[200,244,255] ],
            //HUD_ELEMENT_SEQUENCE_PULSE_TYPE_NEGATIVE
            [ [255,255,255],[255,140,140] ]
];

function DynamicElementRefreshTag(refreshInterval, poolSize) {
    this.refreshInterval = refreshInterval;
    this.entries = new Array();
    this.poolSize = poolSize;
    this.fadeCounter = _fadeDuration / refreshInterval;
    this.initPool();
}

DynamicElementRefreshTag.prototype.initPool = function() {

    for (var i = 0; i < this.poolSize; i++) {
        var entry = new DynamicElementRefreshTagEntry();
        this.entries[i] = entry;
    }
}

DynamicElementRefreshTag.prototype.animateElement = function(element, pulseType) {
    var pulseData = _dynamicElementRefreshTagColors[pulseType];

    for (var i = 0; i < this.poolSize; i++) {
        var entry = this.entries[i];
        if (!entry.getUsed()) {
            entry.setUsed(true);
            entry.setElement(element);
            entry.setCounter(0);
            entry.setColorOne(pulseData[0][0],
                    pulseData[0][1],
                    pulseData[0][2]);
            entry.setColorTwo(pulseData[1][0],
                    pulseData[1][1],
                    pulseData[1][2]);
            return entry;
        }
    }
    return null;
}

DynamicElementRefreshTag.prototype.update = function() {

    for (var i = 0; i < this.poolSize; i++) {
        var entry = this.entries[i];
        if (entry.getUsed()) {
            var counter = entry.getCounter();
            if (counter > this.fadeCounter) {
                entry.getElement().style.backgroundColor = "";
                entry.setUsed(false);
            }
            else {
                var frame = counter / this.fadeCounter * Math.PI;
                var ratio = Math.abs(Math.sin(frame));

                var red = Math.round((entry.redTwo - entry.redOne) * ratio + entry.redOne);
                var green = Math.round((entry.greenTwo - entry.greenOne) * ratio + entry.greenOne);
                var blue = Math.round((entry.blueTwo - entry.blueOne) * ratio + entry.blueOne);

                entry.getElement().style.backgroundColor = "rgb(" + red + "," + green + "," + blue + ")";
                entry.setCounter(counter + 1);
            }
        }
    }
}

function DynamicElementRefreshTagEntry() {
    this.element = null;
    this.counter = 0;
    this.used = false;
    this.redOne = 0;
    this.greenOne = 0;
    this.blueOne = 0;
    this.redTwo = 0;
    this.greenTwo = 0;
    this.blueTwo = 0;
}

DynamicElementRefreshTagEntry.prototype.setElement = function(element) {
    this.element = element;
}

DynamicElementRefreshTagEntry.prototype.getElement = function() {
    return this.element;
}

DynamicElementRefreshTagEntry.prototype.setUsed = function(used) {
    this.used = used;
}

DynamicElementRefreshTagEntry.prototype.getUsed = function() {
    return this.used;
}

DynamicElementRefreshTagEntry.prototype.setCounter = function(counter) {
    this.counter = counter;
}

DynamicElementRefreshTagEntry.prototype.getCounter = function() {
    return this.counter;
}

DynamicElementRefreshTagEntry.prototype.setColorOne = function(red, green, blue) {
    this.redOne = red;
    this.greenOne = green;
    this.blueOne = blue;
}

DynamicElementRefreshTagEntry.prototype.setColorTwo = function(red, green, blue) {
    this.redTwo = red;
    this.greenTwo = green;
    this.blueTwo = blue;
}

            











function clampNormalized(value) {
	if (value < 0) {
		value = 0;
	}
	if (value > 1) {
		value = 1;
	}
	return value;
}
Session = Class.create();
Session.prototype = {

    SESSION_DURATION: 14400000,

    initialize: function(registry) {
        this.registry = registry;
    },

    start: function() {
        this.registry.getSystem().setTimeout(this.callback.bind(this), this.SESSION_DURATION);
    },

    callback: function() {
        var url = this.registry.getLinkUrlGenerator().getErrorPage("EXPIRED");
        this.registry.getPageNavigator().goToPage(url);
    }

 };

var IE=0;
if(document.all) {
 IE=1
}
 
function hide(divId) {

	var div = document.getElementById(divId);
	div.style.display = "none";
}
function show(divId) {

	var div = document.getElementById(divId);
	div.style.display = "block";
}
//User Notification
function UserNotification(system, elementId, timeLeft) {
	this.system = system;
	this.elementId = elementId;
	this.timeLeft = timeLeft;
}

UserNotification.prototype.start = function() {
	if (this.timeLeft > 0) {
		show(this.elementId);
		this.system.setTimeout(this.stop.bind(this), this.timeLeft);
	}
}

UserNotification.prototype.stop = function() {
	hide(this.elementId);
}
function disableForm(theForm) {
	if (document.all || document.getElementById) {
		for (i = 0; i < theForm.length; i++) {
			var tempObj = theForm.elements[i];
			if (tempObj.type.toLowerCase() == "submit" || tempObj.type.toLowerCase() == "reset") {
				tempObj.disabled = true;
			}
		}
	}	
}
var SEC_MILLIS = 1000;
var MIN_MILLIS = 60 * SEC_MILLIS;
var HOUR_MILLIS = (60 * MIN_MILLIS);
var DAYS_MILLIS = (24 * HOUR_MILLIS);

function formatTimeRaw(timeLeft) {
    var timeLeftMillis = timeLeft.getTime();

    var daysLeft = Math.floor(timeLeftMillis / DAYS_MILLIS);
    timeLeftMillis = timeLeftMillis % DAYS_MILLIS;

    var hoursLeft = Math.floor(timeLeftMillis / HOUR_MILLIS);
    timeLeftMillis = timeLeftMillis % HOUR_MILLIS;

    var minutesLeft = Math.floor(timeLeftMillis / MIN_MILLIS);
    timeLeftMillis = timeLeftMillis % MIN_MILLIS;

    var secondsLeft = Math.floor(timeLeftMillis / SEC_MILLIS);

    var timeLeftFormatted = "";
    if (daysLeft > 0) {
        timeLeftFormatted += daysLeft + "d ";
        timeLeftFormatted += hoursLeft + "h ";
        timeLeftFormatted += minutesLeft + "m ";
    }
    else {
        if (hoursLeft > 0) {
            timeLeftFormatted += hoursLeft + "h ";
            timeLeftFormatted += minutesLeft + "m ";
            timeLeftFormatted += secondsLeft + "s ";
        }
        else {
            if (minutesLeft > 0) {
                timeLeftFormatted += minutesLeft + "m ";
                timeLeftFormatted += secondsLeft + "s ";
            }
            else {
                timeLeftFormatted += secondsLeft + "s ";
            }
        }
    }

    return timeLeftFormatted;
}

function formatTimeLeft(timeLeft) {

    var cssClass = "DynamicTimeLeftRegular";

    var timeLeftFormatted = formatTimeRaw(timeLeft);

    if (timeLeft.getTime() <= MIN_MILLIS * 10) {
        cssClass = "DynamicTimeLeftEmphased";
    }

    timeLeftFormatted = "<span class=\"" + cssClass + "\">" + timeLeftFormatted + "</span>";

    return timeLeftFormatted;
}



var DIVE_PRICE_CSS_CLASS = new Array(
	"ListingPriceColorActive",
	"ListingPriceColorInactiveSold",
	"ListingPriceColorInactiveNotSold",
	"ListingPriceColorNotPublished");

var CURRENCY_SYMBOL = "\u00A3";

function formatPriceWithType(aPrice, aPriceType) {

    var content =
		"<span class=\"" + DIVE_PRICE_CSS_CLASS[aPriceType] + "\">" +
            ((aPriceType == LISTING_STATE_ACTIVE) ?   
                formatPrice(aPrice) :
                formatPriceNoSubcent(aPrice)) +
		"</span>";

    return content;
}

function formatRawPriceNoSubcent(aPrice) {
  return formatRawPrice(aPrice, 2);
}

function formatRawPrice(aPrice, precision) {

    var val = aPrice + "";
    var indexOfDot = val.indexOf(".");

    if (indexOfDot < 0) {
        val = val + ".";
        indexOfDot = val.length - 1;
    }

    var val = val + "00000";
    var index = Math.min(indexOfDot + (precision + 1), val.length);

    return result = val.substring(0, index);
}

function formatPriceNoSubcent(aPrice) {
  return CURRENCY_SYMBOL + formatRawPriceNoSubcent(aPrice);
}

function formatPriceChopZeroes(aPrice) {

  var rawPrice = formatRawPrice(aPrice, 4);
  var length = rawPrice.length;
  if (rawPrice.substring(length - 5, length) == ".0000") {
      return CURRENCY_SYMBOL + rawPrice.substring(0, length - 5);
  }

  if (rawPrice.substring(length - 2, length) == "00") {
      return formatPriceNoSubcent(aPrice);
  }

  return formatPrice(aPrice);
}

function formatPrice(aPrice) {

  var rawPrice = "" + formatRawPrice(aPrice, 4);

  var decimalIndex = rawPrice.indexOf(".");

  return "<nobr>" + CURRENCY_SYMBOL + rawPrice.substring(0, decimalIndex + 3) +
         "<font size=\"-2\">" +
         rawPrice.substring(decimalIndex + 3, decimalIndex + 5) +
         "</font></nobr>";
}
var DIVE_TIME_LEFT_CSS_CLASS = new Array(
        "DiveTimeLeftActive",
        "DiveTimeLeftInactiveSold",
        "DiveTimeLeftInactiveNotSold",
        "DiveTimeLeftNotPublished");

DiveTimeDisplay = Class.create();
DiveTimeDisplay.prototype = {

    UNSET_VALUE: -1,

    initialize: function(registry, listingTransferObject, elementName, longDesc, showEndTime) {
        this.element = $(elementName);
        this.listingTransferObject = listingTransferObject;
        this.longDesc = longDesc;
        this.showEndTime = showEndTime;
        this.timeLeft = this.UNSET_VALUE;
        this.catchCount = this.UNSET_VALUE;
        this.listingState = this.UNSET_VALUE;
        this.currentListingId = this.listingTransferObject.listingId;
        this.hudElementSequencerPulse = registry.getHudElementSequencerPulse();
    },

    setTimeLeft: function(timeLeft, listingState) {

        if (this.timeChanged(timeLeft) &&
            this.catchCountChanged() &&
            !listingBecomesActive(this.listingState, listingState) &&
            this.currentListingId == this.listingTransferObject.listingId) {
            this.hudElementSequencerPulse.startNewHudElement(this.element,
                    HUD_ELEMENT_SEQUENCE_PULSE_TYPE_POSITIVE);
        }

        this.timeLeft = timeLeft;
        this.catchCount = this.listingTransferObject.catchCount;
        this.listingState = listingState;
        this.currentListingId = this.listingTransferObject.listingId;
        this.generateHTML(listingState);
    },

    generateHTML: function(listingState) {

        var innerHTML = "<span class=\"" + DIVE_TIME_LEFT_CSS_CLASS[listingState] + "\">";

        if (listingState == LISTING_STATE_NOT_PUBLISHED) {
            if (this.longDesc) {
                innerHTML += "Starts In ";
                innerHTML += "<b>" + formatTimeLeft(this.timeLeft) + "</b>";
            }
            else {
                innerHTML += "Not Started";
                innerHTML +=
                "<br>" +
                "<span class=\"DiveTimeLeftNotPublishedSmallText\">" +
                formatTimeLeft(this.timeLeft)+
                "</span>";
            }
        }
        else {
            if (listingState == LISTING_STATE_ACTIVE) {
                if (this.longDesc) {
                    innerHTML +=
                    "Ends In ";
                }
                innerHTML += "<b>" + formatTimeLeft(this.timeLeft) + "</b>";
            }
            else {

                if (this.longDesc) {
                    innerHTML += "Ended";
                    if (this.showEndTime) {
                        innerHTML +=
                        " <span class=\"DiveTimeLeftEndTime\">" +
                        this.listingTransferObject.formattedEndTime +
                        "</span>";
                    }
                }
                else {
                    innerHTML += "Ended";
                    if (this.showEndTime) {
                        innerHTML +=
                        "<br>" +
                        "<span class=\"DiveTimeLeftEndTime\">" +
                        this.listingTransferObject.formattedEndTime +
                        "</span>";
                    }
                }
            }
        }

        innerHTML += "</span>";
        this.element.innerHTML = innerHTML;
    },

    timeChanged: function(timeLeft) {
        var changed =
                this.timeLeft != this.UNSET_VALUE &&
                Math.abs(timeLeft - this.timeLeft) > 10000;
        return changed;
    },

    catchCountChanged: function() {
        var changed = this.catchCount != this.UNSET_VALUE &&
            this.catchCount != this.listingTransferObject.catchCount;
        return changed;
    }
};

function DiveTimeUpdater(registry, listingTransferObject, elementName, longDesc, showEndTime) {
	this.listingTransferObject = listingTransferObject;
	this.stateCalculator = new ListingStateCalculator(listingTransferObject);
	this.diveTimeDisplay = new DiveTimeDisplay(registry, listingTransferObject, elementName, longDesc, showEndTime);
    this.registry = registry;
}

DiveTimeUpdater.prototype.update = function() {
	this.diveTimeDisplay.setTimeLeft(
		new Date(this.stateCalculator.timeLeftAtDate(this.registry.getDiveTime().getTime())),
		this.stateCalculator.stateAtDate(this.registry.getDiveTime().getTime()));
}
function DivePriceDisplay(elementName) {
	this.element = $(elementName);
}

DivePriceDisplay.prototype.setPrice = function(price, listingState) {
	this.price = price;
	this.generateHTML(listingState);
}

DivePriceDisplay.prototype.generateHTML = function(listingState) {

    var innerHTML;

    if (listingState == LISTING_STATE_NOT_PUBLISHED) {
        innerHTML =
            "<span class=\"" + DIVE_PRICE_CSS_CLASS[LISTING_STATE_NOT_PUBLISHED] + "\">" +
            "???" +
            "</span>";

	}
	else {
        innerHTML = formatPriceWithType(this.price, listingState);
	}

    this.element.innerHTML = innerHTML;
}
function DivePriceUpdater(registry, listingTransferObject, elementName) {
	this.stateCalculator = new ListingStateCalculator(listingTransferObject);
	this.divePriceDisplay = new DivePriceDisplay(elementName);
    this.registry = registry;
}

DivePriceUpdater.prototype.update = function() {

	this.divePriceDisplay.setPrice(
		this.stateCalculator.priceAtDate(this.registry.getDiveTime().getTime()),
		this.stateCalculator.stateAtDate(this.registry.getDiveTime().getTime()));
}
//Catching
function handlePartialCatch(divId, quantityFieldId) {
	if (document.getElementById(quantityFieldId).value > 1) {
		show(divId);
	}
	else {
		hide(divId);
	}
}

function handleEmptyPrice(catchAtCurrentPriceFieldId, priceFieldId) {

	if (document.getElementById(priceFieldId).value=="") {
		document.getElementById(catchAtCurrentPriceFieldId).value="true";
	}
}

function cancelEvent() {
	if( IE &&window.event) { 
		window.event.cancelBubble=true;
		window.event.returnValue=false;
	}
	return false;
}
//Utility
var helpPopupWindow=0;
function helpPopup(name, width, height, resize) {
	var resizeText = 'no';
	if (resize==true) {
		var resizeText = 'yes';
	}

	if(helpPopupWindow) {
    	if(!helpPopupWindow.closed) helpPopupWindow.close();
	}
	helpPopupWindow = open(name, 'name', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=' + resizeText + ',resizable=' + resizeText + ',copyhistory=yes,width='+width+',height='+height+'');
	helpPopupWindow.focus();
	return cancelEvent();
}
function checkAll(field) {
	for (i = 0; i < field.length; i++) {
		if (!field[i].disabled) {
			field[i].checked = true ;
		}
	}
}

function uncheckAll(field) {
	for (i = 0; i < field.length; i++) {
		if (!field[i].disabled) {
			field[i].checked = false ;
		}
	}
}

function anyElementsSelected(field) {
	for (i = 0; i < field.length; i++) {
		if (field[i].checked) {
			return true;
		}
	}
    
    return false;
}

function setWatchOperation(operation) {
	var formElement = document.getElementById('Watch_operation');
	formElement.value = operation;
}

function setWatch(watchId, operation) {
	var formElement = document.getElementById('Watch_watchId');
	formElement.name = 'Watch_watchId';
	formElement.value = watchId;
	setWatchOperation(operation);
}

function setListingOperation(operation) {
	var formElement = document.getElementById('Listing_operation');
	formElement.value = operation;
}

function setListing(listingId, operation) {
	var formElement = document.getElementById('Listing_listingId');
	formElement.name = 'Listing_listingId';
	formElement.value = listingId;
	setListingOperation(operation);
}

function setCatchOperation(operation) {
	var formElement = document.getElementById('Catch_operation');
	formElement.value = operation;
}

function setCatch(catchId, operation) {
	var formElement = document.getElementById('Catch_catchId');
	formElement.name = 'Catch_catchId';
	formElement.value = catchId;
	setCatchOperation(operation);
}

function trim(value) {
   var temp = value;
   var obj = /^(\s*)([\W\w]*)(\b\s*$)/;
   if (obj.test(temp)) { temp = temp.replace(obj, '$2'); }
   var obj = / +/g;
   temp = temp.replace(obj, " ");
   if (temp == " ") { temp = ""; }
   return temp;
}
//Listing Pictures

function setMainPicture(pictureIndex) {

	document.getElementById('Main').src = 
		document.getElementById('Image_'+pictureIndex).src;

	document.getElementById('PictureNumber').innerHTML = currentPicture;
}

function setNextPicture() {
	currentPicture = currentPicture + 1;
	if (currentPicture > numberOfPictures) {
		currentPicture = 1;
	}
	setMainPicture(currentPicture);
	return cancelEvent();
}

function setPreviousPicture() {
	currentPicture = currentPicture - 1;
	if (currentPicture < 1) {
		currentPicture = numberOfPictures;
	}
	setMainPicture(currentPicture);
	return cancelEvent();
}

function setFirstPicture() {
	currentPicture = currentPicture = 1;
	setMainPicture(currentPicture);
	return cancelEvent();
}

function setLastPicture() {
	currentPicture = currentPicture = numberOfPictures;
	setMainPicture(currentPicture);
	return cancelEvent();
}

//Search Options
function searchOptionsUsernameCheck(element) {
	if (!element.checked) {
		document.getElementById('SearchOptionsUsername').value = '';
	}
}

function searchOptionsKeywordsCheck(element) {
	if (!element.checked) {
		document.getElementById('SearchOptionsKeywords').value = '';
	}
}

function searchOptionsUsername(element) {
	if (trim(element.value) != "") {
		document.getElementById('SearchOptionsUsernameCheck').checked = true;
	}
	else {
		document.getElementById('SearchOptionsUsernameCheck').checked = false;
	}
}

function searchOptionsKeywords(element) {
	if (trim(element.value) != "") {
		document.getElementById('SearchOptionsKeywordsCheck').checked = true;
	}
	else {
		document.getElementById('SearchOptionsKeywordsCheck').checked = false;
	}
}

function clearSearchOptions() {
    document.getElementById('SearchOptionsProviderUserCheckHidden').value = false;
	document.getElementById('SearchOptionsKeywordsCheck').checked = false;
	document.getElementById('SearchOptionsUsernameCheck').checked = false;
	document.getElementById('model.state.validatedSearch.diveRestrictionPosition0').checked = true;
	document.getElementById('SearchOptionsUsername').value = '';
	document.getElementById('SearchOptionsKeywords').value = '';
}
ListingSynchronizer = Class.create();
ListingSynchronizer.prototype = {
    initialize: function(registry, listingDataFlagsValue, endingListingStrategy) {
        this.registry = registry;
        this.transferObjects = new Map();
        this.listingDataFlagsValue = listingDataFlagsValue;
        this.endingListingStrategy = endingListingStrategy;
    },

    addListingTransferObject: function(transferObject) {
        this.transferObjects.put("" + transferObject.listingId, transferObject);
    },

    getListingTransferObjectForId: function(listingId) {
        return (this.transferObjects.get("" + listingId));
    },

    listingTransferObjectForIdExists: function(listingId) {
        return (this.getListingTransferObjectForId(listingId) != null);
    },

    getXMLTagName: function() {    
        return "listing";
    },

    getUrlParams: function() {
        var urlParams = this.endingListingStrategy.generateUrlParams(this.transferObjects);

        urlParams.addParameter("listingDataFlags", this.listingDataFlagsValue);

        return urlParams;
    },

    handleListingElement: function(element) {
        var key = "" + stringToNumber(element.childElements[0].text);
        var transferObject = this.endingListingStrategy.locateListingForKey(key, this.transferObjects);
        var index = 0;

        var converter = new XMLToListingTransferObject();
        converter.convert(element, transferObject);

        this.removeListingElementIfEnded(transferObject);
    },

    removeListingElementIfEnded: function(transferObject) {
        if (transferObject.effectiveEndDate.getTime() < this.registry.getDiveTime().getTime().getTime()) {
            this.endingListingStrategy.handleEndedListing("" + transferObject.listingId, this.transferObjects);
        }
    },

    synchronizeState: function(xmlElement) {
        this.handleListingElement(xmlElement);
    },

    synchronizeNeeded: function() {
        return (!this.transferObjects.isEmpty());
    }
};

function ListingStateCalculator(listingTransferObject) {
  this.listingTransferObject = listingTransferObject;
}

ListingStateCalculator.prototype.getEndPrice = function(index) {
    var endingPrice;
    if (this.listingTransferObject.catchCount == 0) {
        endingPrice = this.listingTransferObject.absoluteMinPrice;
    } else {
        endingPrice = this.listingTransferObject.effectiveMinPrice;
    }
    return endingPrice;
}

ListingStateCalculator.prototype.getStartPrice = function(index) {
	return this.listingTransferObject.effectiveMaxPrice;
}

ListingStateCalculator.prototype.priceAtDate = function(aDate) {
    var fraction = this.effectiveFractionOfDiveAtDate(aDate);
    var value = this.convertDiveFractionToPrice(fraction, this.listingTransferObject.effectiveMaxPrice, this.listingTransferObject.effectiveMinPrice);
    return value;
}

ListingStateCalculator.prototype.absolutePriceAtDate = function(aDate) {
    var fraction = this.absoluteFractionOfDiveAtDate(aDate);
    var value = this.convertDiveFractionToPrice(fraction, this.listingTransferObject.absoluteMaxPrice, this.listingTransferObject.absoluteMinPrice);
    return value;
}

ListingStateCalculator.prototype.convertDiveFractionToPrice = function(fraction, maxPrice, minPrice) {
    fraction = clampNormalized(fraction);
    var delta = fraction * (maxPrice - minPrice);
    return maxPrice-delta;
}

ListingStateCalculator.prototype.timeBeforePublish = function(aDate) {
	return (this.listingTransferObject.startDate.getTime() - aDate.getTime());
}

ListingStateCalculator.prototype.timeLeftAtDate = function(aDate) {

	var timeLeftBeforePublish = this.timeBeforePublish(aDate);
	if (timeLeftBeforePublish > 0) {
		return timeLeftBeforePublish;
	}
	else {
        var timeLeft = this.listingTransferObject.effectiveEndDate.getTime() - aDate.getTime();
        return (timeLeft < 0) ? 0 : timeLeft;
	}
}

ListingStateCalculator.prototype.stateAtDate = function(aDate) {

  var fraction = this.effectiveFractionOfDiveAtDate(aDate);
  if (fraction < 0) {
	return LISTING_STATE_NOT_PUBLISHED;
  }
  else {
	  if (fraction < 1) {
	  	return LISTING_STATE_ACTIVE;
	  }
	  else {
	  	if (this.listingTransferObject.soldWhenEnds) {
	 		return LISTING_STATE_INACTIVE_SOLD;
	  	}
	  	else {
	 		return LISTING_STATE_INACTIVE_NOT_SOLD;
		}
	  }
  }
}

ListingStateCalculator.prototype.fractionOfDiveAtDate = function(aDate, endDate) {
  var millis = aDate.getTime() - this.listingTransferObject.startDate.getTime();
  var totalMillis = endDate.getTime() - this.listingTransferObject.startDate.getTime();
  var fraction = millis/totalMillis;
  if (fraction>1)
    fraction = 1;
  return fraction;
}

ListingStateCalculator.prototype.effectiveFractionOfDiveAtDate = function(aDate) {
  var val = this.fractionOfDiveAtDate(aDate, this.listingTransferObject.effectiveEndDate);
  return val;
}

ListingStateCalculator.prototype.absoluteFractionOfDiveAtDate = function(aDate) {
  var val = this.fractionOfDiveAtDate(aDate, this.listingTransferObject.absoluteEndDate);
  return val;
}
var LISTING_STATE_ACTIVE = 0;
var LISTING_STATE_INACTIVE_SOLD = 1;
var LISTING_STATE_INACTIVE_NOT_SOLD = 2;
var LISTING_STATE_NOT_PUBLISHED = 3;

function listingEnded(listingState) {
    return (listingState == LISTING_STATE_INACTIVE_SOLD ||
            listingState == LISTING_STATE_INACTIVE_NOT_SOLD);
}

function listingBecomesActive(oldState, newState) {
    return (oldState == LISTING_STATE_NOT_PUBLISHED &&
            newState == LISTING_STATE_ACTIVE);
}





ListingTransferObject = Class.create();

ListingTransferObject.QUANTITY_LEFT_VALID_FLAG = 0x1;
ListingTransferObject.TITLE_VALID_FLAG = 0x2;
ListingTransferObject.IMAGE_LEFT_VALID_FLAG = 0x4;

ListingTransferObject.prototype = {
    initialize: function (listingId,
						startDate,
						absoluteEndDate,
						effectiveEndDate,
                        absoluteMaxPrice,
                        absoluteMinPrice,
                        effectiveMaxPrice,
                        effectiveMinPrice,
                        soldWhenEnds,
                        formattedEndTime,
                        catchCount,
                        isMultiQuantity,
                        formattedCatchCount,
                        formattedQuantityLeft,
                        formattedPublishTime,
                        formattedMaxPrice,
                        formattedWatchCount,
                        topCatchPrice,
                        formattedTopCatchUserName,
                        topCatchIsFromLoggedInUser,
                        minValidCatchPrice,
                        proposedCatchPrice,
                        formattedTitle,
                        imageId,
                        listingRefiller) {
      this.listingId = listingId;
      this.startDate = new Date(startDate);
      this.absoluteEndDate = new Date(absoluteEndDate);
      this.effectiveEndDate = new Date(effectiveEndDate);
      this.absoluteMaxPrice = absoluteMaxPrice;
      this.absoluteMinPrice = absoluteMinPrice;
      this.effectiveMaxPrice = effectiveMaxPrice;
      this.effectiveMinPrice = effectiveMinPrice;
      this.soldWhenEnds = soldWhenEnds;
      this.formattedEndTime = formattedEndTime;
      this.catchCount = catchCount;
      this.isMultiQuantity = isMultiQuantity;
      this.formattedCatchCount = formattedCatchCount;
      this.formattedQuantityLeft = formattedQuantityLeft;
      this.formattedPublishTime = formattedPublishTime;
      this.formattedMaxPrice = formattedMaxPrice;
      this.formattedWatchCount = formattedWatchCount;
      this.topCatchPrice = topCatchPrice;
      this.formattedTopCatchUserName = formattedTopCatchUserName;
      this.topCatchIsFromLoggedInUser = topCatchIsFromLoggedInUser;
      this.minValidCatchPrice = minValidCatchPrice;
      this.proposedCatchPrice = proposedCatchPrice;
      this.formattedTitle = formattedTitle;
      this.imageId = imageId;

      this.listingRefiller = listingRefiller;
      }
};
function stringToNumber(text) {
   var number = text - 0;
   return number;
}

function stringToBoolean(text) {
   var boolVar = (text == "true") ? true : false;
   return boolVar;
}
PeriodicSynchronizer = Class.create();
PeriodicSynchronizer.prototype = {
    initialize: function(registry) {
        this.system = registry.getSystem();
        this.ajaxRequester = registry.getAjaxRequester();
        this.synchronizables = new Map();
    },

    start: function() {
        this.synchronize();
    },

    registerSynchronizable: function(synchronizable) {
        this.synchronizables.put(synchronizable.getXMLTagName(), synchronizable);
    },

    callback: function(request, responseHeader) {
        var xmlDoc = new REXMLLite(request.responseText);

        for (var i = 0; i < xmlDoc.rootElement.childElements.length; i++) {
            var synchronizable = this.synchronizables.get(xmlDoc.rootElement.childElements[i].name);
            if (synchronizable) {
                synchronizable.synchronizeState(xmlDoc.rootElement.childElements[i]);
            }
        }
    },

    assembleUrlParamsForSynchronizableValues: function(params) {
        var synchronizableValues = this.synchronizables.values();
        for (var index = 0; index < synchronizableValues.length; index++) {
            params.merge(synchronizableValues[index].getUrlParams());
        }
    },

    assembleUrlParams: function() {
        var params = new UrlParams();
        this.assembleUrlParamsForSynchronizableValues(params);
        return params.toQueryString();
    },

    synchronizeNeeded: function() {
        var synchronizableValues = this.synchronizables.values();
        for (var index = 0; index < synchronizableValues.length; index++) {
            if (!synchronizableValues[index].synchronizeNeeded()) {
                return false;
            }
        }
        return true;
    },

    synchronize: function() {
        var urlParam = this.assembleUrlParams();
        if (this.synchronizeNeeded()) {
        var request = this.ajaxRequester.makeRequest("SynchronizeStateAjax.zd",
            {parameters: urlParam, onSuccess: this.callback.bind(this)});
        }

        setTimeout(this.synchronize.bind(this), 5000);
    }
};
function OnLoad(pageRefresher) {
    this.pageRefresher = pageRefresher;
}

OnLoad.prototype.onloadCallback = function() {
   var defaultValue ="1";
   var isBackButtonPageLoad = (defaultValue != document.browserState.buttonState.value);
   document.browserState.buttonState.value=2;
   document.browserState.buttonState.defaultValue=2;

   if (isBackButtonPageLoad) {
      this.pageRefresher.refreshPage();
   }
}
CatchCountDisplay = Class.create();
CatchCountDisplay.prototype = {

    INVALID_STATE: -1,

    initialize: function(registry, elementName, showTextFormat, linkUrl) {
        this.element = $(elementName);
        this.linkUrl = linkUrl;
        this.showTextFormat = showTextFormat;
        this.catchCount = this.INVALID_STATE;
        this.listingState = this.INVALID_STATE;
        this.hudElementSequencerPulse = registry.getHudElementSequencerPulse();
    },

    setCatchCount: function(catchCount, formattedValue, listingState) {

        if (catchCount == 0) {
            if (this.showTextFormat) {
                this.element.innerHTML =
                "<span class=\"CatchHistoryNoCatch\">" +
                               "[" + formattedValue + "]</span>";
            }
            else {
                this.element.innerHTML = catchCount;
            }
        } else {
            var textToDisplay = this.showTextFormat ? formattedValue : catchCount;

            if (this.linkUrl != "") {
                this.element.innerHTML =
                "<a href=\"" + this.linkUrl + "\">" + textToDisplay + "</a>";
            } else {
                this.element.innerHTML = textToDisplay;
            }
        }

        if (this.catchCountChanged(catchCount) &&
            !listingBecomesActive(this.listingState, listingState)) {
            this.hudElementSequencerPulse.startNewHudElement(this.element,
                    HUD_ELEMENT_SEQUENCE_PULSE_TYPE_POSITIVE);
        }
        this.catchCount = catchCount;
        this.listingState = listingState;
    },

    catchCountChanged: function(count) {
        var countChanged = (this.catchCount != this.INVALID_STATE &&
            count != this.catchCount);
        return countChanged;
    }
};
CatchCountUpdater = Class.create();
CatchCountUpdater.prototype = {

    initialize: function(registry, listingTransferObject, catchCountDisplay) {
        this.registry = registry;
        this.listingTransferObject = listingTransferObject;
        this.catchCountDisplay = catchCountDisplay;
        this.stateCalculator = new ListingStateCalculator(listingTransferObject);
    },

    update: function() {
        this.catchCountDisplay.setCatchCount(
                this.listingTransferObject.catchCount,
                this.listingTransferObject.formattedCatchCount,
                this.stateCalculator.stateAtDate(this.registry.getDiveTime().getTime()));
    }
};
QuantityLeftDisplay = Class.create();
QuantityLeftDisplay.prototype = {

    UNSET_VALUE: -1,

    initialize: function(registry, elementName) {
        this.element = $(elementName);
        this.quantityLeft = this.UNSET_VALUE;
        this.hudElementSequencerPulse = registry.getHudElementSequencerPulse();
        this.listingState = this.UNSET_VALUE;
    },

    setQuantityLeft: function(quantityLeft, listingState) {
        this.element.innerHTML = quantityLeft;

        if (this.quantityChanged(quantityLeft) &&
            !listingBecomesActive(this.listingState, listingState)) {
            this.hudElementSequencerPulse.startNewHudElement(this.element,
                    HUD_ELEMENT_SEQUENCE_PULSE_TYPE_POSITIVE);
        }
        this.quantityLeft = quantityLeft;
        this.listingState = listingState;

    },

    quantityChanged: function(quantityLeft){
        var changed = this.quantityLeft != this.UNSET_VALUE && quantityLeft != this.quantityLeft;
        return changed;
    }
};
function QuantityLeftUpdater(registry, listingTransferObject, quantityLeftDisplay) {
	this.listingTransferObject = listingTransferObject;
	this.quantityLeftDisplay = quantityLeftDisplay;
    this.listingStateCalculator = new ListingStateCalculator(listingTransferObject);
    this.registry = registry;
}

QuantityLeftUpdater.prototype.update = function() {
	this.quantityLeftDisplay.setQuantityLeft(
            this.listingTransferObject.formattedQuantityLeft,
            this.listingStateCalculator.stateAtDate(this.registry.getDiveTime().getTime()));
}
function PageRefresher() {
}

PageRefresher.prototype.refreshPage = function() {
    window.location.replace(window.document.location.toString());
}


ListingStateBlockUpdater = Class.create();
ListingStateBlockUpdater.prototype = {

    initialize: function(registry, listingTransferObject, blockDisplay) {
        this.listingTransferObject = listingTransferObject;
        this.blockDisplay = blockDisplay;
        this.stateCalculator = new ListingStateCalculator(listingTransferObject);
        this.needsRefreshListingEnd = true;
        this.needsRefreshListingBegin = true;
        this.registry = registry;
        this.beginningState = this.stateCalculator.stateAtDate(this.registry.getDiveTime().getTime());
        this.blockDisplay.display(this.beginningState);
    },

    update: function() {
        var state = this.stateCalculator.stateAtDate(this.registry.getDiveTime().getTime());
        if (state != this.beginningState){
            if (listingEnded(state) && this.needsRefreshListingEnd) {
                this.needsRefreshListingEnd = false;
                this.blockDisplay.display(state);
            }

            if (state == LISTING_STATE_ACTIVE && this.needsRefreshListingBegin) {
                this.needsRefreshListingBegin = false;
                this.blockDisplay.display(state);
            }
        }
    }
};
function CatchInformationDisplay(catchTimeElementName, priceElementName, quantityElementName, catchState, formattedExecutionTime,
                                 formattedPrice, formattedCount, formattedQuantityWon, formattedQuantityLeft, partialCatch) {
    this.catchState = catchState;
    this.formattedExecutionTime = formattedExecutionTime;
    this.formattedPrice = formattedPrice;
    this.formattedCount = formattedCount;
    this.formattedQuantityWon = formattedQuantityWon;
    this.formattedQuantityLeft = formattedQuantityLeft;
    this.partialCatch = partialCatch;
    this.catchTimeElement = document.getElementById(catchTimeElementName);
    this.priceElement = document.getElementById(priceElementName);
    this.quantityElement = document.getElementById(quantityElementName);
}

CatchInformationDisplay.prototype.setCatchInformation = function(catchTimeLeft) {

    if (catchTimeLeft.getTime() < 0 &&
        this.catchState == CATCH_STATE_ACTIVE){
        this.catchState = CATCH_STATE_EXECUTED;
    }

    this.generateCatchTimeHTML(catchTimeLeft);
    this.generatePriceHTML();
    this.generateCountHTML();
}

CatchInformationDisplay.prototype.generateCatchTimeHTML = function(catchTimeLeft) {
    var innerHTML = "";

    innerHTML += this.beginSpan(CATCH_STATE_CSS_CLASS[this.catchState]);
    if (this.catchState == CATCH_STATE_INACTIVE) {
        innerHTML += "Inactive<br>Not a winning catch";
    } else if (this.catchState == CATCH_STATE_ACTIVE) {
     /*   innerHTML += "Will catch in<br>" + formatTimeRaw(catchTimeLeft);*/  innerHTML += "<br>";
    } else if (this.catchState == CATCH_STATE_EXECUTED) {
        innerHTML += "Executed<br>" + this.formattedExecutionTime;
    }
    innerHTML += this.endSpan();
    this.catchTimeElement.innerHTML = innerHTML;
}

CatchInformationDisplay.prototype.generatePriceHTML = function() {
    var innerHTML = "";
    innerHTML += this.beginSpan(CATCH_STATE_CSS_CLASS[this.catchState]);
    innerHTML += this.formattedPrice;
    innerHTML += this.endSpan();
    this.priceElement.innerHTML = innerHTML;
}

CatchInformationDisplay.prototype.generateCountHTML = function() {
    var innerHTML = "";
    innerHTML += this.beginSpan(CATCH_STATE_CSS_CLASS[this.catchState]);
    innerHTML += this.formattedCount;
    if (this.catchState != CATCH_STATE_INACTIVE) {
        if (this.partialCatch) {
            innerHTML += this.beginDiv("CatchHistoryCatchInfo");
            if (this.catchState == CATCH_STATE_ACTIVE) {
                innerHTML += "You'll catch: ";
            } else {
                innerHTML += "Caught: ";
            }
            innerHTML += this.formattedQuantityWon;
            innerHTML += this.endDiv();
        }

        if (this.catchState == CATCH_STATE_ACTIVE) {
            innerHTML += this.beginDiv("CatchHistoryCatchInfo");
            innerHTML += "Remaining: " + this.formattedQuantityLeft;
            innerHTML += this.endDiv();
        }
    }
    innerHTML += this.endSpan();
    this.quantityElement.innerHTML = innerHTML;
}

CatchInformationDisplay.prototype.beginSpan = function(cssClass) {
    return "<span class=\"" + cssClass + "\">";
}

CatchInformationDisplay.prototype.endSpan = function() {
    return  "</span>";
}

CatchInformationDisplay.prototype.beginDiv = function(cssClass) {
    return "<div class=\"" + cssClass + "\">";
}

CatchInformationDisplay.prototype.endDiv = function() {
    return  "</div>";
}
function CatchInformationUpdater(registry, catchExecutionTime, catchInformationDisplay) {
	this.catchInformationDisplay = catchInformationDisplay;
	this.catchExecutionTime = new Date(catchExecutionTime);
    this.registry = registry;
}

CatchInformationUpdater.prototype.update = function() {
    var timeLeft = new Date(this.catchExecutionTime.getTime() - this.registry.getDiveTime().getTime().getTime());
    this.catchInformationDisplay.setCatchInformation(timeLeft);
}
var CATCH_STATE_EXECUTED = 0;
var CATCH_STATE_INACTIVE = 1;
var CATCH_STATE_ACTIVE = 2;
var CATCH_STATE_RETRACTED = 3;




var PLACE_CATCH_BAR_ACTIVE_COLOR = '#44AA00';
var PLACE_CATCH_BAR_INACTIVE_COLOR = '#CCCCCC';

DynDiveScale = Class.create();
DynDiveScale.prototype = {

    MAX_CATCH_NODES_BEFORE_SCROLL_OPTIMIZING: 10,
    PRICE_BAR_COLOR_FOR_LISTING_STATE: ["#FF6600","#44AA00","#CCCCCC","#000000"],
    CENT_RANGE_OFFSET: -0.005,
    CENT_RANGE_CLIP_OFFSET: -0.01,

    initialize: function(registry, catchInfoPopup, diveScaleState, allowPlaceCatch, help) {
        this.registry = registry;
        this.diveScaleState = diveScaleState;
        this.catchInfoPopup = catchInfoPopup;
        this.allowPlaceCatch = allowPlaceCatch;
        this.help = help;
        this.canvasWidth = 75;
        this.canvasHeight = 235;
        this.viewport = new Viewport(0, this.canvasHeight, this.canvasWidth, 0);
        this.camera = new Camera(0, 0, this.canvasWidth, 100);
        this.transform = new Transform(this.viewport, this.camera);
        this.currentCatchForPopupIndex = -1;
        this.placeCatchMouseDown = false;
        this.catchBarVisibilityFlags = new BitField();
        this.priceBarVisibilityFlags = new BitField();
        this.lowestCatchPriceBarVisibilityFlags = new BitField();
        this.renderScheduleId = this.registry.getRenderScheduler().registerRenderable(this, "diveScale");
        this.eventGroup = new EventGroup(this.registry.getEventHandler());
        this.currentListingState = -1;
        this.initCanvas();
        this.initCatchZones();
        this.initCatchNodes();
        this.initPriceBars();
        this.initHashMarks();
        this.initNotYetActiveBlock();
        this.initEvents();
        this.requestRender();
    },

    addCatch: function(catchTransferObject) {
        this.catchNodes.addCatch(catchTransferObject);
    },

    handlePlaceCatchMouseDown: function(e) {
        if (this.canPlaceCatch()) {
            this.placeCatchMouseDown = true;
        }
    },

    handlePlaceCatchMouseUp: function(e) {
        if (this.placeCatchMouseDown &&
            this.isCatchPriceInActiveRange()) {
            this.registry.getCatchNotifier().notify(formatRawPriceNoSubcent(this.diveScaleState.getPlaceCatchPrice()));
        }

        this.placeCatchMouseDown = false;
    },

    handleHideCatchBar: function (e) {
        if (this.isVerifiedMouseOut(e)) {
            this.catchBarVisibilityFlags.clearAll();
            this.placeCatchMouseDown = false;
            this.help.clearMessage();
            this.requestRender();
        }
    },

    isVerifiedMouseOut: function(event) {
        var x = getCoordX(event);
        var y = getCoordY(event);
        var left = this.registry.getElementDimensions().getLeft(this.clipElement);
        var top = this.registry.getElementDimensions().getTop(this.clipElement);
        var right = this.registry.getElementDimensions().getWidth(this.clipElement) + left;
        var bottom = this.registry.getElementDimensions().getHeight(this.clipElement) + top;

        //        alert("coord:("+x+","+y+") left:"+left+" top:"+top+" right: "+ right+ " bottom:"+bottom);
        if (x <= (left) || x >= (right)) {
            return true;
        }
        if (y <= (top) || y >= (bottom)) {
            return true;
        }
        return false;
    },

    onMouseMoveScale: function(e) {
        if (this.canPlaceCatch()) {
            var top = this.registry.getElementDimensions().getTop(this.element);
            var coord = getCoordY(e);
            var result = coord - top;
            var transformed = this.transform.deviceToWorldY(result);
            this.diveScaleState.setPlaceCatchPosition(transformed);
            this.diveScaleState.setPlaceCatchPrice(Math.max(transformed, 0));
            this.catchBarVisibilityFlags.setAll();
            this.requestRender();
        }

        this.setHelpTextForCatchPrice();
    },

    setHelpTextForCatchPrice: function() {
        if (this.catchNodes.isMouseWithinNode()
                || !this.allowPlaceCatch) {
            return;
        }

        if (listingEnded(this.currentListingState)) {
            this.help.setMessage(DIVE_SCALE_MESSAGE_CANNOT_PLACE_CATCH_DIVE_ENDS);
        } else if (this.isCatchPriceInActiveRange()) {
            this.help.setMessage(DIVE_SCALE_MESSAGE_PLACE_CATCH +
                                 formatPriceNoSubcent(this.diveScaleState.getPlaceCatchPrice()));
        } else {

            if (this.diveScaleState.getPlaceCatchPrice() == 0.0) {
                this.help.setMessage(DIVE_SCALE_MESSAGE_CANNOT_PLACE_ZERO_DOLLAR_CATCH);
            }
            else if (formatRawPriceNoSubcent(this.diveScaleState.getPlaceCatchPrice()) <=
                     formatRawPriceNoSubcent(this.diveScaleState.getLowestAvailableCatchPrice())) {
                this.help.setMessage(DIVE_SCALE_MESSAGE_CANNOT_PLACE_CATCH_DIVE_ENDS);
            } else {
                this.help.setMessage(DIVE_SCALE_MESSAGE_CANNOT_PLACE_CATCH_IN_PAST);
            }
        }
    },
    requestRender: function() {
        this.registry.getRenderScheduler().requestRender(this.renderScheduleId);
    },

    render: function() {
        var stateChanged = this.diveScaleState.getListingState() != this.currentListingState;
        this.currentListingState = this.diveScaleState.getListingState();
        if (this.diveScaleState.getListingState() != LISTING_STATE_NOT_PUBLISHED) {
            if (stateChanged) {
                this.initRenderActiveScale();
            }
            this.renderActiveScale();
        } else {
            if (stateChanged) {
                this.initRenderNotYetActiveScale();
            }
            this.renderNotYetActiveScale();
        }
    },

    initRenderNotYetActiveScale: function() {
        this.catchBarVisibilityFlags.clearAll();
        this.priceBarVisibilityFlags.clearAll();
        this.lowestCatchPriceBarVisibilityFlags.clearAll();
        this.updateVisibilityAndRenderPriceBar(this.catchBarVisibilityFlags, this.catchPriceBar);
        this.updateVisibilityAndRenderPriceBar(this.priceBarVisibilityFlags, this.currentPriceBar);
        this.updateVisibilityAndRenderPriceBar(this.lowestCatchPriceBarVisibilityFlags, this.lowestCatchPriceBar);
        this.hashMarks.hide();
        this.catchZones.setVisible(false);
        this.catchNodes.setVisible(false);
        Element.show(this.notYetActiveBlock);
        this.clipElement.style.backgroundColor = "#eeeeee";
        this.eventGroup.enableEvents(false);
    },

    initRenderActiveScale: function() {
        this.catchZones.setVisible(true);
        this.catchNodes.setVisible(true);
        Element.hide(this.notYetActiveBlock);
        this.clipElement.style.backgroundColor = "#ffffff";
        this.eventGroup.enableEvents(true);
    },

    renderNotYetActiveScale: function() {
        Element.show(this.notYetActiveBlock);
        this.notYetActiveTimeElement.innerHTML = formatTimeLeft(this.diveScaleState.getTimeLeft());
        this.setNotYetActiveBlockDimensions();
    },

    renderActiveScale: function() {
        this.updateCamera();
        this.updateHashMarks();
        this.updatePriceBars();
        this.catchZones.update(this.diveScaleState.getViewBottom(),
                this.diveScaleState.getViewTop());
        this.hashMarks.render();
        this.renderPriceBars();
        this.catchZones.render();
        this.renderCatchNodes();
    },

    renderCatchNodes: function() {

        if (this.catchNodes.getNumberOfActiveCatchNodes() > this.MAX_CATCH_NODES_BEFORE_SCROLL_OPTIMIZING) {
            if (this.diveScaleState.getChangingViewRange() && !this.hiddenCatches) {
                this.catchNodes.hideAll();
                this.hiddenCatches = true;
            }

            if (!this.diveScaleState.getChangingViewRange() && this.hiddenCatches) {
                this.catchNodes.showAll();
                this.hiddenCatches = false;
            }

            if (!this.diveScaleState.getChangingViewRange()) {
                this.catchNodes.render();
            }
        } else {
            this.catchNodes.render();
        }

    },

    isPriceInViewableRange: function(price) {
        return this.isPriceInRange(price,
                this.diveScaleState.getViewBottom(),
                this.diveScaleState.getViewTop());
    },

    isCatchPriceInActiveRange: function() {
        return this.isPriceInRange(this.diveScaleState.getPlaceCatchPrice(),
                this.diveScaleState.getLowestAvailableCatchPrice(),
                this.diveScaleState.getPrice());
    },

    isPriceInRange: function(price, bottom, top) {
        return (price < top && price > bottom);
    },

    updateCurrentPriceBar: function() {
        var formattedPrice;
        formattedPrice = formatPrice(this.diveScaleState.getPrice());

        this.currentPriceBar.setState(this.diveScaleState.getPrice(),
                formattedPrice, "#FF6600");

        if (this.diveScaleState.getListingState() == LISTING_STATE_ACTIVE &&
            this.isPriceInViewableRange(this.diveScaleState.getPrice())) {
            this.priceBarVisibilityFlags.setAll();
        } else {
            this.priceBarVisibilityFlags.clearAll();
        }
    },

    updateLowestCatchPriceBar: function() {

        var color;
        var offset = 0;
        if (this.catchNodes.getNumberOfActiveCatchNodes() > 0) {
            this.lowestCatchPriceBar.enableCoverCentRange(true);
            offset = this.CENT_RANGE_OFFSET;
            color = "#44AA00";
        } else {
            this.lowestCatchPriceBar.enableCoverCentRange(false);
            color = "#888888";
        }

        this.lowestCatchPriceBar.setState(this.diveScaleState.getLowestAvailableCatchPrice() + offset,
                formatPriceNoSubcent(this.diveScaleState.getLowestAvailableCatchPrice()), color);

        this.lowestCatchPriceBarVisibilityFlags.setFlag(PriceBar.BAR_VISIBILITY_FLAG);

        if (this.isPriceInViewableRange(this.diveScaleState.getLowestAvailableCatchPrice() + offset)) {
            this.lowestCatchPriceBarVisibilityFlags.setFlag(PriceBar.PRICE_VISIBILITY_FLAG);
        } else {
            this.lowestCatchPriceBarVisibilityFlags.clearFlag(PriceBar.PRICE_VISIBILITY_FLAG);
        }
    },

    updatePlaceCatchPriceBar: function() {
        if (this.canPlaceCatch()) {
            var color;

            if (this.isCatchPriceInActiveRange()) {
                this.element.style.cursor = "pointer";
                this.clipElement.style.cursor = "pointer";
                color = PLACE_CATCH_BAR_ACTIVE_COLOR;
            } else {
                this.element.style.cursor = "default";
                this.clipElement.style.cursor = "default";
                color = PLACE_CATCH_BAR_INACTIVE_COLOR;
            }

            this.catchPriceBar.setState(this.diveScaleState.getPlaceCatchPosition(),
                    formatPriceNoSubcent(this.diveScaleState.getPlaceCatchPrice()),
                    color);
        } else {
            this.catchBarVisibilityFlags.clearAll();
        }
    },

    canPlaceCatch: function() {
        return this.allowPlaceCatch &&
               this.diveScaleState.getListingState() == LISTING_STATE_ACTIVE;
    },

    updatePriceBars: function() {
        this.updateCurrentPriceBar();
        this.updateLowestCatchPriceBar();
        this.updatePlaceCatchPriceBar();
    },

    updateHashMarks: function() {
        this.hashMarks.update(this.diveScaleState.getViewBottom(),
                this.diveScaleState.getViewTop());
    },

    updateVisibilityAndRenderPriceBar: function(flags, priceBar) {
        priceBar.setVisibility(flags);
        priceBar.render();
    },

    renderPriceBars: function() {
        this.updateVisibilityAndRenderPriceBar(this.catchBarVisibilityFlags, this.catchPriceBar);
        this.updateVisibilityAndRenderPriceBar(this.priceBarVisibilityFlags, this.currentPriceBar);
        this.updateVisibilityAndRenderPriceBar(this.lowestCatchPriceBarVisibilityFlags, this.lowestCatchPriceBar);
    },

    initHashMarks: function() {
        this.hashMarks = new HashMarkDisplay(this.registry, this.clipElementName,
                this.camera,
                this.viewport,
                this.canvasWidth);
    },

    initCatchZones: function() {
        this.catchZones = new CatchZones(this.registry, this.clipElementName,
                this.diveScaleState,
                this.camera,
                this.viewport,
                this.allowPlaceCatch);
    },

    initCatchNodes: function() {
        this.hiddenCatches = false;
        this.hiddenCatches = false;
        this.catchNodes = new CatchNodes(this.registry, 25, this.clipElementName, "CatchNode", 13, 13,
                this.camera,
                this.viewport,
                5, this.catchInfoPopup, this.help);
    },

    initPriceBars: function() {
        this.currentPriceBar = new PriceBar(this.registry, this.clipElementName, this.elementName, this.canvasWidth, this.camera, this.viewport);
        this.currentPriceBar.registerHelpTip(this.help, DIVE_SCALE_MESSAGE_CURRENT_PRICE);
        this.currentPriceBar.show();
        this.lowestCatchPriceBar = new PriceBar(this.registry, this.clipElementName, this.elementName, this.canvasWidth, this.camera, this.viewport);
        this.lowestCatchPriceBar.registerHelpTip(this.help, DIVE_SCALE_MESSAGE_LOWEST_CATCH_PRICE);
        this.lowestCatchPriceBar.show();
        this.catchPriceBar = new PriceBar(this.registry, this.clipElementName, this.elementName, this.canvasWidth, this.camera, this.viewport);
    },

    updateCamera: function() {
        this.camera.lookAtY = (this.diveScaleState.getViewTop() - this.diveScaleState.getViewBottom()) / 2 + this.diveScaleState.getViewBottom();
        this.camera.verticalFOV = this.diveScaleState.getViewTop() - this.diveScaleState.getViewBottom();
    },

    elementBorderFudge: function(borderWidth) {
        if (IE) {
            return borderWidth * 2;
        } else {
            return 0;
        }
    },

    initCanvas: function() {
        this.elementName = "dynDiveScaleDiv";
        this.element = $(this.elementName);
        this.clipElementName = "dynDiveScaleClipDiv";
        this.clipElement = document.createElement("DIV");
        this.clipElement.id = this.clipElementName;
        this.element.appendChild(this.clipElement);

        this.element.style.width = this.viewport.width + this.elementBorderFudge(1) + 'px';
        this.element.style.height = this.viewport.height + this.elementBorderFudge(1) + 'px';
        this.element.style.backgroundColor = '#ffffff';
        this.element.style.position = "relative";

        this.clipElement.style.width = this.viewport.width + 'px';
        this.clipElement.style.height = this.viewport.height + 'px';
        this.clipElement.style.backgroundColor = '#ffffff';
        this.clipElement.style.position = "absolute";
        this.clipElement.style.overflow = "hidden";
    },

    initEvents: function() {
        this.eventGroup.addEvent(this.clipElement, 'mousemove', this.onMouseMoveScale.bindAsEventListener(this));
        this.eventGroup.addEvent(this.clipElement, 'mousedown', this.handlePlaceCatchMouseDown.bindAsEventListener(this));
        this.eventGroup.addEvent(this.clipElement, 'mouseup', this.handlePlaceCatchMouseUp.bindAsEventListener(this));
        this.eventGroup.addEvent(document, 'mousemove', this.handleHideCatchBar.bindAsEventListener(this));
    },


    initNotYetActiveBlock: function() {
        this.notYetActiveBlock = $("notYetActiveInfoPopupSpan");
        this.notYetActiveTimeElement = $("notYetActiveInfoPopupTime");
        this.notYetActiveBlock.style.position = "absolute";
        this.notYetActiveBlock.style.zIndex = '10';
        this.clipElement.appendChild(this.notYetActiveBlock);
        this.setNotYetActiveBlockDimensions();
        Element.hide(this.notYetActiveBlock);
    },

    setNotYetActiveBlockDimensions: function() {
        var dimensions = this.registry.getElementDimensions();
        this.notYetActiveBlock.style.left = this.canvasWidth / 2 - dimensions.getWidth(this.notYetActiveBlock) / 2;
        this.notYetActiveBlock.style.top = this.canvasHeight / 2 - dimensions.getHeight(this.notYetActiveBlock) / 2;
    }
};
function Viewport(left, top, right, bottom) {
    this.left = left;
    this.top = top;
    this.right = right;
    this.bottom = bottom;
    this.width = Math.abs(left - right);
    this.height = Math.abs(top - bottom);
}

function Camera(lookAtX, lookAtY, horizontalFOV, verticalFOV) {
    this.lookAtX = lookAtX;
    this.lookAtY = lookAtY;
    this.horizontalFOV = horizontalFOV;
    this.verticalFOV = verticalFOV;


}

var RENDER_OBJECT_ALIGN_CENTER = 0;
var RENDER_OBJECT_ALIGN_LEFT = 1;
var RENDER_OBJECT_ALIGN_RIGHT = 2;
var RENDER_OBJECT_ALIGN_TOP = 3;
var RENDER_OBJECT_ALIGN_BOTTOM = 4;

RenderObjectBase = Class.create();
RenderObjectBase.prototype = {

    MINIMUM_DISPLAY_HEIGHT: 1,

    initialize: function(parentId, divId) {
        this.divId = divId;
        if (parentId == null) {
            this.parent = document.getElementsByTagName("body")[0];
        } else {
            this.parent = $(parentId);
        }
        this.positionX = 0;
        this.positionY = 0;
        this.opacity = 1.0;
        this.opacitySet = false;
        this.visible = false;
        this.setCamera(null);
        this.setViewport(null);
        this.enableScaling(false);
        this.zIndex = 0;
        this.imageUrl = "";
        this.content = "";
        this.backgroundColor = "";
        this.setSize(0,0);
        this.setHorizontalAlignment(RENDER_OBJECT_ALIGN_CENTER);
        this.setVerticalAlignment(RENDER_OBJECT_ALIGN_CENTER);
        this.enableMinimumDisplayHeight(false);
    },

    setBackgroundColor: function(color) {
        this.backgroundColor = color;
    },

    getBackgroundColor: function() {
        return this.backgroundColor;
    },

    setSize: function(width, height) {
        this.width = width;
        this.height = height;
    },

    setWidth: function(width) {
        this.width = width;
    },

    setHeight: function(height) {
        this.height = height;
    },

    setPosition: function(posX, posY) {
        this.positionX = posX;
        this.positionY = posY;
    },

    setZIndex: function(index) {
        this.zIndex = index;
    },

    getImage: function() {
        return this.imageUrl;
    },

    getElement: function() {
        return this.element;
    },

    getZIndex: function() {
        return this.zIndex;
    },

    enableScaling: function(enable) {
        this.scaleObject = enable;
    },

    enableMinimumDisplayHeight: function(enable) {
        this.enableMinimumHeight = enable;
    },

    getPositionX: function() {
        return this.positionX;
    },

    getPositionY: function() {
        return this.positionY;
    },

    getWidth: function() {
        return this.width;
    },

    getHeight: function() {
        return this.height;
    },

    getOpacity: function() {
        return this.opacity;
    },

    setOpacity: function(opacity) {
        this.opacity = opacity;
        this.opacitySet = true;
    },

    getDivId: function() {
        return this.divId;
    },

    setCamera: function(camera) {
        this.camera = camera;
    },

    setViewport: function(viewport) {
        this.viewport = viewport;
    },

    setHorizontalAlignment: function(alignment) {
        this.horizontalAlignment = alignment;
    },

    setVerticalAlignment