/***************************************************************************
 *   Copyright (C) 2005 by Thierry CHARLES   *
 *   thierry@les-charles.net   *
 *                                                                         *
 *   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.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "tcolor.h"

#include <math.h>

/** renvoie le ratio a utiliser pour normaliser les canaux de la couleur */
float TColor::getNormalizeRatio() const
{
    uint iMax = this->iRed;
    if(this->iGreen > iMax)
        iMax = this->iGreen;
    if(this->iBlue > iMax)
        iMax = this->iBlue;

    if(iMax <= TCOLOR_CHANNEL_MAXVALUE)
        return 1.0;

    return TCOLOR_CHANNEL_MAXVALUE / static_cast<float>(iMax);
}

/** renvoie la valeur "rouge" dans un triplet ou aucune des valeurs ne dépasse 255 */
uint TColor::getNormalizedRed() const
{
    return static_cast<uint>(this->iRed * this->getNormalizeRatio());
}

/** renvoie la valeur "vert" dans un triplet ou aucune des valeurs ne dépasse 255 */
uint TColor::getNormalizedGreen() const
{
    return static_cast<uint>(this->iGreen * this->getNormalizeRatio());
}

/** renvoie la valeur "bleu" dans un triplet ou aucune des valeurs ne dépasse 255 */
uint TColor::getNormalizedBlue() const
{
    return static_cast<uint>(this->iBlue * this->getNormalizeRatio());
}


/** renvoie la valeur "rouge" en notation décimale */
float TColor::getDecimalRed() const
{
    return static_cast<float>(this->iRed) / TCOLOR_CHANNEL_MAXVALUE;
}

/** renvoie la valeur "vert" en notation décimale */
float TColor::getDecimalGreen() const
{
    return static_cast<float>(this->iGreen) / TCOLOR_CHANNEL_MAXVALUE;
}

/** renvoie la valeur "bleu" en notation décimale */
float TColor::getDecimalBlue() const
{
    return static_cast<float>(this->iBlue) / TCOLOR_CHANNEL_MAXVALUE;
}


/** renvoie la valeur "rouge" en notation décimale dans un triplet ou aucune des valeurs ne dépasse 1.0 */
float TColor::getNormalizedDecimalRed() const
{
    return this->getDecimalRed() * this->getNormalizeRatio();
}

/** renvoie la valeur "vert" en notation décimale dans un triplet ou aucune des valeurs ne dépasse 1.0 */
float TColor::getNormalizedDecimalGreen() const
{
    return this->getDecimalGreen() * this->getNormalizeRatio();
}

/** renvoie la valeur "bleu" en notation décimale dans un triplet ou aucune des valeurs ne dépasse 1.0 */
float TColor::getNormalizedDecimalBlue() const
{
    return this->getDecimalBlue() * this->getNormalizeRatio();
}


/** applique un ratio a la couleur pour qu'aucun des cannaux ne dépasse 255 */
void TColor::normalize()
{
    float fNormalizeRatio = this->getNormalizeRatio();
    if (fNormalizeRatio == 1.0)
        return;

    this->ensureRGBIsSet();
    this->iRed = static_cast<uint>(this->iRed * fNormalizeRatio);
    this->iGreen = static_cast<uint>(this->iGreen * fNormalizeRatio);
    this->iBlue = static_cast<uint>(this->iBlue * fNormalizeRatio);
    this->bHSVIsSet = false;
    this->ensureHSVIsSet();
}


/** defini la valeur "rouge" */
void TColor::setRed(const float r)
{
    if(r > 0.0)
        this->iRed = static_cast<uint>(r*TCOLOR_CHANNEL_MAXVALUE);
    else
        this->iRed = 0;
    this->bHSVIsSet = false;
    this->ensureHSVIsSet();
}

/** defini la valeur "vert" */
void TColor::setGreen(const float g)
{
    if(g > 0.0)
        this->iGreen = static_cast<uint>(g*TCOLOR_CHANNEL_MAXVALUE);
    else
        this->iRed = 0;
    this->bHSVIsSet = false;
    this->ensureHSVIsSet();
}

/** defini la valeur "bleu" */
void TColor::setBlue(const float b)
{
    if(b > 0.0)
        this->iBlue = static_cast<uint>(b*TCOLOR_CHANNEL_MAXVALUE);
    else
        this->iRed = 0;
    this->bHSVIsSet = false;
    this->ensureHSVIsSet();
}


/** initialise a partir des données au format dépendant de la librairie wxWidget */
void TColor::initFrom(const wxColour * colour)
{
    this->bRGBIsSet = true;
    this->iRed = static_cast<uint>(colour->Red());
    this->iGreen = static_cast<uint>(colour->Green());
    this->iBlue = static_cast<uint>(colour->Blue());
    this->bHSVIsSet = false;
    this->ensureHSVIsSet();
}

/** initialise a partir des données au format dépendant de la librairie wxWidget */
void TColor::initFrom(const wxColour & colour)
{
    this->initFrom(&colour);
}

/** crée un objet de type dépendant de la lib wxWidget */
wxColour TColor::toWxColour() const
{
    return wxColour(static_cast<unsigned char>(this->getNormalizedRed()),
                    static_cast<unsigned char>(this->getNormalizedGreen()),
                    static_cast<unsigned char>(this->getNormalizedBlue()));
}

/** renvoie la valeur "rouge" */
uint TColor::getRed() const
{
    return this->iRed;
}
/** renvoie la valeur "vert" */
uint TColor::getGreen() const
{
    return this->iGreen;
}
/** renvoie la valeur "bleu" */
uint TColor::getBlue() const
{
    return this->iBlue;
}

/** defini la valeur "rouge" */
void TColor::setRed(const uint r)
{
    this->iRed = r;
    this->bHSVIsSet = false;
    this->ensureHSVIsSet();
}
/** defini la valeur "vert" */
void TColor::setGreen(const uint g)
{
    this->iGreen = g;
    this->bHSVIsSet = false;
    this->ensureHSVIsSet();
}
/** defini la valeur "bleu" */
void TColor::setBlue(const uint b)
{
    this->iBlue = b;
    this->bHSVIsSet = false;
    this->ensureHSVIsSet();
}

/** defini la valeur "hue" */
void TColor::setHue(const unsigned short h)
{
    this->iHue = h;
    this->bRGBIsSet = false;
    this->ensureRGBIsSet();
}
/** defini la valeur "saturation" */
void TColor::setSaturation(const unsigned short s)
{
    this->iSaturation = s;
    this->bRGBIsSet = false;
    this->ensureRGBIsSet();
}
/** defini la valeur "value" */
void TColor::setValue(const uint v)
{
    this->iValue = v;
    this->bRGBIsSet = false;
    this->ensureRGBIsSet();
}

/** s'assure que la valeur RGB est definie */
void TColor::ensureRGBIsSet()
{
    if(this->bRGBIsSet || (!this->bHSVIsSet))
        return;

    if( this->iSaturation == 0 )
    {
        // achromatic (grey)
        this->iRed = this->iValue;
        this->iGreen = this->iValue;
        this->iBlue = this->iValue;
        return;
    }

    int i = this->iHue / 60;            // sector 0 to 5
    while(i > 5)
        i -= 6;
    float f = (this->iHue % 60) / 60.0; // rest of h
    int p = this->iValue * ( 1000 - this->iSaturation );
    int q = this->iValue * ( 1000 - this->iSaturation * f );
    int t = this->iValue * ( 1000 - this->iSaturation * ( 1 - f ) );
    p /= 1000;
    q /= 1000;
    t /= 1000;

    switch( i )
    {
        case 0:
            this->iRed = this->iValue;
            this->iGreen = t;
            this->iBlue = p;
            break;
        case 1:
            this->iRed = q;
            this->iGreen = this->iValue;
            this->iBlue = p;
            break;
        case 2:
            this->iRed = p;
            this->iGreen = this->iValue;
            this->iBlue = t;
            break;
        case 3:
            this->iRed = p;
            this->iGreen = q;
            this->iBlue = this->iValue;
            break;
        case 4:
            this->iRed = t;
            this->iGreen = p;
            this->iBlue = this->iValue;
            break;
        default:        // case 5:
            this->iRed = this->iValue;
            this->iGreen = p;
            this->iBlue = q;
            break;
    }

    this->bRGBIsSet = true;
}

/** s'assure que la valeur HSV est definie */
void TColor::ensureHSVIsSet()
{
    if(this->bHSVIsSet || (!this->bRGBIsSet))
        return;

    uint iMinRGB = this->iRed;
    uint iMaxRGB = this->iRed;
    if(this->iGreen < iMinRGB)
        iMinRGB = this->iGreen;
    if(this->iGreen > iMaxRGB)
        iMaxRGB = this->iGreen;
    if(this->iBlue < iMinRGB)
        iMinRGB = this->iBlue;
    if(this->iBlue > iMaxRGB)
        iMaxRGB = this->iBlue;

    this->iValue = iMaxRGB;
    if(this->iValue == 0)
    {
        this->iHue = 0;
        this->iSaturation = 1000;
        this->bHSVIsSet = true;
        return;
    }
    this->iSaturation = (unsigned short)(1000 * (iMaxRGB - iMinRGB) / iMaxRGB);
    if(this->iSaturation == 0)
    {
        this->iHue = 0;
        this->bHSVIsSet = true;
        return;
    }

    short iTmpHue = 0;
    if (iMaxRGB == this->iRed)
        iTmpHue = 0 + (60*(this->iGreen - this->iBlue)/(iMaxRGB - iMinRGB));
    else if (iMaxRGB == this->iGreen)
        iTmpHue = 120 + (60*(this->iBlue - this->iRed)/(iMaxRGB - iMinRGB));
    else /* iMaxRGB == this->iBlue */
        iTmpHue = 240 + (60*(this->iRed - this->iGreen)/(iMaxRGB - iMinRGB));
    while(iTmpHue < 0)
        iTmpHue += TCOLOR_CHANNEL_MAXVALUE;
    this->iHue = iTmpHue;

    this->bHSVIsSet = true;
}
