/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */

#include <sal/config.h>
#include <sal/mathconf.h>
#include <algorithm>
#include <float.h>
#include <basegfx/color/bcolormodifier.hxx>
#include <comphelper/random.hxx>

namespace basegfx
{
    BColorModifier::~BColorModifier()
    {
    }

    BColorModifier_gray::~BColorModifier_gray()
    {
    }

    ::basegfx::BColor BColorModifier_gray::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
    {
        const double fLuminance(aSourceColor.luminance());

        return ::basegfx::BColor(fLuminance, fLuminance, fLuminance);
    }

    OUString BColorModifier_gray::getModifierName() const
    {
        return u"gray"_ustr;
    }

    BColorModifier_invert::~BColorModifier_invert()
    {
    }

    ::basegfx::BColor BColorModifier_invert::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
    {
        return ::basegfx::BColor(1.0 - aSourceColor.getRed(), 1.0 - aSourceColor.getGreen(), 1.0 - aSourceColor.getBlue());
    }

    OUString BColorModifier_invert::getModifierName() const
    {
        return u"invert"_ustr;
    }

    BColorModifier_luminance_to_alpha::~BColorModifier_luminance_to_alpha()
    {
    }

    ::basegfx::BColor BColorModifier_luminance_to_alpha::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
    {
        const double fAlpha(1.0 - ((aSourceColor.getRed() * 0.2125) + (aSourceColor.getGreen() * 0.7154) + (aSourceColor.getBlue() * 0.0721)));

        return ::basegfx::BColor(fAlpha, fAlpha, fAlpha);
    }

    OUString BColorModifier_luminance_to_alpha::getModifierName() const
    {
        return u"luminance_to_alpha"_ustr;
    }

    BColorModifier_replace::~BColorModifier_replace()
    {
    }

    bool BColorModifier_replace::operator==(const BColorModifier& rCompare) const
    {
        if (!BColorModifier::operator==(rCompare))
            return false;

        const BColorModifier_replace* pCompare(static_cast< const BColorModifier_replace* >(&rCompare));
        return getBColor() == pCompare->getBColor();
    }

    ::basegfx::BColor BColorModifier_replace::getModifiedColor(const ::basegfx::BColor& /*aSourceColor*/) const
    {
        return maBColor;
    }

    OUString BColorModifier_replace::getModifierName() const
    {
        return u"replace"_ustr;
    }

    BColorModifier_interpolate::~BColorModifier_interpolate()
    {
    }

    bool BColorModifier_interpolate::operator==(const BColorModifier& rCompare) const
    {
        if (!BColorModifier::operator==(rCompare))
            return false;

        const BColorModifier_interpolate* pCompare(static_cast< const BColorModifier_interpolate* >(&rCompare));
        return maBColor == pCompare->maBColor && mfValue == pCompare->mfValue;
    }

    ::basegfx::BColor BColorModifier_interpolate::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
    {
        return interpolate(maBColor, aSourceColor, mfValue);
    }

    OUString BColorModifier_interpolate::getModifierName() const
    {
        return u"interpolate"_ustr;
    }

    BColorModifier_matrix::~BColorModifier_matrix()
    {
    }

    bool BColorModifier_matrix::operator==(const BColorModifier& rCompare) const
    {
        if (!BColorModifier::operator==(rCompare))
            return false;

        const BColorModifier_matrix* pCompare(static_cast< const BColorModifier_matrix* >(&rCompare));
        return maVector == pCompare->maVector;
    }

    ::basegfx::BColor BColorModifier_matrix::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
    {
        if (maVector.size() != 20)
            return aSourceColor;

        const double aRed = maVector[0] * aSourceColor.getRed()
            + maVector[1] * aSourceColor.getGreen()
            + maVector[2] * aSourceColor.getBlue()
            + maVector[3] * 1.0
            + maVector[4];
        const double aGreen = maVector[5] * aSourceColor.getRed()
            + maVector[6] * aSourceColor.getGreen()
            + maVector[7] * aSourceColor.getBlue()
            + maVector[8] * 1.0
            + maVector[9];
        const double aBlue = maVector[10] * aSourceColor.getRed()
            + maVector[11] * aSourceColor.getGreen()
            + maVector[12] * aSourceColor.getBlue()
            + maVector[13] * 1.0
            + maVector[14];
        /*TODO: add support for alpha
        const double aAlpha = maVector[15] * aSourceColor.getRed()
            + maVector[16] * aSourceColor.getGreen()
            + maVector[17] * aSourceColor.getBlue()
            + maVector[18] * 1.0
            + maVector[19]);
        */

        return ::basegfx::BColor(
                std::clamp(aRed, 0.0, 1.0),
                std::clamp(aGreen, 0.0, 1.0),
                std::clamp(aBlue, 0.0, 1.0));
    }

    OUString BColorModifier_matrix::getModifierName() const
    {
        return u"matrix"_ustr;
    }

    BColorModifier_saturate::BColorModifier_saturate(double fValue)
    : BColorModifier(basegfx::BColorModifierType::BCMType_saturate)
    {
        maSatMatrix.set(0, 0, 0.213 + 0.787 * fValue);
        maSatMatrix.set(0, 1, 0.715 - 0.715 * fValue);
        maSatMatrix.set(0, 2, 0.072 - 0.072 * fValue);
        maSatMatrix.set(1, 0, 0.213 - 0.213 * fValue);
        maSatMatrix.set(1, 1, 0.715 + 0.285 * fValue);
        maSatMatrix.set(1, 2, 0.072 - 0.072 * fValue);
        maSatMatrix.set(2, 0, 0.213 - 0.213 * fValue);
        maSatMatrix.set(2, 1, 0.715 - 0.715 * fValue);
        maSatMatrix.set(2, 2, 0.072 + 0.928 * fValue);
    }

    BColorModifier_saturate::~BColorModifier_saturate()
    {
    }

    bool BColorModifier_saturate::operator==(const BColorModifier& rCompare) const
    {
        if (!BColorModifier::operator==(rCompare))
            return false;

        const BColorModifier_saturate* pCompare(static_cast< const BColorModifier_saturate* >(&rCompare));
        return maSatMatrix == pCompare->maSatMatrix;
    }

    ::basegfx::BColor BColorModifier_saturate::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
    {
        basegfx::B3DHomMatrix aColorMatrix;
        aColorMatrix.set(0, 0, aSourceColor.getRed());
        aColorMatrix.set(1, 0, aSourceColor.getGreen());
        aColorMatrix.set(2, 0, aSourceColor.getBlue());

        aColorMatrix = maSatMatrix * aColorMatrix;
        return ::basegfx::BColor(aColorMatrix.get(0, 0), aColorMatrix.get(1, 0), aColorMatrix.get(2, 0));
    }

    OUString BColorModifier_saturate::getModifierName() const
    {
        return u"saturate"_ustr;
    }

    BColorModifier_hueRotate::BColorModifier_hueRotate(double fRad)
    : BColorModifier(basegfx::BColorModifierType::BCMType_hueRotate)
    {
        const double fCos = cos(fRad);
        const double fSin = sin(fRad);

        maHueMatrix.set(0, 0, 0.213 + fCos * 0.787 - fSin * 0.213);
        maHueMatrix.set(0, 1, 0.715 - fCos * 0.715 - fSin * 0.715);
        maHueMatrix.set(0, 2, 0.072 - fCos * 0.072 + fSin * 0.928);
        maHueMatrix.set(1, 0, 0.213 - fCos * 0.213 + fSin * 0.143);
        maHueMatrix.set(1, 1, 0.715 + fCos * 0.285 + fSin * 0.140);
        maHueMatrix.set(1, 2, 0.072 - fCos * 0.072 - fSin * 0.283);
        maHueMatrix.set(2, 0, 0.213 - fCos * 0.213 - fSin * 0.787);
        maHueMatrix.set(2, 1, 0.715 - fCos * 0.715 + fSin * 0.715);
        maHueMatrix.set(2, 2, 0.072 + fCos * 0.928 + fSin * 0.072);
    }

    BColorModifier_hueRotate::~BColorModifier_hueRotate()
    {
    }

    bool BColorModifier_hueRotate::operator==(const BColorModifier& rCompare) const
    {
        if (!BColorModifier::operator==(rCompare))
            return false;

        const BColorModifier_hueRotate* pCompare(static_cast< const BColorModifier_hueRotate* >(&rCompare));
        return maHueMatrix == pCompare->maHueMatrix;
    }

    ::basegfx::BColor BColorModifier_hueRotate::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
    {
        basegfx::B3DHomMatrix aColorMatrix;
        aColorMatrix.set(0, 0, aSourceColor.getRed());
        aColorMatrix.set(1, 0, aSourceColor.getGreen());
        aColorMatrix.set(2, 0, aSourceColor.getBlue());

        aColorMatrix = maHueMatrix * aColorMatrix;
        return ::basegfx::BColor(
                std::clamp(aColorMatrix.get(0, 0), 0.0, 1.0),
                std::clamp(aColorMatrix.get(1, 0), 0.0, 1.0),
                std::clamp(aColorMatrix.get(2, 0), 0.0, 1.0));
    }

    OUString BColorModifier_hueRotate::getModifierName() const
    {
        return u"hueRotate"_ustr;
    }

    BColorModifier_black_and_white::~BColorModifier_black_and_white()
    {
    }

    bool BColorModifier_black_and_white::operator==(const BColorModifier& rCompare) const
    {
        if (!BColorModifier::operator==(rCompare))
            return false;

        const BColorModifier_black_and_white* pCompare(static_cast< const BColorModifier_black_and_white* >(&rCompare));
        return mfValue == pCompare->mfValue;
    }

    ::basegfx::BColor BColorModifier_black_and_white::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
    {
        const double fLuminance(aSourceColor.luminance());

        if(fLuminance < mfValue)
        {
            return ::basegfx::BColor::getEmptyBColor();
        }
        else
        {
            return ::basegfx::BColor(1.0, 1.0, 1.0);
        }
    }

    OUString BColorModifier_black_and_white::getModifierName() const
    {
        return u"black_and_white"_ustr;
    }

    BColorModifier_gamma::BColorModifier_gamma(double fValue)
    : BColorModifier(basegfx::BColorModifierType::BCMType_gamma)
    , mfValue(fValue)
    , mfInvValue(fValue)
    , mbUseIt(!basegfx::fTools::equal(fValue, 1.0) && fValue > 0.0 && basegfx::fTools::lessOrEqual(fValue, 10.0))
    {
        if(mbUseIt)
        {
            mfInvValue = 1.0 / mfValue;
        }
    }

    BColorModifier_gamma::~BColorModifier_gamma()
    {
    }

    bool BColorModifier_gamma::operator==(const BColorModifier& rCompare) const
    {
        if (!BColorModifier::operator==(rCompare))
            return false;

        const BColorModifier_gamma* pCompare(static_cast< const BColorModifier_gamma* >(&rCompare));

        // mfValue is sufficient, mfInvValue and mbUseIt are only helper values
        return mfValue == pCompare->mfValue;
    }

    /**
      A fast and approximate std::pow(), good enough for gamma calculations,
      given that the parameter a is in the range [0,1), and b is in the range (0, 0.1]

      Google for "optimised power approximation". The below function is the result
      of reducing various bit-twiddling tricks into fewer operations.
    */
    static double fast_pow(double a, double b)
    {
        sal_math_Double u;
        u.value = a;
        u.w32_parts.msw = static_cast<int>(b * (static_cast<int>(u.w32_parts.msw) - 1072632447) + 1072632447);
        u.w32_parts.lsw = 0;
        return u.value;
    }

    ::basegfx::BColor BColorModifier_gamma::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
    {
        if(mbUseIt)
        {
            ::basegfx::BColor aRetval(
                fast_pow(aSourceColor.getRed(), mfInvValue),
                fast_pow(aSourceColor.getGreen(), mfInvValue),
                fast_pow(aSourceColor.getBlue(), mfInvValue));

            aRetval.clamp();
            return aRetval;
        }
        else
        {
            return aSourceColor;
        }
    }

    OUString BColorModifier_gamma::getModifierName() const
    {
        return u"gamma"_ustr;
    }

    BColorModifier_RGBLuminanceContrast::BColorModifier_RGBLuminanceContrast(double fRed, double fGreen, double fBlue, double fLuminance, double fContrast)
    : BColorModifier(basegfx::BColorModifierType::BCMType_RGBLuminanceContrast)
    , mfRed(std::clamp(fRed, -1.0, 1.0))
    , mfGreen(std::clamp(fGreen, -1.0, 1.0))
    , mfBlue(std::clamp(fBlue, -1.0, 1.0))
    , mfLuminance(std::clamp(fLuminance, -1.0, 1.0))
    , mfContrast(std::clamp(fContrast, -1.0, 1.0))
    , mfContrastOff(1.0)
    , mfRedOff(0.0)
    , mfGreenOff(0.0)
    , mfBlueOff(0.0)
    , mbUseIt(false)
    {
        if(basegfx::fTools::equalZero(mfRed)
            && basegfx::fTools::equalZero(mfGreen)
            && basegfx::fTools::equalZero(mfBlue)
            && basegfx::fTools::equalZero(mfLuminance)
            && basegfx::fTools::equalZero(mfContrast))
            return;

        // calculate slope
        if(mfContrast >= 0.0)
        {
            mfContrastOff = 128.0 / (128.0 - (mfContrast * 127.0));
        }
        else
        {
            mfContrastOff = ( 128.0 + (mfContrast * 127.0)) / 128.0;
        }

        // calculate unified contrast offset
        const double fPreparedContrastOff((128.0 - mfContrastOff * 128.0) / 255.0);
        const double fCombinedOffset(mfLuminance + fPreparedContrastOff);

        // set full offsets
        mfRedOff = mfRed + fCombinedOffset;
        mfGreenOff = mfGreen + fCombinedOffset;
        mfBlueOff = mfBlue + fCombinedOffset;

        mbUseIt = true;
    }

    BColorModifier_RGBLuminanceContrast::~BColorModifier_RGBLuminanceContrast()
    {
    }

    bool BColorModifier_RGBLuminanceContrast::operator==(const BColorModifier& rCompare) const
    {
        if (!BColorModifier::operator==(rCompare))
            return false;

        const BColorModifier_RGBLuminanceContrast* pCompare(static_cast< const BColorModifier_RGBLuminanceContrast* >(&rCompare));

        // no need to compare other values, these are just helpers
        return mfRed == pCompare->mfRed
            && mfGreen == pCompare->mfGreen
            && mfBlue == pCompare->mfBlue
            && mfLuminance == pCompare->mfLuminance
            && mfContrast == pCompare->mfContrast;
    }

    ::basegfx::BColor BColorModifier_RGBLuminanceContrast::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
    {
        if(mbUseIt)
        {
            return basegfx::BColor(
                std::clamp(aSourceColor.getRed() * mfContrastOff + mfRedOff, 0.0, 1.0),
                std::clamp(aSourceColor.getGreen() * mfContrastOff + mfGreenOff, 0.0, 1.0),
                std::clamp(aSourceColor.getBlue() * mfContrastOff + mfBlueOff, 0.0, 1.0));
        }
        else
        {
            return aSourceColor;
        }
    }

    OUString BColorModifier_RGBLuminanceContrast::getModifierName() const
    {
        return u"RGBLuminanceContrast"_ustr;
    }

    BColorModifier_randomize::BColorModifier_randomize(double fRandomPart)
    : BColorModifier(basegfx::BColorModifierType::BCMType_randomize)
    , mfRandomPart(fRandomPart)
    {
    }

    BColorModifier_randomize::~BColorModifier_randomize()
    {
    }

    // compare operator
    bool BColorModifier_randomize::operator==(const BColorModifier& rCompare) const
    {
        if (!BColorModifier::operator==(rCompare))
            return false;

        const BColorModifier_randomize* pCompare(static_cast< const BColorModifier_randomize* >(&rCompare));
        return mfRandomPart == pCompare->mfRandomPart;
    }

    // compute modified color
    ::basegfx::BColor BColorModifier_randomize::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
    {
        if(0.0 >= mfRandomPart)
        {
            // no randomizing, use orig color
            return aSourceColor;
        }

        if(1.0 <= mfRandomPart)
        {
            // full randomized color
            return basegfx::BColor(
                    comphelper::rng::uniform_real_distribution(0.0, nextafter(1.0, DBL_MAX)),
                    comphelper::rng::uniform_real_distribution(0.0, nextafter(1.0, DBL_MAX)),
                    comphelper::rng::uniform_real_distribution(0.0, nextafter(1.0, DBL_MAX)));
        }

        // mixed color
        const double fMulA(1.0 - mfRandomPart);
        return basegfx::BColor(
            aSourceColor.getRed() * fMulA +
                comphelper::rng::uniform_real_distribution(0.0, nextafter(mfRandomPart, DBL_MAX)),
            aSourceColor.getGreen() * fMulA +
                comphelper::rng::uniform_real_distribution(0.0, nextafter(mfRandomPart, DBL_MAX)),
            aSourceColor.getBlue() * fMulA +
                comphelper::rng::uniform_real_distribution(0.0, nextafter(mfRandomPart, DBL_MAX)));
    }

    OUString BColorModifier_randomize::getModifierName() const
    {
        return u"randomize"_ustr;
    }

    ::basegfx::BColor BColorModifierStack::getModifiedColor(const ::basegfx::BColor& rSource) const
    {
        if(maBColorModifiers.empty())
        {
            return rSource;
        }

        ::basegfx::BColor aRetval(rSource);

        for(sal_uInt32 a(maBColorModifiers.size()); a;)
        {
            a--;
            aRetval = maBColorModifiers[a]->getModifiedColor(aRetval);
        }

        return aRetval;
    }

    bool BColorModifierStack::operator==(const BColorModifierStack& rComp) const
    {
        if (count() != rComp.count())
            return false;

        if (0 == count())
            return true;

        for (sal_uInt32 a(0); a < count(); a++)
        {
            // nullptrs are not allowed/expected
            assert(maBColorModifiers[a] != nullptr);
            assert(rComp.maBColorModifiers[a] != nullptr);

            if (!(*maBColorModifiers[a] == *rComp.maBColorModifiers[a]))
                return false;
        }

        return true;
    }
} // end of namespace basegfx

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
