/*-------------------------------    Class    -----------------------------*/

var Class = {

  create: function(prototype) {
    var clazz = function() {
      if (this.initialize) this.initialize.apply(this, arguments)
    }
    if (prototype) clazz.prototype = prototype;
    return clazz;
  },
  
  extend: function(superclass, prototype) {
    prototype = prototype || {};
    var clazz = this.create();
  	if (superclass.constructor == Function) { 
  		Object.extend(clazz, superclass);
    	prototype.superclass = superclass.prototype;
  	} else {
  		prototype.superclass = superclass;
  	}
    prototype.constructor = clazz;
    clazz.prototype = Object.extend(Object.extend({}, prototype.superclass), prototype)
    return clazz;
  }
}

/*--------------------------   InstanceManager   ---------------------------*/

var InstanceManager = {

  classMethods: {

    create: function() {
      var id = this.__instance_id__.apply(this, arguments);
      this.destroy(id);
      var instance = Object.extend({}, this.prototype);
      this.apply(instance, arguments);
      this.instances[id] = instance;
      return instance;
    },

    destroy: function(id) {
      var instance = this.instances[id];
      if (instance) {
        if (instance.destroy) instance.destroy();
        delete this.instances[id];
      }
      return instance;
    },
    
    __instance_id__: function() {
      return typeof(arguments[0]) == "string" ? arguments[0] : arguments[0].id;
    }
  },

  create: function(clazz, id_function) {
    Object.extend(clazz, InstanceManager.classMethods);
    clazz.instances = {};
    if (id_function) clazz.__instance_id__ = id_function;
  }  
};

/*----------------------------   String  -------------------------------*/

// Add trim functions to String
Object.extend(String.prototype, {
  
  ltrim: function(belowCharCode) {
  	if (!belowCharCode) belowCharCode = 33;
  	var i=-1;
  	while (this.charCodeAt(++i)<belowCharCode);
  	return this.substring(i);
  },

  rtrim: function(belowCharCode) {
  	if (!belowCharCode) belowCharCode = 33;
  	var i = this.length;
  	while (this.charCodeAt(--i)<belowCharCode);
  	return this.substring(0, ++i);
  },

  trim: function(belowCharCode) {
  	return this.rtrim(belowCharCode).ltrim(belowCharCode);
  }
});

/*-------------------------------    Event    -----------------------------*/

Object.extend(Event, {
  metaKey: function(event) {
    return navigator.appVersion.match(/Macintosh/) ? event.metaKey : event.ctrlKey;
  }
});

/*------------------------------    Element    ----------------------------*/

Object.extend(Element, {
  setVisibility: function(elements, flag) {
    if (elements.constructor != Array) elements = [elements];
    elements.each(function(element) {
      flag ? Element.show(element) : Element.hide(element);    
    });
  },

  extractId: function(element) {
    return $(element).id.match(/^[^_]*_(\d+)/)[1];
  },

  find: function(element, margin) {
    element = $(element);
    var left = (element.x ? element.x : element.offsetLeft);
    var top  = (element.y ? element.y : element.offsetTop);
    var height = element.clientHeight;
    var width  = element.clientWidth;
  },

  scrolls: function(element) {
    element = $(element);
    return  element.overflow != "hidden" &&
            ( element.scrollWidth  != element.clientWidth ||
              element.scrollHeight != element.clientHeight); 
  },
  
  scrollToElementMargin: 0,
  
  scrollToElement: function(element, target, margin) {
    element = $(element);
    target = $(target);
    margin = margin || Element.scrollToElementMargin;
    var elementPosition = Position.cumulativeOffset(element);
    var targetPosition  = Position.cumulativeOffset(target);
    var deltaX = targetPosition[0] - elementPosition[0];
    var deltaY = targetPosition[1] - elementPosition[1];
    element.scrollLeft = deltaX - margin;
    element.scrollTop  = deltaY - margin;
  },
    
  scrollTo: function(element, scrollToMargin) {
    element = $(element);
    var ancestor = element;
    while (ancestor != document) {
      if (ancestor.parentNode != document) {
        if (Element.scrolls(ancestor.parentNode)) Element.scrollToElement(ancestor.parentNode, ancestor, scrollToMargin);
      } else {
        var position = Position.page(element);
        if (position[0] + element.clientWidth  > ancestor.scrollLeft + ancestor.clientWidth ||
            position[1] + element.clientHeight > ancestor.scrollTop  + ancestor.clientHeight) {
          var minWidth  = Math.min(element.clientWidth,  ancestor.clientWidth);
          var minHeight = Math.min(element.clientHeight, ancestor.clientHeight);
          ancestor.scrollLeft = position[0] - (ancestor.clientWidth  - minWidth)  / 2 - (window.offsetLeft || 0);
          ancestor.scrollTop  = position[1] - (ancestor.clientHeight - minHeight) / 2 - (window.offsetTop  || 0);
        }
      }
      ancestor = ancestor.parentNode;
    }
  },
  
  seek: function(element, effect, effectOptions) {
    element = $(element);
    if (element) {
      this.scrollTo(element);
      new (effect || Effect.Pulsate)(element, effectOptions);
    }
  },
  
  loading: function(element, isLoading) {
    if (isLoading == null) isLoading = true;
    (isLoading ? Element.addClassName : Element.removeClassName)($(element), 'loading');
  }
  
});


/*---------------------------------   On   --------------------------------*/

var On = {

 onload: [],
 onunload: [],

 load: function(action) {
   this.onload.push(action);
 },

 unload: function(action) {
   this.onunload.push(action);
 },

 onLoad: function() {
   On.execute(On.onload);
 },

 onUnload: function() {
   On.execute(On.onunload);
 },

 execute: function(actions) {
   var action;
   while (action = actions.shift()) {
     if (typeof(action) == "function")
       action.call();
     else
       eval(action);
   }
 }
}

Event.observe(window, "load", On.onLoad);
Event.observe(window, 'unload', On.onUnload);


/*-------------------------- Draggable Patch --------------------------------*/

Object.extend(Draggable.prototype, {
  
  endDragWithoutCallbacks: Draggable.prototype.endDrag,
  
  endDrag: function(event) {
    if(!this.dragging && this.onClick) this.onClick(event);
    this.endDragWithoutCallbacks(event);
  },

  initializeWithoutCallbacks: Draggable.prototype.initialize,
  
  initialize: function(element, options) {
    element = $(element);
    if (element.onclick) {
      this.onClick = element.onclick;
      element.onclick = null;
    }
    this.initializeWithoutCallbacks(element, options);
  }
});

/*-------------------------- Autocompleter Patch --------------------------------*/

Object.extend(Ajax.Autocompleter.prototype, {
  
  onKeyPress: function(event) {
    if(this.active) {
      switch(event.keyCode) {
       case Event.KEY_TAB:
       case Event.KEY_RETURN:
         this.selectEntry();
         Event.stop(event);
       case Event.KEY_ESC:
         this.hide();
         this.active = false;
         Event.stop(event);
         return;
       case Event.KEY_LEFT:
       case Event.KEY_RIGHT:
         return;
       case Event.KEY_UP:
         this.markPrevious();
         this.render();
         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
         return;
       case Event.KEY_DOWN:
         this.markNext();
         this.render();
         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
         return;
       }
       this.hide();
       this.active = false;
     } else if(navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0) return;

    this.changed = true;
    this.hasFocus = true;

    if(this.observer) clearTimeout(this.observer);

    if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
      this.onObserverEvent();
    else if(this.options.frequency > 0) this.observer = 
      setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  }
});

InstanceManager.create(Ajax.Autocompleter);
