if(!Event.isBubbled)
{
  Object.extend(Event,
  {
    isBubbled: function(event, originalElement, over)
    {
      var reltg = $((event.relatedTarget) ? event.relatedTarget : ((over)?event.fromElement:event.toElement));
      return (reltg && (reltg ==  originalElement || reltg.descendantOf(originalElement)));
    }
  });
}
      
Compressor = Class.create();
Compressor.Directions = {horizontal: 1, vertical: 2};
Compressor.Transitions = 
{
  backIn: function(p)
  {
    return Math.pow(p, 2) * ((1.618 + 1) * p - 1.618);
  },
  
  backOut: function(p)
  {
    return (1 - (Math.pow(1-p, 2) * ((1.618 + 1) * (1-p) - 1.618)));
  },
  
  elasticIn: function(p)
  {
    return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI / 3);
  },
  
  elasticOut: function(p)
  {
    p = (1-p);
    return 1 - (Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI / 3));
  }
}
Object.extend(Compressor.prototype,
{
  _container: null,
  _options: null,
  _elements: null,
  _locked: false,
  _currentEffect: null,
  
  initialize: function(container, options)
  {
    this._options = 
    {
      fullSize: '120px',
      normalSize: '100px',
      smallSize: '80px',
      duration: 0.3,
      transition: Compressor.Transitions.backOut,
      direction: Compressor.Directions.horizontal,
      defaultOpen: null
    }
    Object.extend(this._options, options||{});
    this._container = $(container);
    this._elements = this._container.getElementsByClassName('compressorElement');
    Event.observe(this._container, 'mouseout', this.restoreElements.bindAsEventListener(this));
    var lthis = this;
    this._elements.each(function(element)
    {
      element._compressor = lthis;
      Event.observe(element, 'mouseover', lthis.toggleElement.bindAsEventListener(element));
    });
    if(this._options.defaultOpen)
      this._toggleElement(this._options.defaultOpen);
  },
  
  restoreElements: function(event)
  {
    if(Event.isBubbled(event, this._container, false))
      return;
    
    if(this._currentEffect)
      this._currentEffect.cancel();

    if(this._options.defaultOpen)
    {
      this._toggleElement(this._options.defaultOpen);
    }
    else
    {      
      var lthis = this;
      var effects = $A();
      var dimension = (this._options.direction == Compressor.Directions.horizontal)?'width':'height';
      this._elements.each(function(element){
        var o = {sync: true, style: dimension + ':' + lthis._options.normalSize};
        effects.push(new Effect.Morph(element, o));
      });
      this._currentEffect = new Effect.Parallel(effects, {duration: this._options.duration, transition: this._options.transition, afterFinish: this.cleanup.bind(this)});
    }
  },
  
  toggleElement: function(event)
  {
    if(Event.isBubbled(event, this, true))
      return;

    var target = this;
    var compressor = this._compressor;
    if(compressor._currentEffect)
      compressor._currentEffect.cancel();
    
    var effects = $A();
    
    var dimension = (compressor._options.direction == Compressor.Directions.horizontal)?'width':'height';
    var o = {sync: true, style: dimension + ':' + compressor._options.fullSize};
    effects.push(new Effect.Morph(target, o));
          
    compressor._elements.each(function(element)
    {
      if(element == target)
      {
        return;
      }
        
      var o = {sync: true, style: dimension + ':' + compressor._options.smallSize};
      effects.push(new Effect.Morph(element, o));
    });
    compressor._currentEffect = new Effect.Parallel(effects, {duration: compressor._options.duration, transition: compressor._options.transition, afterFinish: compressor.cleanup.bind(compressor)});
  },

  _toggleElement: function(target)
  {
    if(this._currentEffect)
      this._currentEffect.cancel();
    
    var effects = $A();
    
    var dimension = (this._options.direction == Compressor.Directions.horizontal)?'width':'height';
    var o = {sync: true, style: dimension + ':' + this._options.fullSize};
    effects.push(new Effect.Morph(target, o));
          
    this._elements.each(function(element)
    {
      if(element == target)
      {
        return;
      }
        
      var o = {sync: true, style: dimension + ':' + this._options.smallSize};
      effects.push(new Effect.Morph(element, o));
    }.bind(this));
    this._currentEffect = new Effect.Parallel(effects, {duration: this._options.duration, transition: this._options.transition, afterFinish: this.cleanup.bind(this)});
  },
  
  cleanup: function()
  {
    if(this._currentEffect)
      this._currentEffect = null;
  }
});