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

#include "vtkAlgorithmOutput.h"
#include "vtkImageActor.h"
#include "vtkImageData.h"
#include "vtkImageMapToWindowLevelColors.h"
#include "vtkImageMapper3D.h"
#include "vtkObjectFactory.h"
#include "vtkPropCollection.h"
#include "vtkTextMapper.h"
#include "vtkTextProperty.h"
#include "vtkViewport.h"
#include "vtkWindow.h"

#include <cmath>

//------------------------------------------------------------------------------
VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkCornerAnnotation);

vtkCxxSetObjectMacro(vtkCornerAnnotation, ImageActor, vtkImageActor);
vtkCxxSetObjectMacro(vtkCornerAnnotation, WindowLevel, vtkImageMapToWindowLevelColors);
vtkCxxSetObjectMacro(vtkCornerAnnotation, TextProperty, vtkTextProperty);

//------------------------------------------------------------------------------
vtkCornerAnnotation::vtkCornerAnnotation()
{
  this->PositionCoordinate->SetCoordinateSystemToNormalizedViewport();
  this->PositionCoordinate->SetValue(0.2, 0.85);

  this->LastSize[0] = 0;
  this->LastSize[1] = 0;

  this->MaximumLineHeight = 1.0;
  this->MinimumFontSize = 6;
  this->MaximumFontSize = 200;
  this->LinearFontScaleFactor = 5.0;
  this->NonlinearFontScaleFactor = 0.35;
  this->FontSize = 15;

  this->TextProperty = vtkTextProperty::New();
  this->TextProperty->ShadowOff();

  for (int i = 0; i < NumTextPositions; i++)
  {
    this->CornerText[i] = nullptr;
    this->TextMapper[i] = vtkTextMapper::New();
    this->TextActor[i] = vtkActor2D::New();
    this->TextActor[i]->SetMapper(this->TextMapper[i]);
  }

  this->ImageActor = nullptr;
  this->LastImageActor = nullptr;
  this->WindowLevel = nullptr;

  this->LevelShift = 0;
  this->LevelScale = 1;

  this->ShowSliceAndImage = 1;
}

//------------------------------------------------------------------------------
vtkCornerAnnotation::~vtkCornerAnnotation()
{
  this->SetTextProperty(nullptr);

  for (int i = 0; i < NumTextPositions; i++)
  {
    delete[] this->CornerText[i];
    this->TextMapper[i]->Delete();
    this->TextActor[i]->Delete();
  }

  this->SetWindowLevel(nullptr);
  this->SetImageActor(nullptr);
}

//------------------------------------------------------------------------------
// Release any graphics resources that are being consumed by this actor.
// The parameter window could be used to determine which graphic
// resources to release.
void vtkCornerAnnotation::ReleaseGraphicsResources(vtkWindow* win)
{
  this->Superclass::ReleaseGraphicsResources(win);
  for (int i = 0; i < NumTextPositions; i++)
  {
    this->TextActor[i]->ReleaseGraphicsResources(win);
  }
}

//------------------------------------------------------------------------------
void vtkCornerAnnotation::TextReplace(vtkImageActor* ia, vtkImageMapToWindowLevelColors* wl)
{
  int i;
  char *text, *text2;
  int slice = 0, slice_max = 0;
  char *rpos, *tmp;
  double window = 0, level = 0;
  long int windowi = 0, leveli = 0;
  vtkImageData *wl_input = nullptr, *ia_input = nullptr;
  int input_type_is_float = 0;

  if (wl)
  {
    window = wl->GetWindow();
    window *= this->LevelScale;
    level = wl->GetLevel();
    level = level * this->LevelScale + this->LevelShift;
    windowi = (long int)window;
    leveli = (long int)level;
    wl_input = vtkImageData::SafeDownCast(wl->GetInput());
    if (wl_input)
    {
      input_type_is_float =
        (wl_input->GetScalarType() == VTK_FLOAT || wl_input->GetScalarType() == VTK_DOUBLE);
    }
  }
  if (ia)
  {
    slice = ia->GetSliceNumber() - ia->GetSliceNumberMin() + 1;
    slice_max = ia->GetSliceNumberMax() - ia->GetSliceNumberMin() + 1;
    ia_input = ia->GetInput();
    if (!wl_input && ia_input)
    {
      input_type_is_float =
        (ia_input->GetScalarType() == VTK_FLOAT || ia_input->GetScalarType() == VTK_DOUBLE);
    }
  }

  // search for tokens, replace and then assign to TextMappers
  for (i = 0; i < NumTextPositions; i++)
  {
    if (this->CornerText[i] && strlen(this->CornerText[i]))
    {
      size_t textSize = strlen(this->CornerText[i]) + 1000;
      text = new char[textSize];
      text2 = new char[textSize];
      strcpy(text, this->CornerText[i]);

      // now do the replacements

      rpos = strstr(text, "<image>");
      while (rpos)
      {
        *rpos = '\0';
        if (ia && this->ShowSliceAndImage)
        {
          snprintf(text2, textSize, "%sImage: %i%s", text, slice, rpos + 7);
        }
        else
        {
          snprintf(text2, textSize, "%s%s", text, rpos + 7);
        }
        tmp = text;
        text = text2;
        text2 = tmp;
        rpos = strstr(text, "<image>");
      }

      rpos = strstr(text, "<image_and_max>");
      while (rpos)
      {
        *rpos = '\0';
        if (ia && this->ShowSliceAndImage)
        {
          snprintf(text2, textSize, "%sImage: %i / %i%s", text, slice, slice_max, rpos + 15);
        }
        else
        {
          snprintf(text2, textSize, "%s%s", text, rpos + 15);
        }
        tmp = text;
        text = text2;
        text2 = tmp;
        rpos = strstr(text, "<image_and_max>");
      }

      rpos = strstr(text, "<slice>");
      while (rpos)
      {
        *rpos = '\0';
        if (ia && this->ShowSliceAndImage)
        {
          snprintf(text2, textSize, "%sSlice: %i%s", text, slice, rpos + 7);
        }
        else
        {
          snprintf(text2, textSize, "%s%s", text, rpos + 7);
        }
        tmp = text;
        text = text2;
        text2 = tmp;
        rpos = strstr(text, "<slice>");
      }

      rpos = strstr(text, "<slice_and_max>");
      while (rpos)
      {
        *rpos = '\0';
        if (ia && this->ShowSliceAndImage)
        {
          snprintf(text2, textSize, "%sSlice: %i / %i%s", text, slice, slice_max, rpos + 15);
        }
        else
        {
          snprintf(text2, textSize, "%s%s", text, rpos + 15);
        }
        tmp = text;
        text = text2;
        text2 = tmp;
        rpos = strstr(text, "<slice_and_max>");
      }

      rpos = strstr(text, "<slice_pos>");
      while (rpos)
      {
        *rpos = '\0';
        if (ia && this->ShowSliceAndImage)
        {
          double* dbounds = ia->GetDisplayBounds();
          int* dext = ia->GetDisplayExtent();
          double pos;
          if (dext[0] == dext[1])
          {
            pos = dbounds[0];
          }
          else if (dext[2] == dext[3])
          {
            pos = dbounds[2];
          }
          else
          {
            pos = dbounds[4];
          }
          snprintf(text2, textSize, "%s%g%s", text, pos, rpos + 11);
        }
        else
        {
          snprintf(text2, textSize, "%s%s", text, rpos + 11);
        }
        tmp = text;
        text = text2;
        text2 = tmp;
        rpos = strstr(text, "<slice_pos>");
      }

      rpos = strstr(text, "<window>");
      while (rpos)
      {
        *rpos = '\0';
        if (wl)
        {
          if (input_type_is_float)
          {
            snprintf(text2, textSize, "%sWindow: %g%s", text, window, rpos + 8);
          }
          else
          {
            snprintf(text2, textSize, "%sWindow: %li%s", text, windowi, rpos + 8);
          }
        }
        else
        {
          snprintf(text2, textSize, "%s%s", text, rpos + 8);
        }
        tmp = text;
        text = text2;
        text2 = tmp;
        rpos = strstr(text, "<window>");
      }

      rpos = strstr(text, "<level>");
      while (rpos)
      {
        *rpos = '\0';
        if (wl)
        {
          if (input_type_is_float)
          {
            snprintf(text2, textSize, "%sLevel: %g%s", text, level, rpos + 7);
          }
          else
          {
            snprintf(text2, textSize, "%sLevel: %li%s", text, leveli, rpos + 7);
          }
        }
        else
        {
          snprintf(text2, textSize, "%s%s", text, rpos + 7);
        }
        tmp = text;
        text = text2;
        text2 = tmp;
        rpos = strstr(text, "<level>");
      }

      rpos = strstr(text, "<window_level>");
      while (rpos)
      {
        *rpos = '\0';
        if (wl)
        {
          if (input_type_is_float)
          {
            snprintf(text2, textSize, "%sWW/WL: %g / %g%s", text, window, level, rpos + 14);
          }
          else
          {
            snprintf(text2, textSize, "%sWW/WL: %li / %li%s", text, windowi, leveli, rpos + 14);
          }
        }
        else
        {
          snprintf(text2, textSize, "%s%s", text, rpos + 14);
        }
        tmp = text;
        text = text2;
        text2 = tmp;
        rpos = strstr(text, "<window_level>");
      }

      this->TextMapper[i]->SetInput(text);
      delete[] text;
      delete[] text2;
    }
    else
    {
      this->TextMapper[i]->SetInput("");
    }
  }
}

//------------------------------------------------------------------------------
int vtkCornerAnnotation::RenderOverlay(vtkViewport* viewport)
{
  // Everything is built, just have to render
  // only render if font is at least minimum font
  if (this->FontSize >= this->MinimumFontSize)
  {
    for (int i = 0; i < NumTextPositions; i++)
    {
      this->TextActor[i]->RenderOverlay(viewport);
    }
  }
  return 1;
}

namespace
{
// Ported from old vtkTextMapper implementation
int GetNumberOfLines(const char* str)
{
  if (str == nullptr || *str == '\0')
  {
    return 0;
  }

  int result = 1;
  while (str != nullptr)
  {
    if ((str = strstr(str, "\n")) != nullptr)
    {
      result++;
      str++; // Skip '\n'
    }
  }
  return result;
}
}

//------------------------------------------------------------------------------
int vtkCornerAnnotation::RenderOpaqueGeometry(vtkViewport* viewport)
{
  int fontSize;

  // Check to see whether we have to rebuild everything
  // If the viewport has changed we may - or may not need
  // to rebuild, it depends on if the projected coords change
  int viewport_size_has_changed = 0;
  if (viewport->GetMTime() > this->BuildTime ||
    (viewport->GetVTKWindow() && viewport->GetVTKWindow()->GetMTime() > this->BuildTime))
  {
    const int* vSize = viewport->GetSize();
    if (this->LastSize[0] != vSize[0] || this->LastSize[1] != vSize[1])
    {
      viewport_size_has_changed = 1;
    }
  }

  // Is there an image actor ?
  vtkImageMapToWindowLevelColors* wl = this->WindowLevel;
  vtkImageActor* ia = nullptr;
  if (this->ImageActor)
  {
    ia = this->ImageActor;
  }
  else
  {
    vtkPropCollection* pc = viewport->GetViewProps();
    int numProps = pc->GetNumberOfItems();
    for (int i = 0; i < numProps; i++)
    {
      ia = vtkImageActor::SafeDownCast(pc->GetItemAsObject(i));
      if (ia)
      {
        if (ia->GetInput() && !wl)
        {
          wl = vtkImageMapToWindowLevelColors::SafeDownCast(ia->GetMapper()->GetInputAlgorithm());
        }
        break;
      }
    }
  }

  int tprop_has_changed = (this->TextProperty && this->TextProperty->GetMTime() > this->BuildTime);

  // Check to see whether we have to rebuild everything
  if (viewport_size_has_changed || tprop_has_changed || (this->GetMTime() > this->BuildTime) ||
    (ia && (ia != this->LastImageActor || ia->GetMTime() > this->BuildTime)) ||
    (wl && wl->GetMTime() > this->BuildTime))
  {
    const int* vSize = viewport->GetSize();

    vtkDebugMacro(<< "Rebuilding text");

    // Replace text
    this->TextReplace(ia, wl);

    // Get the viewport size in display coordinates
    this->LastSize[0] = vSize[0];
    this->LastSize[1] = vSize[1];

    // Only adjust size then the text changes due to non w/l slice reasons
    if (viewport_size_has_changed || tprop_has_changed || this->GetMTime() > this->BuildTime)
    {
      // Rebuid text props.
      // Perform shallow copy here since each individual corner has a
      // different alignment/size but they share the other this->TextProperty
      // attributes.
      fontSize = this->TextMapper[0]->GetTextProperty()->GetFontSize();

      if (tprop_has_changed)
      {
        for (int i = 0; i < NumTextPositions; i++)
        {
          vtkTextProperty* tprop = this->TextMapper[i]->GetTextProperty();
          tprop->ShallowCopy(this->TextProperty);
          tprop->SetFontSize(fontSize);
        }
        this->SetTextActorsJustification();
      }

      // Update all the composing objects to find the best size for the font
      // use the last size as a first guess

      /*
          +---------+
          |2   7   3|
          |         |
          |6       5|
          |         |
          |0   4   1|
          +---------+
      */

      int tempi[2 * NumTextPositions];
      int allZeros = 1;
      for (int i = 0; i < NumTextPositions; i++)
      {
        this->TextMapper[i]->GetSize(viewport, tempi + i * 2);
        if (tempi[2 * i] > 0 || tempi[2 * i + 1] > 0)
        {
          allZeros = 0;
        }
      }

      if (allZeros)
      {
        return 0;
      }

      int height_02 = tempi[1] + tempi[5];  // total height of text in left top/bottom corners
      int height_13 = tempi[3] + tempi[7];  // total height of text in right top/bottom corners
      int height_47 = tempi[9] + tempi[15]; // total height of text at center of top/bottom edges

      int width_01 = tempi[0] + tempi[2];   // total width of text on bottom left/right corners
      int width_23 = tempi[4] + tempi[6];   // total width of text on top left/right corners
      int width_56 = tempi[10] + tempi[12]; // total width of text at center of left/right edges

      int max_width_corners = (width_01 > width_23) ? width_01 : width_23;
      int max_width = (width_56 > max_width_corners) ? width_56 : max_width_corners;

      int num_lines_02 = GetNumberOfLines(this->TextMapper[0]->GetInput()) +
        GetNumberOfLines(this->TextMapper[2]->GetInput());

      int num_lines_13 = GetNumberOfLines(this->TextMapper[1]->GetInput()) +
        GetNumberOfLines(this->TextMapper[3]->GetInput());

      int num_lines_47 = GetNumberOfLines(this->TextMapper[4]->GetInput()) +
        GetNumberOfLines(this->TextMapper[7]->GetInput());

      int line_max_02 =
        (int)(vSize[1] * this->MaximumLineHeight) * (num_lines_02 ? num_lines_02 : 1);

      int line_max_13 =
        (int)(vSize[1] * this->MaximumLineHeight) * (num_lines_13 ? num_lines_13 : 1);

      int line_max_47 =
        (int)(vSize[1] * this->MaximumLineHeight) * (num_lines_47 ? num_lines_47 : 1);

      // Target size is to use 90% of x and y

      int tSize[2];
      tSize[0] = (int)(0.9 * vSize[0]);
      tSize[1] = (int)(0.9 * vSize[1]);

      // While the size is too small increase it

      while (height_02 < tSize[1] && height_13 < tSize[1] && height_47 < tSize[1] &&
        max_width < tSize[0] && height_02 < line_max_02 && height_13 < line_max_13 &&
        height_47 < line_max_47 && fontSize < 100)
      {
        fontSize++;
        for (int i = 0; i < NumTextPositions; i++)
        {
          this->TextMapper[i]->GetTextProperty()->SetFontSize(fontSize);
          this->TextMapper[i]->GetSize(viewport, tempi + i * 2);
        }
        height_02 = tempi[1] + tempi[5];
        height_13 = tempi[3] + tempi[7];
        height_47 = tempi[9] + tempi[15];

        width_01 = tempi[0] + tempi[2];
        width_23 = tempi[4] + tempi[6];
        width_56 = tempi[10] + tempi[12];

        max_width_corners = (width_01 > width_23) ? width_01 : width_23;
        max_width = (width_56 > max_width_corners) ? width_56 : max_width_corners;
      }

      // While the size is too large decrease it

      while ((height_02 > tSize[1] || height_13 > tSize[1] || height_47 > tSize[1] ||
               max_width > tSize[0] || height_02 > line_max_02 || height_13 > line_max_13 ||
               height_47 > line_max_47) &&
        fontSize > 0)
      {
        fontSize--;
        for (int i = 0; i < NumTextPositions; i++)
        {
          this->TextMapper[i]->GetTextProperty()->SetFontSize(fontSize);
          this->TextMapper[i]->GetSize(viewport, tempi + i * 2);
        }
        height_02 = tempi[1] + tempi[5];
        height_13 = tempi[3] + tempi[7];
        height_47 = tempi[9] + tempi[15];

        width_01 = tempi[0] + tempi[2];
        width_23 = tempi[4] + tempi[6];
        width_56 = tempi[10] + tempi[12];

        max_width_corners = (width_01 > width_23) ? width_01 : width_23;
        max_width = (width_56 > max_width_corners) ? width_56 : max_width_corners;
      }

      fontSize =
        static_cast<int>(pow((double)fontSize, NonlinearFontScaleFactor) * LinearFontScaleFactor);
      if (fontSize > this->MaximumFontSize)
      {
        fontSize = this->MaximumFontSize;
      }
      this->FontSize = fontSize;
      for (int i = 0; i < NumTextPositions; i++)
      {
        this->TextMapper[i]->GetTextProperty()->SetFontSize(fontSize);
      }

      // Now set the position of the TextActors

      this->SetTextActorsPosition(vSize);

      for (int i = 0; i < NumTextPositions; i++)
      {
        this->TextActor[i]->SetProperty(this->GetProperty());
      }
    }
    this->BuildTime.Modified();
    this->LastImageActor = ia;
  }

  // Everything is built, just have to render

  if (this->FontSize >= this->MinimumFontSize)
  {
    for (int i = 0; i < NumTextPositions; i++)
    {
      this->TextActor[i]->RenderOpaqueGeometry(viewport);
    }
  }

  return 1;
}

//------------------------------------------------------------------------------
// Description:
// Does this prop have some translucent polygonal geometry?
vtkTypeBool vtkCornerAnnotation::HasTranslucentPolygonalGeometry()
{
  return 0;
}

//------------------------------------------------------------------------------
void vtkCornerAnnotation::SetTextActorsPosition(const int vsize[2])
{
  this->TextActor[LowerLeft]->SetPosition(5, 5);
  this->TextActor[LowerRight]->SetPosition(vsize[0] - 5, 5);
  this->TextActor[UpperLeft]->SetPosition(5, vsize[1] - 5);
  this->TextActor[UpperRight]->SetPosition(vsize[0] - 5, vsize[1] - 5);

  this->TextActor[LowerEdge]->SetPosition(vsize[0] / 2, 5);
  this->TextActor[UpperEdge]->SetPosition(vsize[0] / 2, vsize[1] - 5);
  this->TextActor[LeftEdge]->SetPosition(5, vsize[1] / 2);
  this->TextActor[RightEdge]->SetPosition(vsize[0] - 5, vsize[1] / 2);
}

//------------------------------------------------------------------------------
void vtkCornerAnnotation::SetTextActorsJustification()
{
  vtkTextProperty* tprop = this->TextMapper[LowerLeft]->GetTextProperty();
  tprop->SetJustificationToLeft();
  tprop->SetVerticalJustificationToBottom();

  tprop = this->TextMapper[LowerRight]->GetTextProperty();
  tprop->SetJustificationToRight();
  tprop->SetVerticalJustificationToBottom();

  tprop = this->TextMapper[UpperLeft]->GetTextProperty();
  tprop->SetJustificationToLeft();
  tprop->SetVerticalJustificationToTop();

  tprop = this->TextMapper[UpperRight]->GetTextProperty();
  tprop->SetJustificationToRight();
  tprop->SetVerticalJustificationToTop();

  tprop = this->TextMapper[LowerEdge]->GetTextProperty();
  tprop->SetJustificationToCentered();
  tprop->SetVerticalJustificationToBottom();

  tprop = this->TextMapper[UpperEdge]->GetTextProperty();
  tprop->SetJustificationToCentered();
  tprop->SetVerticalJustificationToTop();

  tprop = this->TextMapper[LeftEdge]->GetTextProperty();
  tprop->SetJustificationToLeft();
  tprop->SetVerticalJustificationToCentered();

  tprop = this->TextMapper[RightEdge]->GetTextProperty();
  tprop->SetJustificationToRight();
  tprop->SetVerticalJustificationToCentered();
}

//------------------------------------------------------------------------------
void vtkCornerAnnotation::SetText(int i, const char* text)
{
  if (i < 0 || i >= NumTextPositions)
  {
    return;
  }

  if (!text || (this->CornerText[i] && (!strcmp(this->CornerText[i], text))))
  {
    return;
  }
  delete[] this->CornerText[i];
  this->CornerText[i] = new char[strlen(text) + 1];
  strcpy(this->CornerText[i], text);
  this->Modified();
}

//------------------------------------------------------------------------------
const char* vtkCornerAnnotation::GetText(int i)
{
  if (i < 0 || i >= NumTextPositions)
  {
    return nullptr;
  }

  return this->CornerText[i];
}

//------------------------------------------------------------------------------
void vtkCornerAnnotation::ClearAllTexts()
{
  for (int i = 0; i < NumTextPositions; i++)
  {
    this->SetText(i, "");
  }
}

//------------------------------------------------------------------------------
void vtkCornerAnnotation::CopyAllTextsFrom(vtkCornerAnnotation* ca)
{
  for (int i = 0; i < NumTextPositions; i++)
  {
    this->SetText(i, ca->GetText(i));
  }
}

//------------------------------------------------------------------------------
void vtkCornerAnnotation::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "ImageActor: " << this->GetImageActor() << endl;
  os << indent << "MinimumFontSize: " << this->GetMinimumFontSize() << endl;
  os << indent << "MaximumFontSize: " << this->GetMaximumFontSize() << endl;
  os << indent << "LinearFontScaleFactor: " << this->GetLinearFontScaleFactor() << endl;
  os << indent << "NonlinearFontScaleFactor: " << this->GetNonlinearFontScaleFactor() << endl;
  os << indent << "WindowLevel: " << this->GetWindowLevel() << endl;
  os << indent << "Mapper: " << this->GetMapper() << endl;
  os << indent << "MaximumLineHeight: " << this->MaximumLineHeight << endl;
  os << indent << "LevelShift: " << this->LevelShift << endl;
  os << indent << "LevelScale: " << this->LevelScale << endl;
  os << indent << "TextProperty: " << this->TextProperty << endl;
  os << indent << "ShowSliceAndImage: " << this->ShowSliceAndImage << endl;
}
VTK_ABI_NAMESPACE_END
