/***************************************************************************
 *   Copyright (c) 2005 Werner Mayer <wmayer[at]users.sourceforge.net>     *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/


#include "PreCompiled.h"
#ifndef _PreComp_
# include <cstdlib>
#endif

#include <Base/Exception.h>

#include "ColorModel.h"


using namespace App;


ColorModelPack ColorModelPack::createRedGreenBlue()
{
    ColorModelPack pack{ColorModelBlueGreenRed(),
                        ColorModelGreenYellowRed(),
                        ColorModelBlueCyanGreen(),
                        "Red-Yellow-Green-Cyan-Blue"};
    return pack;
}

ColorModelPack ColorModelPack::createBlueGreenRed()
{
    ColorModelPack pack{ColorModelRedGreenBlue(),
                        ColorModelGreenCyanBlue(),
                        ColorModelRedYellowGreen(),
                        "Blue-Cyan-Green-Yellow-Red"};
    return pack;
}

ColorModelPack ColorModelPack::createWhiteBlack()
{
    ColorModelPack pack{ColorModelBlackWhite(),
                        ColorModelGrayWhite(),
                        ColorModelBlackGray(),
                        "White-Black"};
    return pack;
}

ColorModelPack ColorModelPack::createBlackWhite()
{
    ColorModelPack pack{ColorModelWhiteBlack(),
                        ColorModelGrayBlack(),
                        ColorModelWhiteGray(),
                        "Black-White"};
    return pack;
}

ColorModelPack ColorModelPack::createRedWhiteBlue()
{
    ColorModelPack pack{ColorModelBlueWhiteRed(),
                        ColorModelWhiteRed(),
                        ColorModelBlueWhite(),
                        "Red-White-Blue"};
    return pack;
}

ColorField::ColorField ()
{
    set(ColorModelBlueGreenRed(), -1.0f, 1.0f, 13);
}

ColorField::ColorField (const ColorModel &rclModel, float fMin, float fMax, std::size_t usCt)
{
    set(rclModel, fMin, fMax, usCt);
}

ColorField::ColorField (const ColorField &rclCF)
  : colorModel(rclCF.colorModel)
  , fMin(rclCF.fMin)
  , fMax(rclCF.fMax)
  , fAscent(rclCF.fAscent)
  , fConstant(rclCF.fConstant)
  , ctColors(rclCF.ctColors)
  , colorField(rclCF.colorField)
{
}

ColorField& ColorField::operator = (const ColorField &rclCF)
{
    colorField = rclCF.colorField;
    return *this;
}

void ColorField::set (const ColorModel &rclModel, float fMin, float fMax, std::size_t usCt)
{
    auto bounds = std::minmax(fMin, fMax);
    if (bounds.second <= bounds.first) {
        throw Base::ValueError("Maximum must be higher than minimum");
    }

    this->fMin = bounds.first;
    this->fMax = bounds.second;
    colorModel = rclModel;
    ctColors = std::max<std::size_t>(usCt, colorModel.getCountColors());
    rebuild();
}

void ColorField::setColorModel (const ColorModel &rclModel)
{
    colorModel = rclModel;
    rebuild();
}

void ColorField::rebuild ()
{
    colorField.resize(ctColors);

    std::size_t usStep = std::min<std::size_t>(ctColors / (colorModel.getCountColors() - 1), ctColors - 1);
    std::size_t usInd1 = 0;
    std::size_t usInd2 = usStep;
    for (std::size_t i = 0; i < (colorModel.getCountColors() - 1); i++) {
        interpolate(colorModel.colors[i], usInd1, colorModel.colors[i+1], usInd2);
        usInd1 = usInd2;
        if ((i + 1) == (colorModel.getCountColors() - 2))
            usInd2 = ctColors - 1;
        else
            usInd2 += usStep;
    }

    fAscent   = float(ctColors) / (fMax - fMin);
    fConstant = -fAscent * fMin;
}

// fuellt das Array von Farbe 1, Index 1 bis Farbe 2, Index 2
void ColorField::interpolate (Color clCol1, std::size_t usInd1, Color clCol2, std::size_t usInd2)
{
    float fStep = 1.0f, fLen = float(usInd2 - usInd1);

    colorField[usInd1] = clCol1;
    colorField[usInd2] = clCol2;

    float fR = (float(clCol2.r) - float(clCol1.r)) / fLen;
    float fG = (float(clCol2.g) - float(clCol1.g)) / fLen;
    float fB = (float(clCol2.b) - float(clCol1.b)) / fLen;

    for (std::size_t i = (usInd1 + 1); i < usInd2; i++) {
        float ucR = clCol1.r + fR * fStep;
        float ucG = clCol1.g + fG * fStep;
        float ucB = clCol1.b + fB * fStep;
        colorField[i] = Color(ucR, ucG, ucB);
        fStep += 1.0f;
    }
}


ColorGradientProfile::ColorGradientProfile()
  : tStyle{ColorBarStyle::FLOW}
  , fMin{}
  , fMax{}
  , ctColors{}
  , tColorModel{}
  , visibility{Visibility::Default}
{

}

bool ColorGradientProfile::isEqual(const ColorGradientProfile& cg) const
{
    if (tStyle != cg.tStyle)
        return false;
    if (fMin != cg.fMin)
        return false;
    if (fMax != cg.fMax)
        return false;
    if (!visibility.isEqual(cg.visibility))
        return false;
    if (tColorModel != cg.tColorModel)
        return false;
    return true;
}


ColorGradient::ColorGradient ()
{
    createStandardPacks();
    setColorModel();
    set(-1.0f, 1.0f, 13, ColorBarStyle::ZERO_BASED, Visibility::Default);
}

ColorGradient::ColorGradient (float fMin, float fMax, std::size_t usCtColors, ColorBarStyle tS, VisibilityFlags flags)
{
    createStandardPacks();
    setColorModel();
    set(fMin, fMax, usCtColors, tS, flags);
}

void ColorGradient::createStandardPacks()
{
    modelPacks.push_back(ColorModelPack::createRedGreenBlue());
    modelPacks.push_back(ColorModelPack::createBlueGreenRed());
    modelPacks.push_back(ColorModelPack::createRedWhiteBlue());
    modelPacks.push_back(ColorModelPack::createWhiteBlack());
    modelPacks.push_back(ColorModelPack::createBlackWhite());
}

std::vector<std::string> ColorGradient::getColorModelNames() const
{
    std::vector<std::string> names;
    names.reserve(modelPacks.size());
    for (const auto& it : modelPacks)
        names.push_back(it.description);
    return names;
}

void ColorGradient::setProfile(const ColorGradientProfile& pro)
{
    profile = pro;
    setColorModel();
    rebuild();
}

void ColorGradient::set (float fMin, float fMax, std::size_t usCt, ColorBarStyle tS, VisibilityFlags flags)
{
    auto bounds = std::minmax(fMin, fMax);
    if (bounds.second <= bounds.first) {
        throw Base::ValueError("Maximum must be higher than minimum");
    }

    profile.fMin = bounds.first;
    profile.fMax = bounds.second;
    profile.ctColors = std::max<std::size_t>(usCt, getMinColors());
    profile.tStyle = tS;
    profile.visibility = flags;
    rebuild();
}

void ColorGradient::rebuild ()
{
    switch (profile.tStyle)
    {
    case ColorBarStyle::FLOW:
        {
            colorField1.set(currentModelPack.totalModel, profile.fMin, profile.fMax, profile.ctColors);
            break;
        }
    case ColorBarStyle::ZERO_BASED:
        {
            if ((profile.fMin < 0.0f) && (profile.fMax > 0.0f))
            {
                colorField1.set(currentModelPack.bottomModel, profile.fMin, 0.0f, profile.ctColors / 2);
                colorField2.set(currentModelPack.topModel, 0.0f, profile.fMax, profile.ctColors / 2);
            }
            else if (profile.fMin >= 0.0f)
                colorField1.set(currentModelPack.topModel, 0.0f, profile.fMax, profile.ctColors);
            else
                colorField1.set(currentModelPack.bottomModel, profile.fMin, 0.0f, profile.ctColors);
            break;
        }
    }
}

std::size_t ColorGradient::getMinColors () const
{
    switch (profile.tStyle)
    {
    case ColorBarStyle::FLOW:
        return colorField1.getMinColors();
    case ColorBarStyle::ZERO_BASED:
        {
            if ((profile.fMin < 0.0f) && (profile.fMax > 0.0f))
                return colorField1.getMinColors() + colorField2.getMinColors();
            else
                return colorField1.getMinColors();
        }
    }
    return 2;
}

void ColorGradient::setColorModel (std::size_t tModel)
{
    profile.tColorModel = tModel;
    setColorModel();
    rebuild();
}

void ColorGradient::setColorModel ()
{
    if (profile.tColorModel < modelPacks.size())
        currentModelPack = modelPacks[profile.tColorModel];

    switch (profile.tStyle)
    {
    case ColorBarStyle::FLOW:
    {
        colorField1.setColorModel(currentModelPack.totalModel);
        colorField2.setColorModel(currentModelPack.bottomModel);
        break;
    }
    case ColorBarStyle::ZERO_BASED:
    {
        colorField1.setColorModel(currentModelPack.topModel);
        colorField2.setColorModel(currentModelPack.bottomModel);
        break;
    }
    }
}

ColorLegend::ColorLegend ()
  : outsideGrayed(false)
{
    // default  blue, green, red
    colorFields.emplace_back(0, 0, 1);
    colorFields.emplace_back(0, 1, 0);
    colorFields.emplace_back(1, 0, 0);

    names.push_back("Min");
    names.push_back("Mid");
    names.push_back("Max");

    values.push_back(-1.0f);
    values.push_back(-0.333f);
    values.push_back(0.333f);
    values.push_back(1.0f);
}

ColorLegend::ColorLegend (const ColorLegend &rclCL)
{
    *this = rclCL;
}

ColorLegend& ColorLegend::operator = (const ColorLegend &rclCL)
{
    colorFields = rclCL.colorFields;
    names       = rclCL.names;
    values      = rclCL.values;
    outsideGrayed = rclCL.outsideGrayed;

    return *this;
}

bool ColorLegend::operator == (const ColorLegend &rclCL) const
{
  return (colorFields.size() == rclCL.colorFields.size())                                 &&
         (names.size() == rclCL.names.size())                                             &&
         (values.size() == rclCL.values.size())                                           &&
          std::equal(colorFields.begin(), colorFields.end(), rclCL.colorFields.begin())   &&
          std::equal(names.begin(), names.end(), rclCL.names.begin())                     &&
          std::equal(values.begin(), values.end(), rclCL.values.begin())                  &&
          outsideGrayed == rclCL.outsideGrayed;
}

float ColorLegend::getValue (std::size_t ulPos) const
{
    if (ulPos < values.size())
        return values[ulPos];
    else
        return 0.0f;
}

bool ColorLegend::setValue (std::size_t ulPos, float fVal)
{
    if (ulPos < values.size())
    {
        values[ulPos] = fVal;
        return true;
    }
    else
        return false;
}

Color ColorLegend::getColor (std::size_t ulPos) const
{
    if (ulPos < colorFields.size())
        return colorFields[ulPos];
    else
        return Color();
}

// color as: 0x00rrggbb
uint32_t ColorLegend::getPackedColor (std::size_t ulPos) const
{
    Color clRGB = getColor(ulPos);
    return clRGB.getPackedValue();
}

std::string ColorLegend::getText (std::size_t ulPos) const
{
    if (ulPos < names.size())
        return names[ulPos];
    else
        return "";
}

std::size_t ColorLegend::addMin (const std::string &rclName)
{
    names.push_front(rclName);
    values.push_front(values.front() - 1.0f);

    Color clNewRGB;
    clNewRGB.r = ((float)rand() / (float)RAND_MAX);
    clNewRGB.g = ((float)rand() / (float)RAND_MAX);
    clNewRGB.b = ((float)rand() / (float)RAND_MAX);

    colorFields.push_front(clNewRGB);

    return colorFields.size() - 1;
}

std::size_t ColorLegend::addMax (const std::string &rclName)
{
    names.push_back(rclName);
    values.push_back(values.back() + 1.0f);

    Color clNewRGB;
    clNewRGB.r = ((float)rand() / (float)RAND_MAX);
    clNewRGB.g = ((float)rand() / (float)RAND_MAX);
    clNewRGB.b = ((float)rand() / (float)RAND_MAX);

    colorFields.push_back(clNewRGB);

    return colorFields.size() - 1;
}

bool ColorLegend::remove (std::size_t ulPos)
{
    if (ulPos < colorFields.size()) {
        colorFields.erase(colorFields.begin() + ulPos);
        names.erase(names.begin() + ulPos);
        values.erase(values.begin() + ulPos);

        return true;
    }

    return false;
}

void ColorLegend::removeFirst ()
{
    if (colorFields.size() > 0) {
        colorFields.erase(colorFields.begin());
        names.erase(names.begin());
        values.erase(values.begin());
    }
}

void ColorLegend::removeLast ()
{
    if (colorFields.size() > 0) {
        colorFields.erase(colorFields.end()-1);
        names.erase(names.end()-1);
        values.erase(values.end()-1);
    }
}

void ColorLegend::resize (std::size_t ulCt)
{
    if ((ulCt < 2) || (ulCt == colorFields.size()))
        return;

    if (ulCt > colorFields.size()) {
        int k = ulCt - colorFields.size();
        for (int i = 0; i < k; i++)
            addMin("new");
    }
    else {
        int k = colorFields.size() - ulCt;
        for (int i = 0; i < k; i++)
            removeLast();
    }
}

bool ColorLegend::setColor (std::size_t ulPos, float ucRed, float ucGreen, float ucBlue)
{
    if (ulPos < names.size()) {
        colorFields[ulPos] = Color(ucRed, ucGreen, ucBlue);
        return true;
    }

    return false;
}

// color as 0x00rrggbb
bool ColorLegend::setColor (std::size_t ulPos, unsigned long ulColor)
{
    unsigned char ucRed   = (unsigned char)((ulColor & 0x00ff0000) >> 16);
    unsigned char ucGreen = (unsigned char)((ulColor & 0x0000ff00) >> 8);
    unsigned char ucBlue  = (unsigned char)(ulColor & 0x000000ff);
    return setColor(ulPos, ucRed, ucGreen, ucBlue);
}

bool ColorLegend::setText (std::size_t ulPos, const std::string &rclName)
{
    if (ulPos < names.size()) {
        names[ulPos] = rclName;
        return true;
    }

    return false;
}
