/* {{{ GPL

 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., 675 Mass Ave, Cambridge, MA 02139, USA.

}}} */

var Color = Class.extend( {

  // {{{ init
  init: function( red, green, blue, alpha ) {
    this.setRGBA( red, green, blue, alpha );
    this.listeners = null;
    
    this.hue = null;
    this.brightness = null;
    this.saturation = null;
  },
  // }}}
  
  // {{{ addListener
  addListener: function( listener ) {
    if ( this.listeners == null ) {
      this.listeners = [ listener ];
    } else {
      this.listeners[ this.listeners.length ] = listener;
    }
  },
  // }}}

  // {{{ setRGB
  setRGB: function( red, green, blue /* 0..255 */, alpha /* 0..1 */ ) {
    this.setRGBAPrivate( red, green, blue, 1 );
    this.notifyColorChanged();
  }, // }}}

  // {{{ setRGBA
  setRGBA: function( red, green, blue /* 0..255 */, alpha /* 0..1 */ ) {
    this.setRGBAPrivate( red, green, blue, alpha );
    this.notifyColorChanged();
  },
  // }}}
  
  // {{{ setRGBAPrivate
  // Does not notify changes to the colors listeners.
  setRGBAPrivate: function( red, green, blue /* 0..255 */, alpha /* 0..1 */ ) {
    
    this.hue = null;
    this.saturation = null;
    this.brightness = null;
    this.str = null;
    
    if ( red < 0 ) red = 0;
    if ( red > 255 ) red = 255;
    if ( green < 0 ) green = 0;
    if ( green > 255 ) green = 255;
    if ( blue < 0 ) blue = 0;
    if ( blue > 255 ) blue = 255;
        
    this.red = Math.floor(red);
    this.green = Math.floor(green);
    this.blue = Math.floor(blue);

    if ( alpha == null ) {
      this.alpha = 1;
    } else {
      if ( alpha < 0 ) alpha = 0;
      if ( alpha > 1 ) alpha = 1;
      this.alpha = alpha;
    }
    
  },
  // }}}
    
  // {{{ setColorTo
  setColorTo: function( color ) {
    this.setRGBA( color.red, color.green, color.blue, color.alpha );
  },
  // }}}
  
  // {{{ notifyColorChanged
  notifyColorChanged: function() {

    if ( this.listeners != null ) {
      for ( var i = 0; i < this.listeners.length; i ++ ) {
        if ( this.listeners[ i ].onColorChanged != null ) {
          this.listeners[ i ].onColorChanged( this );
        }
      }
    }
  },
  // }}}
  
  // {{{ getHue
  getHue: function() {
    this.calcHSB();
    return this.hue;
  },
  // }}}
    
  // {{{ getSaturation
  getSaturation: function() {
    
    this.calcHSB();
    return this.saturation;
    
    /*
    if ( this.red > this.green ) {
      if ( this.red > this.blue ) {
        return this.red / 255;
      } else {
        return this.blue / 255;
      }
    } else {
      if ( this.green > this.blue ) {
        return this.green / 255;
      } else {
        return this.blue / 255;
      }
    }
    */
  },
  // }}}

  // {{{ getBrightness
  getBrightness: function() {

    this.calcHSB();
    return this.brightness;
    
    /*
    if ( this.red > this.green ) {
      if ( this.red > this.blue ) {
        return this.red / 255;
      } else {
        return this.blue / 255;
      }
    } else {
      if ( this.green > this.blue ) {
        return this.green / 255;
      } else {
        return this.blue / 255;
      }
    }
    */

  },
  // }}}
  
  // {{{ calcHSB
  calcHSB: function() {
    if ( this.hue != null ) {
      return;
    }

    var max = this.red;
    var maxChannel = 0
    if (this.green > max) {
      max = this.green;
      // maxChannel = 1;
    }
    if (this.blue > max) {
      max = this.blue;
      // maxChannel = 2;
    }
    
    var min = this.red;
    var minChannel = 0
    if (this.green < min) {
      min = this.green;
      // minChannel = 1;
    }
    if (this.blue < min) {
      min = this.blue;
      // minChannel = 2;
    }

    /*
     If nRed = nMin Then
         d = nGreen - nBlue
         h = 3.0
      ElseIf nGreen = nMin Then
         d = nBlue - nRed
         h = 5.0
      Else
         d = nRed - nGreen
         h = 1.0
      EndIf
      
      nHue = ( h - ( d / (nMax - nMin) ) ) / 6.0
      nSaturation = (nMax - nMin) / nMax
      nBrightness = nMax / 255.0 
    */
    
    if (min == max) {
      // white..black - use red as the (arbitary) hue
      this.hue = 0;
      this.saturation = 0;
      this.brightness = max;
      return;
    }
    
    var d;
    var h;
    if ( this.red == min ) {
      d = this.green - this.blue;
      h = 3;
    } else if ( this.green == min ) {
      d = this.blue - this.red;
      h = 5;
    } else {
      d = this.red - this.green;
      h = 1;
    }
    this.hue = ( h - ( d / ( max - min ) ) ) / 6;
    this.saturation = ( max - min ) / max;
    this.brightness = max;
    
    /*
    var scale = 255 / (max - min);
    var hueColor = new Color();
    hueColor.setRGB( (this.red-min) * scale, (this.green-min) * scale, (this.blue-min) * scale );

    var midChannel = 3 - (minChannel ^ maxChannel);
    var section; // Which sixth of set of hues are we in
    if ( (maxChannel ^ midChannel) == 2 ) {
      section = maxChannel == 2 ? 4 : 5;
    } else {
      section = 2 * maxChannel - ( maxChannel > midChannel ? 1 : 0 );
    }

    var subsection = midChannel == 0 ? hueColor.red : ( midChannel == 1 ? hueColor.green : hueColor.blue );
    if ( 2 * maxChannel != section ) {
      subsection = 255 - subsection;
    }
    
    this.hue = (subsection / 255 + section) / 6;
    */

  },
  // }}}

  // {{{ setHuePrivate
  setHuePrivate: function( hue /* 0..1 */ ) {
    
    var section = Math.floor( hue * 6 );
    var midAmount = Math.floor( 255 * (hue * 6 - section) );
    if ( section & 1 ) {
      midAmount = 255 - midAmount;
    }
    if ( section == 0) {
      this.setRGBAPrivate( 255, midAmount, 0 );
    } else if ( section == 1 ) {
      this.setRGBAPrivate( midAmount, 255, 0 );
    } else if ( section == 2 ) {
      this.setRGBAPrivate( 0, 255, midAmount );
    } else if ( section == 3 ) {
      this.setRGBAPrivate( 0, midAmount, 255 );
    } else if ( section == 4 ) {
      this.setRGBAPrivate( midAmount, 0, 255 );
    } else if ( section == 5 ) {
      this.setRGBAPrivate( 255, 0, midAmount );
    } else {
      this.log.severe( "Failed to create hueColor for hue value:", hue, "section:", section, "midAmount:", midAmount, "dir:", (section & 1) );
      this.setRGBAPrivate( 255,0,0 );
    }

    this.hue = hue;
  },
  // }}}

  // {{{ setHSB
  setHSB: function( hue /* 0..1 */, saturation /* 0..1 */, brightness /* 0..1 */ ) {
    this.setHSBA( hue, saturation, brightness, 1 );
  },
  // }}}
  
  // {{{ setHSBA
  setHSBA: function( hue /* 0..1 */, saturation /* 0..1 */, brightness /* 0..1 */, alpha /* 0..1 */ ) {
        
    if ( hue < 0 ) hue = 0;
    if ( hue >= 1 ) hue = 0;
    if ( saturation < 0 ) saturation = 0;
    if ( saturation > 1 ) saturation = 1;
    if ( brightness < 0 ) brightness = 0;
    if ( brightness > 1 ) brightness = 1;

    if ( alpha == null ) alpha = 1;
    if ( alpha < 0 ) alpha = 0;
    if ( alpha > 1 ) alpha = 1;
    
    this.alpha = alpha;
    
    var bright255 = brightness * 255;
    
    if ( saturation == 0 ) {
      // Greyscale
      this.red = bright255;
      this.green = bright255;
      this.blue = bright255;
    } else {
    
      var iHue = Math.floor( hue * 6 ); // Hue segment around the color wheel (0..5)
      var fHue = hue * 6 - iHue;
      
      if ( (iHue % 2) == 0 ) {
        fHue = 1 - fHue;
      }
      
      var m = bright255 * ( 1 - saturation );
      var n = bright255 * ( 1 - saturation * fHue );
      
      this.log.debug( "mid", m, n, bright255, saturation, fHue );
      
      if ( iHue == 1 ) {
        this.red = n;
        this.green = bright255;
        this.blue = m;
      } else if ( iHue == 2 ) {
        this.red = m;
        this.green = bright255;
        this.blue = n;
      } else if ( iHue == 3 ) {
        this.red = m;
        this.green = n;
        this.blue = bright255;
      } else if ( iHue == 4 ) {
        this.red = n;
        this.green = m;
        this.blue = bright255;
      } else if ( iHue == 5 ) {
        this.red = bright255;
        this.green = m;
        this.blue = n;
      } else {
        this.red = bright255;
        this.green = n;
        this.blue = m;
      }
        
    }  
        
    this.red = Math.floor( this.red );
    this.green = Math.floor( this.green );
    this.blue = Math.floor( this.blue );
    /*
    If nSaturation = 0.0 Then
      ' Grayscale because there is no saturation
      nRed = nBrightness2
      nGreen = nBrightness2
      nBlue = nBrightness2
   Else

      ' Rescale hue to a range of 0.0 to 6.0.
      nHue2 = nHue2 * 6.0
      
      ' Separate hue into int and fractional parts
      iHue = Int( nHue2 )
      fHue = nHue2 - iHue
      
      ' If Hue is even
      If iHue Mod 2 = 0 Then
         fHue = 1.0 - fHue
      EndIf
      
      m = nBrightness2 * (1.0 - nSaturation)
      n = nBrightness2 * (1.0 - (nSaturation * fHue))
      
      Select Case iHue
         Case 1
            nRed = n
            nGreen = nBrightness2
            nBlue = m
         Case 2
            nRed = m
            nGreen = nBrightness2
            nBlue = n
         Case 3
            nRed = m
            nGreen = n
            nBlue = nBrightness2
         Case 4
            nRed = n
            nGreen = m
            nBlue = nBrightness2
         Case 5
            nRed = nBrightness2
            nGreen = m
            nBlue = n
         Case Else
            nRed = nBrightness2
            nGreen = n
            nBlue = m
      End Select
   EndIf
End Sub 
*/

    
    /*
    this.setHuePrivate( hue );
    
    //this.log.debug( "intermediate", this );
    var red = ( this.red + ( 255 - this.red ) * (1 - saturation) ) * brightness;
    var green = ( this.green + ( 255 - this.green ) * (1 - saturation) ) * brightness;
    var blue = ( this.blue + (255 - this.blue) * (1 - saturation) ) * brightness;

    this.setRGBAPrivate( red, green, blue, alpha );
    */

    this.str = null;
    this.hue = hue;
    this.saturation = saturation;
    this.brightness = brightness;
    
    this.notifyColorChanged();
  
  },
  // }}}

  // {{{ setAlpha
  setAlpha: function( alpha ) {
    this.alpha = alpha;
    this.str = null;
    this.notifyColorChanged();
  },
  // }}}
  
  // {{{ hex
  hex: function( value ) {
    var result = value.toString( 16 );
    if ( result.length == 1 ) {
      return "0" + result;
    } else {
      return result;
    }
  },
  // }}}
  
  // {{{ asGoodAs
  asGoodAs: function( other ) {
    if ( (this.alpha == 0) && (other.alpha == 0) ) {
      return true;
    }
    
    return ( (this.red == other.red) && (this.green == other.green) && (this.blue == other.blue) );
  },
  // }}}
  
  // {{{ copy
  copy: function() {
    var result = new Color();
    result.setColorTo( this );
    return result;
  },
  // }}}
  
  // {{{ toString
  toString: function() {
    
    if ( this.str == null ) {  
      if ( this.alpha <= 1 ) {
        this.str = "rgba(" + this.red + "," + this.green + "," + this.blue + "," + this.alpha + ")";
      } else {
        this.str = "#" + this.hex( this.red ) + this.hex( this.green ) + this.hex( this.blue );
      }
    }
    // this.log.debug( "toString", this.str );
    
    return this.str;
  }
  // }}}

});

Color.createFromFloats = function( r,g,b,a ) {
  return new Color( Math.round( r * 255 ), Math.round( g * 255 ), Math.round( b * 255 ), a );
};

Color.prototype.log = new Log( "Color" );

