Object.extend(String.prototype, {
  hexToRgb: function(){
    var hex = this.match(new RegExp('^[#]{0,1}([\\w]{1,2})([\\w]{1,2})([\\w]{1,2})$'));
    var rgb = [];
    for (var i = 1; i < hex.length; i++){
      if (hex[i].length == 1) hex[i] += hex[i];
      rgb.push(parseInt(hex[i], 16));
    }
    return rgb;
  },
  
  hexToHsv: function() {
    var rgb = this.hexToRgb();
    var r = rgb[0] / 255.0;
    var g = rgb[1] / 255.0;
    var b = rgb[2] / 255.0;

    var max = Math.max( r, g, b );
    var min = Math.min( r, g, b );

    var h, s, v;

    v = max;

    if( min == max )
      return [ 0, 0, v ];

    var delta = max - min;

    s = delta / max;

    switch( max ) {
      case r:
        h = ( g - b ) / delta;
        break;
      case g:
        h = ( b - r ) / delta + 2;
        break;
      case b:
        h = ( r - g ) / delta + 4;
        break;
    }
    if( h < 0 )
      h += 6;
    if( h > 6 )
      h -= 6;

    return [ h / 6.0, s, v ];
  }});

Object.extend(Array.prototype, {
  hsvToRgb: function() {
    var h = this[0];
    var s = this[1];
    var v = this[2];

    if( v == 0.0 )
      return [ 0, 0, 0 ];

    var whole = ( h * 6 ).floor();
    var frac = ( h * 6 ) - whole;
    var p = v * ( 1 - s );
    var q = v * ( 1 - s * frac );
    var t = v * ( 1 - s * ( 1 - frac ) );
    
    switch( whole ) {
      case 1: return [ q, v, p ];
      case 2: return [ p, v, t ];
      case 3: return [ p, q, v ];
      case 4: return [ t, p, v ];
      case 5: return [ v, p, q ];
      case 6:
      case 0: return [ v, t, p ];
    }

    return [ NaN, NaN, NaN ];
  },
  
  hsvToHex: function() {
    var rgb = this.hsvToRgb();
    return '#' +
      (rgb[0]*255).floor().toColorPart() +
      (rgb[1]*255).floor().toColorPart() +
      (rgb[2]*255).floor().toColorPart();
  }});

/* Effect.Style is able to move a single style
 * rule on an element from one position to another.
 * It can operate on any element that uses px units,
 * or it can do a HSV based transition on color
 * elements (set unit to '#').
 *
 * Examples:
 *
 * var e = new Effect.Style( div, 'width', { from: 10, to: 20 });
 * e.start_effect(); // div becomes 20px
 * e.rewind_effect(); // div becomes 10px
 *
 * var f = new Effect.Style( div, 'background-color', { from: 'F00', to: '0F0', unit: '#' });
 * e.start_effect(); // div becomes green
 * e.end_effect(); // div becomes red
 *
 * Because of the way Scriptaculous works, Effect.Style probably
 * does not play well in Queues.  However, start_effect() and
 * rewind_effect() are intellegent and may be called in the middle
 * of a transition.
 */
Effect.Style = Class.create(Effect.Base, {
  initialize: function( element, style, options ) {
    this.style = style;
    this.style_options = Object.extend( { unit: 'px' }, options || {} );
    this.effect_state = 'from';
    this.element = $(element);
  },
  _prepare: function() {
    if( this.style_options.unit == '#' ) {
      this.hsvStart = this.style_options.start.hexToHsv();
      this.hsvEnd = this.style_options.end.hexToHsv();
      this.hsvDistance = [
        this.hsvEnd[0] - this.hsvStart[0],
        this.hsvEnd[1] - this.hsvStart[1],
        this.hsvEnd[2] - this.hsvStart[2] ];
      if( this.hsvDistance[0] > .5 )
        this.hsvDistance[0] += -1;
      if( this.hsvDistance[0] < -.5 )
        this.hsvDistance[0] += 1;
    }
  },
  toggle_effect: function() {
    this.effect_state == 'from' ?
    this.start_effect() :
    this.rewind_effect();
  },
  start_effect: function() {
    if( this.effect_state == 'to' )
      return;
    this.effect_state = 'to';
    var now = 0.0;
    if( !Object.isUndefined( this.position ) )
      now = this.position;
    this._prepare();
    this.start( Object.extend( this.style_options, Object.extend( this.style_options.start_options || {}, { from: now, to: 1.0 } )));
  },
  finish_effect: function() {
    if( this.options )
      this.cancel();
    this.effect_state = 'to';
    this.position = 1.0;
  },
  rewind_effect: function() {
    if( this.effect_state == 'from' )
      return;
    this.effect_state = 'from';
    var now = 1.0;
    if( !Object.isUndefined( this.position ) )
      now = this.position;
    this._prepare();
    this.start( Object.extend( this.style_options, Object.extend( this.style_options.rewind_options || {}, { from: now, to: 0.0 } )));
  },
	begin_effect: function() {
		if( this.options )
			this.cancel();
		this.effect_state = 'from';
		this.position = 0.0;
	},
  _calculate_style: function( position ) {
    if( this.style_options.unit == 'px' || this.style_options.unit == '' ) {
      var distance = this.style_options.end - this.style_options.start;
      return (position * distance + this.style_options.start) + this.style_options.unit;
    } else if( this.style_options.unit == '#' ) {
      var hsv = [
        this.hsvStart[0] + position * this.hsvDistance[0],
        this.hsvStart[1] + position * this.hsvDistance[1],
        this.hsvStart[2] + position * this.hsvDistance[2] ];
      if( hsv[0] < 0 )
        hsv[0] += 1;
      if( hsv[0] > 1 )
        hsv[0] -= 1;
      return hsv.hsvToHex();
    }
  },
  update: function( position ) {
    var styles = {}
    styles[ this.style ] = this._calculate_style( position );
    this.element.setStyle( styles );
  }
});

