/******************************** LICENSE ********************************

 Copyright 2007 European Centre for Medium-Range Weather Forecasts (ECMWF)

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at 

    http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

 ******************************** LICENSE ********************************/

/*! \file Colour.cc
    \brief Implementation of Colour class.

    Magics Team - ECMWF 2004

    Started by Sylvie Lamy-Thepaut ECMWF 2002

    Changes:


*/
#include "Colour.h"
#include "MagLog.h"
#include "BaseParameter.h"
#include "XmlNode.h"

/*! \defgroup colours Colours

   \section howToDefine How can colours be defined?
   
   There are more than one way how colour values can be defined within Magics++
   
   - RGB
   - HSL
   
   \todo Where are the web colours defined?
   
   MagException of type BadRGBFormat and BadHSLFormat are thrown in case
   values look wrong.
*/


using namespace magics;

std::map<string, Rgb> Colour::colours_;



Colour::Colour() : rgb_(1., 0., 0., 1.), automatic_(false)
{
	init();
}

Colour::Colour(Rgb& rgb) : rgb_(rgb),  automatic_(false)
{ 
	name_ = tostring(rgb_);
}

Colour::Colour(Hsl& hsl) : rgb_(hsl.rgb()), automatic_(false) 
{ 
	name_ = tostring(rgb_);
}

Colour::Colour(float red, float green, float blue, float alpha) : 
	rgb_(red, green, blue, alpha), automatic_(false)
{
	ostringstream name;
	name << "RGB(" << red <<  "," << green << "," << blue << "," << alpha << ")" << "\n";
	name_ = name.str();
}

Colour::Colour(const string& name) : name_(lowerCase(name)), automatic_(false)
{
	if ( name == "automatic" ) automatic_ = true;
	init();
	try {
		setColour(name);
	}
	catch (BadHslFormat& e)
	{
		name_ = "red";
		rgb_ = Rgb(1., 0., 0.,1.);
	}
}

void Colour::set(const XmlNode& xml)
{
	MagLog::debug() << "set Colour-->" << xml << "\n";
	try {
		setColour(xml.data());
	}
	catch (BadHslFormat& e)
	{
		name_ = "red";
		rgb_ = Rgb(1., 0., 0.,1.);	        	
	}
}

void Colour::setColour(const string& name) 
{
	if ( name.empty() )
	{
		MagLog::warning() << "No colour name given. Red used.\n";
		name_ = "red";
		rgb_ = Rgb(1., 0., 0.);
		MagLog::broadcast();
		return;
	}
	try {
	// get rid of white space...
	string::size_type first = name.find_first_not_of(" ");  
	string::size_type last = name.find_last_not_of(" ");  

	string find = name.substr(first,  last-first+1);    
	name_ = lowerCase(find);

	map<string, Rgb>::iterator colour = colours_.find(name_);
	if (colour == colours_.end())
	{
		string prefix = name_.substr(0,3);

		if ( magCompare(prefix, "rgb") )
		{
			rgb_ = Rgb(name_);
		}
		else if ( magCompare(prefix, "hsl") )
		{
			Hsl hsl(name);
			rgb_ = hsl.rgb();
		}
		else if ( magCompare(name_.substr(0, 1), "#") )
         
		{
            // Web format should on 7 characher or 4 
            if ( name_.size() == 7 ) { 
			    float red = strtol(name_.substr(1, 2).c_str(), 0, 16)/256.;
			    float green = strtol(name_.substr(3, 2).c_str(), 0, 16)/256.;
			    float blue = strtol(name_.substr(5, 2).c_str(), 0, 16)/256.;
			    rgb_ = Rgb(red, green, blue, 1.);	
            }
            else if ( name_.size() == 4) {
                float red = strtol(name_.substr(1, 1).c_str(), 0, 16)/256.;
			    float green = strtol(name_.substr(2, 1).c_str(), 0, 16)/256.;
			    float blue = strtol(name_.substr(3, 1).c_str(), 0, 16)/256.;
			    rgb_ = Rgb(red, green, blue, 1.);
            }
            else {
                MagLog::warning() << "The color [" << name_ << "] is not valid. Red used.\n";
			    name_ = "red";
			    rgb_ = Rgb(1., 0., 0.,1.);	
            }
            		
		}
		else
		{
			MagLog::warning() << "The color [" << name_ << "] is not valid. Red used.\n";
			name_ = "red";
			rgb_ = Rgb(1., 0., 0.,1.);	
		}
	}
	else rgb_ = (*colour).second;
	}
	catch (...) {
		MagLog::warning() << " Invalid colour name " << name  << ". Red used.\n";
				name_ = "red";
				rgb_ = Rgb(1., 0., 0.);
				MagLog::broadcast();
				return;
	}
}

void Colour::setColour(float red, float green, float blue, float alpha)
{
	ostringstream name;

	name << "RGB(" << red <<  "," << green << "," << blue<< "," << alpha << ")" << "\n";
	name_ = name.str();
	rgb_ = Rgb(red, green, blue, alpha);
}

void Colour::setAlpha(float alpha)
{
	ostringstream name;
	
	name << "RGB(" << red() <<  "," << green() << "," << blue()<< "," << alpha << ")" << "\n";
	name_ = name.str();
	rgb_ = Rgb(red(), green(), blue(), alpha);
}

void Colour::scaleLight(float scale_factor)
{	
	Hsl hsl=rgb_.hsl();
	hsl.light_*=scale_factor;
	if(hsl.light_ > 1) hsl.light_=1.;
	Rgb rgb=hsl.rgb();

	setColour(rgb.red_,rgb.green_,rgb.blue_,rgb_.alpha_);
}


Colour::~Colour() {}
	
void Colour::print(ostream& out)  const
{
	
	out <<name_ << "["<<rgb_.red_<<", "<<rgb_.green_<< ", "<< rgb_.blue_ << ", " << rgb_.alpha_<<"] ";
}

Hsl Colour::hsl() const 
{ 
    return rgb_.hsl();
}

Hsl Rgb::hsl() const 
{
    Hsl hsl;
    float min = std::min( red_, std::min(green_, blue_) );    //Min. value of RGB
    float max = std::max( red_, std::max(green_, blue_) );    //Max. value of RGB
    float diff = max - min;             //Delta RGB value

    hsl.light_ = ( max + min ) / 2;
    hsl.alpha_ = alpha_;

    if ( diff == 0 )                     //This is a gray, no chroma...
    {
        hsl.hue_ = 0;                                //HSL results = From 0 to 1
        hsl.saturation_ = 0;
    }
    else                                    //Chromatic data...
    {
        if ( hsl.light_ < 0.5 ) hsl.saturation_ = diff / ( max + min );
        else   hsl.saturation_ = diff / ( 2 - max - min );

        float red   = ( ( ( max - red_)  / 6 ) + ( diff / 2 ) ) / diff;
        float green = ( ( ( max - green_ ) / 6 ) + ( diff / 2 ) ) / diff;
        float blue  = ( ( ( max - blue_ ) / 6 ) + ( diff / 2 ) ) / diff;

        if      ( red_ == max ) hsl.hue_ = blue - green; 
        else if ( green_ == max ) hsl.hue_ = ( 1. / 3. ) + red - blue;
        else if ( blue_ == max ) hsl.hue_ = ( 2. / 3. ) + green - red;

        if ( hsl.hue_ < 0 )  hsl.hue_ += 1;
        if ( hsl.hue_ > 1 )  hsl.hue_ -= 1;
        hsl.hue_ *= 360.;
    }
    return hsl;
}

inline float hue2rgb(float v1, float v2, float vH )             //Function Hue_2_RGB
{
   if ( vH < 0 ) vH += 1;
   if ( vH > 1 ) vH -= 1;
   if ( ( 6 * vH ) < 1 ) return ( v1 + ( v2 - v1 ) * 6 * vH );
   if ( ( 2 * vH ) < 1 ) return ( v2 );
   if ( ( 3 * vH ) < 2 ) return ( v1 + ( v2 - v1 ) * ( ( 2. / 3.) - vH ) * 6 );
   return v1;
}

Rgb Hsl::rgb() const 
{
    Rgb rgb;
    rgb.alpha_ = alpha_;
    float var1, var2;
    if ( saturation_ == 0 ) {
        rgb.red_ = light_;
        rgb.green_ = light_;
        rgb.blue_ = light_;
    }
    else
    {
        if ( light_ < 0.5 ) var2 = light_ * ( 1 + saturation_ );
        else  var2 = ( light_ + saturation_ ) - ( saturation_ * light_ );

        var1 = 2 * light_ - var2;

        rgb.red_ = hue2rgb( var1, var2, (hue_/360.) + ( 1. / 3. ) );
        rgb.green_ = hue2rgb( var1, var2, (hue_/360.) );
        rgb.blue_ = hue2rgb( var1, var2, (hue_/360.) - ( 1. / 3.) );
    }
    if (rgb.red_ < 0.000001) rgb.red_= 0.;
    if (rgb.green_ < 0.000001) rgb.green_= 0.;
    if (rgb.blue_ < 0.000001) rgb.blue_= 0.;
    return rgb;
}


bool Colour::valid(const string& name)
{
	init();
	Colour colour;
	try {
		colour.setColour(name);
	}
	catch (BadHslFormat& e)
	{
	        return false;
	}
	return true;
}


void Colour::init() {
	if (!colours_.empty()) return;
	 colours_["automatic"] = Rgb(0., 0., 0.); // NEEDS FIXING 
	 colours_["none"] = Rgb(-1., -1., -1.); 
	 colours_["background"] = Rgb(1., 1., 1.); 
	 colours_["foreground"] = Rgb(0., 0., 0.); 
	 colours_["red"] = Rgb(1.0000, 0.0000, 0.0000); 
	 colours_["green"] = Rgb(0.0000, 1.0000, 0.0000); 
	 colours_["blue"] = Rgb(0.0000, 0.0000, 1.0000);
	 colours_["yellow"] = Rgb(1.0000, 1.0000, 0.0000); 
	 colours_["cyan"] = Rgb(0.0000, 1.0000, 1.0000); 
	 colours_["magenta"] = Rgb(1.0000, 0.0000, 1.0000); 
	 colours_["black"] = Rgb(0.0000, 0.0000, 0.0000); 
	 colours_["avocado"] = Rgb(0.4225, 0.6500, 0.1950); 
	 colours_["beige"] = Rgb(0.8500, 0.7178, 0.4675); 
	 colours_["brick"] = Rgb(0.6000, 0.0844, 0.0300); 
	 colours_["brown"] = Rgb(0.4078, 0.0643, 0.0000); 
	 colours_["burgundy"] = Rgb(0.5000, 0.0000, 0.1727); 
	 colours_["charcoal"] = Rgb(0.2000, 0.2000, 0.2000); 
	 colours_["chestnut"] = Rgb(0.3200, 0.0112, 0.0000); 
	 colours_["coral"] = Rgb(0.9000, 0.2895, 0.2250); 
	 colours_["cream"] = Rgb(1.0000, 0.8860, 0.6700); 
	 colours_["evergreen"] = Rgb(0.0000, 0.4500, 0.2945); 
	 colours_["gold"] = Rgb(0.7500, 0.5751, 0.0750); 
	 colours_["grey"] = Rgb(0.7000, 0.7000, 0.7000); 
	 colours_["khaki"] = Rgb(0.5800, 0.4798, 0.2900); 
	 colours_["kelly_green"] = Rgb(0.0000, 0.5500, 0.1900); 
	 colours_["lavender"] = Rgb(0.6170, 0.4070, 0.9400); 
	 colours_["mustard"] = Rgb(0.6000, 0.3927, 0.0000); 
	 colours_["navy"] = Rgb(0.0000, 0.0000, 0.4000); 
	 colours_["ochre"] = Rgb(0.6800, 0.4501, 0.0680); 
	 colours_["olive"] = Rgb(0.3012, 0.3765, 0.0000); 
	 colours_["peach"] = Rgb(0.9400, 0.4739, 0.3788); 
	 colours_["pink"] = Rgb(0.9000, 0.3600, 0.4116); 
	 colours_["rose"] = Rgb(0.8000, 0.2400, 0.4335); 
	 colours_["rust"] = Rgb(0.7000, 0.2010, 0.0000); 
	 colours_["sky"] = Rgb(0.4500, 0.6400, 1.0000); 
	 colours_["tan"] = Rgb(0.4000, 0.3309, 0.2000); 
	 colours_["tangerine"] = Rgb(0.8784, 0.4226, 0.0000); 
	 colours_["turquoise"] = Rgb(0.1111, 0.7216, 0.6503); 
	 colours_["violet"] = Rgb(0.4823, 0.0700, 0.7000); 
	 colours_["reddish_purple"] = Rgb(1.0000, 0.0000, 0.8536); 
	 colours_["purple_red"] = Rgb(1.0000, 0.0000, 0.5000); 
	 colours_["purplish_red"] = Rgb(1.0000, 0.0000, 0.2730); 
	 colours_["orangish_red"] = Rgb(1.0000, 0.0381, 0.0000); 
	 colours_["red_orange"] = Rgb(1.0000, 0.1464, 0.0000); 
	 colours_["reddish_orange"] = Rgb(1.0000, 0.3087, 0.0000); 
	 colours_["orange"] = Rgb(1.0000, 0.5000, 0.0000); 
	 colours_["yellowish_orange"] = Rgb(1.0000, 0.6913, 0.0000); 
	 colours_["orange_yellow"] = Rgb(1.0000, 0.8536, 0.0000); 
	 colours_["orangish_yellow"] = Rgb(1.0000, 0.9619, 0.0000); 
	 colours_["greenish_yellow"] = Rgb(0.8536, 1.0000, 0.0000); 
	 colours_["yellow_green"] = Rgb(0.5000, 1.0000, 0.0000); 
	 colours_["yellowish_green"] = Rgb(0.1464, 1.0000, 0.0000); 
	 colours_["bluish_green"] = Rgb(0.0000, 1.0000, 0.5000); 
	 colours_["blue_green"] = Rgb(0.0000, 1.0000, 1.0000); 
	 colours_["greenish_blue"] = Rgb(0.0000, 0.5000, 1.0000); 
	 colours_["purplish_blue"] = Rgb(0.1464, 0.0000, 1.0000); 
	 colours_["blue_purple"] = Rgb(0.5000, 0.0000, 1.0000); 
	 colours_["bluish_purple"] = Rgb(0.8536, 0.0000, 1.0000); 
	 colours_["purple"] = Rgb(1.0000, 0.0000, 1.0000); 
	 colours_["white"] = Rgb(1.0000, 1.0000, 1.0000); 
	 colours_["undefined"] = Rgb(-1., -1., -1.);
}

#include "BackgroundColour.h"
Colour* MagTranslator<string, Colour>::operator()(const string& val )
{
 	 if (Colour::valid(val) ) return new Colour(val);
 	 if ( val == "colour" ) return new Colour();
 	 if ( val == "background_colour" ) return new BackgroundColour();
 	 throw NoFactoryException(val);
}

Rgb::Rgb(const string& name): red_(1.), green_(1.), blue_(1.),alpha_(1.)
{
  istringstream in(name);
  stringbuf token;
  in >> ws;
  in.get(token, '(');

  if (in.eof()) throw BadRgbFormat();

  if (magCompare(token.str(), "rgb"))
  {
	      in.ignore(name.length(), '(');
	      in >> red_; 

	      in.ignore(name.length(), ',');
	      if (in.eof()) throw BadRgbFormat();
	      in >> green_;

	      in.ignore(name.length(), ',');
	      if (in.eof()) throw BadRgbFormat();
	      in >> blue_;


  }
  else if (magCompare(token.str(), "rgba")) {
	      in.ignore(name.length(), '(');
	      in >> red_;



	      in.ignore(name.length(), ',');
	      if (in.eof()) throw BadRgbFormat();	    
	      in >> green_;


	      in.ignore(name.length(), ',');
	      if (in.eof()) throw BadRgbFormat();	    
	      in >> blue_; 

	      in.ignore(name.length(), ',');
	      if (in.eof()) throw BadRgbFormat();	    
	      in >> alpha_; 
	      if (alpha_ <0 || alpha_>1) throw BadRgbFormat();
    }
    else 
          throw BadRgbFormat();
    if ( red_ > 1 || green_ > 1 || blue_ > 1) {
    	// WE are using the second convention ( Colour defined between, 0 and 1)
    	red_ = red_/256;

    	green_ = green_/256;
    	blue_ = blue_/256.;
    }
    if (red_ <0 || red_>1) throw BadRgbFormat();
    if (green_ <0 || green_>1) throw BadRgbFormat();
    if (blue_ <0 || blue_>1) throw BadRgbFormat();
}


Hsl::Hsl(const string& name)
{
    istringstream in(name);
    stringbuf token;
    in >> ws;
    in.get(token, '(');

    if (in.eof()) throw BadHslFormat();

    if (magCompare(token.str(),"HSL")) {

        in.ignore(name.length(), '(');
        in >> hue_; 
        if (hue_ <0 || hue_>360) throw BadHslFormat();

        in.ignore(name.length(), ',');
        if (in.eof()) throw BadHslFormat();	    
        in >> saturation_;
        if ( saturation_ <0 || saturation_>1) throw BadHslFormat();

        in.ignore(name.length(), ',');
        if (in.eof()) throw BadHslFormat();	    
        in >> light_; 
        if ( light_ <0 || light_>1) throw BadHslFormat();
        alpha_=1.;
    }
    else if (magCompare(token.str(),"HSLA")) {
        in.ignore(name.length(), '(');
        in >> hue_; 
        if (hue_ <0 || hue_>360) throw BadHslFormat();

        in.ignore(name.length(), ',');
        if (in.eof()) throw BadHslFormat();
        in >> saturation_;
        if ( saturation_ <0 || saturation_>1) throw BadHslFormat();

        in.ignore(name.length(), ',');
        if (in.eof()) throw BadHslFormat();
        in >> light_; 
        if ( light_ <0 || light_>1) throw BadHslFormat();

        in.ignore(name.length(), ',');
        if (in.eof()) throw BadHslFormat();
        in >> alpha_; 
        if ( alpha_ <0 || alpha_>1) throw BadHslFormat();
    }
    else throw BadHslFormat();
}


istream& operator>>(istream& s, Rgb& p)
{
	string token;
	s.width(3);
	s >> token;

	if (token != "RGB") throw BadRgbFormat();

	s.ignore(256, '(');
	s >> p.red_; 
	if (p.red_ <0 || p.red_>1) throw BadRgbFormat();
		
	s.ignore(256, ',');
	s >> p.green_;
	if (p.green_ <0 || p.green_>1) throw BadRgbFormat();
	s.ignore(256, ',');

	s >> p.blue_; 
	if (p.blue_ <0 || p.blue_>1) throw BadRgbFormat();

	return s; 
}

istream& operator>>(istream& s, Hsl& p)
{
	string token;
	s.width(3);
	s >> token;

	if (token != "HSL") throw BadHslFormat();

	s.ignore(256, '(');
	s >> p.hue_; 
	if (p.hue_ <0 || p.hue_>360) throw BadHslFormat();

	s.ignore(256, ',');
	s >> p.saturation_;
	if (p.saturation_ <0 || p.saturation_>1) throw BadHslFormat();
	s.ignore(256, ',');

	s >> p.light_; 
	if (p.light_ <0 || p.light_>1) throw BadHslFormat();
	return s; 
}
bool Colour::none() const
{
	static Colour none("none");
	return *this == none;
}
