// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkLookupTableWithEnabling.h"

#include "vtkArrayDispatch.h"
#include "vtkBitArray.h"
#include "vtkDataArrayRange.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkVariant.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkLookupTableWithEnabling);

vtkCxxSetObjectMacro(vtkLookupTableWithEnabling, EnabledArray, vtkDataArray);

// Construct with range=(0,1); and hsv ranges set up for rainbow color table
// (from red to blue).
vtkLookupTableWithEnabling::vtkLookupTableWithEnabling(int sze, int ext)
  : vtkLookupTable(sze, ext)
{
  this->EnabledArray = nullptr;
}

//------------------------------------------------------------------------------
vtkLookupTableWithEnabling::~vtkLookupTableWithEnabling()
{
  if (this->EnabledArray)
  {
    this->EnabledArray->Delete();
    this->EnabledArray = nullptr;
  }
}

//------------------------------------------------------------------------------
void vtkLookupTableWithEnabling::DisableColor(unsigned char r, unsigned char g, unsigned char b,
  unsigned char* rd, unsigned char* gd, unsigned char* bd)
{
  double rgb[3], hsv[3];
  rgb[0] = static_cast<double>(r);
  rgb[1] = static_cast<double>(g);
  rgb[2] = static_cast<double>(b);
  vtkMath::RGBToHSV(rgb, hsv);
  hsv[1] = 0;
  hsv[2] = 0;
  vtkMath::HSVToRGB(hsv, rgb);
  *rd = static_cast<unsigned char>(rgb[0]);
  *gd = static_cast<unsigned char>(rgb[1]);
  *bd = static_cast<unsigned char>(rgb[2]);
}

//------------------------------------------------------------------------------
// There is a little more to this than simply taking the log10 of the
// two range values: we do conversion of negative ranges to positive
// ranges, and conversion of zero to a 'very small number'
static void vtkLookupTableWithEnablingLogRange(const double range[2], double logRange[2])
{
  double rmin = range[0];
  double rmax = range[1];

  if (rmin == 0)
  {
    rmin = 1.0e-6 * (rmax - rmin);
    if (rmax < 0)
    {
      rmin = -rmin;
    }
  }
  if (rmax == 0)
  {
    rmax = 1.0e-6 * (rmin - rmax);
    if (rmin < 0)
    {
      rmax = -rmax;
    }
  }
  if (rmin < 0 && rmax < 0)
  {
    logRange[0] = log10(-rmin);
    logRange[1] = log10(-rmax);
  }
  else if (rmin > 0 && rmax > 0)
  {
    logRange[0] = log10(rmin);
    logRange[1] = log10(rmax);
  }
  else
  {
    logRange[0] = 0.;
    logRange[1] = 0.;
  }
}

//------------------------------------------------------------------------------
// Apply log to value, with appropriate constraints.
inline double vtkApplyLogScale(double v, const double range[2], const double logRange[2])
{
  // is the range set for negative numbers?
  if (range[0] < 0)
  {
    if (v < 0)
    {
      v = log10(-v);
    }
    else if (range[0] > range[1])
    {
      v = logRange[0];
    }
    else
    {
      v = logRange[1];
    }
  }
  else
  {
    if (v > 0)
    {
      v = log10(v);
    }
    else if (range[0] < range[1])
    {
      v = logRange[0];
    }
    else
    {
      v = logRange[1];
    }
  }
  return v;
}

//------------------------------------------------------------------------------
// Apply shift/scale to the scalar value v and do table lookup.
inline const unsigned char* vtkLinearLookup(
  double v, const unsigned char* table, double maxIndex, double shift, double scale)
{
  double findx = (v + shift) * scale;
  findx = std::max<double>(findx, 0);
  findx = std::min(findx, maxIndex);
  return &table[4 * static_cast<int>(findx)];
  /* round
  return &table[4*(int)(findx + 0.5f)];
  */
}

//------------------------------------------------------------------------------
// accelerate the mapping by copying the data in 32-bit chunks instead
// of 8-bit chunks
struct vtkLookupTableWithEnablingMapData
{
  template <class TArray, class T = vtk::GetAPIType<TArray>>
  void operator()(TArray* inputArray, vtkLookupTableWithEnabling* self, unsigned char* output,
    int length, int inIncr, int vectorComponent, int outFormat)
  {
    auto input = vtk::DataArrayValueRange<vtk::detail::DynamicTupleSize, T>(inputArray).begin() +
      vectorComponent;
    int i = length;
    const double* range = self->GetTableRange();
    double maxIndex = self->GetNumberOfColors() - 1;
    double shift, scale;
    const unsigned char* table = self->GetPointer(0);
    const unsigned char* cptr;
    double alpha;
    unsigned char r, g, b;

    bool hasEnabledArray = false;
    if (self->GetEnabledArray() && self->GetEnabledArray()->GetNumberOfTuples() == length)
    {
      hasEnabledArray = true;
    }

    if ((alpha = self->GetAlpha()) >= 1.0) // no blending required
    {
      if (self->GetScale() == VTK_SCALE_LOG10)
      {
        double val;
        double logRange[2];
        vtkLookupTableWithEnablingLogRange(range, logRange);
        shift = -logRange[0];
        if (logRange[1] <= logRange[0])
        {
          scale = VTK_DOUBLE_MAX;
        }
        else
        {
          /* while this looks like the wrong scale, it is the correct scale
           * taking into account the truncation to int that happens below. */
          scale = (maxIndex + 1) / (logRange[1] - logRange[0]);
        }
        if (outFormat == VTK_RGBA)
        {
          while (--i >= 0)
          {
            val = vtkApplyLogScale(*input, range, logRange);
            cptr = vtkLinearLookup(val, table, maxIndex, shift, scale);
            if (hasEnabledArray && !self->GetEnabledArray()->GetTuple1(length - i - 1))
            {
              self->DisableColor(cptr[0], cptr[1], cptr[2], &r, &g, &b);
              *output++ = r;
              *output++ = g;
              *output++ = b;
              cptr += 3;
            }
            else
            {
              *output++ = *cptr++;
              *output++ = *cptr++;
              *output++ = *cptr++;
            }
            *output++ = *cptr++;
            input += inIncr;
          }
        }
        else if (outFormat == VTK_RGB)
        {
          while (--i >= 0)
          {
            val = vtkApplyLogScale(*input, range, logRange);
            cptr = vtkLinearLookup(val, table, maxIndex, shift, scale);
            if (hasEnabledArray && !self->GetEnabledArray()->GetTuple1(length - i - 1))
            {
              self->DisableColor(cptr[0], cptr[1], cptr[2], &r, &g, &b);
              *output++ = r;
              *output++ = g;
              *output++ = b;
            }
            else
            {
              *output++ = *cptr++;
              *output++ = *cptr++;
              *output++ = *cptr++;
            }
            input += inIncr;
          }
        }
        else if (outFormat == VTK_LUMINANCE_ALPHA)
        {
          while (--i >= 0)
          {
            val = vtkApplyLogScale(*input, range, logRange);
            cptr = vtkLinearLookup(val, table, maxIndex, shift, scale);
            *output++ =
              static_cast<unsigned char>(cptr[0] * 0.30 + cptr[1] * 0.59 + cptr[2] * 0.11 + 0.5);
            *output++ = cptr[3];
            input += inIncr;
          }
        }
        else // outFormat == VTK_LUMINANCE
        {
          while (--i >= 0)
          {
            val = vtkApplyLogScale(*input, range, logRange);
            cptr = vtkLinearLookup(val, table, maxIndex, shift, scale);
            *output++ =
              static_cast<unsigned char>(cptr[0] * 0.30 + cptr[1] * 0.59 + cptr[2] * 0.11 + 0.5);
            input += inIncr;
          }
        }
      } // if log scale

      else // not log scale
      {
        shift = -range[0];
        if (range[1] <= range[0])
        {
          scale = VTK_DOUBLE_MAX;
        }
        else
        {
          /* while this looks like the wrong scale, it is the correct scale
           * taking into account the truncation to int that happens below. */
          scale = (maxIndex + 1) / (range[1] - range[0]);
        }

        if (outFormat == VTK_RGBA)
        {
          while (--i >= 0)
          {
            cptr = vtkLinearLookup(*input, table, maxIndex, shift, scale);
            if (hasEnabledArray && !self->GetEnabledArray()->GetTuple1(length - i - 1))
            {
              self->DisableColor(cptr[0], cptr[1], cptr[2], &r, &g, &b);
              *output++ = r;
              *output++ = g;
              *output++ = b;
              cptr += 3;
              *output++ = static_cast<unsigned char>((*cptr) * 0.2);
              cptr++;
            }
            else
            {
              *output++ = *cptr++;
              *output++ = *cptr++;
              *output++ = *cptr++;
              *output++ = *cptr++;
            }
            input += inIncr;
          }
        }
        else if (outFormat == VTK_RGB)
        {
          while (--i >= 0)
          {
            cptr = vtkLinearLookup(*input, table, maxIndex, shift, scale);
            if (hasEnabledArray && !self->GetEnabledArray()->GetTuple1(length - i - 1))
            {
              self->DisableColor(cptr[0], cptr[1], cptr[2], &r, &g, &b);
              *output++ = r;
              *output++ = g;
              *output++ = b;
            }
            else
            {
              *output++ = *cptr++;
              *output++ = *cptr++;
              *output++ = *cptr++;
            }
            input += inIncr;
          }
        }
        else if (outFormat == VTK_LUMINANCE_ALPHA)
        {
          while (--i >= 0)
          {
            cptr = vtkLinearLookup(*input, table, maxIndex, shift, scale);
            *output++ =
              static_cast<unsigned char>(cptr[0] * 0.30 + cptr[1] * 0.59 + cptr[2] * 0.11 + 0.5);
            *output++ = cptr[3];
            input += inIncr;
          }
        }
        else // outFormat == VTK_LUMINANCE
        {
          while (--i >= 0)
          {
            cptr = vtkLinearLookup(*input, table, maxIndex, shift, scale);
            *output++ =
              static_cast<unsigned char>(cptr[0] * 0.30 + cptr[1] * 0.59 + cptr[2] * 0.11 + 0.5);
            input += inIncr;
          }
        }
      } // if not log lookup
    } // if blending not needed

    else // blend with the specified alpha
    {
      if (self->GetScale() == VTK_SCALE_LOG10)
      {
        double val;
        double logRange[2];
        vtkLookupTableWithEnablingLogRange(range, logRange);
        shift = -logRange[0];
        if (logRange[1] <= logRange[0])
        {
          scale = VTK_DOUBLE_MAX;
        }
        else
        {
          /* while this looks like the wrong scale, it is the correct scale
           * taking into account the truncation to int that happens below. */
          scale = (maxIndex + 1) / (logRange[1] - logRange[0]);
        }
        if (outFormat == VTK_RGBA)
        {
          while (--i >= 0)
          {
            val = vtkApplyLogScale(*input, range, logRange);
            cptr = vtkLinearLookup(val, table, maxIndex, shift, scale);
            if (hasEnabledArray && !self->GetEnabledArray()->GetTuple1(length - i - 1))
            {
              self->DisableColor(cptr[0], cptr[1], cptr[2], &r, &g, &b);
              *output++ = r;
              *output++ = g;
              *output++ = b;
              cptr += 3;
            }
            else
            {
              *output++ = *cptr++;
              *output++ = *cptr++;
              *output++ = *cptr++;
            }
            *output++ = static_cast<unsigned char>((*cptr) * alpha);
            cptr++;
            input += inIncr;
          }
        }
        else if (outFormat == VTK_RGB)
        {
          while (--i >= 0)
          {
            val = vtkApplyLogScale(*input, range, logRange);
            cptr = vtkLinearLookup(val, table, maxIndex, shift, scale);
            if (hasEnabledArray && !self->GetEnabledArray()->GetTuple1(length - i - 1))
            {
              self->DisableColor(cptr[0], cptr[1], cptr[2], &r, &g, &b);
              *output++ = r;
              *output++ = g;
              *output++ = b;
            }
            else
            {
              *output++ = *cptr++;
              *output++ = *cptr++;
              *output++ = *cptr++;
            }
            input += inIncr;
          }
        }
        else if (outFormat == VTK_LUMINANCE_ALPHA)
        {
          while (--i >= 0)
          {
            val = vtkApplyLogScale(*input, range, logRange);
            cptr = vtkLinearLookup(val, table, maxIndex, shift, scale);
            *output++ =
              static_cast<unsigned char>(cptr[0] * 0.30 + cptr[1] * 0.59 + cptr[2] * 0.11 + 0.5);
            *output++ = static_cast<unsigned char>(alpha * cptr[3]);
            input += inIncr;
          }
        }
        else // outFormat == VTK_LUMINANCE
        {
          while (--i >= 0)
          {
            val = vtkApplyLogScale(*input, range, logRange);
            cptr = vtkLinearLookup(val, table, maxIndex, shift, scale);
            *output++ =
              static_cast<unsigned char>(cptr[0] * 0.30 + cptr[1] * 0.59 + cptr[2] * 0.11 + 0.5);
            input += inIncr;
          }
        }
      } // log scale with blending

      else // no log scale with blending
      {
        shift = -range[0];
        if (range[1] <= range[0])
        {
          scale = VTK_DOUBLE_MAX;
        }
        else
        {
          /* while this looks like the wrong scale, it is the correct scale
           * taking into account the truncation to int that happens below. */
          scale = (maxIndex + 1) / (range[1] - range[0]);
        }

        if (outFormat == VTK_RGBA)
        {
          while (--i >= 0)
          {
            cptr = vtkLinearLookup(*input, table, maxIndex, shift, scale);
            if (hasEnabledArray && !self->GetEnabledArray()->GetTuple1(length - i - 1))
            {
              self->DisableColor(cptr[0], cptr[1], cptr[2], &r, &g, &b);
              *output++ = r;
              *output++ = g;
              *output++ = b;
              cptr += 3;
              *output++ = static_cast<unsigned char>((*cptr) * alpha * 0.2);
              cptr++;
            }
            else
            {
              *output++ = *cptr++;
              *output++ = *cptr++;
              *output++ = *cptr++;
              if (hasEnabledArray)
              {
                *output++ = *cptr++;
              }
              else
              {
                *output++ = static_cast<unsigned char>((*cptr) * alpha);
                cptr++;
              }
            }
            input += inIncr;
          }
        }
        else if (outFormat == VTK_RGB)
        {
          while (--i >= 0)
          {
            cptr = vtkLinearLookup(*input, table, maxIndex, shift, scale);
            if (hasEnabledArray && !self->GetEnabledArray()->GetTuple1(length - i - 1))
            {
              self->DisableColor(cptr[0], cptr[1], cptr[2], &r, &g, &b);
              *output++ = r;
              *output++ = g;
              *output++ = b;
            }
            else
            {
              *output++ = *cptr++;
              *output++ = *cptr++;
              *output++ = *cptr++;
            }
            input += inIncr;
          }
        }
        else if (outFormat == VTK_LUMINANCE_ALPHA)
        {
          while (--i >= 0)
          {
            cptr = vtkLinearLookup(*input, table, maxIndex, shift, scale);
            *output++ =
              static_cast<unsigned char>(cptr[0] * 0.30 + cptr[1] * 0.59 + cptr[2] * 0.11 + 0.5);
            *output++ = static_cast<unsigned char>(cptr[3] * alpha);
            input += inIncr;
          }
        }
        else // outFormat == VTK_LUMINANCE
        {
          while (--i >= 0)
          {
            cptr = vtkLinearLookup(*input, table, maxIndex, shift, scale);
            *output++ =
              static_cast<unsigned char>(cptr[0] * 0.30 + cptr[1] * 0.59 + cptr[2] * 0.11 + 0.5);
            input += inIncr;
          }
        }
      } // no log scale
    } // alpha blending
  }
};

//------------------------------------------------------------------------------
void vtkLookupTableWithEnabling::MapScalarsThroughTable(vtkAbstractArray* input,
  unsigned char* outPtr, int numberOfTuples, int numberOfComponents, int vectorComponent,
  int outputFormat)
{
  vtkLookupTableWithEnablingMapData worker;
  using Arrays = vtkTypeList::Append<vtkArrayDispatch::AllArrays, vtkBitArray>::Result;
  if (!vtkArrayDispatch::DispatchByArray<Arrays>::Execute(input, worker, this, outPtr,
        numberOfTuples, numberOfComponents, vectorComponent, outputFormat))
  {
    if (auto da = vtkDataArray::SafeDownCast(input))
    {
      switch (da->GetDataType())
      {
        vtkTemplateMacro((worker.template operator()<vtkDataArray, VTK_TT>(
          da, this, outPtr, numberOfTuples, numberOfComponents, vectorComponent, outputFormat)));
      }
    }
    else
    {
      vtkErrorMacro(<< "MapScalarsThroughTable: Unknown input ScalarType "
                    << input->GetDataTypeAsString());
    }
  }
}

//------------------------------------------------------------------------------
void vtkLookupTableWithEnabling::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  os << indent << "EnabledArray: ";
  if (this->EnabledArray)
  {
    this->EnabledArray->PrintSelf(os << "\n", indent.GetNextIndent());
  }
  else
  {
    // Should not happen
    os << "(none)\n";
  }
}
VTK_ABI_NAMESPACE_END
