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

#include "vtk_glad.h"

#include "vtkActor2D.h"
#include "vtkDataArray.h"
#include "vtkImageData.h"
#include "vtkObjectFactory.h"
#include "vtkViewport.h"
#
#include "vtkWindow.h"

#include "vtkCellArray.h"
#include "vtkFloatArray.h"
#include "vtkOpenGLRenderWindow.h"
#include "vtkOpenGLState.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper2D.h"
#include "vtkProperty2D.h"
#include "vtkTexture.h"
#include "vtkTexturedActor2D.h"
#include "vtkTrivialProducer.h"
#include "vtkUnsignedCharArray.h"

#include "vtkNew.h"

#include "vtkOpenGLError.h"

#include <cmath>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkOpenGLImageMapper);

vtkOpenGLImageMapper::vtkOpenGLImageMapper()
{
  this->Actor = vtkTexturedActor2D::New();
  vtkNew<vtkPolyDataMapper2D> mapper;
  vtkNew<vtkPolyData> polydata;
  vtkNew<vtkPoints> points;
  points->SetNumberOfPoints(4);
  polydata->SetPoints(points);

  vtkNew<vtkCellArray> tris;
  tris->InsertNextCell(3);
  tris->InsertCellPoint(0);
  tris->InsertCellPoint(1);
  tris->InsertCellPoint(2);
  tris->InsertNextCell(3);
  tris->InsertCellPoint(0);
  tris->InsertCellPoint(2);
  tris->InsertCellPoint(3);
  polydata->SetPolys(tris);

  vtkNew<vtkTrivialProducer> prod;
  prod->SetOutput(polydata);

  // Set some properties.
  mapper->SetInputConnection(prod->GetOutputPort());
  this->Actor->SetMapper(mapper);

  vtkNew<vtkTexture> texture;
  texture->RepeatOff();
  this->Actor->SetTexture(texture);

  vtkNew<vtkFloatArray> tcoords;
  tcoords->SetNumberOfComponents(2);
  tcoords->SetNumberOfTuples(4);
  polydata->GetPointData()->SetTCoords(tcoords);
}

vtkOpenGLImageMapper::~vtkOpenGLImageMapper()
{
  this->Actor->UnRegister(this);
}

//------------------------------------------------------------------------------
// Release the graphics resources used by this texture.
void vtkOpenGLImageMapper::ReleaseGraphicsResources(vtkWindow* renWin)
{
  this->Actor->ReleaseGraphicsResources(renWin);
}

//------------------------------------------------------------------------------
// I know #define can be evil, but this macro absolutely ensures
// that the code will be inlined.  The macro expects 'val' to
// be predefined to the same type as y

#define vtkClampToUnsignedChar(x, y)                                                               \
  do                                                                                               \
  {                                                                                                \
    val = (y);                                                                                     \
    if (val < 0)                                                                                   \
    {                                                                                              \
      val = 0;                                                                                     \
    }                                                                                              \
    if (val > 255)                                                                                 \
    {                                                                                              \
      val = 255;                                                                                   \
    }                                                                                              \
    (x) = static_cast<unsigned char>(val);                                                         \
  } while (false)
/* should do proper rounding, as follows:
 *
 * XXX(ben.boeckel): This is not proper rounding. This will round the value
 * just less than 0.5 to 1.0 due to IEEE floating point rounding rules. *That*
 * is the number to add.
  (x) = (unsigned char)(val + 0.5f); \
*/

// the bit-shift must be done after the comparison to zero
// because bit-shift is undefined behaviour for negative numbers
#define vtkClampIntToUnsignedChar(x, y, shift)                                                     \
  do                                                                                               \
  {                                                                                                \
    val = (y);                                                                                     \
    if (val < 0)                                                                                   \
    {                                                                                              \
      val = 0;                                                                                     \
    }                                                                                              \
    val >>= (shift);                                                                               \
    if (val > 255)                                                                                 \
    {                                                                                              \
      val = 255;                                                                                   \
    }                                                                                              \
    (x) = static_cast<unsigned char>(val);                                                         \
  } while (false)

// pad an integer to a multiply of four, for OpenGL
inline int vtkPadToFour(int n)
{
  return ((n + 3) / 4) * 4;
}

//---------------------------------------------------------------
// render the image by doing the following:
// 1) apply shift and scale to pixel values
// 2) clamp to [0,255] and convert to unsigned char
// 3) draw using DrawPixels

template <class T>
void vtkOpenGLImageMapperRenderDouble(vtkOpenGLImageMapper* self, vtkImageData* data, T* dataPtr,
  double shift, double scale, vtkViewport* viewport)
{
  vtkOpenGLClearErrorMacro();

  int inMin0 = self->DisplayExtent[0];
  int inMax0 = self->DisplayExtent[1];
  int inMin1 = self->DisplayExtent[2];
  int inMax1 = self->DisplayExtent[3];

  int width = inMax0 - inMin0 + 1;
  int height = inMax1 - inMin1 + 1;

  vtkIdType tempIncs[3];
  data->GetIncrements(tempIncs);
  vtkIdType inInc1 = tempIncs[1];

  int bpp = data->GetNumberOfScalarComponents();
  double range[2];
  data->GetPointData()->GetScalars()->GetDataTypeRange(range);

  auto ostate = static_cast<vtkOpenGLRenderWindow*>(viewport->GetVTKWindow())->GetState();
  ostate->vtkglPixelStorei(GL_UNPACK_ALIGNMENT, 1);

  // reformat data into unsigned char

  T* inPtr = dataPtr;
  T* inPtr1 = inPtr;

  int i;
  int j = height;

  unsigned char* newPtr;
  if (bpp < 4)
  {
    newPtr = new unsigned char[vtkPadToFour(3 * width * height)];
  }
  else
  {
    newPtr = new unsigned char[4 * width * height];
  }

  unsigned char* ptr = newPtr;
  double val;
  unsigned char tmp;

  while (--j >= 0)
  {
    inPtr = inPtr1;
    i = width;
    switch (bpp)
    {
      case 1:
        while (--i >= 0)
        {
          vtkClampToUnsignedChar(tmp, ((*inPtr++ + shift) * scale));
          *ptr++ = tmp;
          *ptr++ = tmp;
          *ptr++ = tmp;
        }
        break;

      case 2:
        while (--i >= 0)
        {
          vtkClampToUnsignedChar(tmp, ((*inPtr++ + shift) * scale));
          *ptr++ = tmp;
          vtkClampToUnsignedChar(*ptr++, ((*inPtr++ + shift) * scale));
          *ptr++ = tmp;
        }
        break;

      case 3:
        while (--i >= 0)
        {
          vtkClampToUnsignedChar(*ptr++, ((*inPtr++ + shift) * scale));
          vtkClampToUnsignedChar(*ptr++, ((*inPtr++ + shift) * scale));
          vtkClampToUnsignedChar(*ptr++, ((*inPtr++ + shift) * scale));
        }
        break;

      default:
        while (--i >= 0)
        {
          vtkClampToUnsignedChar(*ptr++, ((*inPtr++ + shift) * scale));
          vtkClampToUnsignedChar(*ptr++, ((*inPtr++ + shift) * scale));
          vtkClampToUnsignedChar(*ptr++, ((*inPtr++ + shift) * scale));
          vtkClampToUnsignedChar(*ptr++, ((*inPtr++ + shift) * scale));
          inPtr += bpp - 4;
        }
        break;
    }
    inPtr1 += inInc1;
  }

  self->DrawPixels(viewport, width, height, ((bpp < 4) ? 3 : 4), static_cast<void*>(newPtr));

  delete[] newPtr;

  vtkOpenGLStaticCheckErrorMacro("failed after ImageMapperRenderDouble");
}

//---------------------------------------------------------------
// Same as above, but uses fixed-point math for shift and scale.
// The number of bits used for the fraction is determined from the
// scale.  Enough bits are always left over for the integer that
// overflow cannot occur.

template <class T>
void vtkOpenGLImageMapperRenderShort(vtkOpenGLImageMapper* self, vtkImageData* data, T* dataPtr,
  double shift, double scale, vtkViewport* viewport)
{
  vtkOpenGLClearErrorMacro();

  int inMin0 = self->DisplayExtent[0];
  int inMax0 = self->DisplayExtent[1];
  int inMin1 = self->DisplayExtent[2];
  int inMax1 = self->DisplayExtent[3];

  int width = inMax0 - inMin0 + 1;
  int height = inMax1 - inMin1 + 1;

  vtkIdType tempIncs[3];
  data->GetIncrements(tempIncs);
  vtkIdType inInc1 = tempIncs[1];

  int bpp = data->GetNumberOfScalarComponents();

  double range[2];
  data->GetPointData()->GetScalars()->GetDataTypeRange(range);

  auto ostate = static_cast<vtkOpenGLRenderWindow*>(viewport->GetVTKWindow())->GetState();
  ostate->vtkglPixelStorei(GL_UNPACK_ALIGNMENT, 1);

  // Find the number of bits to use for the fraction:
  // continue increasing the bits until there is an overflow
  // in the worst case, then decrease by 1.
  // The "*2.0" and "*1.0" ensure that the comparison is done
  // with double-precision math.
  int bitShift = 0;
  double absScale = std::abs(scale);

  while ((static_cast<long>(1 << bitShift) * absScale) * 2.0 * USHRT_MAX < INT_MAX * 1.0)
  {
    bitShift++;
  }
  bitShift--;
  bitShift = std::max(bitShift, 0);

  long sscale = static_cast<long>(scale * (1 << bitShift));
  long sshift = static_cast<long>(sscale * shift);
  /* should do proper rounding, as follows:
  long sscale = (long) floor(scale*(1 << bitShift) + 0.5);
  long sshift = (long) floor((scale*shift + 0.5)*(1 << bitShift));
  */
  long val;
  unsigned char tmp;

  T* inPtr = dataPtr;
  T* inPtr1 = inPtr;

  int i;
  int j = height;

  unsigned char* newPtr;
  if (bpp < 4)
  {
    newPtr = new unsigned char[vtkPadToFour(3 * width * height)];
  }
  else
  {
    newPtr = new unsigned char[4 * width * height];
  }

  unsigned char* ptr = newPtr;

  while (--j >= 0)
  {
    inPtr = inPtr1;
    i = width;

    switch (bpp)
    {
      case 1:
        while (--i >= 0)
        {
          vtkClampIntToUnsignedChar(tmp, (*inPtr++ * sscale + sshift), bitShift);
          *ptr++ = tmp;
          *ptr++ = tmp;
          *ptr++ = tmp;
        }
        break;

      case 2:
        while (--i >= 0)
        {
          vtkClampIntToUnsignedChar(tmp, (*inPtr++ * sscale + sshift), bitShift);
          *ptr++ = tmp;
          vtkClampIntToUnsignedChar(*ptr++, (*inPtr++ * sscale + sshift), bitShift);
          *ptr++ = tmp;
        }
        break;

      case 3:
        while (--i >= 0)
        {
          vtkClampIntToUnsignedChar(*ptr++, (*inPtr++ * sscale + sshift), bitShift);
          vtkClampIntToUnsignedChar(*ptr++, (*inPtr++ * sscale + sshift), bitShift);
          vtkClampIntToUnsignedChar(*ptr++, (*inPtr++ * sscale + sshift), bitShift);
        }
        break;

      default:
        while (--i >= 0)
        {
          vtkClampIntToUnsignedChar(*ptr++, (*inPtr++ * sscale + sshift), bitShift);
          vtkClampIntToUnsignedChar(*ptr++, (*inPtr++ * sscale + sshift), bitShift);
          vtkClampIntToUnsignedChar(*ptr++, (*inPtr++ * sscale + sshift), bitShift);
          vtkClampIntToUnsignedChar(*ptr++, (*inPtr++ * sscale + sshift), bitShift);
          inPtr += bpp - 4;
        }
        break;
    }
    inPtr1 += inInc1;
  }

  self->DrawPixels(viewport, width, height, ((bpp < 4) ? 3 : 4), static_cast<void*>(newPtr));

  delete[] newPtr;

  vtkOpenGLStaticCheckErrorMacro("failed after ImageMapperRenderShort");
}

//---------------------------------------------------------------
// render unsigned char data without any shift/scale

template <class T>
void vtkOpenGLImageMapperRenderChar(
  vtkOpenGLImageMapper* self, vtkImageData* data, T* dataPtr, vtkViewport* viewport)
{
  vtkOpenGLClearErrorMacro();

  int inMin0 = self->DisplayExtent[0];
  int inMax0 = self->DisplayExtent[1];
  int inMin1 = self->DisplayExtent[2];
  int inMax1 = self->DisplayExtent[3];

  int width = inMax0 - inMin0 + 1;
  int height = inMax1 - inMin1 + 1;

  vtkIdType tempIncs[3];
  data->GetIncrements(tempIncs);
  vtkIdType inInc1 = tempIncs[1];

  int bpp = data->GetPointData()->GetScalars()->GetNumberOfComponents();

  double range[2];
  data->GetPointData()->GetScalars()->GetDataTypeRange(range);

  auto ostate = static_cast<vtkOpenGLRenderWindow*>(viewport->GetVTKWindow())->GetState();
  ostate->vtkglPixelStorei(GL_UNPACK_ALIGNMENT, 1);

  //
  if (bpp == 3)
  { // feed through RGB bytes without reformatting
    if (inInc1 != width * bpp)
    {
      ostate->vtkglPixelStorei(GL_UNPACK_ROW_LENGTH, inInc1 / bpp);
    }
    self->DrawPixels(viewport, width, height, 3, static_cast<void*>(dataPtr));
  }
  else if (bpp == 4)
  { // feed through RGBA bytes without reformatting
    if (inInc1 != width * bpp)
    {
      ostate->vtkglPixelStorei(GL_UNPACK_ROW_LENGTH, inInc1 / bpp);
    }
    self->DrawPixels(viewport, width, height, 4, static_cast<void*>(dataPtr));
  }
  else
  { // feed through other bytes without reformatting
    T* inPtr = dataPtr;
    T* inPtr1 = inPtr;
    unsigned char tmp;

    int i;
    int j = height;

    unsigned char* newPtr;
    int nC = 4;
    if (bpp == 1 || bpp == 3)
    {
      nC = 3;
    }
    if (nC == 3)
    {
      newPtr = new unsigned char[vtkPadToFour(nC * width * height)];
    }
    else
    {
      newPtr = new unsigned char[nC * width * height];
    }

    unsigned char* ptr = newPtr;

    while (--j >= 0)
    {
      inPtr = inPtr1;
      i = width;

      switch (bpp)
      {
        case 1:
          while (--i >= 0)
          {
            *ptr++ = tmp = *inPtr++;
            *ptr++ = tmp;
            *ptr++ = tmp;
          }
          break;

        case 2:
          while (--i >= 0)
          {
            *ptr++ = tmp = *inPtr++;
            *ptr++ = tmp;
            *ptr++ = tmp;
            *ptr++ = *inPtr++;
          }
          break;

        case 3:
          while (--i >= 0)
          {
            *ptr++ = *inPtr++;
            *ptr++ = *inPtr++;
            *ptr++ = *inPtr++;
          }
          break;

        default:
          while (--i >= 0)
          {
            *ptr++ = *inPtr++;
            *ptr++ = *inPtr++;
            *ptr++ = *inPtr++;
            *ptr++ = *inPtr++;
            inPtr += bpp - 4;
          }
          break;
      }
      inPtr1 += inInc1;
    }

    self->DrawPixels(viewport, width, height, nC, static_cast<void*>(newPtr));

    delete[] newPtr;
  }

  ostate->vtkglPixelStorei(GL_UNPACK_ROW_LENGTH, 0);

  vtkOpenGLStaticCheckErrorMacro("failed after ImageMapperRenderChar");
}

//------------------------------------------------------------------------------
// Define overloads to help the template macro below dispatch to a
// suitable implementation for each type.  The last argument is of
// type "long" for the template and of type "int" for the
// non-templates.  The template macro's call to this function always
// passes a literal "1" as the last argument, which requires a
// conversion to produce a long.  This helps broken compilers select
// the non-template even when the template is otherwise an equal
// match.
template <class T>
void vtkOpenGLImageMapperRender(vtkOpenGLImageMapper* self, vtkImageData* data, T* dataPtr,
  double shift, double scale, vtkViewport* viewport)
{
  vtkOpenGLImageMapperRenderDouble(self, data, dataPtr, shift, scale, viewport);
}

static void vtkOpenGLImageMapperRender(vtkOpenGLImageMapper* self, vtkImageData* data,
  char* dataPtr, double shift, double scale, vtkViewport* viewport)
{
  if (shift == 0.0 && scale == 1.0)
  {
    vtkOpenGLImageMapperRenderChar(self, data, dataPtr, viewport);
  }
  else
  {
    vtkOpenGLImageMapperRenderShort(self, data, dataPtr, shift, scale, viewport);
  }
}

static void vtkOpenGLImageMapperRender(vtkOpenGLImageMapper* self, vtkImageData* data,
  unsigned char* dataPtr, double shift, double scale, vtkViewport* viewport)
{
  if (shift == 0.0 && scale == 1.0)
  {
    vtkOpenGLImageMapperRenderChar(self, data, dataPtr, viewport);
  }
  else
  {
    vtkOpenGLImageMapperRenderShort(self, data, dataPtr, shift, scale, viewport);
  }
}

static void vtkOpenGLImageMapperRender(vtkOpenGLImageMapper* self, vtkImageData* data,
  signed char* dataPtr, double shift, double scale, vtkViewport* viewport)
{
  if (shift == 0.0 && scale == 1.0)
  {
    vtkOpenGLImageMapperRenderChar(self, data, dataPtr, viewport);
  }
  else
  {
    vtkOpenGLImageMapperRenderShort(self, data, dataPtr, shift, scale, viewport);
  }
}

static void vtkOpenGLImageMapperRender(vtkOpenGLImageMapper* self, vtkImageData* data,
  short* dataPtr, double shift, double scale, vtkViewport* viewport)
{
  vtkOpenGLImageMapperRenderShort(self, data, dataPtr, shift, scale, viewport);
}

static void vtkOpenGLImageMapperRender(vtkOpenGLImageMapper* self, vtkImageData* data,
  unsigned short* dataPtr, double shift, double scale, vtkViewport* viewport)
{
  vtkOpenGLImageMapperRenderShort(self, data, dataPtr, shift, scale, viewport);
}

//------------------------------------------------------------------------------
// Expects data to be X, Y, components

void vtkOpenGLImageMapper::RenderData(vtkViewport* viewport, vtkImageData* data, vtkActor2D* actor)
{
  void* ptr0;
  double shift, scale;

  vtkWindow* window = viewport->GetVTKWindow();
  if (!window)
  {
    vtkErrorMacro(<< "vtkOpenGLImageMapper::RenderData - no window set for viewport");
    return;
  }

  if (!data->GetPointData()->GetScalars())
  {
    return;
  }

  this->Actor->SetProperty(actor->GetProperty());

  // Make this window current. May have become not current due to
  // data updates since the render started.
  window->MakeCurrent();

  vtkOpenGLClearErrorMacro();

  shift = this->GetColorShift();
  scale = this->GetColorScale();

  ptr0 =
    data->GetScalarPointer(this->DisplayExtent[0], this->DisplayExtent[2], this->DisplayExtent[4]);

  // Get the position of the image actor
  int* actorPos = actor->GetActualPositionCoordinate()->GetComputedViewportValue(viewport);
  // negative positions will already be clipped to viewport
  actorPos[0] += this->PositionAdjustment[0];
  actorPos[1] += this->PositionAdjustment[1];

  this->Actor->SetPosition(actorPos[0], actorPos[1]);
  this->Actor->SetPosition2(actor->GetPosition2());

  switch (data->GetPointData()->GetScalars()->GetDataType())
  {
    vtkTemplateMacro(
      vtkOpenGLImageMapperRender(this, data, static_cast<VTK_TT*>(ptr0), shift, scale, viewport));
    default:
      vtkErrorMacro(<< "Unsupported image type: " << data->GetScalarType());
  }

  vtkOpenGLCheckErrorMacro("failed after RenderData");
}

void vtkOpenGLImageMapper::DrawPixels(
  vtkViewport* viewport, int width, int height, int numComponents, void* data)
{
  int* actorPos = this->Actor->GetActualPositionCoordinate()->GetComputedViewportValue(viewport);
  int* actorPos2 = this->Actor->GetActualPosition2Coordinate()->GetComputedViewportValue(viewport);

  float xscale = 1.0;
  float yscale = 1.0;
  if (this->GetRenderToRectangle())
  {
    int rectwidth = (actorPos2[0] - actorPos[0]) + 1;
    int rectheight = (actorPos2[1] - actorPos[1]) + 1;
    xscale = static_cast<float>(rectwidth) / width;
    yscale = static_cast<float>(rectheight) / height;
  }

  vtkPolyData* pd = vtkPolyDataMapper2D::SafeDownCast(this->Actor->GetMapper())->GetInput();
  vtkPoints* points = pd->GetPoints();
  points->SetPoint(0, 0.0, 0.0, 0);
  points->SetPoint(1, width * xscale, 0.0, 0);
  points->SetPoint(2, width * xscale, height * yscale, 0);
  points->SetPoint(3, 0.0, height * yscale, 0);
  points->GetData()->Modified();

  vtkDataArray* tcoords = pd->GetPointData()->GetTCoords();
  float tmp[2];
  tmp[0] = 0;
  tmp[1] = 0;
  tcoords->SetTuple(0, tmp);
  tmp[0] = 1.0;
  tcoords->SetTuple(1, tmp);
  tmp[1] = 1.0;
  tcoords->SetTuple(2, tmp);
  tmp[0] = 0.0;
  tcoords->SetTuple(3, tmp);
  tcoords->Modified();

  vtkImageData* id = vtkImageData::New();
  id->SetExtent(0, width - 1, 0, height - 1, 0, 0);
  vtkUnsignedCharArray* uca = vtkUnsignedCharArray::New();
  uca->SetNumberOfComponents(numComponents);
  uca->SetArray((unsigned char*)data, width * height * numComponents, true);
  id->GetPointData()->SetScalars(uca);
  uca->Delete();

  this->Actor->GetTexture()->SetInputData(id);

  this->Actor->RenderOverlay(viewport);
  id->Delete();
}

void vtkOpenGLImageMapper::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
}
VTK_ABI_NAMESPACE_END
