/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  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.txt
 *
 *  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.
 *
 *=========================================================================*/
#ifndef itkLabelToRGBFunctor_h
#define itkLabelToRGBFunctor_h

#include <vector>
#include "itkNumericTraits.h"

namespace itk
{
namespace Functor
{
/**
 *\class LabelToRGBFunctor
 *  \brief Functor for converting labels into RGB triplets.
 *
 * This functor class used internally by LabelToRGBImageFilter
 *
 * This code was contributed in the Insight Journal paper:
 * "The watershed transform in ITK - discussion and new developments"
 * by Beare R., Lehmann G.
 * https://hdl.handle.net/1926/202
 * http://www.insight-journal.org/browse/publication/92
 *
 * \author Gaetan Lehmann. Biologie du Developpement et de la Reproduction,
 * INRA de Jouy-en-Josas, France.
 *
 * \author Richard Beare. Department of
 * Medicine, Monash University, Melbourne, Australia.
 *
 * \sa LabelToRGBImageFilter
 *
 * \ingroup ITKImageFusion
 */
template <typename TLabel, typename TRGBPixel>
class LabelToRGBFunctor
{
public:
  using Self = LabelToRGBFunctor;

  LabelToRGBFunctor()
  {
    using ValueType = typename TRGBPixel::ValueType;

    // the following colors are from "R", and named:
    // "red"             "green3"          "blue"            "cyan"
    //"magenta"         "darkorange1"     "darkgreen"       "blueviolet"
    //"brown4"          "navy"            "yellow4"         "violetred1"
    //"salmon4"         "turquoise4"      "sienna3"         "darkorchid1"
    //"springgreen4"    "mediumvioletred" "orangered3"      "lightseagreen"
    //"slateblue"       "deeppink1"       "aquamarine4"     "royalblue1"
    //"tomato3"         "mediumblue"      "violetred4"      "darkmagenta"
    //"violet"          "red4"
    // They are a good selection of distinct colors for plotting and
    // overlays.
    constexpr size_t        numColors = 30;
    constexpr unsigned char colors[numColors][3] = {
      { 255, 0, 0 },   { 0, 205, 0 },    { 0, 0, 255 },    { 0, 255, 255 },  { 255, 0, 255 },   { 255, 127, 0 },
      { 0, 100, 0 },   { 138, 43, 226 }, { 139, 35, 35 },  { 0, 0, 128 },    { 139, 139, 0 },   { 255, 62, 150 },
      { 139, 76, 57 }, { 0, 134, 139 },  { 205, 104, 57 }, { 191, 62, 255 }, { 0, 139, 69 },    { 199, 21, 133 },
      { 205, 55, 0 },  { 32, 178, 170 }, { 106, 90, 205 }, { 255, 20, 147 }, { 69, 139, 116 },  { 72, 118, 255 },
      { 205, 79, 57 }, { 0, 0, 205 },    { 139, 34, 82 },  { 139, 0, 139 },  { 238, 130, 238 }, { 139, 0, 0 }
    };

    for (auto & color : colors)
    {
      AddColor(color[0], color[1], color[2]);
    }

    // provide some default value for external use (outside
    // LabelToRGBImageFilter)
    // Inside LabelToRGBImageFilter, the values are always initialized
    NumericTraits<TRGBPixel>::SetLength(m_BackgroundColor, 3);
    m_BackgroundColor.Fill(NumericTraits<ValueType>::ZeroValue());
    m_BackgroundValue = NumericTraits<TLabel>::ZeroValue();
  }

  inline TRGBPixel
  operator()(const TLabel & p) const
  {
    // value is background
    // return a gray pixel with the same intensity than the label pixel
    if (p == m_BackgroundValue)
    {
      return m_BackgroundColor;
    }

    // else, return a colored pixel from the color table
    return m_Colors[p % m_Colors.size()];
  }

  void
  AddColor(unsigned char r, unsigned char g, unsigned char b)
  {
    TRGBPixel rgbPixel;
    NumericTraits<TRGBPixel>::SetLength(rgbPixel, 3);

    using ValueType = typename TRGBPixel::ValueType;

    ValueType m = NumericTraits<ValueType>::max();

    rgbPixel[0] = static_cast<ValueType>(static_cast<double>(r) / 255 * m);
    rgbPixel[1] = static_cast<ValueType>(static_cast<double>(g) / 255 * m);
    rgbPixel[2] = static_cast<ValueType>(static_cast<double>(b) / 255 * m);
    m_Colors.push_back(rgbPixel);
  }

  // Empty the color LUT
  void
  ResetColors()
  {
    m_Colors.clear();
  }

  // Get number of colors in the LUT
  unsigned int
  GetNumberOfColors() const
  {
    return static_cast<unsigned int>(m_Colors.size());
  }

  bool
  operator!=(const Self & l) const
  {
    if (m_BackgroundColor != l.m_BackgroundColor || m_BackgroundValue != l.m_BackgroundValue ||
        m_Colors.size() != l.m_Colors.size())
    {
      return true;
    }

    // We need to check each color to see if it's different
    for (typename std::vector<TRGBPixel>::size_type i = 0; i < m_Colors.size(); ++i)
    {
      if (m_Colors[i] != l.m_Colors[i])
      {
        return true;
      }
    }
    return false;
  }

  bool
  operator==(const Self & other) const
  {
    return !(*this != other);
  }

  void
  SetBackgroundValue(TLabel v)
  {
    m_BackgroundValue = v;
  }

  void
  SetBackgroundColor(TRGBPixel rgb)
  {
    m_BackgroundColor = rgb;
  }

  ~LabelToRGBFunctor() = default;

  std::vector<TRGBPixel> m_Colors;

  TRGBPixel m_BackgroundColor;

  TLabel m_BackgroundValue;
};
} // end namespace Functor
} // end namespace itk

#endif
