/*  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];
  }
}
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
// 
// See scriptaculous.js for full license.  

/* ------------- element ext -------------- */  
 
// converts rgb() and #xxx to #xxxxxx format,  
// returns self (or first argument) if not convertable  
String.prototype.parseColor = function() {  
  var color = '#';  
  if(this.slice(0,4) == 'rgb(') {  
    var cols = this.slice(4,this.length-1).split(',');  
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
  } else {  
    if(this.slice(0,1) == '#') {  
      if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
      if(this.length==7) color = this.toLowerCase();  
    }  
  }  
  return(color.length==7 ? color : (arguments[0] || this));  
}

Element.collectTextNodes = function(element) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  }).flatten().join('');
}

Element.collectTextNodesIgnoreClass = function(element, className) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
        Element.collectTextNodesIgnoreClass(node, className) : ''));
  }).flatten().join('');
}

Element.setStyle = function(element, style) {
  element = $(element);
  for(k in style) element.style[k.camelize()] = style[k];
}

Element.setContentZoom = function(element, percent) {  
  Element.setStyle(element, {fontSize: (percent/100) + 'em'});   
  if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);  
}

Element.getOpacity = function(element){  
  var opacity;
  if (opacity = Element.getStyle(element, 'opacity'))  
    return parseFloat(opacity);  
  if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))  
    if(opacity[1]) return parseFloat(opacity[1]) / 100;  
  return 1.0;  
}

Element.setOpacity = function(element, value){  
  element= $(element);  
  if (value == 1){
    Element.setStyle(element, { opacity: 
      (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 
      0.999999 : null });
    if(/MSIE/.test(navigator.userAgent))  
      Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});  
  } else {  
    if(value < 0.00001) value = 0;  
    Element.setStyle(element, {opacity: value});
    if(/MSIE/.test(navigator.userAgent))  
     Element.setStyle(element, 
       { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
                 'alpha(opacity='+value*100+')' });  
  }   
}  
 
Element.getInlineOpacity = function(element){  
  return $(element).style.opacity || '';
}  

Element.childrenWithClassName = function(element, className) {  
  return $A($(element).getElementsByTagName('*')).select(
    function(c) { return Element.hasClassName(c, className) });
}

Array.prototype.call = function() {
  var args = arguments;
  this.each(function(f){ f.apply(this, args) });
}

/*--------------------------------------------------------------------------*/

var Effect = {
  tagifyText: function(element) {
    var tagifyStyle = 'position:relative';
    if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
    element = $(element);
    $A(element.childNodes).each( function(child) {
      if(child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            Builder.node('span',{style: tagifyStyle},
              character == ' ' ? String.fromCharCode(160) : character), 
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if(((typeof element == 'object') || 
        (typeof element == 'function')) && 
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;
      
    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || {});
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  },
  PAIRS: {
    'slide':  ['SlideDown','SlideUp'],
    'blind':  ['BlindDown','BlindUp'],
    'appear': ['Appear','Fade']
  },
  toggle: function(element, effect) {
    element = $(element);
    effect = (effect || 'appear').toLowerCase();
    var options = Object.extend({
      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    }, arguments[2] || {});
    Effect[Element.visible(element) ? 
      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  }
};

var Effect2 = Effect; // deprecated

/* ------------- transitions ------------- */

Effect.Transitions = {}

Effect.Transitions.linear = function(pos) {
  return pos;
}
Effect.Transitions.sinoidal = function(pos) {
  return (-Math.cos(pos*Math.PI)/2) + 0.5;
}
Effect.Transitions.reverse  = function(pos) {
  return 1-pos;
}
Effect.Transitions.flicker = function(pos) {
  return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
}
Effect.Transitions.wobble = function(pos) {
  return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
}
Effect.Transitions.pulse = function(pos) {
  return (Math.floor(pos*10) % 2 == 0 ? 
    (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
}
Effect.Transitions.none = function(pos) {
  return 0;
}
Effect.Transitions.full = function(pos) {
  return 1;
}

/* ------------- core effects ------------- */

Effect.ScopedQueue = Class.create();
Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
  initialize: function() {
    this.effects  = [];
    this.interval = null;
  },
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();
    
    var position = (typeof effect.options.queue == 'string') ? 
      effect.options.queue : effect.options.queue.position;
    
    switch(position) {
      case 'front':
        // move unstarted effects after this effect  
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }
    
    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);
    
    if(!this.interval) 
      this.interval = setInterval(this.loop.bind(this), 40);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if(this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    this.effects.invoke('loop', timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if(typeof queueName != 'string') return queueName;
    
    if(!this.instances[queueName])
      this.instances[queueName] = new Effect.ScopedQueue();
      
    return this.instances[queueName];
  }
}
Effect.Queue = Effect.Queues.get('global');

Effect.DefaultOptions = {
  transition: Effect.Transitions.sinoidal,
  duration:   1.0,   // seconds
  fps:        25.0,  // max. 25fps due to Effect.Queue implementation
  sync:       false, // true for combining
  from:       0.0,
  to:         1.0,
  delay:      0.0,
  queue:      'parallel'
}

Effect.Base = function() {};
Effect.Base.prototype = {
  position: null,
  start: function(options) {
    this.options      = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
    this.currentFrame = 0;
    this.state        = 'idle';
    this.startOn      = this.options.delay*1000;
    this.finishOn     = this.startOn + (this.options.duration*1000);
    this.event('beforeStart');
    if(!this.options.sync)
      Effect.Queues.get(typeof this.options.queue == 'string' ? 
        'global' : this.options.queue.scope).add(this);
  },
  loop: function(timePos) {
    if(timePos >= this.startOn) {
      if(timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if(this.finish) this.finish(); 
        this.event('afterFinish');
        return;  
      }
      var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn);
      var frame = Math.round(pos * this.options.fps * this.options.duration);
      if(frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  render: function(pos) {
    if(this.state == 'idle') {
      this.state = 'running';
      this.event('beforeSetup');
      if(this.setup) this.setup();
      this.event('afterSetup');
    }
    if(this.state == 'running') {
      if(this.options.transition) pos = this.options.transition(pos);
      pos *= (this.options.to-this.options.from);
      pos += this.options.from;
      this.position = pos;
      this.event('beforeUpdate');
      if(this.update) this.update(pos);
      this.event('afterUpdate');
    }
  },
  cancel: function() {
    if(!this.options.sync)
      Effect.Queues.get(typeof this.options.queue == 'string' ? 
        'global' : this.options.queue.scope).remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if(this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
}

Effect.Parallel = Class.create();
Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
  initialize: function(effects) {
    this.effects = effects || [];
    this.start(arguments[1]);
  },
  update: function(position) {
    this.effects.invoke('render', position);
  },
  finish: function(position) {
    this.effects.each( function(effect) {
      effect.render(1.0);
      effect.cancel();
      effect.event('beforeFinish');
      if(effect.finish) effect.finish(position);
      effect.event('afterFinish');
    });
  }
});

Effect.Opacity = Class.create();
Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    // make this work on IE on elements without 'layout'
    if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
      Element.setStyle(this.element, {zoom: 1});
    var options = Object.extend({
      from: Element.getOpacity(this.element) || 0.0,
      to:   1.0
    }, arguments[1] || {});
    this.start(options);
  },
  update: function(position) {
    Element.setOpacity(this.element, position);
  }
});

Effect.Move = Class.create();
Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    var options = Object.extend({
      x:    0,
      y:    0,
      mode: 'relative'
    }, arguments[1] || {});
    this.start(options);
  },
  setup: function() {
    // Bug in Opera: Opera returns the "real" position of a static element or
    // relative element that does not have top/left explicitly set.
    // ==> Always set top and left for position relative elements in your stylesheets 
    // (to 0 if you do not need them) 
    Element.makePositioned(this.element);
    this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
    this.originalTop  = parseFloat(Element.getStyle(this.element,'top')  || '0');
    if(this.options.mode == 'absolute') {
      // absolute movement, so we need to calc deltaX and deltaY
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    Element.setStyle(this.element, {
      left: this.options.x  * position + this.originalLeft + 'px',
      top:  this.options.y  * position + this.originalTop  + 'px'
    });
  }
});

// for backwards compatibility
Effect.MoveBy = function(element, toTop, toLeft) {
  return new Effect.Move(element, 
    Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
};

Effect.Scale = Class.create();
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
  initialize: function(element, percent) {
    this.element = $(element)
    var options = Object.extend({
      scaleX: true,
      scaleY: true,
      scaleContent: true,
      scaleFromCenter: false,
      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
      scaleFrom: 100.0,
      scaleTo:   percent
    }, arguments[2] || {});
    this.start(options);
  },
  setup: function() {
    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = Element.getStyle(this.element,'position');
    
    this.originalStyle = {};
    ['top','left','width','height','fontSize'].each( function(k) {
      this.originalStyle[k] = this.element.style[k];
    }.bind(this));
      
    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;
    
    var fontSize = Element.getStyle(this.element,'font-size') || '100%';
    ['em','px','%'].each( function(fontSizeType) {
      if(fontSize.indexOf(fontSizeType)>0) {
        this.fontSize     = parseFloat(fontSize);
        this.fontSizeType = fontSizeType;
      }
    }.bind(this));
    
    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
    
    this.dims = null;
    if(this.options.scaleMode=='box')
      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    if(/^content/.test(this.options.scaleMode))
      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    if(!this.dims)
      this.dims = [this.options.scaleMode.originalHeight,
                   this.options.scaleMode.originalWidth];
  },
  update: function(position) {
    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    if(this.options.scaleContent && this.fontSize)
      Element.setStyle(this.element, {fontSize: this.fontSize * currentScale + this.fontSizeType });
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  },
  finish: function(position) {
    if (this.restoreAfterFinish) Element.setStyle(this.element, this.originalStyle);
  },
  setDimensions: function(height, width) {
    var d = {};
    if(this.options.scaleX) d.width = width + 'px';
    if(this.options.scaleY) d.height = height + 'px';
    if(this.options.scaleFromCenter) {
      var topd  = (height - this.dims[0])/2;
      var leftd = (width  - this.dims[1])/2;
      if(this.elementPositioning == 'absolute') {
        if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
      } else {
        if(this.options.scaleY) d.top = -topd + 'px';
        if(this.options.scaleX) d.left = -leftd + 'px';
      }
    }
    Element.setStyle(this.element, d);
  }
});

Effect.Highlight = Class.create();
Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
    this.start(options);
  },
  setup: function() {
    // Prevent executing on elements not in the layout flow
    if(Element.getStyle(this.element, 'display')=='none') { this.cancel(); return; }
    // Disable background image during the effect
    this.oldStyle = {
      backgroundImage: Element.getStyle(this.element, 'background-image') };
    Element.setStyle(this.element, {backgroundImage: 'none'});
    if(!this.options.endcolor)
      this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff');
    if(!this.options.restorecolor)
      this.options.restorecolor = Element.getStyle(this.element, 'background-color');
    // init color calculations
    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  },
  update: function(position) {
    Element.setStyle(this.element,{backgroundColor: $R(0,2).inject('#',function(m,v,i){
      return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
  },
  finish: function() {
    Element.setStyle(this.element, Object.extend(this.oldStyle, {
      backgroundColor: this.options.restorecolor
    }));
  }
});

Effect.ScrollTo = Class.create();
Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    this.start(arguments[1] || {});
  },
  setup: function() {
    Position.prepare();
    var offsets = Position.cumulativeOffset(this.element);
    if(this.options.offset) offsets[1] += this.options.offset;
    var max = window.innerHeight ? 
      window.height - window.innerHeight :
      document.body.scrollHeight - 
        (document.documentElement.clientHeight ? 
          document.documentElement.clientHeight : document.body.clientHeight);
    this.scrollStart = Position.deltaY;
    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
  },
  update: function(position) {
    Position.prepare();
    window.scrollTo(Position.deltaX, 
      this.scrollStart + (position*this.delta));
  }
});

/* ------------- combination effects ------------- */

Effect.Fade = function(element) {
  var oldOpacity = Element.getInlineOpacity(element);
  var options = Object.extend({
  from: Element.getOpacity(element) || 1.0,
  to:   0.0,
  afterFinishInternal: function(effect) { with(Element) { 
    if(effect.options.to!=0) return;
    hide(effect.element);
    setStyle(effect.element, {opacity: oldOpacity}); }}
  }, arguments[1] || {});
  return new Effect.Opacity(element,options);
}

Effect.Appear = function(element) {
  var options = Object.extend({
  from: (Element.getStyle(element, 'display') == 'none' ? 0.0 : Element.getOpacity(element) || 0.0),
  to:   1.0,
  beforeSetup: function(effect) { with(Element) {
    setOpacity(effect.element, effect.options.from);
    show(effect.element); }}
  }, arguments[1] || {});
  return new Effect.Opacity(element,options);
}

Effect.Puff = function(element) {
  element = $(element);
  var oldStyle = { opacity: Element.getInlineOpacity(element), position: Element.getStyle(element, 'position') };
  return new Effect.Parallel(
   [ new Effect.Scale(element, 200, 
      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
     Object.extend({ duration: 1.0, 
      beforeSetupInternal: function(effect) { with(Element) {
        setStyle(effect.effects[0].element, {position: 'absolute'}); }},
      afterFinishInternal: function(effect) { with(Element) {
         hide(effect.effects[0].element);
         setStyle(effect.effects[0].element, oldStyle); }}
     }, arguments[1] || {})
   );
}

Effect.BlindUp = function(element) {
  element = $(element);
  Element.makeClipping(element);
  return new Effect.Scale(element, 0, 
    Object.extend({ scaleContent: false, 
      scaleX: false, 
      restoreAfterFinish: true,
      afterFinishInternal: function(effect) { with(Element) {
        [hide, undoClipping].call(effect.element); }} 
    }, arguments[1] || {})
  );
}

Effect.BlindDown = function(element) {
  element = $(element);
  var oldHeight = Element.getStyle(element, 'height');
  var elementDimensions = Element.getDimensions(element);
  return new Effect.Scale(element, 100, 
    Object.extend({ scaleContent: false, 
      scaleX: false,
      scaleFrom: 0,
      scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
      restoreAfterFinish: true,
      afterSetup: function(effect) { with(Element) {
        makeClipping(effect.element);
        setStyle(effect.element, {height: '0px'});
        show(effect.element); 
      }},  
      afterFinishInternal: function(effect) { with(Element) {
        undoClipping(effect.element);
        setStyle(effect.element, {height: oldHeight});
      }}
    }, arguments[1] || {})
  );
}

Effect.SwitchOff = function(element) {
  element = $(element);
  var oldOpacity = Element.getInlineOpacity(element);
  return new Effect.Appear(element, { 
    duration: 0.4,
    from: 0,
    transition: Effect.Transitions.flicker,
    afterFinishInternal: function(effect) {
      new Effect.Scale(effect.element, 1, { 
        duration: 0.3, scaleFromCenter: true,
        scaleX: false, scaleContent: false, restoreAfterFinish: true,
        beforeSetup: function(effect) { with(Element) {
          [makePositioned,makeClipping].call(effect.element);
        }},
        afterFinishInternal: function(effect) { with(Element) {
          [hide,undoClipping,undoPositioned].call(effect.element);
          setStyle(effect.element, {opacity: oldOpacity});
        }}
      })
    }
  });
}

Effect.DropOut = function(element) {
  element = $(element);
  var oldStyle = {
    top: Element.getStyle(element, 'top'),
    left: Element.getStyle(element, 'left'),
    opacity: Element.getInlineOpacity(element) };
  return new Effect.Parallel(
    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
    Object.extend(
      { duration: 0.5,
        beforeSetup: function(effect) { with(Element) {
          makePositioned(effect.effects[0].element); }},
        afterFinishInternal: function(effect) { with(Element) {
          [hide, undoPositioned].call(effect.effects[0].element);
          setStyle(effect.effects[0].element, oldStyle); }} 
      }, arguments[1] || {}));
}

Effect.Shake = function(element) {
  element = $(element);
  var oldStyle = {
    top: Element.getStyle(element, 'top'),
    left: Element.getStyle(element, 'left') };
	  return new Effect.Move(element, 
	    { x:  20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
	  new Effect.Move(effect.element,
	    { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
	  new Effect.Move(effect.element,
	    { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
	  new Effect.Move(effect.element,
	    { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
	  new Effect.Move(effect.element,
	    { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
	  new Effect.Move(effect.element,
	    { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { with(Element) {
        undoPositioned(effect.element);
        setStyle(effect.element, oldStyle);
  }}}) }}) }}) }}) }}) }});
}

Effect.SlideDown = function(element) {
  element = $(element);
  Element.cleanWhitespace(element);
  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
  var elementDimensions = Element.getDimensions(element);
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleX: false, 
    scaleFrom: 0,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) { with(Element) {
      makePositioned(effect.element);
      makePositioned(effect.element.firstChild);
      if(window.opera) setStyle(effect.element, {top: ''});
      makeClipping(effect.element);
      setStyle(effect.element, {height: '0px'});
      show(element); }},
    afterUpdateInternal: function(effect) { with(Element) {
      setStyle(effect.element.firstChild, {bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
    afterFinishInternal: function(effect) { with(Element) {
      undoClipping(effect.element); 
      // IE will crash if child is undoPositioned first
      if(/MSIE/.test(navigator.userAgent)){
        undoPositioned(effect.element);
        undoPositioned(effect.element.firstChild);
      }else{
        undoPositioned(effect.element.firstChild);
        undoPositioned(effect.element);
      }
      setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
    }, arguments[1] || {})
  );
}
  
Effect.SlideUp = function(element) {
  element = $(element);
  Element.cleanWhitespace(element);
  var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
  return new Effect.Scale(element, 0, 
   Object.extend({ scaleContent: false, 
    scaleX: false, 
    scaleMode: 'box',
    scaleFrom: 100,
    restoreAfterFinish: true,
    beforeStartInternal: function(effect) { with(Element) {
      makePositioned(effect.element);
      makePositioned(effect.element.firstChild);
      if(window.opera) setStyle(effect.element, {top: ''});
      makeClipping(effect.element);
      show(element); }},  
    afterUpdateInternal: function(effect) { with(Element) {
      setStyle(effect.element.firstChild, {bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
    afterFinishInternal: function(effect) { with(Element) {
        [hide, undoClipping].call(effect.element); 
        undoPositioned(effect.element.firstChild);
        undoPositioned(effect.element);
        setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
   }, arguments[1] || {})
  );
}

// Bug in opera makes the TD containing this element expand for a instance after finish 
Effect.Squish = function(element) {
  return new Effect.Scale(element, window.opera ? 1 : 0, 
    { restoreAfterFinish: true,
      beforeSetup: function(effect) { with(Element) {
        makeClipping(effect.element); }},  
      afterFinishInternal: function(effect) { with(Element) {
        hide(effect.element); 
        undoClipping(effect.element); }}
  });
}

Effect.Grow = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.full
  }, arguments[1] || {});
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: Element.getInlineOpacity(element) };

  var dims = Element.getDimensions(element);    
  var initialMoveX, initialMoveY;
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      initialMoveX = initialMoveY = moveX = moveY = 0; 
      break;
    case 'top-right':
      initialMoveX = dims.width;
      initialMoveY = moveY = 0;
      moveX = -dims.width;
      break;
    case 'bottom-left':
      initialMoveX = moveX = 0;
      initialMoveY = dims.height;
      moveY = -dims.height;
      break;
    case 'bottom-right':
      initialMoveX = dims.width;
      initialMoveY = dims.height;
      moveX = -dims.width;
      moveY = -dims.height;
      break;
    case 'center':
      initialMoveX = dims.width / 2;
      initialMoveY = dims.height / 2;
      moveX = -dims.width / 2;
      moveY = -dims.height / 2;
      break;
  }
  
  return new Effect.Move(element, {
    x: initialMoveX,
    y: initialMoveY,
    duration: 0.01, 
    beforeSetup: function(effect) { with(Element) {
      hide(effect.element);
      makeClipping(effect.element);
      makePositioned(effect.element);
    }},
    afterFinishInternal: function(effect) {
      new Effect.Parallel(
        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
          new Effect.Scale(effect.element, 100, {
            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
        ], Object.extend({
             beforeSetup: function(effect) { with(Element) {
               setStyle(effect.effects[0].element, {height: '0px'});
               show(effect.effects[0].element); }},
             afterFinishInternal: function(effect) { with(Element) {
               [undoClipping, undoPositioned].call(effect.effects[0].element); 
               setStyle(effect.effects[0].element, oldStyle); }}
           }, options)
      )
    }
  });
}

Effect.Shrink = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.none
  }, arguments[1] || {});
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: Element.getInlineOpacity(element) };

  var dims = Element.getDimensions(element);
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      moveX = moveY = 0;
      break;
    case 'top-right':
      moveX = dims.width;
      moveY = 0;
      break;
    case 'bottom-left':
      moveX = 0;
      moveY = dims.height;
      break;
    case 'bottom-right':
      moveX = dims.width;
      moveY = dims.height;
      break;
    case 'center':  
      moveX = dims.width / 2;
      moveY = dims.height / 2;
      break;
  }
  
  return new Effect.Parallel(
    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
    ], Object.extend({            
         beforeStartInternal: function(effect) { with(Element) {
           [makePositioned, makeClipping].call(effect.effects[0].element) }},
         afterFinishInternal: function(effect) { with(Element) {
           [hide, undoClipping, undoPositioned].call(effect.effects[0].element);
           setStyle(effect.effects[0].element, oldStyle); }}
       }, options)
  );
}

Effect.Pulsate = function(element) {
  element = $(element);
  var options    = arguments[1] || {};
  var oldOpacity = Element.getInlineOpacity(element);
  var transition = options.transition || Effect.Transitions.sinoidal;
  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
  reverser.bind(transition);
  return new Effect.Opacity(element, 
    Object.extend(Object.extend({  duration: 3.0, from: 0,
      afterFinishInternal: function(effect) { Element.setStyle(effect.element, {opacity: oldOpacity}); }
    }, options), {transition: reverser}));
}

Effect.Fold = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height };
  Element.makeClipping(element);
  return new Effect.Scale(element, 5, Object.extend({   
    scaleContent: false,
    scaleX: false,
    afterFinishInternal: function(effect) {
    new Effect.Scale(element, 1, { 
      scaleContent: false, 
      scaleY: false,
      afterFinishInternal: function(effect) { with(Element) {
        [hide, undoClipping].call(effect.element); 
        setStyle(effect.element, oldStyle);
      }} });
  }}, arguments[1] || {}));
}
function Pokkari() {}

Pokkari.AttachEvent = function(element,event,func)
{
	event = event.toLowerCase();
	if (element.attachEvent) 
	{
		element.attachEvent("on"+event,func);
	}
	else if (element.addEventListener)
	{
		element.addEventListener(event,func,true);
	}
}

Pokkari.Debug = function(message)
{
	Pokkari.DebugWindowWrite(message);
}

Pokkari.Error = function(message)
{
	window.alert(message);
}

Pokkari.HandleException = function(error,message)
{
	var errMsg = error.message || error.description;
	Pokkari.DebugWindowWrite(message+": "+errMsg);
}

Pokkari.ShowDebugWindow = function()
{
	Pokkari.debugWindow = window.open('/scripts/index.html','debugWindow','width=300,height=200,scrollbars=yes,resizable=yes');
	/*
	var element = document.getElementById('pokkariDebugWindow');
	var top = window.pageYOffset ? window.pageYOffset : document.body.scrollTop;
	element.style.top = top;	
	element.style.display = "block";
	*/
}

Pokkari.DebugWindowWrite = function(message)
{
/*
	var element = document.getElementById('pokkariDebugWindow');
	var top = window.pageYOffset ? window.pageYOffset : document.body.scrollTop;

	if (element && element.style.display != "none")
	{
		element.innerHTML = message + "<br />" + element.innerHTML;
		element.style.top = top;
		element.style.display = "block";
	}
*/
	if (Pokkari.debugWindow && Pokkari.debugWindow.document && Pokkari.debugWindow.document.body)
	{
		var element = Pokkari.debugWindow.document.body;
		element.innerHTML = message + "<br />" + element.innerHTML;
	}
}

Pokkari.UrlReplace = function(param,value)
{
	var parts = window.location.href.split("?");
	
	if (parts.length < 2)
		return window.location.href + "?" + param + "=" + escape(value);
	
	var query = parts[1].split("#")[0];
	var pattern = "\\b" + param + "=[^&;]*";
	var replace = param + "=" + escape(value);
	
	var regex = new RegExp(pattern);
	if (regex.test(window.location.href))
		return window.location.href.replace(regex,replace);
	else
		return window.location.href + "&" + param + "=" + escape(value);
}

Pokkari.SetCookiePreference = function(params) {
        if(params.name) {
                var today = new Date();
                var expire = new Date();
                expire.setTime(today.getTime() + 3600000*24*1);
                document.cookie = 'users_prefs_'+params.name+'='+escape(params.value)+";path=/;expires="+expire.toGMTString();
                if(params.reload) {
                        window.location.reload(true);
                }
        } else {
                throw("Cannot set cookie preference without name");
        }
}

Pokkari.includedScripts = {};

Pokkari.includes = function(scriptName) 
{
	Pokkari.includedScripts[scriptName] = true;
}

Pokkari.requires = function(scriptName) 
{
	var url = '/scripts/' + scriptName + '.js';	
	
	if (!Pokkari.includedScripts[scriptName])
	{
		Pokkari.includedScripts[scriptName] = true;

		if (document.readyState && document.readyState != "completed")
		{
			document.write("<script type='text/javascript' src='"+url+"'></script>");
		}
		else 
		{
			var script = document.createElement("script");
			script.type = 'text/javascript';
			script.src = url;
			var body = document.body || document.documentElement;	
			body.appendChild(script);
		}
	}

	return true;
}

Pokkari.defaultThumbnailUrl = "/skin/blipnew/placeholder_video.gif";

Pokkari.includes("pokkari");

Pokkari.includes("pokkariDom");

// Constructor for PokkariElement
function PokkariElement(params) {
	// Load parameters passed in as associative array
	for (var param in params) {
		this[param] = params[param];
	}

	// void anything returns the undefined value
	if (this.element != void(0) && this.element.constructor == HTMLElement) {
		this.bindToHTMLElement(this.element);
	}

}

// Called after construction -- does nothing here, override to do stuff at
// construction time.
PokkariElement.prototype.onConstruct = function() {}

/**
 * Find siblings of a given class name 
 */

/*
PokkariElement.prototype.getSiblingsByClassName = function(className,element) {

	if(arguments.length == 1) {
		element = this.element;
	}

	alert("Class name: " + className + " Element: " + element);

	var matchedSiblings = new Array();

	alert("Parent node: " + element.parentNode.parentNode);

	for(var i = 0; i < element.parentNode.parentNode.getElementsByTagName('*'); i++) {
		alert("got one: " + element.parentNode.getElementsByTagName('*')[i]);
		if(element.parentNode.getElementsByTagName('*')[i].className.match(className)) {
			matchedSiblings.push(element.parentNode.getElementsByTagName('*')[i]);
		}
	}

	return matchedSiblings;
}
*/

// Constructor.prototype is the proper way to make a method.
PokkariElement.prototype.bindToHTMLElement = function(e) 
{
	// Set the element property
	this.element = e;
	
	// Create a circular reference so we can get back here from the DOM
	this.element.pokkariElement = this;

	// Element index for event handling
	this.pokkariElementIndex = e.pokkariElementIndex;
	
	if (!this.immutable_params) {
		this.setParamsFromAttributes(e);
	}

	this.isBound();
}

PokkariElement.prototype.isBound = function() {
	// noop
}

// Parses pokkariParameters and returns an associative array.
PokkariElement.GetParamsFromAttributes = function(e)
{
	if (e.getAttribute("pokkariParameters")) {
		var attr = e.getAttribute("pokkariParameters");
		var moreParams = eval("var a={"+attr+"}; a;");
		for (var param in moreParams) {
			if(typeof(moreParams[param]) == "string") {
				moreParams[param] = unescape(moreParams[param]);
			}
		}
	}
	
	return moreParams;
}

// Sets the current pokkariElement's parameters according to attributes.
PokkariElement.prototype.setParamsFromAttributes = function(e) 
{
	var params = PokkariElement.GetParamsFromAttributes(e);
	for (var param in params) {
		this[param] = params[param];
	}
}

function createObjectFromXML(topElement) {
/*	var child;
	if (topElement.hasChildNodes()) {
		var obj = new Object();
		var children = topElement.childNodes;
		for (var i=0; i<children.length; i++) {
			child = children[i];
			if (child.nodeType == 3) { // Text node
				return child.nodeValue;
			}
			else if (obj[child.tagName].prototype == Array) {
				obj.push(createObjectFromXML(child));
			}
			else if (obj[child.tagName != void(0)) {
				obj = new Array();
				obj.push(createObjectFromXML(child));
			}
			else {
				obj[child.tagName] = createObjectFromXML(child);
			}
		}
		return obj;
	}
*/  // TODO FIXME Ill conceived -- rethink
}
		
PokkariElement.prototype.fromXML = function(topElement) {
	// TODO FIXME		
}

PokkariElement.prototype.toXML = function() {
	// TODO FIXME
}

PokkariElement.prototype.fromXMLById = function(id) {
	// This will get the <xml> element, we actually need to load from the
	// firstChild of this.
	var xmlElement = document.getElementById(id);
	this.fromXML(xmlElement.firstChild);
}

PokkariElement.prototype.toXMLString = function() {
	var element = this.toXML();
	var name = this.toString().toLowerCase();
	return "<"+name+">"+element.innerHTML+"</"+name+">";
}

PokkariElement.prototype.eventHandler = function(func,context) {
	var funcCode = "var self = PokkariElement.AllElements[" +
		this.pokkariElementIndex + "].pokkariElement;\n" +
		"if (!e) e = window.event;\n" +
		"var sender = e && (e.target || e.srcElement);\n" + 
		"var context = " + 
			(typeof(context) == undefined ? "null" : "'"+context+"'") +
		";\n" +
		"var code = " + func + "\n" +
		"return code(self,sender,e,arguments,context);";

	return new Function("e",funcCode);
}

PokkariElement.destroy = function(element)
{
	if (typeof(element) == "string")
		element = document.getElementById(element);
	
	if (!element)
		return null;

	element.pokkariElement = null;
	PokkariElement.AllElements[element.pokkariElementIndex] = null;
	element.pokkariElementIndex = null;
}

PokkariElement.cast = function(element,type,params)
{
	if (typeof(element) == "string")
		element = document.getElementById(element);
	
	if (!element)
		return null;

	PokkariElement.AllElements.push(element);
	element.pokkariElementIndex = PokkariElement.AllElements.length - 1;
	
	var obj;	
	obj = eval("new "+type+"()");

	if (!obj)
		return null;
		
	obj.bindToHTMLElement(element);

	for (var key in params) {
		if (typeof(obj[key]) == "undefined")
			obj[key] = params[key];
	}

	if (typeof(obj.onConstruct) == "function") {
		obj.onConstruct();
	}
	
	if (document.readyState == "completed") {
		if (typeof(obj.onLoadHandler) == "function") {
			obj.onLoadHandler();
		}
	}

	return obj;
}

// Casts all HTMLElements by using name and attributes.
PokkariElement.CastHTMLElements = function() {
	var element;
	var type;
	var obj;
	var attribute;

	// Look for elements named 'PokkariElement' 
	var pokkariElements = PokkariElement.GetPokkariElements();

	for (var i=0; i<pokkariElements.length; i++) {
	
		element = pokkariElements[i];

		// If the element already has a pokkariElementIndex then it was already cast.
		if (typeof(element.pokkariElementIndex)=="undefined")
		{
			element.pokkariElementIndex = i;

			attribute = element.getAttribute("pokkariParameters") ?
				element.getAttribute("pokkariParameters") :
				"";

			// Create an object as specified by type 
			if (element.getAttribute("pokkariType")) {
				type = element.getAttribute("pokkariType");
				try {
					obj = eval("new "+type+"()");
				}
				catch (e) { 
					alert("While creating a "+type+": \""+e.message+"\"\n"+attribute);
					continue;
				}
			
				// Now try to bind that to object to the element.
				if (obj != void(0)) {
					try {
						obj.bindToHTMLElement(element);
					}
					catch (e) {
						alert("While binding: "+e.message+"\n"+attribute);
						continue;
					}
				}
	
				if (obj.onConstruct != void(0) && 
					typeof obj.onConstruct == "function") 
				{
					try {
						obj.onConstruct();
					}
					catch (e) {
						alert("onConstruct: "+e.message);
						continue;
					}
				}
			}
		}
	}
}

// Executes onLoad="" attributes and obj.onLoadHandler functions
PokkariElement.ExecuteOnLoadHandlers = function() {
	var element;

	// Get all the PokkariElements
	var pokkariElements = PokkariElement.GetPokkariElements();
	
	for (var i=0; i<pokkariElements.length; i++) {
		element = pokkariElements[i];

		// Make sure it's actually a pokkariElement..
		if (element.pokkariElement != void(0)) {
		    if (element.pokkariElement.oninit != void(0) && typeof(element.pokkariElement.oninit) == "function") {
		        element.pokkariElement.oninit();
		    }
			// If onLoadHandler exists and is a function, call it.
			if (element.pokkariElement.onLoadHandler != void(0) && typeof(element.pokkariElement.onLoadHandler) == "function") {
				element.pokkariElement.onLoadHandler();
			}
			// If the HTMLElement has onLoad attribute, eval it.
			if (element.getAttribute("onLoad")) {
				element.pokkariElement.onLoadInlineHandler = new Function(
					element.getAttribute("onLoad")
				);
				element.pokkariElement.onLoadInlineHandler();
			}
		}
	}
}

// IE executes attached events in random order, so we need to make sure ours
// run in order..
PokkariElement._OnLoadHandler = function()
{
	PokkariElement.CastHTMLElements();
	PokkariElement.ExecuteOnLoadHandlers();
}

PokkariElement.GetPokkariElements = function()
{
	if (!PokkariElement.AllElementsFound) {

		var element_types = ["div","table","tr","td","input","textarea","select"];
	
		for (var i=0; i<element_types.length; i++) {
			var elements = document.getElementsByTagName(element_types[i]);
			for (var j=0; j<elements.length; j++) {
				if (elements[j].name == "PokkariElement" ||
					elements[j].getAttribute('name') == "PokkariElement" ||
					elements[j].getAttribute('pokkarielement') == "pokkarielement"
				) {
					PokkariElement.AllElements.push(elements[j]);
				}
			}
		}

		PokkariElement.AllElementsFound = true;
	}
	
	return PokkariElement.AllElements;
}

PokkariElement.AllElements = [];
PokkariElement.AllElementsFound = false;

PokkariElement.prototype.attachEvent = function(eventName,func)
{
    if (!document.all) {
        eventName = eventName.replace(/^on/,"");        
        this.element.addEventListener(eventName,func,true);
    }
    else {
        this.element.attachEvent(eventName,func);
    }
}

// Set up onLoad 
Pokkari.AttachEvent(window,"load",PokkariElement._OnLoadHandler);

/**
 * Used to add attachEvent support to Mozilla browsers
 */
function PokkariElementAttachEvent(eventName,func)
{
    // Strip on the leading on.
    eventName = eventName.replace(/^on/,"");
        
    this.addEventListener(eventName,func,true);
}

// Add attachEvent support to Mozilla.
if (!document.all)
{
    window.attachEvent = PokkariElementAttachEvent;
    document.attachEvent = PokkariElementAttachEvent;
    if(Element.prototype) {
    	Element.prototype.attachEvent = PokkariElementAttachEvent;
    } else {
	Element.attachEvent = PokkariElementAttachEvent;
    }
}

pokkariChooser = function() {
	this.init(this);
}

pokkariChooser.prototype = new PokkariElement();
pokkariChooser.constructor = pokkariChooser;

pokkariChooser.prototype.init = function() {
	this.topchoices = new Array();
	this.choicepanes = new Array();
}

pokkariChooser.prototype.onConstruct = function() {
	
	this.findChooserTriggers();
	this.findChooserPanes();
}

pokkariChooser.prototype.findChooserTriggers = function() {

	var potentialTriggers = this.element.getElementsByTagName('input');
	for(var i = 0; i < potentialTriggers.length; i++) {
		if(
			potentialTriggers[i].getAttribute('pokkariType') == 'pokkariChooserTriger'
		) {
			potentialTriggers[i].chooser = this;
		}
	}
}

pokkariChooser.prototype.findChooserPanes = function() {
	// All chooser panes must be divs
	var potentialPanes = this.element.getElementsByTagName('div');
	for(var i = 0; i < potentialPanes.length; i++) {
		/*
			We don't really have to cast our chooser panes as anything,
			although at some point we may want to cast them as
			PokkariChooserPanes.  Right now it just doesn't feel all that
			necessary.
		*/
		
	}
}

pokkariChooserTrigger = function() {
	alert("Got a chooser trigger");
	this.init(this);
}

pokkariChooserTrigger.prototype = new PokkariElement();
pokkariChooserTrigger.constructor = pokkariChooserTrigger;

pokkariChooserTrigger.prototype.init = function() {
	this.chooser = void(0);
}

pokkariChooserTrigger.prototype.onConstruct = function() {
	
	this.element.onchange = function() {
		alert("Hi there!");
	}	
	
}
function PokkariXmlRequest(params) {
	if (params)
	{
		this.url = params.url;
		this.method = params.method;
	}

	if (!this.method)
		this.method = "GET";
}

// Set this up as a subclass of PokkariElement
PokkariXmlRequest.prototype = new PokkariElement();
PokkariXmlRequest.prototype.constructor = PokkariXmlRequest;

// Initialize our lookup table.
PokkariXmlRequest.lookupTable = new Object();

PokkariXmlRequest.prototype.send = function(data)
{
	var url = this.url;

	// Fix the URL to make sure it's going to xmlhttprequest skin.
	if (!url.match(/skin=xmlhttprequest/)) {
		if (!url.match(/\?/)) {
			url += "?skin=xmlhttprequest";
		}
		else {
			url += ";skin=xmlhttprequest";
		}
	}
	
	// Save our object so we can get it later.
	this.key = PokkariXmlRequest.StoreObject(this);

	// Create the request object, or throw an error.
	if (window.XMLHttpRequest) {
		this.request = new XMLHttpRequest();
	}
	else if (window.ActiveXObject) {
		this.request = new ActiveXObject("Microsoft.XMLHTTP");
	}

	if (!this.request) {
		throw new Error("Could not create XML-HTTP Request object.");
	}

	// Send the request.
	try {
		// When it callsback, get our object and call our handler
		this.request.onreadystatechange = new Function("PokkariXmlRequest.GetObject("+this.key+").onreadystatechange()");
		this.request.open(this.method,url,true);
		this.request.send(data);
	}
	catch (exception) {
		alert("While sending XML-HTTP request: "+exception.message);
	}
}

// This is our handler called from the new Function in send.
PokkariXmlRequest.prototype.onreadystatechange = function()
{
	if (this.request.readyState == 4) {
		if (this.request.status == 200) {
			// Send this as a parameter incase we override without subclassing
			this.onsuccess(this); 
		}
		else {
			this.onfailure(this);
		}
		// We're done with the request so now delete the key
		PokkariXmlRequest.DeleteObject(this.key);
		delete this.key;
		
		// Call the oncompleted method
		this.oncompleted(this);
	}
	else if (this.request.readyState == 1) {
		this.onloading(this);
	}
}

// This is the default onsuccess method.  It should be overridden
PokkariXmlRequest.prototype.onsuccess = function(object) 
{
	alert("Got back: "+ this.request.responseText);
}

// Called when the request fails.
PokkariXmlRequest.prototype.onfailure = function(object)
{
	//alert(this.request.status + " " + this.request.statusText);
	throw new Error("XML-HTTP Request Server Error: "+this.request.status+" "+this.request.statusText);
}

// Called while the request is loading.  Override to display something if you want.
PokkariXmlRequest.prototype.onloading = function (object) {}

// Called after the request completes.  Override to hide something if you want.
PokkariXmlRequest.prototype.oncompleted = function (object) {}

// Convenience methods so we don't end up saying request.request.whatever;
PokkariXmlRequest.prototype.getResponseXml = function() {
	return this.request.responseXML;
}

PokkariXmlRequest.prototype.getResponseText = function() {
	return this.request.responseText;
}

PokkariXmlRequest.prototype.getStatus = function() {
	return this.request.status;
}

PokkariXmlRequest.prototype.getStatusText = function() {
	return this.request.statusText;
}

// Stores an object in our lookup table.
PokkariXmlRequest.StoreObject = function(object)
{
	var key = PokkariXmlRequest.GenerateKey();
	PokkariXmlRequest.lookupTable[key] = object;
	return key;
}

// Removes an object from the lookupTable.
PokkariXmlRequest.DeleteObject = function(key)
{
	if (key != void(0)) {
		delete PokkariXmlRequest.lookupTable[key];
	}
}

// Get an object from the lookuptable by key.
PokkariXmlRequest.GetObject = function(key)
{
	return PokkariXmlRequest.lookupTable[key];
}

PokkariXmlRequest.GenerateKey = function()
{
	var random = Math.floor(Math.random() * 10000);
	var count = 1;
	
	while (PokkariXmlRequest.lookupTable[random] != void(0)) {
		random = Math.floor(Math.random() * 10000);
		if (count++ > 100) {
			throw new Error("Could not locate a unique key after 100 tries");
		}
	}

	return random;
}


function PokkariPost(params) {

	if (params) {
		this.posts_id			=	params.posts_id;
		this.item_id			=	params.item_id;
		this.item_type			=	params.item_type;
		this.title			=	params.title;
		this.wikiword			=	params.wikiword;
		this.categories_id		=	params.categories_id;
		this.category			=	params.category;
		this.enable_permissions_widget	=	params.enable_permissions_widget;
		this.enable_folders_widget	=	params.enable_folders_widget;
		this.permissions_use_buckets	=	params.permissions_use_buckets;
	}
}

// Set this up a subclass of PokkariElement..
PokkariPost.prototype = new PokkariElement();
PokkariPost.prototype.constructor = PokkariPost;

PokkariPost.prototype.onConstruct = function() {

	if (this.enable_folders_widget) {
		this.folders_widget = document.getElementById('folders_widget');
	}

	if (this.enable_permissions_widget) {
		this.permissions_widget = document.getElementById('permissions_widget');
	}

	if (this.folders_widget && this.enable_permissions_widget) {

		/* TODO - FIXME - Have to check whether we're on autopilot here
		   and only update the permissions widget if we are on autopilot
		*/
		
		var foldSelect = function(e) {
			e.target.pokkariPostElement.updatePotentialPermissions(e.target.options[e.target.selectedIndex].value);
		}

		this.folders_widget.pokkariPostElement = this;
	
		try {
			this.folders_widget.addEventListener('change',foldSelect,true);
		} catch(e) {
			this.folders_widget.addevent('change',foldSelect);
		}
	}
}

PokkariPost.prototype.fromXML = function(domObject) {

		this.posts_id = domObject.getElementsByTagName('id')[0].firstChild.nodeValue;
		this.title = domObject.getElementsByTagName('title')[0].firstChild.nodeValue;
		this.item_type = domObject.getElementsByTagName('item_type')[0].firstChild.nodeValue;
		this.item_id = domObject.getElementsByTagName('item_id')[0].firstChild.nodeValue;
		this.top_element = domObject;

}

PokkariPost.prototype.toXMLString = function() {
	var xml = "<post>\n";
		xml += "<id>"+this.posts_id+"</id>\n";
		xml += "<guid>"+this.posts_guid+"</guid>\n";
		xml += "<item_type>"+this.item_type+"</item_type>\n";
		xml += "<item_id>"+this.item_id+"</item_id>\n";
		xml += "<title>"+this.title+"</title>\n";
		xml += "</post>\n";

	return xml;
}

PokkariPost.prototype.getPermissionsEditable = function(folders_id) {
	
	// Only folders have editable permissions
	if(this.item_type != 'folder') {
		return false;
	}

	if(this.permissions_use_buckets) {
		/*
		   With buckets folders are only editable if their
		   parent is root
		*/

		if(folders_id == 0 || folders_id == void(0)) {
			return true;
		} else {
			return false;
		}
		
	} else {
		// Without buckets folders are always editable
		return true;
	}
	
}

PokkariPost.prototype.updatePotentialPermissions = function(folders_id) {
	/*
	   Now we want to get a new permissions value system via
	   xmlhttprequest so we can show the user what he's getting
	   himself into by moving his post
	*/

	var request = new PokkariXmlRequest();
	request.pokkariPost = this;
	request.url = "/posts/get_permissions?folders_id="+folders_id;
	request.onsuccess = this.receivePotentialPermissions;
	request.onfailure = this.potentialPermissionsFailure;
	request.send();

}

PokkariPost.prototype.receivePotentialPermissions = function(request) {
	/* 
		TODO - FIXME

		We're doing this the wrong way right now.
		The permissions widget should be a PokkariElement subclass
		that simply accepts our XML (or at least a parsed
		version of our XML) as input, and will rewrite itself
		for us.

		We're not doing that right now, but we should RSN.
	*/

	// These are the types of permissions Mango currently supports
	var permTypes = new Array('r','w','a');
	
	// Get our groups
	var groups = request.getResponseXml().getElementsByTagName('group');

	var permissionsEditability = request.pokkariPost.getPermissionsEditable(request.getResponseXml().getElementsByTagName('folders_id')[0].firstChild.nodeValue);

	// Loop through our groups
	for(var i = 0; i < groups.length; i++) {
		
		// Loop through our permissions types and set them in
		// the widget according to what we have in XML
		for(var j = 0; j < permTypes.length; j++) {

			var checkbox = document.getElementById('pm_'+groups[i].getElementsByTagName('guid')[0].firstChild.nodeValue+'_'+permTypes[j]);

			/* enable and disable the permissions widget as
			   appropriate
			*/
			if(permissionsEditability) {
				checkbox.disabled = false;
			} else {
				checkbox.disabled = true;
			}

			if(groups[i].getElementsByTagName(permTypes[j])[0].firstChild == void(0)) {
				checkbox.checked = false;	
			} else if(groups[i].getElementsByTagName(permTypes[j])[0].firstChild.nodeValue == 1) {
				checkbox.checked = true;
			} else {
				checkbox.checked = false;
			}
		}
	}
}

PokkariPost.prototype.failedPotentialPermissions = function(request) {

	alert("Failed to retrieve permissions of selected folder.  Permissions will still be set properly, but no permissions preview is available.");

}

PokkariPost.prototype.processPotentialPermissions = function() {
	alert("Processing potential permissions");
}

PokkariPost.prototype.addMetaHTML = function(html) {
	var element = document.getElementById("post_meta_add_"+this.posts_id);

	if (element != undefined) {
		element.innerHTML += html;
	}
}

/* toggleToolsMenuItem(button,div) (Public) [JD]

Similar to toggleHiddenDiv, but also selects the button, 
deselects all other buttons, and hides all other divs that
are tools related.
*/
PokkariPost.prototype.toggleTool = function(tool) {
	//var top_element = document.getElementById('post_'+this.posts_id);
	var button = document.getElementById('tools_menu_'+tool+'_'+this.posts_id);
	var tool = document.getElementById('container_hidden_'+tool+'_'+this.posts_id);
	var all_buttons = document.getElementsByName('tools_menu');
	var all_tools = document.getElementsByName('tools_tool');

	// Highlight the selected button, show the selected tool.
	if (tool.style.display == "block") {
		button.className = "item";
		tool.style.display = "none";
	}
	else {
		button.className = "item item_selected";
		tool.style.display = "block";
	}
		
	// Unhighlight and hide all appropriate DIVs
	for(var i=0; i<all_buttons.length; i++) {
		if (all_buttons[i].id != button.id) {
			all_buttons[i].className = "item";		
		}

		if (all_tools[i].id != tool.id) {
			all_tools[i].style.display = "none";
		}
	}
}

PokkariPost.getCurrentPost = function(element) {
	while(element.pokkariElement == void(0)) {
		if (element.parentNode) {
			element = element.parentNode;
		}
		else {
			alert("Could not locate pokkariElement");
			return;
		}
	}
	return element.pokkariElement;
}
		
PokkariPost.prototype.validateField = function(field,requirements,billboard) {

	if(!field.value && requirements['required']) {
		this.setError(billboard,'The "' + field.name + '" field is required.');
		return false;
	}

	if(requirements.extension) {
		var extensionValidates = false;
		for(var i = 0; i < requirements.extension.length; i++) {
			if(field.value.match(new RegExp("\." + requirements.extension[i] + "$","i"))) {
				extensionValidates = true;
			}
		}

		if(!extensionValidates) {
			this.setError(billboard,'<p>The file you have selected for uploading ("' + field.value + '") is of a type that we don\'t support for this purpose.  If you\'d like us to support this file type please contact support with your request.</p><p>Files you upload here must have an extension like: <b>' + requirements.extension.join(', ') + '</b>.</p>');
			return false;
		}
	}

	this.setError(billboard,'');
	return true;

}

PokkariPost.prototype.setMessage = function(billboard,message) {
	
	if(message) {
		billboard.innerHTML = message;
		billboard.style.display = 'block';
		fader.fade(billboard,'ffffff','666699');
	} else {
		billboard.style.display = 'none';
	}
	
}

PokkariPost.prototype.setError = function(billboard,message) {
	
	if(message) {
		billboard.innerHTML = message;
		billboard.style.display = 'block';
		fader.fade(billboard,'ffffff','CC6666');
	} else {
		billboard.style.display = 'none';
	}
	
}

PokkariPost.bookmark = function(posts_id,bookmarker,new_text) {
        if(!posts_id) {
                alert("You must provide a posts ID");
                return false;
        }

        var request = new PokkariXmlRequest("/bookmarks/add");
        request.posts_id = posts_id;
        request.bookmarker = bookmarker;
        request.new_text = new_text;
        request.url = "/bookmarks/add/?posts_id="+posts_id;
        request.onsuccess = PokkariPost.bookmarked;
        request.onfailure = PokkariPost.bookmarkFailure;
        request.send();
}

PokkariPost.bookmarked = function(request) {

        if(request.bookmarker && request.new_text) {
                request.bookmarker.innerHTML = request.new_text;
        } else {
                alert("Bookmarking successful");
        }

        if($('mini_bookmarks_list')) {
                PokkariPost.updateBookmarks($('mini_bookmarks_list'),request.posts_id);
        }

}

PokkariPost.bookmarkFailure = function(request) {
        alert("Failure!");
}

PokkariPost.updateBookmarks = function(obj,posts_id) {

        var updater = new Ajax.Updater(
                obj.id,
                '/bookmarks/view/?view=bookmarks_quick&no_wrap=1&pagelen=-1&posts_id='+posts_id,
                {
                        method  :       'get'
                });
}

PokkariPost.unbookmark = function(posts_id,bookmarker,new_text) {
        if(!posts_id) {
                alert("You must provide a posts ID");
                return false;
        }

        var request = new PokkariXmlRequest("/bookmarks/remove");
        request.posts_id = posts_id;
        request.bookmarker = bookmarker;
        request.new_text = new_text;
        request.url = "/bookmarks/remove/?posts_id="+posts_id;
        request.onsuccess = PokkariPost.unbookmarked;
        request.onfailure = PokkariPost.unbookmarkFailure;
        request.send();
}

PokkariPost.unbookmarked = function(request) {

        if(request.bookmarker) {
                switch(request.bookmarker.tagName) {
                        case 'TR':
                                request.bookmarker.style.display = 'none';
                        break
                        default:
                                if(request.new_text) {
                                        request.bookmarker.innerHTML = request.new_text;
                                } else {
                                        alert("Bookmark removal successful!");
                                }
                        break
                }
        }

        if($('mini_bookmarks_list')) {
                PokkariPost.updateBookmarks($('mini_bookmarks_list'),request.posts_id);
        }                                                                   

}

PokkariPost.unbookmarkFailure = function(request) {
        alert("Failure!");
}

PokkariPost.prototype.show = function() {
	this.element.style.display = "";  // Defualt
}

PokkariPost.prototype.hide = function() {
	this.element.style.display = "none";
}

function PokkariEditable(params) {

	if (params) {
		this.url = params.url;
		this.innerId = params.innerId;
		this.innerElement = params.innerElement;
		this.name = params.name;
	}
}

PokkariEditable.prototype = new PokkariXmlRequest();
PokkariEditable.prototype.constructor = PokkariXmlRequest;

PokkariEditable.prototype._setInnerElement = function()
{
	if (this.innerId) {
		this.innerElement = document.getElementById(this.innerId);
	}
	// Otherwise get the first inner DIV..
	else {
		var subelements = this.element.getElementsByTagName("div");
		this.innerElement = subelements[0];
	}
}

PokkariEditable.prototype.edit = function()
{
	if (!this.innerElement)
		this._setInnerElement();

	if (this.element.style.display == "none")
		this.element.style.display = "block";

	this.element.style.minHeight = "300px";
	this.innerElement.style.minHeight = "300px";

	if (this.innerElement.style.pixelHeight && 
		this.innerElement.style.pixelHeight < 300)
		this.innerElement.style.pixelHeight = 300;
	
	this.editorId = "mce_editor_" + tinyMCE.idCounter;

	tinyMCE.addMCEControl(this.innerElement);
}

PokkariEditable.prototype.getHTML = function()
{
	var editor = tinyMCE.instances[this.editorId];

	if (!editor)
		return void(0);

	tinyMCE._setHTML(editor.getDoc(), editor.getBody().innerHTML);

	var html = editor.getBody().innerHTML;

	return html;
}

PokkariEditable.prototype.save = function()
{
	alert("Feature not implemented");
}

PokkariEditable.prototype.onsuccess = function()
{
}

PokkariEditable.prototype.onloading = function ()
{
	var element = document.getElementById("xmlhttploading");
	var top = window.pageYOffset ? window.pageYOffset : document.body.scrollTop;

	if (element != void(0) && top != void(0)) {
		element.style.top = top;
		element.style.display = "block";
	}
}

PokkariEditable.prototype.oncompleted = function()
{
	var element = document.getElementById("xmlhttploading");
	if (element != void(0)) {
		element.style.display = "none";
	}
}

PokkariEditable.GetEditableElements = function()
{
	var elements = PokkariElement.GetPokkariElements();
	var results = new Array();
	
	for (var i in elements)
	{
		if (elements[i].pokkariElement instanceof PokkariEditable)
			results.push(elements[i]);
	}

	return results;
}

PokkariEditable.EditAll = function()
{
	var elements = PokkariEditable.GetEditableElements();
	for (var i in elements)
	{
		elements[i].pokkariElement.edit();
	}
}

PokkariEditable.SaveAll = function(data)
{
	var elements = PokkariEditable.GetEditableElements();
	var e;
	var post;
	var url;

	for (var i in elements)
	{
		e = elements[i].pokkariElement;
		url = e.url;
		post += escape(e.name) + "=" + escape(e.getHTML()) + "&";
	}

	post += data;

	if (url)
	{
		var request = new PokkariXmlRequest();
		request.url = url;
		request.onsuccess = function() { window.location.reload(); }
		request.onloading = PokkariEditable.prototype.onloading;
		request.oncompleted = PokkariEditable.prototype.oncompleted;

		request.send(post);
	}
}

function PokkariFileReplacer(params) {
	if (params)
	{
	}

}

PokkariFileReplacer.prototype = new PokkariElement();
PokkariFileReplacer.prototype.constructor = PokkariElement;

PokkariFileReplacer.prototype.onLoadHandler = function()
{
	this.button = this.element.getElementsByTagName("button")[0];
	this.text = this.element.getElementsByTagName("input")[0];
	this.file = this.element.getElementsByTagName("input")[1];
	this.select = this.element.getElementsByTagName("select")[0];

	Pokkari.AttachEvent(this.button,"click",
		this.eventHandler(this.onReplaceClick));
}

PokkariFileReplacer.prototype.onReplaceClick = function(self,sender,e)
{
	
	self.text.style.display = "none";
	self.file.style.display = "inline";
	self.button.style.display = "none";

	if (self.select) {
		self.select.disabled = false;
	}

	// I'm handling it thanks.
	return true;
}
/**
 *	PokkariElementTabber class
 *	subclasses @PokkariElement
*/

function  PokkariElementTabber(params) {

	this.tabs = new Object();
}

PokkariElementTabber.prototype = new PokkariElement();
PokkariElementTabber.prototype.constructor = PokkariElementTabber;
PokkariElementTabber.prototype.superclass = PokkariElement;

PokkariElementTabber.prototype.getAnchors = function() {
	
	var collection = new Array();
	
	if(this.element) {
		var lis = this.element.getElementsByTagName('LI');
		
		for(var i = 0; i < lis.length; i++) {
			for(var ii = 0; ii < lis[i].childNodes.length; ii++) {
				if(
					lis[i].childNodes[ii].nodeType == 1 &&
					lis[i].childNodes[ii].getAttribute('tabelementpartner')
				) {
					collection.push(lis[i].childNodes[ii]);
				}
			}
		}
	}

	return collection;
}

PokkariElementTabber.prototype.isBound = function() {

	var anchors = this.getAnchors();

	for(var i = 0; i < anchors.length; i++) {
		anchors[i].PokkariElementTabber = this;
		anchors[i].onclick = new Function("this.PokkariElementTabber.performSwitch(this); this.PokkariElementTabber.memorializeInCookies(this.getAttribute('tabelementpartner'));");
		if(anchors[i].className.match('active')) {
			this.performSwitch(anchors[i]);
		}
	}

}

PokkariElementTabber.prototype.hideTabElementPartners = function() {
	
	var anchors = this.getAnchors();

	for(var i = 0; i < anchors.length; i++) {
		if(anchors[i].getAttribute('tabelementpartner')) {
			anchors[i].className = anchors[i].className.replace(/(\s+)?active(\s+)?/,'');
			document.getElementById(anchors[i].getAttribute('tabelementpartner')).style.display = 'none';
		}
	}
}

PokkariElementTabber.prototype.setActiveTabElementPartner = function(id,callback) {

	this.hideTabElementPartners();
	
	document.getElementById(id).style.display = 'block';

	if(callback) {
		eval(callback+"()");
	}
	
}

PokkariElementTabber.prototype.performSwitch = function(anchor) {

	if(anchor.getAttribute('tabelementpartner')) {
		this.setActiveTabElementPartner(anchor.getAttribute('tabelementpartner'),anchor.getAttribute('tabelementcallback'));
		anchor.className += " active";
		anchor.blur();
	} else {
		// nooop, at least for now
	}

}

PokkariElementTabber.prototype.memorializeInCookies = function(selectedId) {

	if(typeof(Pokkari.SetCookiePreference) == "function") {
		var anchors = this.getAnchors();
		if(this.name) {
			Pokkari.SetCookiePreference({
				name : 'ts_' + this.name + '_hs',
				value : 1
			});
		}
		for(var i = 0; i < anchors.length; i++) {
			if(anchors[i].getAttribute('tabelementpartner') == selectedId) {
				Pokkari.SetCookiePreference({
					name : 'tab_' + anchors[i].getAttribute('tabelementpartner') + '_s',
					value : 1
				});
			} else {
				Pokkari.SetCookiePreference({
					name : 'tab_' + anchors[i].getAttribute('tabelementpartner') + '_s',
					value : 0
				});
			}
		}
	}
}

/**
 *	PokkariPlayer class
 * 	subclasses @PokkariElement
 *	For writing of video player inline
 *	$Header: /usr/local/cvsroot/otter/html/scripts/pokkariPlayer.js,v 1.1.1.1 2007/01/08 21:30:06 juday Exp $
*/

function PokkariPlayer(params) { 
	this.MAX_WIDTH = 500;
	this.MAX_HEIGHT = 500;
}

PokkariPlayer.prototype = new Object();
PokkariPlayer.prototype.constructor = PokkariPlayer;

PokkariPlayer.prototype.setSiteUrl = function(url) {
	this.site_url = url;
}

PokkariPlayer.prototype.getSiteUrl = function() {
	if(this.site_url) { 
		return this.site_url;
	} else {
		return '';
	}
}

PokkariPlayer.prototype.setPrimaryMediaUrl = function(url) {
	this.primary_media_url = url;
}

PokkariPlayer.prototype.getPrimaryMediaUrl = function() {
	return this.primary_media_url;
}

PokkariPlayer.prototype.setPostsId = function(id) {
	this.posts_id = id;
}

PokkariPlayer.prototype.getPostsId = function() {
	return this.posts_id;
}

PokkariPlayer.prototype.setWidth = function(width) {
	if(width == -1) { width = 320 }
	this.width = width;
}

PokkariPlayer.prototype.setResizeRatio = function(ratio) {
	this.resizeRatio = ratio;
}

PokkariPlayer.prototype.getResizeRatio = function() {
	return this.resizeRatio;
}

/**
* TODO - FIXME
* You MUST call getWidth() before getHeight() - otherwise you'll get
* the wrong aspect ratio.
* Should probably move this into a single getDimensions() method.
*/
PokkariPlayer.prototype.getWidth = function() {
	if(this.width) {
		if(this.width < this.MAX_WIDTH) {
			return this.width;
		} else {
			this.setResizeRatio(this.MAX_WIDTH / this.width);
			return this.MAX_WIDTH;
		}
	} else {
		return '320';
	}
}

PokkariPlayer.prototype.getPlayerWidth = function() {
	return this.getWidth();
}

PokkariPlayer.prototype.setHeight = function(height) {

	if(height == -1) { height = 240 }

	this.height = height;
}

PokkariPlayer.prototype.getHeight = function() {
	if(this.height) {
		if(this.getResizeRatio()) {
			return this.height * this.getResizeRatio();
		} else {
			if(this.height < this.MAX_HEIGHT) {
				return this.height;
			} else {
				return this.MAX_HEIGHT;
			}
		}
	} else {
		return '260';
	}
}

PokkariPlayer.prototype.getPlayerHeight = function() {
	return this.getHeight();
}

PokkariPlayer.prototype.setAutoPlay = function(ap) {
	this.autoPlay = ap;
}

PokkariPlayer.prototype.getAutoPlay = function() {
	if(this.autoPlay) {
		return true;
	} else {
		return false;
	}
}

PokkariPlayer.prototype.setPlayerTarget = function(obj) {
	this.playerTarget = obj;	
}

PokkariPlayer.prototype.getPlayerTarget = function() {
	return this.playerTarget;
}

PokkariPlayer.prototype.getPlayer = function() {
	return $('video_player_object');
}

PokkariPlayer.prototype.getTime = function() {
	var player = this.getPlayer();

	if (typeof(player) != "undefined") {
		if (typeof(player.controls) != "undefined") {
			return player.controls.currentPosition;
		}
		else if (typeof(player.GetTime) != "undefined") {
			return player.GetTime() / player.GetTimeScale();
		}
		else if (typeof(player.GetVariable) != "undefined") {
			return player.GetVariable("videoCurrentTime");
		}
	}

	return null;
}

PokkariPlayer.prototype.setTime = function() {
	throw("Cannot set time on generic embed");
}

PokkariPlayer.prototype.getDuration = function() {
	throw("Cannot get duration from generic embed");
}

PokkariPlayer.prototype.getStatus = function() {
	var player = this.getPlayer();

	if (typeof(player) != "undefined") {
		if (typeof(player.playState) != "undefined") {
			return PokkariWindowsPlayer.prototype.getStatus.apply(this);
		}
		else if (typeof(player.GetPluginStatus) != "undefined") {
			return PokkariQuicktimePlayer.prototype.getStatus.apply(this);
		}
		else if (typeof(player.GetVariable) != "undefined") {
			return PokkariFlashPlayer.prototype.getStatus.apply(this);
		}
	}

	return null;
}

PokkariPlayer.prototype.setPermalinkUrl = function(url) {
	this.permalinkUrl = url;
}

PokkariPlayer.prototype.getPermalinkUrl = function() {
	return this.permalinkUrl;
}

PokkariPlayer.prototype.setAdvertisingType = function(t) {
	this.adType = t;
}

PokkariPlayer.prototype.getAdvertisingType = function() {
	return this.adType;
}

PokkariPlayer.prototype.setPrerollAnimationUrl = function(value) {
	this.prerollAnimationUrl = value;
}

PokkariPlayer.prototype.getPrerollAnimationUrl = function() {
	return this.prerollAnimationUrl;
}

PokkariPlayer.prototype.setPostrollAnimationUrl = function(value) {
	this.postrollAnimationUrl = value;
}

PokkariPlayer.prototype.getPostrollAnimationUrl = function() {
	return this.postrollAnimationUrl;
}

PokkariPlayer.prototype.getPostsTitle = function() {
	return this.postsTitle;
}

PokkariPlayer.prototype.setPostsTitle = function(value) {
	this.postsTitle = value;
}

PokkariPlayer.prototype.getUsersId = function() {
	return this.usersId;
}

PokkariPlayer.prototype.setUsersId = function(value) {
	this.usersId = value;
}

PokkariPlayer.prototype.getUsersLogin = function() {
	return this.usersLogin;
}

PokkariPlayer.prototype.setUsersLogin = function(value) {
	this.usersLogin = value;
}

PokkariPlayer.prototype.getTopics = function() {
	return this.topics;
}

PokkariPlayer.prototype.setTopics = function(value) {
	this.topics = value;
}


PokkariPlayer.prototype.convertTimeToSeconds = function(timecode) {
	var time = timecode.replace(/;\d+$/,"");
	var timeParts = time.split(':');
	var result = timeParts[0]*60*60 + timeParts[1]*60 + timeParts[2];
	return result;
}

PokkariPlayer.prototype.convertSecondsToTime = function(s) {
	var d = new Date(s*1000);
	return d.toGMTString().substr(17,8);
}

PokkariPlayer.prototype.ensureSeconds = function(p) {
	if (isNaN(p)) { return this.convertTimeToSeconds(p); }
	else { return p; }
}

PokkariPlayer.prototype.ensureTime = function(p) {
	if (!isNaN(p)) { return this.convertSecondsToTime(p); }
	else { return p; }
}

PokkariPlayer.prototype.getDocumentWidth = function(d) {
	if (!d) d = document;

	if (d.body && typeof(d.body.clientWidth != "undefined"))
		return d.body.clientWidth;
	else if (d.documentElement && typeof(d.documentElement.clientWidth != "undefined"))
		return d.documentElement.clientWidth;
	else if (typeof(window.innerWidth) != "undefined") 
		return window.innerWidth;
}

PokkariPlayer.prototype.getDocumentHeight = function(d) {
	if (!d) d = document;

	if (d.body && typeof(d.body.clientHeight != "undefined"))
		return d.body.clientHeight;
	else if (d.documentElement && typeof(d.documentElement.clientHeight != "undefined"))
		return d.documentElement.clientHeight;
	else if (typeof(window.innerHeight) != "undefined") 
		return window.innerHeight;
}

PokkariPlayer.prototype.openFullscreen = function() {
	var width = window.screen.availWidth;
	var height = window.screen.availHeight;

	this.getPlayerTarget().innerHTML = "<b>Full-screen mode, make sure pop-ups aren't blocked</b>";
	
	this.fullWindow = window.open("","fullWindow","top=0,left=0,status=0,toolbar=0,titlebar=0,resizable=0,location=0,fullscreen=1,directories=0,width="+width+",height="+height);
	this.fullWindow.document.open("text/html","replace");
	this.fullWindow.document.write("<html><head><title>" + document.title + 
		" (Full Screen)</title></head><body style='padding:0; margin:0; background-color: black;'>" +
		"<table width='100%' height='100%'><tr><td align='center' valign='middle'>" +
		"<div id='video_player' style='margin:auto'></div>" +
		"</td></tr></table></body></html>"
	);
	this.fullWindow.document.close();
	this.fullWindow.document.title = document.title + " (Full Screen)";
	var div = this.fullWindow.document.getElementById('video_player');
	this.setPlayerTarget(div);

	this.setFullscreenSize(this.fullWindow.document);
	this.setAutoPlay(true);
	
	this.render();
}

PokkariPlayer.prototype.setFullscreenSize = function(d) 
{
	var width = this.getWidth();
	var height = this.getHeight();
	var doc_width = this.getDocumentWidth(d);
	var doc_height = this.getDocumentHeight(d);
	var aspect = width/height;

	if (!d) d = document;
	
	width = doc_width - (20 * aspect);
	height = Math.round(width / aspect);
	
	if (width < height || height > doc_height) {
		height = doc_height - 20;
		width = Math.round(height*aspect);
	}
	
	this.MAX_WIDTH = width; 
	this.MAX_HEIGHT = height;
	this.setWidth(width);
	this.setHeight(height);
}

PokkariPlayer.prototype.render = function() {

	if(!this.getPrimaryMediaUrl()) {
		throw("Cannot render without primary media URL");
	}
        if(!this.getPlayerTarget()) {
                throw("Cannot render without player target");
        }

	var autoPlay = (this.getAutoPlay()) ? 'true' : 'false';

	var html = '<embed src="' + this.getPrimaryMediaUrl() + '" autoplay="' + autoPlay + '" controller="true" width="' + this.getPlayerWidth() + '" height="' + this.getPlayerHeight() + '" scale="aspect" EnableJavaScript="true" ></embed>';

	this.getPlayerTarget().style.width = this.getPlayerWidth() + "px";
	this.getPlayerTarget().style.height = this.getPlayerHeight() + "px";

	this.getPlayerTarget().innerHTML = html;
}

PokkariPlayer.GetInstanceByMimeType = function(type) {

	var obj;

        switch(type) {
                case 'video/quicktime':
			return new PokkariQuicktimePlayer();
                break;
		case 'video/mpg':
			return new PokkariQuicktimePlayer();
		break;
		case 'video/mpeg':
			return new PokkariQuicktimePlayer();
		break;
		case 'video/mp4':
			return new PokkariQuicktimePlayer();
		break;
		case 'video/x-dv':
			return new PokkariQuicktimePlayer();
		break;
		case 'video/x-flv':
			return new PokkariFlashPlayer();
		break;
		case 'video/ms-wmv':
			return new PokkariWindowsPlayer();
		break;
		case 'video/x-ms-wmv':
			return new PokkariWindowsPlayer();
		break;
		case 'video/ms-wmv,video/x-ms-wmv':
			return new PokkariWindowsPlayer();
		break;
		case 'video/msvideo':
			return new PokkariWindowsPlayer();
		break;
		case 'application/ogg':
			return new PokkariTheoraPlayer();
		break;
		case 'video/theora':
			return new PokkariTheoraPlayer();
		break;
		case 'video/vnd.objectvideo':
			return new PokkariQuicktimePlayer();
		break;
		case 'image/jpeg':
			return new PokkariImagePlayer();
		break;
		case 'image/png':
			return new PokkariImagePlayer();
		break;
		case 'image/bmp':
			return new PokkariImagePlayer();
		break;
		case 'image/gif':
			return new PokkariImagePlayer();
		break;
		case 'audio/mpeg':
			return new PokkariMp3Player();
		break;
		default:
			return new PokkariPlayer();
		break;
        }

}

function PokkariWindowsPlayer(params) {

}

PokkariWindowsPlayer.prototype = new PokkariPlayer();
PokkariWindowsPlayer.prototype.constructor = PokkariWindowsPlayer;
PokkariWindowsPlayer.prototype.superclass = PokkariPlayer;

PokkariWindowsPlayer.prototype.getPrimaryMediaUrl = function() {

	if (window.navigator.platform == "Win32" &&
		(this.getAdvertisingType() == "postroller" || 
		this.getPrerollAnimationUrl() || 
		this.getPostrollAnimationUrl()))
	{
		var url = new Url(this.getPermalinkUrl());
		url.setQueryParam("skin","asx");
		url.setQueryParam("preurl",this.getPrerollAnimationUrl());
		url.setQueryParam("posturl",this.getPostrollAnimationUrl());

		return url.getUrl();
	}
	else {
		return PokkariPlayer.prototype.getPrimaryMediaUrl.apply(this,[]);
	}
}

PokkariWindowsPlayer.prototype.getPlayerWidth = function() {
	return this.getWidth();
}

PokkariWindowsPlayer.prototype.getPlayerHeight = function() {
	if(document.all) {
		return parseInt(this.getHeight()) + 26;
	} else {
		return parseInt(this.getHeight()) + 40;
	}
}

PokkariWindowsPlayer.prototype.render = function() {
	
	if(!this.getPrimaryMediaUrl()) {
		throw("Cannot render without primary media URL");
	}
	if(!this.getPlayerTarget()) {
		throw("Cannot render without player target");
	}

	var autoPlay = (this.getAutoPlay()) ? 'true' : 'false';

	var html = '<object id="video_player_object" width="' + this.getPlayerWidth() + '" height="' + this.getPlayerHeight() + '" classid="CLSID:22d6f312-b0f6-11d0-94ab-0080c74c7e95" codebase="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701" standby="Loading Microsoft Windows Media Player components...", type="application/x-oleobject">';


	html += '<param name="fileName" value="' + this.getPrimaryMediaUrl() + '">';
	html += '<param name="animationStart" value="true">';

	if (typeof(this.startTime) != "undefined" && this.startTime) {
		html += '<param name="currentPosition" value="'+this.startTime+'">';
		autoPlay = true;
	}

	html += '<param name="AutoStart" value="' + autoPlay + '">';
	html += '<param name="showControls" value="true">';
	html += '<param name="transparentatStart" value="false">';
	html += '<param name="loop" value="false">';

	html += '<embed type="application/x-mplayer2" pluginspage="http://www.microsoft.com/Windows/MediaPlayer/" id="video_player_object" name="video_player_object" displaysize="4" autosize="-1" bgcolor="darkblue" showcontrols="true" showtracker="-1" showdisplay="0" showstatusbar="-1" videoborder3d="-1" width="' + this.getPlayerWidth() + '" height="' + this.getPlayerHeight() + '" src="' + this.getPrimaryMediaUrl() + '" autostart="' + autoPlay + '" designtimesp="5311" loop="false"';
	if (typeof(this.startTime) != "undefined" && this.startTime) {
		html += ' currentPosition="'+this.startTime+'"';
	}
	
	html += '></embed>';
	html += '</object>';

	this.getPlayerTarget().style.width = this.getPlayerWidth() + "px";
	this.getPlayerTarget().style.height = this.getPlayerHeight() + "px";

	this.getPlayerTarget().innerHTML = html;
}

PokkariWindowsPlayer.prototype.setTime = function(time) {
	var s = this.ensureSeconds(time);
	var player = this.getPlayer();

	if (typeof(player) != "undefined") {
		if (typeof(player.object) != "undefined") 
			player.object.CurrentPosition = s;
		else if (typeof(player.controls) != "undefined")
			player.controls.currentPosition = s;
	}
}

PokkariWindowsPlayer.prototype.getTime = function() {
	var player = this.getPlayer();

	if (typeof(player) != "undefined") {
		if (typeof(player.object) != "undefined")
			return player.object.CurrentPosition;
		else if (typeof(player.controls) != "undefined")
			return player.controls.currentPosition;
	}
	
	return null;
}

PokkariWindowsPlayer.prototype.getDuration = function() {
	var player = this.getPlayer();

	if (typeof(player) != "undefined") {
		if (typeof(player.currentMedia) != "undefined")
			return player.currentMedia.duration;
		else if (typeof(player.object) != "undefined")
			return player.object.Duration;
	}

	return null;
}

PokkariWindowsPlayer.prototype.getStatus = function() {
	// TODO FIXME Convert status
	//Value 	State 	Description
	//0 	Undefined 	Windows Media Player is in an undefined state.
	//1 	Stopped 	Playback of the current media item is stopped.
	//2 	Paused 	Playback of the current media item is paused. When a media item is paused, resuming playback begins from the same location.
	//3 	Playing 	The current media item is playing.
	//4 	ScanForward 	The current media item is fast forwarding.
	//5 	ScanReverse 	The current media item is fast rewinding.
	//6 	Buffering 	The current media item is getting additional data from the server.
	//7 	Waiting 	Connection is established, but the server is not sending data. Waiting for session to begin.
	//8 	MediaEnded 	Media item has completed playback.
	//9 	Transitioning 	Preparing new media item.
	//10 	Ready 	Ready to begin playing.
	//11 	Reconnecting
	var player = this.getPlayer();

	if (typeof(player) != "undefined") {
		var state;
		
		if (typeof(player.object) != "undefined") 
			state = player.object.PlayState;
		else if (typeof(player.playState) != "undefined")
			state = player.playState;

		if (state == 2)
			return "Paused";
		else if (state == 3)
			return "Playing";
		else if (state == 6)
			return "Loading";
		else if (state == 7)
			return "Waiting";
		else if (state == 8)
			return "Played";
		else if (state == 10)
			return "Complete";
		else
			return "Undefined";
	}
	else {
		return "Unsupported";
	}
}


function PokkariQuicktimePlayer(params) {

}

PokkariQuicktimePlayer.prototype = new PokkariPlayer();
PokkariQuicktimePlayer.prototype.constructor = PokkariQuicktimePlayer;
PokkariQuicktimePlayer.prototype.superclass = PokkariPlayer;

PokkariQuicktimePlayer.prototype.getPrimaryMediaUrl = function() {

	return PokkariPlayer.prototype.getPrimaryMediaUrl.apply(this,[]);

	if (this.getAdvertisingType() == "postroller" ||
		this.getPrerollAnimationUrl() ||
		this.getPostrollAnimationUrl()) 
	{
		var url = new Url(this.getPermalinkUrl());
		url.setQueryParam("skin","smil");
		url.setQueryParam("preurl",this.getPrerollAnimationUrl());
		url.setQueryParam("posturl",this.getPostrollAnimationUrl());
		return url.getUrl();
	}
	else {
		return PokkariPlayer.prototype.getPrimaryMediaUrl.apply(this,[]);
	}
}

PokkariQuicktimePlayer.prototype.getTime = function() {
	var player = this.getPlayer();

	return player.GetTime() / player.GetTimeScale();
}

PokkariQuicktimePlayer.prototype.setTime = function(time) {
	var player = this.getPlayer();

	time = time * player.GetTimeScale();

	player.SetTime(time);
}

PokkariQuicktimePlayer.prototype.getDuration = function() {
	var player = this.getPlayer();

	return player.GetDuration() / player.GetTimeScale();
}

PokkariQuicktimePlayer.prototype.getPlayer = function() {
	
	return document.video_player_object;
	
}

PokkariQuicktimePlayer.prototype.getStatus = function() {
	var player = this.getPlayer();

	if (player && typeof(player.GetPluginStatus) != "undefined") {
		var status = player.GetPluginStatus();
		
		if (status == "Completed" || status == "Playable") {
			var time = player.GetTime();
			var totalTime = player.GetDuration();
			var rate = player.GetRate();
			
			if (time == totalTime)
				return "Played";
			else if (rate > 0)
				return "Playing";
			else if (rate == 0)
				return "Paused";
			else
				return status;
		}
		else
			return status;
	}
	
	return "Undefined";
}

PokkariQuicktimePlayer.prototype.getPlayerWidth = function() {
	return this.getWidth();
}

PokkariQuicktimePlayer.prototype.getPlayerHeight = function() {
	return parseInt(this.getHeight()) + 20;
}

PokkariQuicktimePlayer.prototype.render = function() {
	if(!this.getPrimaryMediaUrl()) {
		throw("Cannot render without primary media URL");
	}
	if(!this.getPlayerTarget()) {
		throw("Cannot render without player target");
	}

	var autoPlay = (this.getAutoPlay()) ? 'true' : 'false';

	var html = '<object id="video_player_object" classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" width="' + this.getPlayerWidth() + 
		'" height="' + this.getPlayerHeight() + '" codebase="http://www.apple.com/qtactivex/qtplugin.cab">';

	html += '<param name="src" value="' + this.getPrimaryMediaUrl() + '">';
	html += '<param name="autoplay" value="' + autoPlay + '">';
	html += '<param name="controller" value="true">';
	html += '<param name="uimode" value="full">';
	html += '<param name="scale" value="aspect">';

	html += '<embed name="video_player_object" src="' + this.getPrimaryMediaUrl() + '" autoplay="' + autoPlay + 
		'" controller="true" width="' + this.getPlayerWidth() + '" height="' + this.getPlayerHeight() + 
		'" scale="aspect" EnableJavaScript="true" type="video/quicktime"></embed>';

	html += '</object>';

	this.getPlayerTarget().style.width = this.getPlayerWidth() + "px";
	this.getPlayerTarget().style.height = this.getPlayerHeight() + "px";

	this.getPlayerTarget().innerHTML = html;

}

function PokkariImagePlayer(params) {

}

PokkariImagePlayer.prototype = new PokkariPlayer();
PokkariImagePlayer.prototype.constructor = PokkariImagePlayer;
PokkariImagePlayer.prototype.superclass = PokkariPlayer;

PokkariImagePlayer.prototype.render = function() {

	var html = '<img src="' + this.getPrimaryMediaUrl() + '" width="' + this.getWidth() + '" height="' + this.getHeight() + '" />';

	this.getPlayerTarget().style.width = this.getWidth() + "px";
	this.getPlayerTarget().style.height = this.getHeight() + "px";

	this.getPlayerTarget().innerHTML = html;
}

PokkariImagePlayer.prototype.getStatus = function() {
	return "Played";
}

function PokkariFlashPlayer(params) {

}

PokkariFlashPlayer.prototype = new PokkariPlayer();
PokkariFlashPlayer.prototype.constructor = PokkariFlashPlayer;
PokkariFlashPlayer.prototype.superclass = PokkariPlayer;

PokkariFlashPlayer.prototype.getPlayerHeight = function() {
	return parseInt(this.getHeight()) + 20;
}

PokkariFlashPlayer.prototype.render = function() {

	var hasProductInstall = DetectFlashVer(6, 0, 65);
	var hasRequestedVersion = DetectFlashVer(8, 0, 0);

	if(hasProductInstall && hasRequestedVersion) {

		var autoPlay = (this.getAutoPlay()) ? '1' : '0';
		var query = "file=" + escape(this.getPrimaryMediaUrl());// + "&autoStart=" + autoPlay;
		if (this.getAdvertisingType() == "postroller") {
			query += "&trailerMovie=http://ad3.postroller.com/ad/12060%3Fvid=" + this.getPostsId();
		}
		else if (this.getAdvertisingType() == "immense") {
			query += "&adProvider=immense&forceDefaultAd=1";
		}
		else if (this.getPostrollAnimationUrl()) {
			query += "&trailerMovie=" + this.getPostrollAnimationUrl();
		}
		if (this.getPrerollAnimationUrl()) {
			query += "&thumbNail=" + this.getPrerollAnimationUrl();
		}
		if (this.getUsersId()) {
			query += "&userId=" + escape(this.getUsersId());
		}
		if (this.getUsersLogin()) {
			query += "&userLogin=" + escape(this.getUsersLogin());
		}
		if(this.getPostsTitle()) {
			query += "&postsTitle=" + escape(this.getPostsTitle());
		}
		if(this.getPostsId()) {
			query += "&postsId=" + escape(this.getPostsId());
		}
		if(this.getTopics()) {
			query += "&topics=" + escape(this.getTopics());
		}

		var html = '<object id="video_player_object" type="application/x-shockwave-flash" width="' + this.getPlayerWidth() + '" height="' + this.getPlayerHeight() + '" wmode="transparent" data="' + this.getSiteUrl() + '/scripts/flash/blipplayer.swf?' + query + '">';
		html += '<param name="movie" value="' + this.getSiteUrl() + '/scripts/flash/blipplayer.swf?' + query + '">';
		html += '<param name="flashvars" value="' + query + '" />';
		html += '<param name="wmode" value="transparent" />';
		html += '</object>';

		this.getPlayerTarget().style.width = this.getPlayerWidth() + "px";
		this.getPlayerTarget().style.height = this.getPlayerHeight() + "px";
		this.getPlayerTarget().innerHTML = html;

	} else {
		var html = '<div class="flash_error_msg">'
		+ '<div class="user_info_title">Ooops</div>'
		+ '<p>If you\'d like to see this video you\'ll need to install <a href="http://www.adobe.com/go/getflashplayer/">Macromedia Flash Player 8</a> (please note that we require Flash 8, and an installation of an earlier version like Flash 7 will not work).</p>'
		+ '<a href="http://www.adobe.com/go/getflashplayer"><img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" /></a>'
		+ '<p>If you feel you\'ve reached this message in error, please <a href="mailto:support@blip.tv">let us know</a>.</p>'
		+ '</div></div>';
		this.getPlayerTarget().innerHTML = html;
	}
}

PokkariFlashPlayer.prototype.getTime = function() {
	var player = this.getPlayer();
	
	return player.GetVariable("videoCurrentTime");
}

PokkariFlashPlayer.prototype.setTime = function(value) {
	var player = this.getPlayer();

	player.SetVariable("videoSetCurrentTime",value);
}

PokkariFlashPlayer.prototype.getDuration = function(value) {
	var player = this.getPlayer();

	return player.GetVariable("videoTotalTime");
}

PokkariFlashPlayer.prototype.getStatus = function(value) {
	var player = this.getPlayer();

	if (player && typeof(player.GetVariable) != "undefined") {
		var status = player.GetVariable("_root.currentVideoStatus");

		return status || "Undefined";
	}
}

function PokkariTheoraPlayer(params) {
}

PokkariTheoraPlayer.prototype = new PokkariPlayer();
PokkariTheoraPlayer.prototype.constructor = PokkariTheoraPlayer;
PokkariTheoraPlayer.prototype.superclass = PokkariPlayer;  // This isnt necessary is it?

PokkariTheoraPlayer.prototype.render = function() {
	var url = this.getPrimaryMediaUrl();
	var width = this.getWidth();
	var height = this.getHeight();

	var html =  '<object classid="clsid:CAFEEFAC-0014-0002-0000-ABCDEFFEDCBA"\n' +
		'width="' + width + '" height="' + height + '"\n' +
	    'codebase="http://java.sun.com/products/plugin/autodl/jinstall-1_4_2-windows-i586.cab#Version=1,4,2,0">\n' +
		'<param name="code" value="com.fluendo.player.Cortado.class" />\n' +
		'<param name="codebase" value="/cortado.jar" />\n' +
		'<param name="archive" value="/cortado.jar" />\n' + 
	    '<param name="type" value="application/x-java-applet;jpi-version=1.4.2">\n' +
	    '<param name="scriptable" value="true">\n' +	
		'<param name="url" value="' + url + '" />\n' +
		'<param name="local" value="false" />\n' +
		'<param name="keepaspect" value="true" />\n' +
		'<comment>\n' +		
		'<APPLET code="com.fluendo.player.Cortado.class"\n' +
		'codebase="/cortado.jar"\n' + 
		'archive="/cortado.jar"\n' + 
        'width="' + width + '" height="' + height + '">\n' +
  		'<PARAM name="url" value="' + url + '"/>\n' +
	  	'<PARAM name="local" value="false"/>\n' +
	  	'<PARAM name="keepaspect" value="true"/>\n' +
		'</APPLET>\n' +
		'</comment>\n' +
		'</object>\n';

	this.getPlayerTarget().innerHTML = html;
}

function PokkariMp3Player(params) {

}

PokkariMp3Player.prototype = new PokkariPlayer();
PokkariMp3Player.prototype.constructor = PokkariMp3Player;
PokkariMp3Player.prototype.superclass = PokkariPlayer;

PokkariMp3Player.prototype.render = function() {

	var autoPlay = (this.getAutoPlay()) ? 'true' : 'false';
	var query = "song_url=" + escape(this.getPrimaryMediaUrl()) + "&autoplay=" + autoPlay;

	var html = '<object id="video_player_object" type="application/x-shockwave-flash" width="' + this.getPlayerWidth() + '" height="' + this.getPlayerHeight() + '" wmode="transparent" data="' + this.getSiteUrl() + '/scripts/flash/blipmp3player.swf?' + query + '">';
	html += '<param name="movie" value="' + this.getSiteUrl() + '/scripts/flash/blipmp3player.swf?' + query + '">';
	html += '<param name="flashvars" value="' + query + '" />';
	html += '<param name="wmode" value="transparent" />';
	html += '</object>';

	this.getPlayerTarget().innerHTML = html;
}

function PokkariRating(params) {
	PokkariXmlRequest.apply(this,[params]);
}

PokkariRating.prototype = new PokkariXmlRequest();
PokkariRating.prototype.constructor = PokkariRating;

PokkariRating.prototype.onLoadHandler = function()
{
	var star;
	var images = this.element.getElementsByTagName("img");
	this.stars = new Array();
	for (var i=0; i<images.length; i++) {
		if (images[i].className.match(/star/))
			this.stars.push(images[i]);
	}
	
	if (!this.stars || !this.stars.length) {
		throw new Error("Could not locate img children for stars");
	}

	if (!this.disabled) 
	{
		for (var i=0; i<this.stars.length; i++) {
			star = this.stars[i];
			star.rating = i+1;
		
			Pokkari.AttachEvent(star,"click",
				this.eventHandler(this.onStarClick));

			Pokkari.AttachEvent(star,"mouseover",
				this.eventHandler(this.onStarMouseOver));

			Pokkari.AttachEvent(star,"mouseout",
				this.eventHandler(this.onStarMouseOut));

			star.style.cursor = "pointer";
		}
	}

	if (this.messageId) {
		this.messageElement = $(this.messageId);
	}

	if (this.messageElement) {
		this.messageElement.oldHTML = this.messageElement.innerHTML;
	}

	if (typeof(this.messages) == "string") {
		this.messages = this.messages.split(',');
	}

	if (this.voteShowId) {
		this.voteShowElement = $(this.voteShowId);
	}

	if (this.voteHideId) {
		this.voteHideElement = $(this.voteHideId);
	}

	if (!this.imageExtension) {
		this.imageExtension = 'gif';
	}

	if (typeof(this.imageColors) == "string")
		this.imageColors = this.imageColors.split(',');
	else if (!this.imageColors)
		this.imageColors = ['null','blue','red','purple'];
	
	if (typeof(this.imageSides) == "string")
		this.imageSides = this.imageSides.split(',');
	else if (!this.imageSides)
		this.imageSides = ['left','right'];

	if (!this.imageRoot)
		this.imageRoot = '/images/star_';
	
	// Defined modes: half (lrlrlrlrlr), whole (llllllllll), scale (lllllrrrrr)
	if (this.mode) {
		this.mode = this.mode.toLowerCase();
	}
	else {
		this.mode = 'half';
	}

	this.preload();
}

PokkariRating.prototype.onStarClick = function(self,sender,e)
{
	if (self.disabled) { return; }

	self.disabled = true;

	self.url = "/" + self.itemType + "/rate/" + self.itemId + "?rating=" +
		sender.rating + "&entropy=" + Math.floor(Math.random()*10000);

	self.send();
}

PokkariRating.prototype.getSide = function(i) 
{
	if (this.mode == 'scale') {
		return (i<5) ? this.imageSides[0] : this.imageSides[1];
	}
	else if (this.mode == 'half') {
		return this.imageSides[(i % 2)];
	}
	else {
		return this.imageSides[0];
	}
}

PokkariRating.prototype.setStarColor = function(i,c) 
{
	var side = this.getSide(i);
	var color = this.imageColors[c];

	if (this.stars[i])
		this.stars[i].src = PokkariRating.preloadedImages[color+"_"+side].src;
}

PokkariRating.prototype.renderScaleOver = function(rating) 
{
	if (rating == null)
	{
		for(var i=0; i<10; i++)
			this.setStarColor(i,0);
	}
	else if (rating<6)
	{
		for(var i=1; i<rating; i++)
			this.setStarColor(i-1,0);
			
		for (var i=rating; i<=5; i++) 
			this.setStarColor(i-1,1);
			
		for (var i=6; i<=10; i++)
			this.setStarColor(i-1,0);
	}
    else 
	{
		for(var i=1; i<=5; i++)
			this.setStarColor(i-1,0);

		for (var i=6; i<=rating; i++)
			this.setStarColor(i-1,1);

		for (var i=rating+1; i<=10; i++)
			this.setStarColor(i-1,0);
	}
}

PokkariRating.prototype.renderScaleOut = function() 
{
	var rating = this.currentRating;

	if (rating != null)
		rating = Math.round(this.currentRating);

	this.renderScaleOver(rating);
}

PokkariRating.prototype.renderNormalOver = function(rating) 
{
	var myRating = rating;
	var siteRating = this.currentRating || 0;
	var color = null;
	var element = null;
	
	for(var x=1; x<=myRating; x++) {
		if (this.imageColors.length == 4)
			this.setStarColor(x-1,Math.round(siteRating) >= x ? 3 : 2);
		else
			this.setStarColor(x-1,1);
		
		//element = this.stars[x-1];
		//element.src = PokkariRating.preloadedImages[color+"_"+side].src;
	}

	if (this.imageColors.length < 4) {
		for(var x=myRating+1; x<=this.stars.length; x++) {
			//side = this.getSide(x);
			//color = this.imageColors[0];
		
			//element = this.stars[x-1];
			//element.src = PokkariRating.preloadedImages[color+"_"+side].src;
			this.setStarColor(x-1,0);
		}
	}
}

PokkariRating.prototype.renderNormalOut = function(rating)
{
	var siteRating = this.currentRating || 0;
	var side = null;
	var color = null;
	var element = null;
	var x;
	for(x=1; x<=this.stars.length; x++) 
	{
		this.setStarColor(x-1,Math.round(siteRating) >= x ? 1 : 0);
		//side = this.getSide(x);
		//color = Math.round(siteRating) >= x ? this.imageColors[1] : this.imageColors[0];
		
		//element = this.stars[x-1];
		//element.src = PokkariRating.preloadedImages[color+"_"+side].src;
	}
}

PokkariRating.prototype.renderMessageOver = function(rating) 
{
	if (this.messages) 
	{
		this.messageElement.innerHTML = this.messages[rating-1];
	}
}

PokkariRating.prototype.renderMessageOut = function() 
{
	this.messageElement.innerHTML = this.messageElement.oldHTML;
}

PokkariRating.prototype.onStarMouseOver = function(self,sender,e)
{
	if (self.disabled) { return; }

	if (self.mode == 'scale') 
	{
		self.renderScaleOver(sender.rating);
	}
	else 
	{
		self.renderNormalOver(sender.rating);
	}
	
	if (self.messageElement)
	{
		self.renderMessageOver(sender.rating);
	}
}

PokkariRating.prototype.onStarMouseOut = function(self,sender,e)
{
	if (self.disabled) { return; }

	if (self.mode == 'scale')
	{
		self.renderScaleOut();
	}
	else 
	{
		self.renderNormalOut();
	}
	
	if (self.messageElement)
	{
		self.renderMessageOut();
	}
}

PokkariRating.prototype.onsuccess = function(request)
{
	var xml = request.getResponseXml();
	var topNode = xml.documentElement;
	var response = topNode.getElementsByTagName("response")[0];
	var error = response.getElementsByTagName("error")[0];
	var notice = response.getElementsByTagName("notice")[0];

	if (error) {
		window.alert("Error: " + error.firstChild.nodeValue);
	}
	else if (notice) {
		window.alert(notice.firstChild.nodeValue);
	}
	else {
		var type = response.getElementsByTagName("type")[0].firstChild.nodeValue;
		var id = response.getElementsByTagName("id")[0].firstChild.nodeValue;
		var myRating = response.getElementsByTagName("myRating")[0].firstChild.nodeValue;
		var siteRating = response.getElementsByTagName("siteRating")[0].firstChild.nodeValue;

		this.disabled = true;
		this.currentRating = siteRating;
		this.myRating = myRating;
		this.onStarMouseOver(this,this.stars[myRating-1],null);

		if (this.messageElement) {
			if (this.voteMessage) {
				this.messageElement.innerHTML = this.voteMessage;
			}
			else {
				this.messageElement.innerHTML = "Thank You For Voting!";
			}
		}

		if (this.voteShowElement) {
			this.voteShowElement.style.display = 'block';
		}
		if (this.voteHideElement) {
			this.voteHideElement.style.display = 'none';
		}

		// debugger;
		if (typeof(this.onVoted) == "function")
		{
			this.onVoted(this,request,null);
		}
		else if (typeof(this.onVoted) == "string")
		{
			var func = new Function("self","sender","e",this.onVoted);
			func(this,request,null);
		}
	}
}

PokkariRating.prototype.preload = function()
{
	var img;
	var colors = this.imageColors;
	var sides = this.imageSides;
	var ext = this.imageExtenion;
	var root = this.imageRoot;

	PokkariRating.preload(root,colors,sides,ext);
}

PokkariRating.preload = function(root,colors,sides,ext)
{
	if (!PokkariRating.preloadedImages)
	{
		PokkariRating.preloadedImages = new Object();
		for (var i=0; i<colors.length; i++) {
			for (var j=0; j<sides.length; j++) {
				img = new Image();
				img.src = root + colors[i] + '_' + sides[j] + '.' + ext;
				PokkariRating.preloadedImages[colors[i]+"_"+sides[j]] = img;
			}
		}
	}
}

function PokkariRotatingTip(params) {
}

PokkariRotatingTip.prototype = new PokkariXmlRequest();
PokkariRotatingTip.prototype.constructor = PokkariRotatingTip;

PokkariRotatingTip.prototype.show = function()
{
	this.locateElements();
	this.url = "/tips?ignore=1234";
	this.timer = window.setInterval("document.getElementById('"+this.element.id+"').pokkariElement.ontick();",10000);

	// Non-IE browsers can't do two XmlHttpRequests at once, and since we've probably just fired UploadMonitor, let's 
	// throw in a delay.
	if (document.all)
		this.ontick(this,this);	
	else
		window.setTimeout("document.getElementById('"+this.element.id+"').pokkariElement.ontick();",500);
}

PokkariRotatingTip.prototype.locateElements = function()
{
	var elements = this.element.getElementsByTagName("*");
	for (var i=0; i<elements.length; i++)
	{
		if (elements[i].innerHTML)
		{
			if (elements[i].innerHTML == "title")
				this.titleElement = elements[i];
			else if (elements[i].innerHTML == "body")
				this.bodyElement = elements[i];
		}
	}

	if (!this.titleElement || !this.bodyElement)
		throw new Error("Could not locate title and/or body elements");
}

PokkariRotatingTip.prototype.ontick = function()
{
	// Trick to convince IE to actually update..
	this.url = this.url.substring(0,this.url.length-4) +
			(Math.floor(Math.random()*9000)+1000);
	this.send();
}

PokkariRotatingTip.prototype.onsuccess = function() 
{
	var xml = this.getResponseXml();
	var topNode = xml.documentElement;
	var title = topNode.getElementsByTagName("title")[0].firstChild.nodeValue;
	var body = topNode.getElementsByTagName("body")[0].firstChild.nodeValue;

	this.paint(title,body);
}

PokkariRotatingTip.prototype.paint = function(title,body) 
{
	if (body) 
	{
		if (this.element.style.display != "block")
			this.element.style.display = "block";

		this.titleElement.innerHTML = title;
		this.bodyElement.innerHTML = body;
	}
}


/**
 *	PokkariScriptEditor class
 *	subclasses @PokkariElement
*/

function  PokkariScriptEditor(params) {
	this.fields = new Object();

	this.baseURL = BLIP_SITE_URL + '/posts/?skin=js&has_thumbnail=1';
}

PokkariScriptEditor.prototype = new PokkariElement();
PokkariScriptEditor.prototype.constructor = PokkariScriptEditor;
PokkariScriptEditor.prototype.superclass = PokkariElement;

PokkariScriptEditor.prototype.getFields = function() {
	
	var collection = new Array();

	if(this.element) {
		var inputs = this.element.getElementsByTagName('INPUT');
		for(var i = 0; i < inputs.length; i++) {
			if(inputs[i].getAttribute('scripteditorfield')) {
				collection.push(inputs[i]);
			}
		}
		var selects = this.element.getElementsByTagName('SELECT');
		for(var i = 0; i < selects.length; i++) {
			if(selects[i].getAttribute('scripteditorfield')) {
				collection.push(selects[i]);
			}
		}
	}

	return collection;
}

PokkariScriptEditor.prototype.getCodeRepository = function() {
	
	var textareas = this.element.getElementsByTagName('TEXTAREA');

	for(var i = 0; i < textareas.length; i++) {
		if(textareas[i].getAttribute('scripteditorcode')) {
			return textareas[i];
		}
	}
	
}

PokkariScriptEditor.prototype.isBound = function() {

	this.fields = this.getFields();

	this.codeRepository = this.getCodeRepository();

	for(var i = 0; i < this.fields.length; i++) {
		this.fields[i].PokkariScriptEditor = this;
		this.fields[i].onkeyup = new Function("this.PokkariScriptEditor.changedField(this);");
		this.fields[i].onchange = new Function("this.PokkariScriptEditor.changedField(this);");
	}

	if(document.getElementById('blip_sidebar_container')) {
		this.blip_sidebar_container = document.getElementById('blip_sidebar_container');
	}
}

PokkariScriptEditor.prototype.changedField = function(field) {
	this.updateCodeRepository();
}

PokkariScriptEditor.prototype.updateCodeRepository = function() {
	
	var string = '<script type="text/javascript" src="' + this.getScriptURL() + '"></script>';
	this.codeRepository.value = string;
	
}

PokkariScriptEditor.prototype.updatePreview = function() {
	
	this.blip_sidebar_container.innerHTML = '';

	var script = document.createElement('script');
	script.type = 'text/javascript';
	script.src = this.getScriptURL() + "&output_method=dom"; 
	
	this.blip_sidebar_container.appendChild(script);
}

PokkariScriptEditor.prototype.getScriptURL = function() {

	var url = '';
	
	for (var i = 0; i < this.fields.length; i++) {
		url += "&" + escape(this.fields[i].name) + "=" + escape(this.fields[i].value);
	}

	return this.baseURL + url;
}
function TemplateEditor(params) {
	this.init(params);
}

TemplateEditor.prototype = new PokkariXmlRequest();
TemplateEditor.prototype.constructor = TemplateEditor;

TemplateEditor.prototype.init = function() {
	
	this.baseURL = "/templates/preview/";
	this.method = "GET";

	this.onsuccess = this.receivePreview;
	this.onfailure = this.receivePreviewFailure;
}

TemplateEditor.onChange = function () {
	alert("Hellooooo!");
}

TemplateEditor.prototype.onConstruct = function() {

	if(
		document.getElementById('templatePreviewPane')
	) {
		this.previewPane = document.getElementById('templatePreviewPane');
	}

	if(this.element.tagName.toLowerCase().match(/select/)) {
		try {
			this.element.addEventListener('change',new Function("this.pokkariElement.updatePreview(); this.pokkariElement.onTemplateChange();"),false);
		} catch(e) {
			this.element.onchange = new Function("this.pokkariElement.updatePreview(); this.pokkariElement.onTemplateChange();");
		}
	}

	if(document.getElementById('share_tags')) {
		document.getElementById('share_tags').TemplateEditor = this.element;
		try {
			$('share_tags').addEventListener('change',new Function("this.TemplateEditor.pokkariElement.updatePreview();"),false);
		} catch(e) {
			$('share_tags').onchange = new Function("this.TemplateEditor.pokkariElement.updatePreview();");
		}

		this.share_tags = document.getElementById('share_tags');
	}

	if($('playback_style')) {
		$('playback_style').TemplateEditor = this.element;
		try {
			$('playback_style').addEventListener('change',new Function("this.TemplateEditor.pokkariElement.updatePreview();"),false);
		} catch(e) {
			$('playback_style').onchange = new Function("this.TemplateEditor.pokkariElement.updatePreview();");
		}

		this.playback_style = $('playback_style');
	}

	if($('include_format_links')) {
		$('include_format_links').TemplateEditor = this.element;
		try {
			$('include_format_links').addEventListener('change',new Function("this.TemplateEditor.pokkariElement.updatePreview();"),false);
		} catch(e) {
			$('include_format_links').onchange = new Function("this.TemplateEditor.pokkariElement.updatePreview();");
		}

		this.include_format_links = $('include_format_links');
	}

	if($('ctp_text')) {
		$('ctp_text').TemplateEditor = this.element;
		try {
			$('ctp_text').addEventListener('change',new Function("this.TemplateEditor.pokkariElement.updatePreview();"),false);
		} catch(e) {
			$('ctp_text').onchange = new Function("this.TemplateEditor.pokkariElement.updatePreview();");	
		}
		this.ctp_text = $('ctp_text');
	}

	if($('preferred_file_type_name')) {
		$('preferred_file_type_name').TemplateEditor = this.element;
		try {
		$('preferred_file_type_name').addEventListener("change",new Function("this.TemplateEditor.pokkariElement.updatePreview();"));
		} catch(e) { 
			$('preferred_file_type_name').onchange = new Function("this.TemplateEditor.pokkariElement.updatePreview();");
		}
		this.preferred_file_type_name = $('preferred_file_type_name');
	}

	if(this.previewPane.style.display != 'none') {
		this.updatePreview();
	}

	if(this.element.getAttribute('blogid')) {
		this.blogid = this.element.getAttribute('blogid');
	}

	this.updatePreview();

}

TemplateEditor.prototype.togglePreview = function() {
	
	if(this.previewPane) {
		if(this.previewPane.style.display == 'none') {
			this.updatePreview();
		} else {
			this.previewPane.style.display = 'none';
		}
	}
}

TemplateEditor.prototype.onTemplateChange = function() {
	if(this.element.tagName.toLowerCase().match(/select/)) {
		var isLegacy = this.element.options[this.element.selectedIndex].getAttribute("legacy");

		var warnings = document.getElementsByClassName('legacy_warning');
		for(var i = 0; i < warnings.length; i++) {
			if(isLegacy) {
				warnings[i].style.display = '';
			} else {
				warnings[i].style.display = 'none';
			}
		}

		var advancedHelp = document.getElementsByClassName('advanced_help');

		for(var i = 0; i < advancedHelp.length; i++) {
			if(isLegacy) {
				advancedHelp[i].style.display = 'none';	
			} else {
				advancedHelp[i].style.display = '';
			}
		}

		var advancedOptions = document.getElementsByClassName('advanced_option');

		for(var i = 0; i < advancedOptions.length; i++) {
			if(isLegacy) {
				advancedOptions[i].disabled = true;
			} else {
				advancedOptions[i].disabled = false;
			}
		}
	}
}

TemplateEditor.prototype.updatePreview = function() {

	if(this.element.tagName.toLowerCase().match(/select/)) {
		this.url = this.baseURL + "?id=" + this.element.value; 
	} else {
		this.url = this.baseURL + "?template=" + escape(this.element.value);
	}

	if(this.blogid) {
		this.url += '&blogId='+this.blogid; 
	}

	if(this.share_tags) {
		var share_tags = 0;
		if(this.share_tags.checked) {
			share_tags = 1;
		}
		this.url += '&share_tags='+share_tags;
	}

	if(this.playback_style) {
		this.url += '&playback_style='+this.playback_style.options[this.playback_style.selectedIndex].getAttribute('style_name');
	}

	if(this.include_format_links) {
		var ifl = 0;
		if(this.include_format_links.checked) {
			ifl = 1;
		}
		this.url += '&include_format_links='+ifl;
	}

	if(this.ctp_text) {
		this.url += '&ctp_text='+escape(this.ctp_text.value);
	}

	if(this.preferred_file_type_name) {
		this.url += '&preferred_file_type_name='+escape(this.preferred_file_type_name.value);
	}

	this.send();
}

TemplateEditor.prototype.receivePreview = function() {
	
	if(this.getResponseXml().getElementsByTagName('parsed')[0]) {
		var preview = this.getResponseXml().getElementsByTagName('parsed')[0].firstChild.nodeValue;
		this.displayPreview({ markup : preview });
	} else {
		if(this.getResponseXml().getElementsByTagName('error')[0]) {
			this.receivePreviewFailure(this.getResponseXml().getElementsByTagName('error')[0].firstChild.nodeValue);
		}
	}
}

TemplateEditor.prototype.receivePreviewFailure = function(error) {

	this.previewPane.innerHTML = "<h1>Preview failed</h1>";
	if(error) {
		this.previewPane.innerHTML += "<p>"+error+"</p>";
	}

}

TemplateEditor.prototype.displayPreview = function(params) {

	if(this.previewPane) {
		this.previewPane.innerHTML = params['markup'];
		if(this.previewPane.style.display == 'none') {
			this.previewPane.style.display = 'block';
		}
	}
	
}
/*
	By Mike Hudack for Pokkari, Inc.
	Tuesday August 30, 2005
*/
function TopicEditor(params) {
	this.init(params);
}

TopicEditor.prototype = new PokkariXmlRequest();
TopicEditor.prototype.constructor = TopicEditor;

TopicEditor.prototype.init = function() {

	this.url = "/topics/suggest/";
	this.method = "GET";

	this.onsuccess = this.receiveSuggestions;
	this.onfailure = this.receiveSuggestionsFailure; 
}

TopicEditor.prototype.onConstruct = function() {
	
	/*
		Let's set our onchange
	*/

	function onkeyup(e) {
		return this.pokkariElement.onKeyUp(e);
	}

	function onkeypress(e) {
		return this.pokkariElement.onKeyPress(e);
	}

	if(this.element) {
		try {
			this.element.addEventListener('keyup',onkeyup,false);
			this.element.addEventListener('keypress',onkeypress,false);
		} catch(e) {
			this.element.attachEvent('keyup',onkeyup);
			this.element.attachEvent('kepress',onkeypress);
		}
	} else { }
	
	/* 
	We need to find if we have an auto-completion zone.  These can be
	siblings.
	*/

	if(document.getElementById('autoCompletionZoneContainer')) {
		this.autoCompletionZoneContainer = document.getElementById('autoCompletionZoneContainer');
	} 

	this.getSuggestions();
}

TopicEditor.prototype.getSuggestions = function() {
	
	this.send();

}

/*
	A nice and complicated method to get the juices flowing.
*/
TopicEditor.prototype.receiveSuggestions = function() {

	// We have fresh suggestions, so clear the old ones
	this.suggestions = new Array();

	for(var i = 0; i < this.getResponseXml().getElementsByTagName('class').length; i++) {
		var suggestionClass = this.getResponseXml().getElementsByTagName('class')[i];
		var className = suggestionClass.getElementsByTagName('className')[0].firstChild.nodeValue;
		if(!this.suggestions[className]) {
			this.suggestions[className] = new Array();
			this.suggestions[className]['suggestions'] = new Array();
			var acz = document.createElement('div');
			var aczObj = new AutoCompletionZone();
			aczObj.EditPalette = this.element;
			aczObj.bindToHTMLElement(acz);
			aczObj.setLabel(className);
			aczObj.onConstruct();
			// first ACZ is primary
			if(i == 0) {
				aczObj.setPrimary(true);
				this.setPrimaryCompletionZone(acz);
			}	
			this.suggestions[className]['autoCompletionZone'] = acz;
			if(this.autoCompletionZoneContainer) {
				this.autoCompletionZoneContainer.appendChild(acz);
			} else {
				this.element.parentNode.appendChild(acz);
			}
		} else {
			this.suggestions[className]['suggestions'].length = -1;	
		}
		for(var ii = 0; ii < suggestionClass.getElementsByTagName('name').length; ii++) {
			var firstLetter = suggestionClass.getElementsByTagName('name')[ii].firstChild.nodeValue.substring(0,1);	
			if(!this.suggestions[className]['suggestions'][firstLetter]) {
				this.suggestions[className]['suggestions'][firstLetter] = new Array();
			}
			this.suggestions[className]['suggestions'][firstLetter].push(suggestionClass.getElementsByTagName('name')[ii].firstChild.nodeValue);
		}
	}

}

TopicEditor.prototype.receiveSuggestionsFailure = function() {
	/*
		Stoics fail silently, without upsetting other people.
	*/
}

TopicEditor.prototype.getCurrentTopics = function() {
	
	var topics = new Array();

	if(this.element.value.match(/,/)) {
		topics = this.element.value.split(",");
	} else {
		topics = this.element.value.split(" ");
	}

	return topics;
}

/**
 * @param {String} topic
 *
 * Sets the currentWorkingTopic to topic (first argument)
*/
TopicEditor.prototype.setCurrentTopic = function(topic) {

	var topics = this.getCurrentTopics();

	if(!this.element.value.match(/,/) && topics.length >= 2) {
		topic = topic.replace(/\W+/,"_");
	}

	var currentWorkingTopic = topics[topics.length - 1];
	var re = eval("/"+currentWorkingTopic+"$/");
	this.element.value = this.element.value.replace(re,topic);

	if(this.element.value.match(/,/) || topics.length <= 1) {
		this.element.value += ', ';
	} else {
		this.element.value += ' ';
	}

	this.clearAutoCompletionZones();

	this.element.focus();
}

TopicEditor.prototype.clearAutoCompletionZones = function() {

	for(var suggestionClass in this.suggestions) {
		if(this.suggestions[suggestionClass]['autoCompletionZone']) {
			this.suggestions[suggestionClass]['autoCompletionZone'].pokkariElement.clearCandidates();
		}
	}
}

TopicEditor.prototype.onKeyPress = function(e) {
	
	if(e.keyCode == 9) {
		try {
			if(this.getPrimaryCompletionZone() && this.getPrimaryCompletionZone().pokkariElement.getTopSuggestion()) {
				this.setCurrentTopic(this.getPrimaryCompletionZone().pokkariElement.getTopSuggestion());
			}
		} catch(e) {
			alert(e);
		}
		e.preventDefault();
	}

	return true;
	
}

TopicEditor.prototype.onKeyUp = function(e) {
	// First, we have to find what we're really working on 

	var topics = this.getCurrentTopics();

	// CWT is last topic discovered, with leading spaces removed
	var currentWorkingTopic = topics[topics.length - 1].replace(/^\s+/,""); 

	this.doSuggest(currentWorkingTopic);
}

TopicEditor.prototype.doSuggest = function(target) {
	
	for(var suggestionClass in this.suggestions) {
		var potentials = this.suggestions[suggestionClass]['suggestions'][target.substring(0,1)];
		var toSuggest = new Array;
		if(potentials != void(0) && potentials.length) {
			for(var i = 0; i < potentials.length; i++) {
				if(potentials[i].indexOf(target) == 0) {
					toSuggest.push(potentials[i]);
				}
			}
		}

		if(toSuggest.length > 7) {
			toSuggest.length = 7;
		}
		
		if(this.suggestions[suggestionClass]['autoCompletionZone']) {
			this.suggestions[suggestionClass]['autoCompletionZone'].pokkariElement.newCandidates(toSuggest);	
		}
	}
}

TopicEditor.prototype.setPrimaryCompletionZone = function(zone) {
	this.primaryCompletionZone = zone;
}

TopicEditor.prototype.getPrimaryCompletionZone = function() {
	return this.primaryCompletionZone;
}
function PokkariUploadMonitor(params) {
	if (params)
	{
		this.form_cookie = params.form_cookie;
	}

	this.filenameSpan = document.getElementById("upload_monitor_filename");
	this.progressDiv = document.getElementById("upload_monitor_progressbar_inner");
	this.timeleftSpan = document.getElementById("upload_monitor_timeleft");
	this.sentSpan = document.getElementById("upload_monitor_sent");
	this.totalSpan = document.getElementById("upload_monitor_total");
	this.rateSpan = document.getElementById("upload_monitor_rate");
	this.predictionRatio = 0.8;
	this.baseRead = 0;
}

// Set this up as a subclass of PokkariXmlRequest
PokkariUploadMonitor.prototype = new PokkariXmlRequest("/upload/status");
PokkariUploadMonitor.prototype.constructor = PokkariXmlRequest;

// Show the upload monitor and start updating..
PokkariUploadMonitor.prototype.show = function()
{
	// Update the PokkariXmlRequest url with the form_cookie set on construct
	this.url = "/upload/status?form_cookie="+this.form_cookie + "&1234";

	// Display the widget
	this.element.style.display = "block";

        // if we have a form, hide it
		if($('post_form_table') && !window.navigator.userAgent.match(/KHTML/)) {
			$('post_form_table').style.display = 'none';
			// and we have to scroll to the top or everything gets fubar'd
			window.scroll(0,0);
		}

	var context = this.element.id;

	this.tickCount = 0;
	this.timer = window.setInterval("document.getElementById('"+
		context+"').pokkariElement.ontick()",500);
	//Pokkari.ShowDebugWindow();
	//Pokkari.Debug("Starting transfer..");
	this.paint();
}

PokkariUploadMonitor.prototype.onabort = function() 
{
	//Pokkari.Debug("Transfer aborted");
	window.clearInterval(this.timer);
	this.element.style.display = "none";
	delete this.currentStatus;
	delete this.lastPrediction;
}

PokkariUploadMonitor.prototype.ontick = function()
{
	// Cancel on abort.  Only works in IE.
	if (this.readyState && this.readyState != document.readyState) {
		this.onabort();
	}
	// Every tenth tick, request an update..
	else if (this.tickCount % 10 == 0)
	{
		// Trick to convince IE to actually update..A
		this.url = this.url.substring(0,this.url.length-4) +
			(Math.floor(Math.random()*9000)+1000);
	
		this.send();
	}
	else if (this.currentStatus && !this.stalled)
	{
		var now = (new Date()).getTime();

		if (this.lastPrediction)
		{
			var elapsed = now - this.lastPrediction;
			this.currentStatus.read +=
				elapsed/1000 *
				this.currentStatus.rate *
				this.predictionRatio;

			// Sometimes post processing takes awhile, so don't predict over the total.
			if (this.currentStatus.read > this.currentStatus.total)
				this.currentStatus.read = this.currentStatus.total;

			this.calculateStatus(this.currentStatus);
			this.paint();
		}

		this.lastPrediction = now;	
	}

	this.tickCount++;

	// Set our ready state
	this.readyState = document.readyState;
}

PokkariUploadMonitor.prototype.onsuccess = function(object)
{
	var status = this.getStatusFromResponse();

	delete this.lastPrediction;

	this.stalled = this.lastRead ? (status.read == this.lastRead) : false;
	this.lastRead = status.read;
	this.currentStatus = status;

	//if (this.stalled)
		//Pokkari.Debug("Stalled");

	this.paint();
}

PokkariUploadMonitor.prototype.paint = function()
{
	var status = this.currentStatus;

	if (!status || isNaN(status.remainPercent) || !isFinite(status.remainPercent) || !status.rate)
	{
		this.filenameSpan.innerHTML = "(Beginning Transfer.. Please wait...)";
		this.timeleftSpan.innerHTML = "--";
		this.sentSpan.innerHTML = "--";
		this.totalSpan.innerHTML = "--";
		this.rateSpan.innerHTML = "--";
		return;
	}
	else if (this.stalled)
	{
		this.rateSpan.innerHTML = "Stalled";
		return;
	}

	try {
		this.filenameSpan.innerHTML = status.filename;

		//Pokkari.Debug(this.progressDiv.style.width);
		if (status.remainPercent > 0 + this.progressDiv.style.width.replace(/\D/g,""))
			this.progressDiv.style.width = status.remainPercent + "%";

		if (status.remainMin > 0) {
			this.timeleftSpan.innerHTML = status.remainMin + " min " + status.remainSec + " sec";
		}
		else {
			this.timeleftSpan.innerHTML = status.remainSec + " sec";
		}
	
		if (status.readM > 1) {
			this.sentSpan.innerHTML = status.readM + " MB";
		}
		else if (status.readK > 1) {
			this.sentSpan.innerHTML = status.readK + " KB";
		}
		else {
			this.sentSpan.innerHTML = status.read + " bytes";
		}

		if (status.totalM > 1) {
			this.totalSpan.innerHTML = status.totalM + " MB";
		}
		else if (status.totalK > 1) {
			this.totalSpan.innerHTML = status.totalK + " KB";
		}
		else {
			this.totalSpan.innerHTML = status.total + " bytes";
		}
	
		if (this.maxSize && status.totalM > this.maxSize) {
			this.rateSpan.innerHTML = "<span style='color:red'>WARNING:  The size of this file exceeds our upload limit.  It may be rejected.</span>";
		}
		else if (status.rateK > 1) {
			this.rateSpan.innerHTML = status.rateK + " KB/s";
		}
		else {
			this.rateSpan.innerHTML = status.rate + " bytes/s";
		}
	}
	catch (exception)
	{
		Pokkari.HandleException(exception,"In paint");
	}
}

PokkariUploadMonitor.prototype.getStatusFromResponse = function()
{
	try {
		var xml = this.getResponseXml();
		var topNode = xml.documentElement;
		var response = topNode.getElementsByTagName("response")[0];
		var status = new Object();
		status.guid = response.getElementsByTagName("guid")[0].firstChild.nodeValue;
		status.filename = response.getElementsByTagName("filename")[0].firstChild.nodeValue;
		status.start = parseInt(response.getElementsByTagName("start")[0].firstChild.nodeValue);
		status.update = parseInt(response.getElementsByTagName("update")[0].firstChild.nodeValue);
		status.read = parseInt(response.getElementsByTagName("read")[0].firstChild.nodeValue);
		status.total = parseInt(response.getElementsByTagName("total")[0].firstChild.nodeValue);
		status.now = (new Date()).getTime();

		// Check to see if the filename has changed..
		if (this.currentStatus && 
			this.currentStatus.filename && 
			status.filename != this.currentStatus.filename) 
		{
			// In a single transfer situation, our total is the sum of all uploads
			if (this.handleMultipleFileTypes) {
				this.baseRead = this.currentStatus.read;
				//Pokkari.Debug("Base read now " + this.baseRead);
			}
			// In multiple asset situation, each transfer is separate, therefore, reset.
			else {
				delete this.currentStatus;
				delete this.lastPrediction;
				this.progressDiv.style.width = "1px";
			}
		}

		//Pokkari.Debug("Base Read=" + this.baseRead + " Read=" + status.read);

		// Add any already read from previous files..
		status.read += this.baseRead;

		this.calculateStatus(status);

		return status;
	}
	catch (exception)
	{
		Pokkari.HandleException(exception,"In getStatus");
	}
}


PokkariUploadMonitor.prototype.calculateStatus = function(status)
{
	// Calculations..
	try {
		//Pokkari.Debug("Read=" + status.read);

		status.elapsed = status.update - status.start;
		status.readK = Math.round(status.read / 102.4) / 10;
		status.totalK = Math.round(status.total / 102.4) / 10;
		status.readM = Math.round(status.readK / 102.4) / 10;
		status.totalM = Math.round(status.totalK / 102.4) / 10;
		status.rate = status.elapsed ? Math.round(status.read / status.elapsed) : 0;
		status.rateK = Math.round(status.rate / 102.4) / 10;
		status.remainPercent = status.total ? Math.round(status.read/status.total*100) : 0;
		status.remainBytes = status.total - status.read;
		status.remainTime = status.rate ? Math.round(status.remainBytes / status.rate) : 99*60*60;
		status.remainMin = Math.floor(status.remainTime / 60);
		status.remainSec = status.remainTime % 60;
	}
	catch (exception)
	{
		Pokkari.HandleException(exception,"In calculate");
	}
	return status;
}
//<div class='validator'
//	name='PokkariElement'
//	pokkariType='PokkariValidator'
//	pokkariParameters='targetId:"target",expression:"parseInt(value) >= 1 && parseInt(value) <= 10",formId:"theForm"'
//>The number was invalid</div>

// Set targetId to the id of the input element
// Set formId to the id of the form element
// Set expression to the expression to be validated -- 'value' is the value
// Set the class of the div to be validator for display:none and red.
// Set mode to 'static' if you're going to use visibility:hidden
function PokkariValidator(params) {
	PokkariElement.apply(this,[params]);
}
PokkariValidator.prototype = new PokkariElement();
PokkariValidator.prototype.constructor = PokkariValidator;

PokkariValidator.AllValidators = new Array();
PokkariValidator.AllValidatorsEventSet = false;
PokkariValidator.ValidateAllOnSubmit = null;
PokkariValidator.Group = 'default';

PokkariValidator.prototype.onLoadHandler = function()
{
	if (this.targetId)
		this.targetElement = document.getElementById(this.targetId);

	if (!this.targetElement)
		throw new Error('Invalid targetElement or targetId specified');

	if (this.expression)
		this.callback = new Function("value","return "+this.expression);
	else if (typeof(this.callback) == "string") {
		var a;
		eval("a = " + this.callback);
		this.callback = a;
	}

	if (!this.callback || typeof(this.callback) != "function")
		throw new Error('Invalid expression or callback function specified');

	if (this.formId)
		this.formElement = document.getElementById(this.formId);

	if (!this.validateEvent)
		this.validateEvent = "blur";

	Pokkari.AttachEvent(this.targetElement,
		this.validateEvent,
		this.eventHandler(this.onValidate));

	if (this.formElement)
	{
		if (!PokkariValidator.AllValidatorsEventSet) 
		{
			PokkariValidator.AllValidatorsEventSet = true;
			if (this.formElement.onsubmit) {
				PokkariValidator.ValidateAllOnSubmit = this.formElement.onsubmit;
			}
			this.formElement.onsubmit = PokkariValidator.ValidateAll;
		}

		PokkariValidator.AllValidators.push(this);
	}
	
	if (!this.mode)
		this.mode = 'dynamic';
}

PokkariValidator.prototype.show = function()
{
	if (this.mode == 'dynamic')
		this.element.style.display = 'block';
	else
		this.element.style.visibility = 'visible';
}

PokkariValidator.prototype.hide = function()
{
	if (this.mode == 'dynamic')
		this.element.style.display = 'none';
	else
		this.element.style.visibility = 'hidden';
}

PokkariValidator.prototype.getTargetValue = function()
{
	return this.targetElement.value;
}

PokkariValidator.prototype.focus = function()
{
	this.targetElement.focus();
}

PokkariValidator.prototype.validate = function(focus)
{
	// If this validator has a group specified, and it's not our current
	// group then return validated.
	if (this.group && this.group != PokkariValidator.Group) {
		return true;
	}

	var value = this.getTargetValue();
	var result = this.callback(value);
	
	if (result)
		this.hide();
	else {
		this.show();
		if (this.focus && focus) { this.focus(); }
	}	
	return result;
}

PokkariValidator.prototype.onValidate = function(self,sender,e)
{
	return self.validate();
}

PokkariValidator.ValidateAll = function(e)
{
	var result = true;
	
	for (var i=0; i<PokkariValidator.AllValidators.length; i++)
	{
		// We'll focus the first result that fails.
		result = PokkariValidator.AllValidators[i].validate(result) && result;
	}

	if (result && PokkariValidator.ValidateAllOnSubmit) 
	{
		PokkariValidator.ValidateAllOnSubmit(e);
	}

	return result || false; 
}

function findPosX(obj)
{
	var curleft = 0;
	if (obj.offsetParent)
	{
		while (obj.offsetParent)
		{
			curleft += obj.offsetLeft
			obj = obj.offsetParent;
		}
	}
	else if (obj.x)
		curleft += obj.x;
	return curleft;
}

function findPosY(obj)
{
	var curtop = 0;
	if (obj.offsetParent)
	{
		while (obj.offsetParent)
		{
			curtop += obj.offsetTop
			obj = obj.offsetParent;
		}
	}
	else if (obj.y)
		curtop += obj.y;
	return curtop;
}

function setListPos(id,id2) {
	e = document.getElementById(id);
	//window.alert("x:"+findPosX(e) + " y:"+findPosY(e));
	p = document.getElementById(id2);
	p.style.left = (findPosX(e)-113)+"px";
	p.style.top = findPosY(e)+"px";
	p.style.visibility = "visible";
}

function setSecondaryPos(id,id2,flush_left) {
        e = $(id);
        p = $(id2);
        if(flush_left) {
                p.style.left = "15px";
        } else {
                p.style.left = (findPosX(e)+125)+"px";
        }
        p.style.top = (findPosY(e)-23)+"px";
        p.style.visibility = "visible";

        if((p.offsetWidth + findPosX(p) + 125) > self.screen.availWidth) {
                p.style.left = self.screen.availWidth - (p.offsetWidth * 2) - 125 + "px";
        }

}
function rate_over(type,id,myRating,siteRating) {
	for(var x=1; x<=myRating; x++) {
		var side = x % 2 == 0 ? "right" : "left";
		var color = Math.round(siteRating) >= x ? "purple" : "red";
		
		var element = document.getElementById("Star_"+type+id+"_"+x);
		element.src="/images/star_"+color+"_"+side+".gif";
	}
}

function rate_out(type,id,myRating,siteRating) {
	for(var x=1; x<=myRating; x++) {
		var side = x % 2 == 0 ? "right" : "left";
		var color = Math.round(siteRating) >= x ? "blue" : "null";
		
		var element = document.getElementById("Star_"+type+id+"_"+x);
		element.src="/images/star_"+color+"_"+side+".gif";
	}
}

function rate_disable(type,id) {
	for(var x=1; x<=10; x++) {
		var element = document.getElementById("Rate_"+type+id+"_"+x);
		if (element) {
			element.href = "javascript:void(0)";
			element.disableme = true;
		}
	}
}

function rate_post(type,id,myRating,siteRating) {
	var request = new PokkariXmlRequest();

	request.url = "/"+type+"/rate/"+id+"?rating="+myRating;
	request.onsuccess = rate_success;
	request.onfailure = rate_failure;
	request.onloading = rate_loading;
	request.oncompleted = rate_completed;
	request.send();
/*
	var url = "/"+type+"/rate/"+id+"?rating="+myRating+"&skin=xmlhttprequest";
	if (window.XMLHttpRequest) {
		rate_req = new XMLHttpRequest();
		rate_req.onreadystatechange = rate_req_change;
		rate_req.open("GET",url,true);
		rate_req.send(null);
	}
	else if (window.ActiveXObject) {
		rate_req = new ActiveXObject("Microsoft.XMLHTTP");
		if (rate_req) {
			rate_req.onreadystatechange = rate_req_change;
			rate_req.open("GET",url,true);
			rate_req.send();
		}
	}
*/
}
/*
function rate_req_change() {
	if (rate_req.readyState == 4) {
		if (rate_req.status == 200) {
			var xml = rate_req.responseXML;
			var topNode = xml.documentElement;
			var response = topNode.getElementsByTagName("response")[0];
			var error = response.getElementsByTagName("error")[0];
			var notice = response.getElementsByTagName("notice")[0];

			if (error) {
				window.alert("Error: " + error.firstChild.nodeValue);
			}
			else if (notice) {
				window.alert(notice.firstChild.nodeValue);
			}
			else {
				var type = response.getElementsByTagName("type")[0].firstChild.nodeValue;
				var id = response.getElementsByTagName("id")[0].firstChild.nodeValue;
				var myRating = response.getElementsByTagName("myRating")[0].firstChild.nodeValue;
				var siteRating = response.getElementsByTagName("siteRating")[0].firstChild.nodeValue;

				rate_disable(type,id);
				rate_over(type,id,myRating,siteRating);
				var r = document.getElementById("Rating_"+type+id);
				r.innerHTML = siteRating;
				var v = document.getElementById("Votes_"+type+id);
				v.innerHTML = parseInt(v.innerHTML) + 1;

			}	
		}
		else {
			alert("There is a problem retrieving the XML data:\n" +
			rate_req.statusText);
		}
	}
}
*/

function rate_success(request)
{
	var xml = request.getResponseXml();
	var topNode = xml.documentElement;
	var response = topNode.getElementsByTagName("response")[0];
	var error = response.getElementsByTagName("error")[0];
	var notice = response.getElementsByTagName("notice")[0];

	if (error) {
		window.alert("Error: " + error.firstChild.nodeValue);
	}
	else if (notice) {
		window.alert(notice.firstChild.nodeValue);
	}
	else {
		var type = response.getElementsByTagName("type")[0].firstChild.nodeValue;
		var id = response.getElementsByTagName("id")[0].firstChild.nodeValue;
		var myRating = response.getElementsByTagName("myRating")[0].firstChild.nodeValue;
		var siteRating = response.getElementsByTagName("siteRating")[0].firstChild.nodeValue;

		rate_disable(type,id);
		rate_over(type,id,myRating,siteRating);
		var r = document.getElementById("Rating_"+type+id);
		if (r)
			r.innerHTML = siteRating;
		var v = document.getElementById("Votes_"+type+id);
		if (v)
			v.innerHTML = parseInt(v.innerHTML) + 1;
	}
}

function rate_failure(request)
{
	alert("There is a problem retrieving the XML data:\n" +
		request.getStatusText());
}

function rate_loading(request)
{
	var element = document.getElementById("xmlhttploading");
	var top = window.pageYOffset ? window.pageYOffset : document.body.scrollTop;

	if (element != void(0) && top != void(0)) {
		element.style.top = top;
		element.style.display = "block";
	}
}

function rate_completed(request)
{
	var element = document.getElementById("xmlhttploading");
	if (element != void(0)) {
		element.style.display = "none";
	}
}
function queryChanger(params) {
	// noop
}

queryChanger.prototype = new PokkariElement();
queryChanger.prototype.constructor = queryChanger;

queryChanger.prototype.onConstruct = function() {

	this.element.onchange = function() {
		this.pokkariElement.onChange();
	}
}

queryChanger.prototype.onChange = function() {
	if (this.multipleKeys) 
	{
		var changes = Url.ParseQuery(this.element.value);
		var removes = this.removeKeys && this.removeKeys.split(/\s*,\s*/);

		this.changeMultipleParameters(changes,removes);
	}
	else 
	{
		this.changeQueryParameter({ key : this.key, value : this.element.value, remove: this.removeKeys });
	}
}

queryChanger.prototype.changeMultipleParameters = function(changes,removes)
{
	var url = new Url(window.location.href);

	if (removes)
	{
		for(var i=0; i<removes.length; i++)
		{
			url.removeQueryParam(removes[i]);
		}
	}

	if (changes)
	{
		for(var i in changes)
		{
			if (typeof(changes[i]) != "function")
				url.setQueryParam(i,changes[i]);
		}
	}

	if (this.changeRoot)
		window.location.href = Url.ReplaceQuery(this.changeRoot,Url.MakeQuery(url.query));
	else
		window.location.href = url.getUrl();
}

queryChanger.prototype.changeQueryParameter = function(params) {
	var url = new Url(window.location.href);

	if (params.remove) 
	{ 
		var removes = params.remove.split(/\s*,\s*/);
		for (var i=0; i<removes.length; i++)
			url.removeQueryParam(removes[i]);
	}

	if (params.key)
	{
		url.setQueryParam(params.key,params.value);
	}
	
	window.location.href = url.getUrl();
/*
	var href = window.location.href;

	if(href.match(/\?/)) {
		var paramFinder = new RegExp(params.key+"=[^;&]*");
		if(href.match(paramFinder)) {
			href = href.replace(paramFinder,params.key+"="+params.value);
		} else {
			href += "&"+params.key+"="+params.value;
		}

		if (params.remove) {
			if (typeof(params.remove) == "string")
				params.remove = params.remove.split(/,/);

			for (var i=0; i<params.remove.length; i++) {
				var removeFinder = new RegExp(params.remove[i]+"=[^&;]*[&;]?");
				href = href.replace(removeFinder,'');
			}
		}
	} else {
		href += "?"+params.key+"="+params.value;
	}

	window.location.href = href;
*/
}

AutoCompletionZone = function() {
	this.init(this);
}

AutoCompletionZone.prototype = new PokkariElement();
AutoCompletionZone.constructor = AutoCompletionZone;

AutoCompletionZone.prototype.onConstruct = function() {

	/*
		Our EditPalette may have been instantiated before us 
	*/
	if(this.element.EditPalette) {
		this.EditPalette = this.element.EditPalette;
	}

	this.element.className = "autoCompletionZone";
}

AutoCompletionZone.prototype.init = function() {
	// nooop
}

AutoCompletionZone.prototype.setLabel = function(labelText) {
	var label = document.createElement('label'); 
	label.innerHTML = labelText;
	this.element.appendChild(label);
}

AutoCompletionZone.prototype.clearCandidates = function() {

	while(this.element.getElementsByTagName('a').length) {
		this.element.getElementsByTagName('a')[0].parentNode.removeChild(this.element.getElementsByTagName('a')[0]);
	}

	this.setTopSuggestion('');

}

AutoCompletionZone.prototype.newCandidates = function(candidates) {

	this.clearCandidates();

	for(var i = 0; i < candidates.length; i++) {
		var tagElement = document.createElement('a');
		tagElement.innerHTML = candidates[i];
		tagElement.className = 'suggestion';

		tagElement.href = 'javascript:void(0);';

		tagElement.onclick = new Function("this.parentNode.pokkariElement.acceptSuggestion(this.innerHTML)");

		/*
			The first suggestion in the primary AutoCompletionZone
			is used for tag copmletion
		*/
		if(this.isPrimary() && i == 0) {
			tagElement.style.color = 'red';
			this.setTopSuggestion(candidates[i]);
		}

		this.element.appendChild(tagElement);
	}

}

AutoCompletionZone.prototype.acceptSuggestion = function(suggestion) {
	if(this.EditPalette) {
		this.EditPalette.pokkariElement.setCurrentTopic(suggestion);
		this.EditPalette.focus();
	} else { }
}

AutoCompletionZone.prototype.setPrimary = function(primary) {
	this.primary = primary;
}

AutoCompletionZone.prototype.isPrimary = function() {
	return this.primary;
}

AutoCompletionZone.prototype.setTopSuggestion = function(topSuggestion) {
	this.topSuggestion = topSuggestion;
}

AutoCompletionZone.prototype.getTopSuggestion = function() {
	return this.topSuggestion;
}
function sideBox(params) {
	
	if(params) {

	}
}

sideBox.prototype = new PokkariElement();
sideBox.prototype.constructor = sideBox;

sideBox.prototype.onConstruct = function() {

	if(document.getElementById('side0_auto')) {
		if(
			this.element.parentNode.id != 'side0' &&
			this.element.parentNode.id != 'side0_auto'	
		) {
			// move to the sidebar if we're not already there
			document.getElementById('side0_auto').appendChild(this.element);
			this.element.style.display = '';
		}
	}
	
}
function Url(url) {
	this.setUrl(url);
}

Url.prototype.getUrl = function() {
	if (this.urlDirty) {
		this.updateUrl();
	}

	return this.url;
}

Url.prototype.setUrl = function(url) {
	this.url = url;
	this.query = this.parseQuery();
	this.urlDirty = false;
}

Url.prototype.getQuery = function() {
	return Url.GetQuery(this.url);
}

Url.prototype.parseQuery = function() {
	return Url.ParseQuery(this.getQuery());
}

Url.prototype.getQueryParam = function(name) {
	return this.query[name];
}

Url.prototype.setQueryParam = function(name,value) {
	this.query[name] = value;
	this.urlDirty = true;
}

Url.prototype.removeQueryParam = function(name,value) {
	delete this.query[name];
	this.urlDirty = true;
}

Url.prototype.updateUrl = function() {
	this.url = Url.ReplaceQuery(this.url,Url.MakeQuery(this.query));
	this.urlDirty = false;
}

Url.prototype.getServer = function() {
    return Url.GetServer(this.url);
}

Url.GetQuery = function(url) {
	if (typeof(url) == "undefined" || !url)
		return null;

	var parts = url.split("?");
	var query = parts[1];
	if (query) {
		return query.split("#")[0];
	}

	return null;
}

Url.ParseQuery = function(query) {
	var result = new Object();

	if (typeof(query) != "undefined" && query) {
		// Split it into name/value pairs
		var crumbs = query.split(/[&;]/);
		for (var i=0; i<crumbs.length; i++) {
			// Split the name and value
			var crumb = crumbs[i].split("=");

			// No equals means that it's a keyword
			if (crumb.length < 2) {
				if (result.keywords) {
					result.keywords += ",";
				}
				else {
					result.keywords = "";
				}

				result.keywords += unescape(crumb[0]);
			}
			else {
				result[unescape(crumb[0])] = unescape(crumb[1]);
			}
		}
	}

	return result;
}

Url.MakeQuery = function(object) {
	if (typeof(object) == "undefined" || !object)
		return null;

	var result = "";

	for (var i in object) {
		if (typeof(object[i]) == "number" || typeof(object[i]) == "string") {
			if (result) {
				result += "&";
			}

			result += escape(i) + "=" + escape(object[i]);
		}
	}

	return result;
}

Url.ReplaceQuery = function(url,query) {
	var parts = url.split("?");
	var u = parts[0];
	var a;

	if (parts[1]) {
		parts = parts[1].split("#");
		a = parts[1];
	}

	if (query) {
		u += "?" + query;
	}

	if (a) {
		u += "#" + a;
	}

	return u;
}

Url.ChangeQueryParam = function(url,param,value)
{
	url = new Url(url);
	url.setQueryParam(param,value);

	return url.getUrl();
}

Url.ChangeLocationQueryParam = function(param,value)
{
	window.location.href = Url.ChangeQueryParam(window.location.href,param,value);
}

Url.GetServer = function(url)
{
    var server = url.replace(/.*?:\/\/([\w\.]+).*/,"$1");

    return server;
}
// RATING RELATED FUNCTIONS

// FOLDING TREE MENU
function initTree() {
	/* Original Code by sean@frontierit.com */
	if (!document.getElementById) return;
	
	var aTrees = document.getElementsByTagName('UL');
	
	if (aTrees.length > 0) {
		for (var i = 0; i < aTrees.length; i++) {
			if (aTrees[i].className == "ftm") {
				ftm(aTrees[i]);
			}
		}
	}
}

function ftm(menu) {

	var docs  = menu.getElementsByTagName('LI');

	for (var i = 0; i < docs.length; i++) {
        var oHref = document.createElement("IMG");
        oHref.src = "image/x.gif";
        oHref.style.display = 'inline';
        oHref.style.marginRight = '3px';
        // oHref.style.paddingBottom = '4px';

		if (docs[i].getElementsByTagName('UL').length > 0 ) {

			oHref.src = "image/plus.gif";
			oHref.style.cursor = 'hand';
			
			oHref.onmousedown = function() {
				if (this.parentNode.childNodes[2].style.display == '' || 
					this.parentNode.childNodes[2].style.display == 'none' ) {
					this.parentNode.childNodes[2].style.display = 'block';
					this.src = "image/minus.gif";
				
				} else {
					this.parentNode.childNodes[2].style.display = 'none';
					this.src = "image/plus.gif";
				}
			}
		}
		docs[i].insertBefore(oHref,docs[i].firstChild);
	}
	
	/* Expand Folding Tree Menu to current url if it exists in the tree */
	for (var i = 0; i < docs.length; i++) {

		if (docs[i].childNodes[1].nodeName == 'A' &&
			docs[i].childNodes[1].href == location ) {
			docs[i].firstChild.src = "image/selected.gif";
			var q = docs[i].parentNode;
			
			while (q.className != 'ftm') {
				q.firstChild.src = "image/minus.gif";
	          	q.style.display = 'block';
	          	q = q.parentNode;
    	    }
		} 
	}
}

window.onload = initTree;


// COMMENT QUOTING
    function showCommentReplyForm(attachedTo,id,subject,author) {
        try {
			// Grabs the comments form at the bottom of the page
			// and inserts it in there
            var commentsFormContainer = document.getElementById("commentForm_Container");
            var newCommentsFormContainer = document.getElementById("commentForm_Container_Reply_"+id);
            newCommentsFormContainer.innerHTML = commentsFormContainer.innerHTML;
            newCommentsFormContainer.style.display = "block";
	    	var quotedText = getFormattedQuoteBlock(author);
            // We get our underlying HTMLFormElement
            var newCommentsForm = newCommentsFormContainer.firstChild.action ? newCommentsFormContainer.firstChild : newCommentsFormContainer.firstChild.nextSibling;
            // Let's see if we can't walk the DOM to set our quoted text
			var textareas = newCommentsForm.getElementsByTagName("*");
            var textareas = newCommentsForm.getElementsByTagName("textarea");
            for(i = 0; i < textareas.length; i++) {
                if(textareas[i].getAttribute("name") == "body") {
                    textareas[i].value = quotedText;
                }
            }
            var inputs = newCommentsForm.getElementsByTagName("input");
            for(i = 0; i < inputs.length; i++) {
                if(inputs[i].getAttribute("name") == "reply") {
                    inputs[i].value = id;
                }
                else if(inputs[i].getAttribute("name") == "subject") {
                    inputs[i].value = "Re: "+subject    
                }
            }   
        } catch(e) {
            // We'll redirect to the normal form
            alert("Exception: "+e);
            window.location.href = "/?s=c;attached_to="+attachedTo+";reply="+id+";subject=RE: "+subject+";cmd=post";
        }
    }
    function getFormattedQuoteBlock(author) {
	var quotedText  = (document.all) ? document.selection.createRange().text : window.getSelection();
	if(author) {
		quotedText = author+" wrote:\n<blockquote>"+quotedText+"</blockquote>";
	}
	else {
		quotedText = "<blockquote>\n"+quotedText+"\n</blockquote>";
	}
	return quotedText;
    }


// OTHER FUNCTIONS
	function toggleNavBox(header,name) {
		// Get references to the objects
		img = header.firstChild.firstChild;
		box = header.nextSibling.style ? header.nextSibling : header.nextSibling.nextSibling;
		// Update the values
		if (box.style.display == 'none') {
			img.src = '/skin/otter/nav.down.gif';
			box.style.display = 'block';
			document.cookie ='show_'+name+'="yes"';
		} else {
			img.src = '/skin/otter/nav.right.gif';
			box.style.display = 'none';
			document.cookie = 'show_'+name+'="no"';
		}
	}
	function getCookie(name) {
		var dc = document.cookie;
		var prefix = name + "=";
		var begin = dc.indexOf("; " + prefix);
		if (begin == -1) {
			begin = dc.indexOf(prefix);
		if (begin != 0) return null;
		} else
			begin += 2;
		var end = document.cookie.indexOf(";", begin);
		if (end == -1)
			end = dc.length;
		return unescape(dc.substring(begin + prefix.length, end));
	}

/* toggleHiddenDiv(buttonId, divId) (Public) [JD]

Hide or unhide a div layer using an onClick.  The innerHTML of the
buttonId's element is changed so that it will start with << or end
with >> depending on the state of the hidden div.
*/
function toggleHiddenDiv(buttonId, divId, startColor, destinationColor, hideInnerHTML, showInnerHTML)
{
	var button;
	if(buttonId) {
		button = document.getElementById(buttonId);
	}

	if (buttonId && button == undefined) {
		window.alert('Cannot locate '+buttonId);
		return false;
	}

	var div = document.getElementById(divId);
	if (div == undefined) {
		window.alert('Cannot locate '+divId);
	}

	if(buttonId) {
		var s = button.innerHTML;
		s = s.replace("&nbsp;","");
		s = s.replace("&gt;","");
		s = s.replace("&lt;","");
		s = s.replace("<","");
		s = s.replace(">","");
	}

	if(!startColor) {
		startColor = '99FF33'
	}
	if(!destinationColor) {
		destinationColor = 'ffffff';
	}

	if (div.style.display != 'block') {
		if(buttonId) {
			if(button.tagName.toLowerCase() == "img") {
					button.src = showInnerHTML;
			} else {
				if (hideInnerHTML) {
					button.innerHTML = unescape(showInnerHTML);
				} else {
					button.innerHTML = "&lt;&nbsp;" + s;
				}
			}
		}
		div.style.display = 'block';
		if (typeof(fader) != "undefined") 
			fader.fade(div,startColor,destinationColor);
	}
	else {
		if(buttonId) {
			if(button.tagName.toLowerCase() == "img") {
				button.src = hideInnerHTML;
			} else {
				if (showInnerHTML) {
					button.innerHTML = unescape(hideInnerHTML);
				} else {
					button.innerHTML = s + "&nbsp;&gt;";
				}
			}
		}
		div.style.display = 'none';
	}

	return true;
}

/* addFormWarning(warningText) (Public) [JD]

Adds text to the warnings div of a cotnainer_form

*/
function addFormWarning(warningText) {
	var element = document.getElementById("form_warnings");

	if (element != undefined) {
		var newWarning = document.createElement('div');
		newWarning.className = 'warnings_generic';
		newWarning.innerHTML = '<span class="warning_text">WARNING:</span> ' + warningText;
		element.appendChild(newWarning);
	}
}

/* addFormOnSubmit(js) (Public) [JD]

Adds text to the form elements onSubmit clause

*/
function addFormOnSubmit(js) {
	var element = document.getElementById("form_post");

	if (element != undefined) {
		element.onSubmit = element.onSubmit == undefined ? js : element.onSubmit+js;
	}
}

/**
 * AccordionControl is an expanding/collasing control.  It finds any child controls with coorisponding CSS class
 * names matching tabClassName and contentClassName and makes them into summary/detail views.  Each tab and content
 * child element should be a block element (such as div).   Clicking the tab element will cause the content element
 * to expand or contract.
 * @constructor
 * @param {String} tabClassName CSS Class name that will signal a summary block element
 * @param {String} contentClassName CSS Class name that will signal a detail block element
 * @param {Boolean} singleSelect If set, only one detail block element will be expanded at a time
 */
function AccordionControl(params)
{
    PokkariElement.apply(this,[params]);
}

AccordionControl.prototype = new PokkariElement();
AccordionControl.prototype.constructor = AccordionControl;

/**
 * Initializer
 * @private
 */
AccordionControl.prototype.oninit = function()
{
    if (!this.tabClassName)
        throw new Error("Missing init parameter: tabClassName");
    
    if (!this.contentClassName)
        throw new Error("Missing init parameter: contentClassName");
 
    this.tabControls = new Array();
    this.contentControls = new Array();
    this.attachChildControls(this.element);

    // TODO Add cookie save support
    
    if (this.tabControls.length != this.contentControls.length)
        throw new Error("Unequal number of tab and content controls");        
}

/**
 * Called from oninit to find summary and detail controls, and attach events.  If the summary control contains an image
 * it will be assumed to be accordionCollapsed.gif (FIXME)
 * @private
 * @param {Object} src Element from which to get the summary/detail controls.  Called from oninit.  Elsewhere?
 */
AccordionControl.prototype.attachChildControls = function(src)
{
    var divs = src.getElementsByTagName("div");
    
    for (var i=0; i<divs.length; i++)
    {
        if (divs[i].className && (
			divs[i].className == this.tabClassName ||
			divs[i].className == this.tabExpandedClassName))
        {
            divs[i].tabNumber = this.tabControls.length;
            divs[i].unselectable = true;
			divs[i].selected = (divs[i].className == this.tabExpandedClassName);
            
            var imgs = divs[i].getElementsByTagName("img");
            if (imgs && imgs.length > 0)
                divs[i].tabIcon = imgs[0];
            
            this.tabControls.push(divs[i]);
            this.attachTabEvents(divs[i]);
        }
        else if (divs[i].className == this.contentClassName)
        {
            this.contentControls.push(divs[i]);
        }
    }
}

/**
 * Called from attachChildControls to attach an onclick event to the summary controls
 * @param {Object} tabControl Summary element to attach events to
 * @private
 */
AccordionControl.prototype.attachTabEvents = function(tabControl)
{
    tabControl.attachEvent("onclick",this.eventHandler(this.onToggleTab))
}

/**
 * Event handler, finds the appropriate summary control by inspecting the sender or sender's parents and calls toggleTab.
 * @param {Element} self Context of this object (use instead of 'this')
 * @param {Element} sender Object who trigger the event (not necessary the object with the event attached)
 * @param {Object} e Event arguments object (see IE window.event)
 * @returns True, no further event handling will take place.
 */
AccordionControl.prototype.onToggleTab = function(self,sender,e)
{
    self.toggleTab(self.findTabFromSender(sender));
        
    return true;
}

/**
 * Finds the appropriate summary control by looking at an element and its' parents.
 * @private
 */
AccordionControl.prototype.findTabFromSender = function(sender)
{
    var node = sender;
    while (node && typeof(node.tabNumber) == "undefined")
    {
        node = node.parentNode;
    }
    
    return node;
}

/**
 * Toggles the visibility for the detail portion of this summary/detail pair.  If tabControl implements a onExpand
 * method, it will be called before displaying the details.  Similarly if tabControl onCollapse exists, it will
 * be called prior to hiding details.  Use onExpand to implement callback loading.
 * @param {Object} tabControl Summary element for which the coorisponding detail element will be shown or hidden.
 */
AccordionControl.prototype.toggleTab = function(tabControl)
{
    var tabNumber = tabControl.tabNumber;
    var contentControl = this.contentControls[tabNumber];
    var selected = tabControl.selected;

    if (this.singleSelect)    
        this.collapseAll();

    if (!selected)
    {
        tabControl.selected = true;
        if (tabControl.onExpand)
            tabControl.onExpand(this,tabControl,null);
        
        if (tabControl.tabIcon) 
            tabControl.tabIcon.src = '/images/accordionExpanded.gif';

		if (this.tabExpandedClassName) {
			tabControl.className = this.tabClassName;
		}
            
       	contentControl.style.display = "block";

	if(typeof(Pokkari.SetCookiePreference) == "function" && tabControl.id) {
		Pokkari.SetCookiePreference({
			name : 'acs_' + tabControl.id + '_expanded',
			value : 1
		});
		Pokkari.SetCookiePreference({
			name : 'acs_' + tabControl.id + '_collapsed',
			value : 0
		});
	}
    }
    else
    {
        tabControl.selected = false;
        if (tabControl.onCollapse)
            tabControl.onCollapse(this,tabControl,null);
            
        if (tabControl.tabIcon) 
            tabControl.tabIcon.src = '/images/accordionCollapsed.gif';
        
		if (this.tabExpandedClassName) {
			tabControl.className = this.tabExpandedClassName;
		}
            
       	contentControl.style.display = "none";

	if(typeof(Pokkari.SetCookiePreference) == "function" && tabControl.id) {
		Pokkari.SetCookiePreference({
			name : 'acs_' + tabControl.id + '_collapsed',
			value : 1
		});
		Pokkari.SetCookiePreference({
			name : 'acs_' + tabControl.id + '_expanded',
			value : 0
		});
	}
    }   
}

/**
 * Collapse all controls, hiding all details.
 */
AccordionControl.prototype.collapseAll = function()
{
    for (var i=0; i<this.contentControls.length; i++)
    {
        this.contentControls[i].style.display = "none";
        this.tabControls[i].selected = false;
    }
}
var PokkariCaptcha = Class.create();
PokkariCaptcha.prototype = {
	initialize: function() {
		this.uuid = new UUID();
	},

	getHTML: function() {
		return "<input type='hidden' name='captchas_guid' value='" + this.uuid + "' />\n" +
			"<img src='/captchas/" + this.uuid + "' />\n";
	},
	
	write: function() {
		var html = this.getHTML();
		document.write(html);
	},

	captchaIsAppended: function(element) {
		// TODO FIXME Naive approach, use a className to make sure.
		var imgs = element.getElementsByTagName("img");
		return (imgs && imgs.length);
	},

	appendChildTo: function(element) {
		if (element && element.appendChild) {
			if (!this.captchaIsAppended(element)) {
				var hidden = document.createElement("input");
				hidden.type = "hidden";
				hidden.name = "captchas_guid";
				hidden.value = this.uuid;
				element.appendChild(hidden);

				var img = document.createElement('img');
				img.style.width = "172px";
				img.style.height = "64px";

				// Give IE time to figure out its DOM handling before showing the image.
				if (document.all) {
					img.src = "/blank.gif";
					window.captchaImage = img;
					window.setTimeout("window.captchaImage.src = '/captchas/"+this.uuid+"'; window.captchaImage = null;",50);
				}
				else {
					img.src = "/captchas/" + this.uuid;
				}

				element.appendChild(img);
			}
		}
	}
}

PokkariCaptcha.include = function() {
	var captcha = new PokkariCaptcha();
	captcha.write();
}

PokkariCaptcha.appendChildTo = function(element) {
	var captcha = new PokkariCaptcha();
	captcha.appendChildTo(element);
}
/*

uuid.js - Version 0.1
JavaScript Class to create a UUID like identifier

Copyright (C) 2006, Erik Giberti (AF-Design), All rights reserved.

This program is free software; you can redistribute it and/or modify it under 
the terms of the GNU General Public License as published by the Free Software 
Foundation; either version 2 of the License, or (at your option) any later 
version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License along with 
this program; if not, write to the Free Software Foundation, Inc., 59 Temple 
Place, Suite 330, Boston, MA 02111-1307 USA

The latest version of this file can be downloaded from
http://www.af-design.com/resources/javascript_uuid.php

HISTORY:
6/5/06 - Initial Release

*/


// on creation of a UUID object, set it's initial value
function UUID(){
	this.id = this.createUUID();
}



// When asked what this Object is, lie and return it's value
UUID.prototype.valueOf = function(){ return this.id; }
UUID.prototype.toString = function(){ return this.id; }



//
// INSTANCE SPECIFIC METHODS
//



UUID.prototype.createUUID = function(){
	// JavaScript Version of UUID implementation.
	//
	// Copyright 2006 Erik Giberti, all rights reserved.
	//
	// Loose interpretation of the specification DCE 1.1: Remote Procedure Call
	// described at http://www.opengroup.org/onlinepubs/009629399/apdxa.htm#tagtcjh_37
	// since JavaScript doesn't allow access to internal systems, the last 48 bits 
	// of the node section is made up using a series of random numbers (6 octets long).
	//  
	var dg = UUID.timeInMs(new Date(1582, 10, 15, 0, 0, 0, 0));
	var dc = UUID.timeInMs(new Date());
	var t = dc - dg;
	var h = '-';
	var tl = UUID.getIntegerBits(t,0,31);
	var tm = UUID.getIntegerBits(t,32,47);
	var thv = UUID.getIntegerBits(t,48,59) + '1'; // version 1, security version is 2
	var csar = UUID.getIntegerBits(UUID.randrange(0,4095),0,7);
	var csl = UUID.getIntegerBits(UUID.randrange(0,4095),0,7);

	// since detection of anything about the machine/browser is far to buggy, 
	// include some more random numbers here
	// if nic or at least an IP can be obtained reliably, that should be put in
	// here instead.
	var n = UUID.getIntegerBits(UUID.randrange(0,8191),0,7) + 
			UUID.getIntegerBits(UUID.randrange(0,8191),8,15) + 
			UUID.getIntegerBits(UUID.randrange(0,8191),0,7) + 
			UUID.getIntegerBits(UUID.randrange(0,8191),8,15) + 
			UUID.getIntegerBits(UUID.randrange(0,8191),0,15); // this last number is two octets long
	return tl + h + tm + h + thv + h + csar + csl + h + n; 
}



//
// GENERAL METHODS (Not instance specific)
//



// Pull out only certain bits from a very large integer, used to get the time
// code information for the first part of a UUID. Will return zero's if there 
// aren't enough bits to shift where it needs to.
UUID.getIntegerBits = function(val,start,end){
	var base16 = UUID.returnBase(val,16);
	var quadArray = new Array();
	var quadString = '';
	var i = 0;
	for(i=0;i<base16.length;i++){
		quadArray.push(base16.substring(i,i+1));	
	}
	for(i=Math.floor(start/4);i<=Math.floor(end/4);i++){
		if(!quadArray[i] || quadArray[i] == '') quadString += '0';
		else quadString += quadArray[i];
	}
	return quadString;
}

// Numeric Base Conversion algorithm from irt.org
// In base 16: 0=0, 5=5, 10=A, 15=F
UUID.returnBase = function(number, base){
	//
	// Copyright 1996-2006 irt.org, All Rights Reserved.	
	//
	// Downloaded from: http://www.irt.org/script/146.htm	
	// modified to work in this class by Erik Giberti
	var convert = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
    if (number < base) var output = convert[number];
    else {
        var MSD = '' + Math.floor(number / base);
        var LSD = number - MSD*base;
        if (MSD >= base) var output = this.returnBase(MSD,base) + convert[LSD];
        else var output = convert[MSD] + convert[LSD];
    }
    return output;
}

// This is approximate but should get the job done for general use.
// It gets an approximation of the provided date in milliseconds. WARNING:
// some implementations of JavaScript will choke with these large numbers
// and so the absolute value is used to avoid issues where the implementation
// begin's at the negative value.
UUID.timeInMs = function(d){
	var ms_per_second = 100; // constant
	var ms_per_minute = 6000; // ms_per second * 60;
	var ms_per_hour   = 360000; // ms_per_minute * 60;
	var ms_per_day    = 8640000; // ms_per_hour * 24;
	var ms_per_month  = 207360000; // ms_per_day * 30;
	var ms_per_year   = 75686400000; // ms_per_day * 365;
	return Math.abs((d.getUTCFullYear() * ms_per_year) + (d.getUTCMonth() * ms_per_month) + (d.getUTCDate() * ms_per_day) + (d.getUTCHours() * ms_per_hour) + (d.getUTCMinutes() * ms_per_minute) + (d.getUTCSeconds() * ms_per_second) + d.getUTCMilliseconds());
}

// pick a random number within a range of numbers
// int c randrange(int a, int b); where a <= c <= b
UUID.randrange = function(min,max){
	var num = Math.round(Math.random() * max);
	if(num < min){ 
		num = min;
	} else if (num > max) {
		num = max;
	}
	return num;
}

// end of UUID class file

var Safexss = {
	Version: '0.1',
  	prototypeVersion: parseFloat(Prototype.Version.split(".")[0] + "." + Prototype.Version.split(".")[1])
}

if((typeof Prototype=='undefined') || Safexss.prototypeVersion < 1.3)
      throw("Safexss requires the Prototype JavaScript framework >= 1.3");

Safexss.includeScript = function (url) {    
	var script = document.createElement("script");
	script.type = 'text/javascript';
	script.src = url;
	var body = document.body || document.documentElement;	
	body.appendChild(script);
	return script;
}

Safexss.Transport = Class.create();
Safexss.Transport.prototype = {
    initialize: function(options) {
        this.onreadystatechange = null;
        this.readyState = 0;
        this.responseText = "";
        this.responseXml = null;
        this.status = 0;
        this.statusText = "";
        this.translateUrl = Safexss.Transport.translateUrl;
        Object.extend(this,options||{});
        
        this.responseHeaders = {};
        // Set up context callback
        Safexss.Context.register(this);
    },
    abort: function() {
        throw new Error("Not implemented");
    },
    getAllResponseHeaders: function() {            
        var result = "";
        for (var header in this.responseHeaders)
        {   
            var pair = this.responseHeaders[header];
            result += pair[0] + ": " + pair[1] + "\n";
        }            
        
        return result;
    },    
    getResponseHeader: function(header) {
		var responseHeader = this.responseHeaders[header.toLowerCase()];
	
        return responseHeader && responseHeader[1];
    },
    setRequestHeader: function(header,value) {},
    setResponseHeader: function(header,value) {
        this.responseHeaders[header.toLowerCase()]=[header,value];
    },
    importAllResponseHeaders: function(headers) {
        for (var header in headers) 
            this.setResponseHeader(header,headers[header]);
    },
    open: function(method,url,async,user,pass) {
        if (method.toLowerCase() != "get")
            throw new Error("Safexss.Transport only supports GET requests");

        // Create and initialize script object
    	this.script = document.createElement("script");
	    this.script.type = 'text/javascript';
        
	    this.script.src = this.translateUrl(url);
    },
    send: function(content) {
        // Append script object
        var body = document.body || document.documentElement;
        body.appendChild(this.script);
        
        // Update readyState
        this.changeReadyState(1);
    },
    dispatch: function(data,headers) {
        this.status = 200;
        this.statusText = "OK";
        this.responseData = data;
        if (typeof(data) == "string" || typeof(data) == "number")
        {
            this.responseText = data;
            this.responseXml = this.getDocument();
            this.setResponseHeader("Content-Type",(this.responseXml == null || 
                this.responseXml.documentElement == null ||
                this.responseXml.documentElement.tagName == "parsererror") ?
                    "text/plain" : "text/xml");
        }
        else
        {
            this.responseText = data.toJSONString();
            this.setResponseHeader("Content-Type","text/javascript");
        }
        
        if (typeof(headers) == "object")
            this.importAllResponseHeaders(headers);

        this.changeReadyState(4);        
    },
    getDocument: function() {
        var text = this.responseText;
        return Try.these(
            function() { 
                var doc = new DOMParser().parseFromString(text,"text/xml");
                return doc;
            },
            function() {
                var doc = new ActiveXObject("Microsoft.XMLDOM");
                doc.loadXML(text);
                return doc;
            }        
        ) || false;                
    },
    changeReadyState: function(state) {
        this.readyState = state;
        if (typeof(this.onreadystatechange) == "function")
            this.onreadystatechange();
    },
    dispatchError: function(code,text,headers) {
        this.status = code;
        this.statusText = text;
        if (typeof(headers)=="object")
            this.importAllResponseHeaders(headers);
            
        this.changeReadyState(4);
    }
}

Safexss.Transport.translateUrl = function(url) {
    return url + ((url.match(/\?/)) ? "&" : "?") + 
        "callback=Safexss.callbacks%5B"+this.context+"%5D.dispatch";
}

Safexss.callbacks = $H({});

Safexss.Context = {
	register: function(transport) {
		for (var i=0; i<10; i++) {
			var context = this.generateContext();
			if (!Safexss.callbacks[context]) {
				transport.context = context;
				Safexss.callbacks[context] = transport;

				return context;
			}
		}
		throw new Error("Could not locate free space for context");
	},
    unregister: function(context) {
        Safexss.callbacks[context] = null;
        delete Safexss.callbacks[context];
    },    
    transport: function(context) {
        if (Safexss.callbacks[context])
            return Safexss.callbacks[context];
        else
            throw new Error("No transport found for context");
    },    
	dispatch: function(context, data) {
	    this.transport(context).dispatch(data);
	},    
    dispatchError: function(context, code, text) {
        this.transport(context).dispatchError(code,text);
    },
	generateContext: function() {
        return Math.floor(Math.random()*900000)+100000;
	}
}

Safexss.Request = new Class.create();
Object.extend(Safexss.Request.prototype,Ajax.Request.prototype);
Safexss.Request.prototype.initialize = function(url, options) {
    this.transport = new Safexss.Transport();
    this.setOptions(Object.extend({ method:"get" },options||{}));
    this.request(url);
}
function PokkariJsonRequest(params) {
	if (params)
	{
		this.url = params.url;
	}

	this.method = "GET";
}

// Set this up as a subclass of PokkariElement
PokkariJsonRequest.prototype = new PokkariXmlRequest();
PokkariJsonRequest.prototype.constructor = PokkariJsonRequest;

PokkariJsonRequest.prototype.send = function()
{
	var url = this.url;

	// Fix the URL to make sure it's going to xmlhttprequest skin.
	if (!url.match(/skin=json/)) {
		if (!url.match(/\?/)) {
			url += "?skin=json";
		}
		else {
			url += "&skin=json";
		}
	}
	
	// Save our object so we can get it later.
	this.key = PokkariXmlRequest.StoreObject(this);

	// Create the request object, or throw an error.
	this.request = new Safexss.Transport();

	if (!this.request) {
		throw new Error("Could not create Safexss.Transport object.");
	}

	// Send the request.
	try {
		// When it callsback, get our object and call our handler
		this.request.onreadystatechange = new Function("PokkariXmlRequest.GetObject("+this.key+").onreadystatechange()");
		this.request.open(this.method,url,true);
		this.request.send();
	}
	catch (exception) {
		alert("While sending JSON request: "+exception.message);
	}
}

PokkariJsonRequest.prototype.getResponseData = function() {
	return this.request.responseData;
}


function PokkariPostField(params) {
}


PokkariPostField.prototype = new PokkariXmlRequest();
PokkariPostField.prototype.constructor = PokkariPostField;

PokkariPostField.prototype.onConstruct = function() {

	// Get the value of the field if we didn't get one explicitly
	if(!this.value) {
		this.previousValue = this.element.innerHTML;
	}

	// Make the field editable
	if(this.can_edit) {
		this.editableState();
	}
}

PokkariPostField.prototype.getValue = function() {
	var value;
	if(this.value) {
		value = this.value;
	} else {
		value = this.previousValue;
	}
	
	if(value == this.nullValue) {
		return "";
	} else {
		return value;
	}
}

PokkariPostField.prototype.setValue = function(v) {
	this.value = v;
}

PokkariPostField.prototype.getHumanValue = function() {
	if(this.humanValue) {
		return this.humanValue;
	} else {
		return this.getValue();
	}
}

PokkariPostField.prototype.setHumanValue = function(hv) {
	this.humanValue = hv;
}

PokkariPostField.prototype.getPreviousValue = function() {
	return this.previousValue;
}

PokkariPostField.prototype.setPreviousValue = function(pv) {
	this.previousValue = pv;
}

PokkariPostField.prototype.getWidgetType = function() {
	return this.widgetType;
}

PokkariPostField.prototype.setWidgetType = function(wt) {
	this.widgetType = wt;
}

PokkariPostField.prototype.getWidgetWidth = function() {
	if(this.widgetWidth) {
		return this.widgetWidth;
	} else {
		return 30;
	}
}

PokkariPostField.prototype.setWidgetWidth = function(wx) {
	this.widgetWidth = wx;
}

PokkariPostField.prototype.getWidgetHeight = function() {
	return this.widgetHeight;
}

PokkariPostField.prototype.setWidgetHeight = function(wh) {
	this.widgetHeight = wh;
}

PokkariPostField.prototype.setAcceptableValues = function(av) {
	this.acceptableValues = av;
}

PokkariPostField.prototype.getAcceptableValues = function() {
	return this.acceptableValues;
}

PokkariPostField.prototype.getFieldName = function() {
	return this.fieldName;
}

PokkariPostField.prototype.setFieldName = function(fn) {
	this.fieldName = fn;
}

PokkariPostField.prototype.getItemType = function() {
	return this.item_type;
}

PokkariPostField.prototype.setItemType = function(it) {
	this.item_type = it;
}

PokkariPostField.prototype.getItemId = function() {
	return this.item_id;
}

PokkariPostField.prototype.setItemId = function(id) {
	this.item_id = id;
}

PokkariPostField.prototype.editableState = function() {

	if(this.state == "saving" || this.state == "edit") {
		this.element.innerHTML = this.getHumanValue();
	}

	this.element.className += ' inline_editable';
	if(!this.element.title) {
		this.element.title = "Click to edit";
	}
	this.element.onclick = function() { this.pokkariElement.editState(); };

	this.state = "normal";
}

PokkariPostField.prototype.savingState = function() {
	this.state = "saving";

	this.element.innerHTML = "<i>Saving...</i>";
}

PokkariPostField.prototype.editState = function() {
	
	this.element.className = this.element.className.replace(/(\s+)?inline_editable/,'');
	
	this.element.innerHTML = "";
	this.element.onclick = "";
	this.element.title = "";

	this.element.appendChild(this.getWidget());
	if(this.getWidgetType() == "textarea") {
		this.element.appendChild(document.createElement('br'));
	}
	this.element.appendChild(this.getSaveButton());
	this.element.appendChild(this.getAbandonButton());

	if($("field_editor_" + this.getFieldName())) {
		$("field_editor_" + this.getFieldName()).focus();
	}

	this.state = "edit";

}

PokkariPostField.prototype.getWidget = function() {

	var widget;
	// TODO FIXME Break this up into smaller methods.
	switch(this.getWidgetType()) {
		case 'textarea':
			widget		= document.createElement('textarea');
			if(this.getWidgetWidth().match(/px/)) {
				widget.style.width = this.getWidgetWidth();
			} else {
				widget.cols	= this.getWidgetWidth();
			}
			if(this.getWidgetHeight()) {
				if(this.getWidgetHeight().match(/px/)) {
					widget.style.height = this.getWidgetHeight();
				} else {
					widget.rows = this.getWidgetHeight();
				}
			}
			widget.name	= this.getFieldName();
			widget.value	= this.getValue();
		break;
		case 'select':
			widget	= document.createElement('select');

			if(!this.getAcceptableValues()) {
				throw("Cannot construct select widget without acceptable values");
			}
			
			if(this.getWidgetHeight()) {
				if(!this.getWidgetHeight().match(/px/)) {
					widget.size = this.getWidgetHeight();
				}
			}

			for(var i = 0; i < this.getAcceptableValues().length; i++) {

				var av = this.getAcceptableValues()[i];

				var option = document.createElement('option');
				option.text = av.text;
				option.value = av.value;
				if(av['value'] == this.getValue()) {
					option.setAttribute("selected","selected");
				}

				if (widget.options && widget.options.add) {
					widget.options.add(option);
				}
				else {
					widget.appendChild(option);
				}
			}

			widget.name = this.getFieldName();
		break;
		case 'topic':
			/*
				NOTE: You need to setWidth() with a pixel
				value for a topic widget.
			*/
			// var container = document.createElement('div');

			widget		= document.createElement('input');
			if(this.getWidgetWidth().match(/px/)) {
				widget.style.width = this.getWidgetWidth();
			}

			widget.name	= this.getFieldName();
			widget.value	= this.getValue();

			widget.setAttribute("pokkarielement","pokkarielement");
			widget.setAttribute("pokkariType","TopicEditor");
			widget.setAttribute("autocomplete","off");

			/*

			widget.className = "field_editor";
			widget.id = "field_editor_" + this.getFieldName();

			var autoCompleter = document.createElement('div');
			if(this.getWidgetWidth().match(/px/)) {
				autoCompleter.style.width = this.getWidgetWidth();
			}
			autoCompleter.id = "autoCompletionZoneContainer";
			autoCompleter.style.border = "1px solid red;";

			alert("I'm instantiating my object");
			var obj = new TopicEditor({autoCompletionZoneContainer : autoCompleter});
			obj.bindToHTMLElement(widget);
			obj.onConstruct();

			container.appendChild(widget);
			container.appendChild(autoCompleter);

			return container;
			*/
		break;
		default:
			widget 		= document.createElement('input');
			if(this.getWidgetWidth().match(/px/)) {
				widget.style.width = this.getWidgetWidth();
			} else {
				widget.size 	= this.getWidgetWidth(); 
			}
			widget.name	= this.getFieldName();
			widget.value	= this.getValue();
		break;
	}

	widget.className = "field_editor";
	widget.id       = "field_editor_" + this.getFieldName();

	return widget;
}

PokkariPostField.prototype.getSaveButton = function() {
	
	var saveButton = document.createElement("button");
	saveButton.innerHTML = "Save";
	saveButton.onclick = function() { this.parentNode.pokkariElement.save(); };
	saveButton.title = "Save your changes";
	saveButton.style.marginLeft = "5px";

	return saveButton;
}

PokkariPostField.prototype.getAbandonButton = function() {

	var abandonButton = document.createElement("button");
	abandonButton.innerHTML = "Cancel";
	// abandonButton.onclick = function() { alert("TK"); };
	abandonButton.onclick = function() { this.parentNode.pokkariElement.editableState(); };

	abandonButton.title = "Abandon your changes";

	return abandonButton;

}

PokkariPostField.prototype.getWidgetValue = function() {
	return $("field_editor_" + this.getFieldName()).value;
}

PokkariPostField.prototype.save = function() {
	/* 
		Double-check that the user has permission to do this
		(Additional permissions validation will happen server-side)
	*/

	if(!this.can_edit) {
		alert("Sorry, you don't seem to have permission to edit this field.");
		throw("User does not have permission to perform this operation");
	}

	this.setValue($("field_editor_" + this.getFieldName()).value);

	if(this.getWidgetType() == "select") {
		var hv = $("field_editor_" + this.getFieldName()).options[$("field_editor_" + this.getFieldName()).selectedIndex].text;
		this.setHumanValue(hv);
	}

	this.url = "/" + this.getItemType() + "/post/";

	/*
		We should be sending this through as data in an arg to send()
		Seems like when we do that, though, it shows up as a string
		under the key POSTDATA.
	*/
	
	this.url += "?id="+this.getItemId()+"&item_type="+this.getItemType()+"&"+this.fieldName+"="+this.value+"&post=post"

	this.url += '&skin=xmlhttprequest';

	this.method = "POST";
	this.send();

	this.savingState();

}

PokkariPostField.prototype.onsuccess = function() {

	this.editableState();

	if(this.on_change) {
		eval(this.on_change + "("+this.value+")");
	}

}

PokkariPostField.prototype.onfailure = function() {
	alert("We have an error");
	alert(this.getResponseText());
}
