/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_slideshow.hxx"

#include <hslcolor.hxx>
#include <rgbcolor.hxx>

#include <basegfx/numeric/ftools.hxx>

#include <cmath> // for fmod
#include <algorithm>


namespace slideshow
{
    namespace internal
    {
        namespace
        {
            // helper functions
            // ================

            double getMagic( double nLuminance, double nSaturation )
            {
                if( nLuminance <= 0.5 )
                    return nLuminance*(1.0 + nSaturation);
                else
                    return nLuminance + nSaturation - nLuminance*nSaturation;
            }

            HSLColor::HSLTriple rgb2hsl( double nRed, double nGreen, double nBlue )
            {
                // r,g,b in [0,1], h in [0,360] and s,l in [0,1]
                HSLColor::HSLTriple aRes;

                const double nMax( ::std::max(nRed,::std::max(nGreen, nBlue)) );
                const double nMin( ::std::min(nRed,::std::min(nGreen, nBlue)) );
                
                const double nDelta( nMax - nMin );

                aRes.mnLuminance = (nMax + nMin) / 2.0;
    
                if( ::basegfx::fTools::equalZero( nDelta ) )
                {
                    aRes.mnSaturation = 0.0;
        
                    // hue undefined (achromatic case)
                    aRes.mnHue = 0.0;
                }
                else
                {
                    aRes.mnSaturation = aRes.mnLuminance > 0.5 ? 
                        nDelta/(2.0-nMax-nMin) :
                        nDelta/(nMax + nMin);

                    if( nRed == nMax )
                        aRes.mnHue = (nGreen - nBlue)/nDelta;
                    else if( nGreen == nMax )
                        aRes.mnHue = 2.0 + (nBlue - nRed)/nDelta;
                    else if( nBlue == nMax )
                        aRes.mnHue = 4.0 + (nRed - nGreen)/nDelta;

                    aRes.mnHue *= 60.0;

                    if( aRes.mnHue < 0.0 )
                        aRes.mnHue += 360.0;
                }

                return aRes;
            }

            double hsl2rgbHelper( double nValue1, double nValue2, double nHue )
            {
                // clamp hue to [0,360]
                nHue = fmod( nHue, 360.0 );

                // cope with wrap-arounds
                if( nHue < 0.0 )
                    nHue += 360.0;

                if( nHue < 60.0 )
                    return nValue1 + (nValue2 - nValue1)*nHue/60.0;
                else if( nHue < 180.0 )
                    return nValue2;
                else if( nHue < 240.0 )
                    return nValue1 + (nValue2 - nValue1)*(240.0 - nHue)/60.0;
                else 
                    return nValue1;
            }

            RGBColor::RGBTriple hsl2rgb( double nHue, double nSaturation, double nLuminance )
            {
                if( ::basegfx::fTools::equalZero( nSaturation ) )
                    return RGBColor::RGBTriple(0.0, 0.0, nLuminance );

                const double nVal1( getMagic(nLuminance, nSaturation) );
                const double nVal2( 2.0*nLuminance - nVal1 );

                RGBColor::RGBTriple aRes;

                aRes.mnRed = hsl2rgbHelper( nVal2,
                                            nVal1, 
                                            nHue + 120.0 );
                aRes.mnGreen = hsl2rgbHelper( nVal2, 
                                              nVal1,
                                              nHue );
                aRes.mnBlue = hsl2rgbHelper( nVal2,
                                             nVal1,
                                             nHue - 120.0 );

                return aRes;
            }

            /// Truncate range of value to [0,1]
            double truncateRangeStd( double nVal )
            {
                return ::std::max( 0.0, 
                                   ::std::min( 1.0, 
                                               nVal ) );
            }

            /// Truncate range of value to [0,360]
            double truncateRangeHue( double nVal )
            {
                return ::std::max( 0.0, 
                                   ::std::min( 360.0, 
                                               nVal ) );
            }

            /// convert RGB color to sal_uInt8, truncate range appropriately before
            sal_uInt8 colorToInt( double nCol )
            {
                return static_cast< sal_uInt8 >( 
                    ::basegfx::fround( truncateRangeStd( nCol ) * 255.0 ) );
            }
        }



        // HSLColor
        // ===============================================

        HSLColor::HSLTriple::HSLTriple() :
            mnHue(),
            mnSaturation(),
            mnLuminance()
        {
        }

        HSLColor::HSLTriple::HSLTriple( double nHue, double nSaturation, double nLuminance ) :
            mnHue( nHue ),
            mnSaturation( nSaturation ),
            mnLuminance( nLuminance )
        {
        }
       
        HSLColor::HSLColor() :
            maHSLTriple( 0.0, 0.0, 0.0 ),
            mnMagicValue( getMagic( maHSLTriple.mnLuminance,
                                    maHSLTriple.mnSaturation ) )
        {
        }

        HSLColor::HSLColor( ::cppcanvas::Color::IntSRGBA nRGBColor ) :
            maHSLTriple( rgb2hsl( ::cppcanvas::getRed( nRGBColor ) / 255.0,
                                  ::cppcanvas::getGreen( nRGBColor ) / 255.0,
                                  ::cppcanvas::getBlue( nRGBColor ) / 255.0 ) ),
            mnMagicValue( getMagic( maHSLTriple.mnLuminance,
                                    maHSLTriple.mnSaturation ) )
        {
        }

        HSLColor::HSLColor( double nHue, double nSaturation, double nLuminance ) :
            maHSLTriple( nHue, nSaturation, nLuminance ),
            mnMagicValue( getMagic( maHSLTriple.mnLuminance,
                                    maHSLTriple.mnSaturation ) )
        {
        }
        
        HSLColor::HSLColor( const RGBColor& rColor ) :
            maHSLTriple( rgb2hsl( truncateRangeStd( rColor.getRed() ), 
                                  truncateRangeStd( rColor.getGreen() ),
                                  truncateRangeStd( rColor.getBlue() ) ) ),
            mnMagicValue( getMagic( maHSLTriple.mnLuminance,
                                    maHSLTriple.mnSaturation ) )
        {
        }
                         
        double HSLColor::getHue() const
        {
            return maHSLTriple.mnHue;
        }

        double HSLColor::getSaturation() const
        {
            return maHSLTriple.mnSaturation;
        }

        double HSLColor::getLuminance() const
        {
            return maHSLTriple.mnLuminance;
        }

        double HSLColor::getRed() const
        {
            if( ::basegfx::fTools::equalZero( getSaturation() ) )
                return getLuminance();

            return hsl2rgbHelper( 2.0*getLuminance() - mnMagicValue, 
                                  mnMagicValue, 
                                  getHue() + 120.0 );
        }

        double HSLColor::getGreen() const
        {
            if( ::basegfx::fTools::equalZero( getSaturation() ) )
                return getLuminance();

            return hsl2rgbHelper( 2.0*getLuminance() - mnMagicValue, 
                                  mnMagicValue, 
                                  getHue() );
        }

        double HSLColor::getBlue() const
        {
            if( ::basegfx::fTools::equalZero( getSaturation() ) )
                return getLuminance();

            return hsl2rgbHelper( 2.0*getLuminance() - mnMagicValue, 
                                  mnMagicValue, 
                                  getHue() - 120.0 );
        }

        RGBColor HSLColor::getRGBColor() const
        {
            RGBColor::RGBTriple aColor( hsl2rgb( getHue(),
                                                 getSaturation(),
                                                 getLuminance() ) );
            return RGBColor( aColor.mnRed, aColor.mnGreen, aColor.mnBlue );
        }
         
        RGBColor::RGBColor(const RGBColor& rLHS)
        {
            maRGBTriple.mnRed = rLHS.getRed();
            maRGBTriple.mnGreen = rLHS.getGreen();
            maRGBTriple.mnBlue = rLHS.getBlue();
        }

        RGBColor& RGBColor::operator=( const RGBColor& rLHS ){

            maRGBTriple.mnRed = rLHS.getRed();
            maRGBTriple.mnGreen = rLHS.getGreen();
            maRGBTriple.mnBlue = rLHS.getBlue();
            return *this;
        }        

        HSLColor operator+( const HSLColor& rLHS, const HSLColor& rRHS )
        {
            return HSLColor( rLHS.getHue() + rRHS.getHue(),
                             rLHS.getSaturation() + rRHS.getSaturation(),
                             rLHS.getLuminance() + rRHS.getLuminance() );
        }

        HSLColor operator*( const HSLColor& rLHS, const HSLColor& rRHS )
        {
            return HSLColor( rLHS.getHue() * rRHS.getHue(),
                             rLHS.getSaturation() * rRHS.getSaturation(),
                             rLHS.getLuminance() * rRHS.getLuminance() );
        }

        HSLColor operator*( double nFactor, const HSLColor& rRHS )
        {
            return HSLColor( nFactor * rRHS.getHue(),
                             nFactor * rRHS.getSaturation(),
                             nFactor * rRHS.getLuminance() );
        }

        HSLColor interpolate( const HSLColor& rFrom, const HSLColor& rTo, double t, bool bCCW )
        {
            const double nFromHue( rFrom.getHue() );
            const double nToHue	 ( rTo.getHue()   );

            double nHue=0.0;

            if( nFromHue <= nToHue && !bCCW )
            {
                // interpolate hue clockwise. That is, hue starts at
                // high values and ends at low ones. Therefore, we
                // must 'cross' the 360 degrees and start at low
                // values again (imagine the hues to lie on the
                // circle, where values above 360 degrees are mapped
                // back to [0,360)).
                nHue = (1.0-t)*(nFromHue + 360.0) + t*nToHue;
            }
            else if( nFromHue > nToHue && bCCW )
            {
                // interpolate hue counter-clockwise. That is, hue
                // starts at high values and ends at low
                // ones. Therefore, we must 'cross' the 360 degrees
                // and start at low values again (imagine the hues to
                // lie on the circle, where values above 360 degrees
                // are mapped back to [0,360)).
                nHue = (1.0-t)*nFromHue + t*(nToHue + 360.0);
            }
            else
            {
                // interpolate hue counter-clockwise. That is, hue
                // starts at low values and ends at high ones (imagine
                // the hue value as degrees on a circle, with
                // increasing values going counter-clockwise)
                nHue = (1.0-t)*nFromHue + t*nToHue;
            }

            return HSLColor( nHue,
                             (1.0-t)*rFrom.getSaturation() + t*rTo.getSaturation(),
                             (1.0-t)*rFrom.getLuminance() + t*rTo.getLuminance() );
        }

        

        // RGBColor
        // ===============================================


        RGBColor::RGBTriple::RGBTriple() :
            mnRed(),
            mnGreen(),
            mnBlue()
        {
        }
       
        RGBColor::RGBTriple::RGBTriple( double nRed, double nGreen, double nBlue ) :
            mnRed( nRed ),
            mnGreen( nGreen ),
            mnBlue( nBlue )
        {
        }
       
        RGBColor::RGBColor() :
            maRGBTriple( 0.0, 0.0, 0.0 )
        {
        }

        RGBColor::RGBColor( ::cppcanvas::Color::IntSRGBA nRGBColor ) :
            maRGBTriple( ::cppcanvas::getRed( nRGBColor ) / 255.0,
                         ::cppcanvas::getGreen( nRGBColor ) / 255.0,
                         ::cppcanvas::getBlue( nRGBColor ) / 255.0 )
        {
        }

        RGBColor::RGBColor( double nRed, double nGreen, double nBlue ) :
            maRGBTriple( nRed, nGreen, nBlue )
        {
        }
        
        RGBColor::RGBColor( const HSLColor& rColor ) :
            maRGBTriple( hsl2rgb( truncateRangeHue( rColor.getHue() ), 
                                  truncateRangeStd( rColor.getSaturation() ),
                                  truncateRangeStd( rColor.getLuminance() ) ) )
        {
        }
                         
        double RGBColor::getHue() const
        {
            return rgb2hsl( getRed(),
                            getGreen(),
                            getBlue() ).mnHue;
        }

        double RGBColor::getSaturation() const
        {
            return rgb2hsl( getRed(),
                            getGreen(),
                            getBlue() ).mnSaturation;
        }

        double RGBColor::getLuminance() const
        {
            return rgb2hsl( getRed(),
                            getGreen(),
                            getBlue() ).mnLuminance;
        }

        double RGBColor::getRed() const
        {
            return maRGBTriple.mnRed;
        }

        double RGBColor::getGreen() const
        {
            return maRGBTriple.mnGreen;
        }

        double RGBColor::getBlue() const
        {
            return maRGBTriple.mnBlue;
        }

        HSLColor RGBColor::getHSLColor() const
        {
            HSLColor::HSLTriple aColor( rgb2hsl( getRed(),
                                                 getGreen(),
                                                 getBlue() ) );
            return HSLColor( aColor.mnHue, aColor.mnSaturation, aColor.mnLuminance );
        }        

        ::cppcanvas::Color::IntSRGBA RGBColor::getIntegerColor() const
        {
            return ::cppcanvas::makeColor( colorToInt( getRed() ),
                                           colorToInt( getGreen() ),
                                           colorToInt( getBlue() ),
                                           255 );
        }
        
        RGBColor operator+( const RGBColor& rLHS, const RGBColor& rRHS )
        {
            return RGBColor( rLHS.getRed() + rRHS.getRed(),
                             rLHS.getGreen() + rRHS.getGreen(),
                             rLHS.getBlue() + rRHS.getBlue() );
        }

        RGBColor operator*( const RGBColor& rLHS, const RGBColor& rRHS )
        {
            return RGBColor( rLHS.getRed() * rRHS.getRed(),
                             rLHS.getGreen() * rRHS.getGreen(),
                             rLHS.getBlue() * rRHS.getBlue() );
        }

        RGBColor operator*( double nFactor, const RGBColor& rRHS )
        {
            return RGBColor( nFactor * rRHS.getRed(),
                             nFactor * rRHS.getGreen(),
                             nFactor * rRHS.getBlue() );
        }

        RGBColor interpolate( const RGBColor& rFrom, const RGBColor& rTo, double t )
        {
            return RGBColor( (1.0-t)*rFrom.getRed() + t*rTo.getRed(), 
                             (1.0-t)*rFrom.getGreen() + t*rTo.getGreen(),
                             (1.0-t)*rFrom.getBlue() + t*rTo.getBlue() );
        }
    }
}
