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

#include "vtkActor.h"
#include "vtkAppendPolyData.h"
#include "vtkCellArray.h"
#include "vtkDoubleArray.h"
#include "vtkGlyph2D.h"
#include "vtkGlyph3D.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkPolyDataMapper2D.h"
#include "vtkProperty.h"
#include "vtkProperty2D.h"
#include "vtkTextActor.h"
#include "vtkTextProperty.h"
#include "vtkTrivialProducer.h"
#include "vtkViewport.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkCaptionActor2D);

vtkCxxSetObjectMacro(vtkCaptionActor2D, CaptionTextProperty, vtkTextProperty);

class vtkCaptionActor2DConnection : public vtkAlgorithm
{
public:
  static vtkCaptionActor2DConnection* New();
  vtkTypeMacro(vtkCaptionActor2DConnection, vtkAlgorithm);

  vtkCaptionActor2DConnection() { this->SetNumberOfInputPorts(1); }
};

vtkStandardNewMacro(vtkCaptionActor2DConnection);

//------------------------------------------------------------------------------
vtkCaptionActor2D::vtkCaptionActor2D()
{
  // Positioning information
  this->AttachmentPointCoordinate = vtkCoordinate::New();
  this->AttachmentPointCoordinate->SetCoordinateSystemToWorld();
  this->AttachmentPointCoordinate->SetValue(0.0, 0.0, 0.0);

  this->PositionCoordinate->SetCoordinateSystemToDisplay();
  this->PositionCoordinate->SetReferenceCoordinate(this->AttachmentPointCoordinate);
  this->PositionCoordinate->SetValue(static_cast<double>(10), static_cast<double>(10));

  // This sets up the Position2Coordinate
  this->vtkActor2D::SetWidth(0.25);
  this->vtkActor2D::SetHeight(0.10);

  this->Border = 1;
  this->Leader = 1;
  this->AttachEdgeOnly = 0;
  this->ThreeDimensionalLeader = 1;
  this->LeaderGlyphSize = 0.025;
  this->MaximumLeaderGlyphSize = 20;

  this->LeaderGlyphConnectionHolder = vtkCaptionActor2DConnection::New();

  // Control font properties
  this->Padding = 3;

  this->CaptionTextProperty = vtkTextProperty::New();
  this->CaptionTextProperty->SetBold(1);
  this->CaptionTextProperty->SetItalic(1);
  this->CaptionTextProperty->SetShadow(1);
  this->CaptionTextProperty->SetFontFamily(VTK_ARIAL);
  this->CaptionTextProperty->SetJustification(VTK_TEXT_LEFT);
  this->CaptionTextProperty->SetVerticalJustification(VTK_TEXT_BOTTOM);

  // What is actually drawn
  this->TextActor = vtkTextActor::New();
  this->TextActor->GetPositionCoordinate()->SetCoordinateSystemToDisplay();
  this->TextActor->GetPositionCoordinate()->SetReferenceCoordinate(nullptr);
  this->TextActor->GetPosition2Coordinate()->SetCoordinateSystemToDisplay();
  this->TextActor->GetPosition2Coordinate()->SetReferenceCoordinate(nullptr);
  this->TextActor->SetTextScaleModeToProp();
  this->TextActor->SetTextProperty(this->CaptionTextProperty);

  this->BorderPolyData = vtkPolyData::New();
  vtkPoints* pts = vtkPoints::New();
  pts->SetNumberOfPoints(4);
  this->BorderPolyData->SetPoints(pts);
  pts->Delete();
  vtkCellArray* border = vtkCellArray::New();
  border->InsertNextCell(5);
  border->InsertCellPoint(0);
  border->InsertCellPoint(1);
  border->InsertCellPoint(2);
  border->InsertCellPoint(3);
  border->InsertCellPoint(0);
  this->BorderPolyData->SetLines(border);
  border->Delete();

  this->BorderMapper = vtkPolyDataMapper2D::New();
  this->BorderMapper->SetInputData(this->BorderPolyData);
  this->BorderActor = vtkActor2D::New();
  this->BorderActor->SetMapper(this->BorderMapper);

  // Set border mapper coordinate system to Display.
  vtkCoordinate* coord = vtkCoordinate::New();
  coord->SetCoordinateSystemToDisplay();
  this->BorderMapper->SetTransformCoordinate(coord);
  coord->Delete();

  // This is for glyphing the head of the leader
  // A single point with a vector for glyph orientation
  this->HeadPolyData = vtkPolyData::New();
  pts = vtkPoints::New();
  pts->SetNumberOfPoints(1);
  this->HeadPolyData->SetPoints(pts);
  pts->Delete();
  vtkDoubleArray* vecs = vtkDoubleArray::New();
  vecs->SetNumberOfComponents(3);
  vecs->SetNumberOfTuples(1);
  this->HeadPolyData->GetPointData()->SetVectors(vecs);
  vecs->Delete();

  // This is the leader (line) from the attachment point to the caption
  this->LeaderPolyData = vtkPolyData::New();
  pts = vtkPoints::New();
  pts->SetNumberOfPoints(2);
  this->LeaderPolyData->SetPoints(pts);
  pts->Delete();
  vtkCellArray* leader = vtkCellArray::New();
  leader->InsertNextCell(2);
  leader->InsertCellPoint(0);
  leader->InsertCellPoint(1); // at the attachment point
  this->LeaderPolyData->SetLines(leader);
  leader->Delete();

  // Used to generate the glyph on the leader head
  this->HeadGlyph = vtkGlyph3D::New();
  this->HeadGlyph->SetInputData(this->HeadPolyData);
  this->HeadGlyph->SetScaleModeToDataScalingOff();
  this->HeadGlyph->SetScaleFactor(0.1);

  // Appends the leader and the glyph head
  this->AppendLeader = vtkAppendPolyData::New();
  this->AppendLeader->AddInputData(this->LeaderPolyData);
  this->AppendLeader->AddInputConnection(this->HeadGlyph->GetOutputPort());

  // Used to transform from world to other coordinate systems
  this->MapperCoordinate2D = vtkCoordinate::New();
  this->MapperCoordinate2D->SetCoordinateSystemToWorld();

  // If 2D leader is used, then use this mapper/actor combination
  this->LeaderMapper2D = vtkPolyDataMapper2D::New();
  this->LeaderMapper2D->SetTransformCoordinate(this->MapperCoordinate2D);
  this->LeaderActor2D = vtkActor2D::New();
  this->LeaderActor2D->SetMapper(this->LeaderMapper2D);

  // If 3D leader is used, then use this mapper/actor combination
  this->LeaderMapper3D = vtkPolyDataMapper::New();
  this->LeaderActor3D = vtkActor::New();
  this->LeaderActor3D->SetMapper(this->LeaderMapper3D);
}

//------------------------------------------------------------------------------
vtkCaptionActor2D::~vtkCaptionActor2D()
{
  this->AttachmentPointCoordinate->Delete();

  this->TextActor->Delete();

  this->LeaderGlyphConnectionHolder->Delete();
  this->LeaderGlyphConnectionHolder = nullptr;

  this->BorderPolyData->Delete();
  this->BorderMapper->Delete();
  this->BorderActor->Delete();

  this->HeadPolyData->Delete();
  this->LeaderPolyData->Delete();
  this->HeadGlyph->Delete();
  this->AppendLeader->Delete();

  this->MapperCoordinate2D->Delete();

  this->LeaderMapper2D->Delete();
  this->LeaderActor2D->Delete();

  this->LeaderMapper3D->Delete();
  this->LeaderActor3D->Delete();

  this->SetCaptionTextProperty(nullptr);
}

//------------------------------------------------------------------------------
void vtkCaptionActor2D::SetLeaderGlyphConnection(vtkAlgorithmOutput* ao)
{
  this->LeaderGlyphConnectionHolder->SetInputConnection(ao);
}

//------------------------------------------------------------------------------
void vtkCaptionActor2D::SetLeaderGlyphData(vtkPolyData* leader)
{
  vtkTrivialProducer* tp = vtkTrivialProducer::New();
  tp->SetOutput(leader);
  this->SetLeaderGlyphConnection(tp->GetOutputPort());
  tp->Delete();
}

//------------------------------------------------------------------------------
vtkPolyData* vtkCaptionActor2D::GetLeaderGlyph()
{
  if (this->LeaderGlyphConnectionHolder->GetNumberOfInputConnections(0) < 1)
  {
    return nullptr;
  }
  return vtkPolyData::SafeDownCast(this->LeaderGlyphConnectionHolder->GetInputDataObject(0, 0));
}

//------------------------------------------------------------------------------
void vtkCaptionActor2D::SetCaption(const char* caption)
{
  this->TextActor->SetInput(caption);
}

//------------------------------------------------------------------------------
char* vtkCaptionActor2D::GetCaption()
{
  return this->TextActor->GetInput();
}

//------------------------------------------------------------------------------
// 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 vtkCaptionActor2D::ReleaseGraphicsResources(vtkWindow* win)
{
  this->TextActor->ReleaseGraphicsResources(win);
  this->BorderActor->ReleaseGraphicsResources(win);
  this->LeaderActor2D->ReleaseGraphicsResources(win);
  this->LeaderActor3D->ReleaseGraphicsResources(win);
}

int vtkCaptionActor2D::RenderOverlay(vtkViewport* viewport)
{
  int renderedSomething = 0;

  renderedSomething += this->TextActor->RenderOverlay(viewport);

  if (this->Border)
  {
    renderedSomething += this->BorderActor->RenderOverlay(viewport);
  }

  if (this->Leader)
  {
    if (this->ThreeDimensionalLeader)
    {
      renderedSomething += this->LeaderActor3D->RenderOverlay(viewport);
    }
    else
    {
      renderedSomething += this->LeaderActor2D->RenderOverlay(viewport);
    }
  }

  return renderedSomething;
}

//------------------------------------------------------------------------------
int vtkCaptionActor2D::RenderOpaqueGeometry(vtkViewport* viewport)
{
  // Build the caption (almost always needed so we don't check mtime)
  vtkDebugMacro(<< "Rebuilding caption");

  // compute coordinates and set point values
  //
  double *w1, *w2;
  int *x1, *x2, *x3;
  double p1[4], p2[4], p3[4];
  x1 = this->AttachmentPointCoordinate->GetComputedDisplayValue(viewport);
  x2 = this->PositionCoordinate->GetComputedDisplayValue(viewport);
  x3 = this->Position2Coordinate->GetComputedDisplayValue(viewport);
  p1[0] = (double)x1[0];
  p1[1] = (double)x1[1];
  p1[2] = 0.0;
  p2[0] = (double)x2[0];
  p2[1] = (double)x2[1];
  p2[2] = p1[2];
  p3[0] = (double)x3[0];
  p3[1] = (double)x3[1];
  p3[2] = p1[2];

  // Set up the scaled text - take into account the padding
  this->TextActor->SetTextProperty(this->CaptionTextProperty);
  this->TextActor->GetPositionCoordinate()->SetValue(
    p2[0] + this->Padding, p2[1] + this->Padding, 0.0);
  this->TextActor->GetPosition2Coordinate()->SetValue(
    p3[0] - this->Padding, p3[1] - this->Padding, 0.0);

  // Define the border
  vtkPoints* pts = this->BorderPolyData->GetPoints();
  pts->SetPoint(0, p2);
  pts->SetPoint(1, p3[0], p2[1], p1[2]);
  pts->SetPoint(2, p3[0], p3[1], p1[2]);
  pts->SetPoint(3, p2[0], p3[1], p1[2]);

  // Define the leader. Have to find the closest point from the
  // border to the attachment point. We look at the four vertices
  // and four edge centers.
  double d2, minD2, pt[3], minPt[3];
  minD2 = VTK_DOUBLE_MAX;

  minPt[0] = p2[0];
  minPt[1] = p2[1];

  pt[0] = p2[0];
  pt[1] = p2[1];
  pt[2] = minPt[2] = 0.0;
  if (!this->AttachEdgeOnly && (d2 = vtkMath::Distance2BetweenPoints(p1, pt)) < minD2)
  {
    minD2 = d2;
    minPt[0] = pt[0];
    minPt[1] = pt[1];
  }

  pt[0] = (p2[0] + p3[0]) / 2.0;
  if ((d2 = vtkMath::Distance2BetweenPoints(p1, pt)) < minD2)
  {
    minD2 = d2;
    minPt[0] = pt[0];
    minPt[1] = pt[1];
  }

  pt[0] = p3[0];
  if (!this->AttachEdgeOnly && (d2 = vtkMath::Distance2BetweenPoints(p1, pt)) < minD2)
  {
    minD2 = d2;
    minPt[0] = pt[0];
    minPt[1] = pt[1];
  }

  pt[1] = (p2[1] + p3[1]) / 2.0;
  if ((d2 = vtkMath::Distance2BetweenPoints(p1, pt)) < minD2)
  {
    minD2 = d2;
    minPt[0] = pt[0];
    minPt[1] = pt[1];
  }

  pt[1] = p3[1];
  if (!this->AttachEdgeOnly && (d2 = vtkMath::Distance2BetweenPoints(p1, pt)) < minD2)
  {
    minD2 = d2;
    minPt[0] = pt[0];
    minPt[1] = pt[1];
  }

  pt[0] = (p2[0] + p3[0]) / 2.0;
  if ((d2 = vtkMath::Distance2BetweenPoints(p1, pt)) < minD2)
  {
    minD2 = d2;
    minPt[0] = pt[0];
    minPt[1] = pt[1];
  }

  pt[0] = p2[0];
  if (!this->AttachEdgeOnly && (d2 = vtkMath::Distance2BetweenPoints(p1, pt)) < minD2)
  {
    minD2 = d2;
    minPt[0] = pt[0];
    minPt[1] = pt[1];
  }

  pt[1] = (p2[1] + p3[1]) / 2.0;
  if ((d2 = vtkMath::Distance2BetweenPoints(p1, pt)) < minD2)
  {
    minD2 = d2;
    minPt[0] = pt[0];
    minPt[1] = pt[1];
  }

  // Set the leader coordinates in appropriate coordinate system
  // The pipeline is connected differently depending on the dimension
  // and availability of a leader head.
  if (this->Leader)
  {
    pts = this->LeaderPolyData->GetPoints();

    w1 = this->AttachmentPointCoordinate->GetComputedWorldValue(viewport);
    viewport->SetWorldPoint(w1[0], w1[1], w1[2], 1.0);
    viewport->WorldToView();
    viewport->GetViewPoint(p1);

    // minPt is in display coordinates and it is OK
    double val[3];
    val[0] = minPt[0];
    val[1] = minPt[1];
    val[2] = 0;
    // convert to view
    viewport->DisplayToNormalizedDisplay(val[0], val[1]);
    viewport->NormalizedDisplayToViewport(val[0], val[1]);
    viewport->ViewportToNormalizedViewport(val[0], val[1]);
    viewport->NormalizedViewportToView(val[0], val[1], val[2]);

    // use the zvalue from the attach point
    val[2] = p1[2];
    viewport->SetViewPoint(val);
    viewport->ViewToWorld();
    double w3[4];
    viewport->GetWorldPoint(w3);
    if (w3[3] != 0.0)
    {
      w3[0] /= w3[3];
      w3[1] /= w3[3];
      w3[2] /= w3[3];
    }
    w2 = w3;

    pts->SetPoint(0, w1);
    pts->SetPoint(1, w2);
    this->HeadPolyData->GetPoints()->SetPoint(0, w1);
    this->HeadPolyData->GetPointData()->GetVectors()->SetTuple3(
      0, w1[0] - w2[0], w1[1] - w2[1], w1[2] - w2[2]);

    pts->Modified();
    this->HeadPolyData->Modified();
  }

  if (this->GetLeaderGlyph())
  {
    this->LeaderGlyphConnectionHolder->GetInputAlgorithm()->Update();

    // compute the scale
    double length = this->GetLeaderGlyph()->GetLength();
    const int* sze = viewport->GetSize();
    int numPixels = static_cast<int>(
      this->LeaderGlyphSize * sqrt(static_cast<double>(sze[0] * sze[0] + sze[1] * sze[1])));
    numPixels =
      (numPixels > this->MaximumLeaderGlyphSize ? this->MaximumLeaderGlyphSize : numPixels);

    w1 = this->AttachmentPointCoordinate->GetComputedWorldValue(viewport);
    // determine the number of units length per pixel, by getting world coordinates of point 1 pixel
    // away from attachment point:
    viewport->SetWorldPoint(w1[0], w1[1], w1[2], 1.0);
    viewport->WorldToDisplay();
    double dispPt[4];
    viewport->GetDisplayPoint(dispPt);
    dispPt[0] = dispPt[0] + 1;
    dispPt[1] = dispPt[1] + 1;
    viewport->SetDisplayPoint(dispPt);
    viewport->DisplayToWorld();
    viewport->GetWorldPoint(p2);
    if (p2[3] != 0.0)
    {
      p2[0] /= p2[3];
      p2[1] /= p2[3];
      p2[2] /= p2[3];
    }
    // Factor 1.4142 (sqrt(2)) makes up for the use of "diagonals" in length
    // calculations; otherwise the scale factor tends to be too small
    double sf = 1.4142 * numPixels * sqrt(vtkMath::Distance2BetweenPoints(w1, p2)) / length;

    vtkDebugMacro(<< "Scale factor: " << sf);

    this->HeadGlyph->SetSourceData(this->GetLeaderGlyph());
    this->HeadGlyph->SetScaleFactor(sf);

    this->LeaderMapper2D->SetInputConnection(this->AppendLeader->GetOutputPort());
    this->LeaderMapper3D->SetInputConnection(this->AppendLeader->GetOutputPort());
    this->AppendLeader->Update();
  }
  else
  {
    this->LeaderMapper2D->SetInputData(this->LeaderPolyData);
    this->LeaderMapper3D->SetInputData(this->LeaderPolyData);
  }

  // assign properties
  //
  this->BorderActor->SetProperty(this->GetProperty());
  this->LeaderActor2D->SetProperty(this->GetProperty());
  this->LeaderActor3D->GetProperty()->SetColor(this->GetProperty()->GetColor());
  // Copy the property into the text actor and reset the color -- otherwise the
  // text shadow will be colored the same as the text.
  this->TextActor->GetProperty()->DeepCopy(this->GetProperty());
  this->TextActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
  this->TextActor->GetProperty()->SetOpacity(1.0);

  // Okay we are ready to render something
  int renderedSomething = 0;
  renderedSomething += this->TextActor->RenderOpaqueGeometry(viewport);
  if (this->Border)
  {
    renderedSomething += this->BorderActor->RenderOpaqueGeometry(viewport);
  }

  if (this->Leader)
  {
    if (this->ThreeDimensionalLeader)
    {
      renderedSomething += this->LeaderActor3D->RenderOpaqueGeometry(viewport);
    }
    else
    {
      renderedSomething += this->LeaderActor2D->RenderOpaqueGeometry(viewport);
    }
  }

  return renderedSomething;
}

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

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

  os << indent << "Text Actor: " << this->TextActor << "\n";
  if (this->CaptionTextProperty)
  {
    os << indent << "Caption Text Property:\n";
    this->CaptionTextProperty->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << indent << "Caption Text Property: (none)\n";
  }

  os << indent << "Caption: ";
  if (this->TextActor->GetInput())
  {
    os << this->TextActor->GetInput() << "\n";
  }
  else
  {
    os << "(none)\n";
  }

  os << indent << "Leader: " << (this->Leader ? "On\n" : "Off\n");
  os << indent << "Three Dimensional Leader: " << (this->ThreeDimensionalLeader ? "On\n" : "Off\n");
  os << indent << "Leader Glyph Size: " << this->LeaderGlyphSize << "\n";
  os << indent << "MaximumLeader Glyph Size: " << this->MaximumLeaderGlyphSize << "\n";
  if (!this->GetLeaderGlyph())
  {
    os << indent << "Leader Glyph: (none)\n";
  }
  else
  {
    os << indent << "Leader Glyph: (" << this->GetLeaderGlyph() << ")\n";
  }
  os << indent << "Padding: " << this->Padding << "\n";
  os << indent << "Border: " << (this->Border ? "On\n" : "Off\n");
  os << indent << "AttachEdgeOnly: " << (this->AttachEdgeOnly ? "On\n" : "Off\n");
}

//------------------------------------------------------------------------------
void vtkCaptionActor2D::ShallowCopy(vtkProp* prop)
{
  vtkCaptionActor2D* a = vtkCaptionActor2D::SafeDownCast(prop);
  if (a != nullptr)
  {
    this->SetCaption(a->GetCaption());
    this->SetAttachmentPoint(a->GetAttachmentPoint());
    this->SetBorder(a->GetBorder());
    this->SetLeader(a->GetLeader());
    this->SetThreeDimensionalLeader(a->GetThreeDimensionalLeader());
    if (a->LeaderGlyphConnectionHolder->GetNumberOfInputConnections(0) < 1)
    {
      this->SetLeaderGlyphConnection(nullptr);
    }
    else
    {
      this->SetLeaderGlyphConnection(a->LeaderGlyphConnectionHolder->GetInputConnection(0, 0));
    }
    this->SetLeaderGlyphSize(a->GetLeaderGlyphSize());
    this->SetMaximumLeaderGlyphSize(a->GetMaximumLeaderGlyphSize());
    this->SetPadding(a->GetPadding());
    this->SetCaptionTextProperty(a->GetCaptionTextProperty());
  }

  // Now do superclass
  this->vtkActor2D::ShallowCopy(prop);
}
VTK_ABI_NAMESPACE_END
