// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkLookupTableWithEnabling.h"
#include "vtkBitArray.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkVariant.h"
#include <cassert>

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(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, double range[2], 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 unsigned char* vtkLinearLookup(
  double v, 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
template <class T>
void vtkLookupTableWithEnablingMapData(vtkLookupTableWithEnabling* self, T* input,
  unsigned char* output, int length, int inIncr, int outFormat)
{
  int i = length;
  double* range = self->GetTableRange();
  double maxIndex = self->GetNumberOfColors() - 1;
  double shift, scale;
  unsigned char* table = self->GetPointer(0);
  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::MapScalarsThroughTable2(void* input, unsigned char* output,
  int inputDataType, int numberOfValues, int inputIncrement, int outputFormat)
{
  switch (inputDataType)
  {
    case VTK_BIT:
    {
      vtkIdType i, id;
      vtkBitArray* bitArray = vtkBitArray::New();
      bitArray->SetVoidArray(input, numberOfValues, 1);
      vtkUnsignedCharArray* newInput = vtkUnsignedCharArray::New();
      newInput->SetNumberOfValues(numberOfValues);
      for (id = i = 0; i < numberOfValues; i++, id += inputIncrement)
      {
        newInput->SetValue(i, bitArray->GetValue(id));
      }
      vtkLookupTableWithEnablingMapData(this, static_cast<unsigned char*>(newInput->GetPointer(0)),
        output, numberOfValues, inputIncrement, outputFormat);
      newInput->Delete();
      bitArray->Delete();
    }
    break;

      vtkTemplateMacro(vtkLookupTableWithEnablingMapData(
        this, static_cast<VTK_TT*>(input), output, numberOfValues, inputIncrement, outputFormat));
    default:
      vtkErrorMacro(<< "MapImageThroughTable: Unknown input ScalarType");
      return;
  }
}

//------------------------------------------------------------------------------
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
