// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkBalloonRepresentation.h"
#include "vtkActor2D.h"
#include "vtkCellArray.h"
#include "vtkFloatArray.h"
#include "vtkImageData.h"
#include "vtkInteractorObserver.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper2D.h"
#include "vtkProperty2D.h"
#include "vtkRenderer.h"
#include "vtkTextActor.h"
#include "vtkTextMapper.h"
#include "vtkTextProperty.h"
#include "vtkTexture.h"
#include "vtkTexturedActor2D.h"
#include "vtkWindow.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkBalloonRepresentation);

vtkCxxSetObjectMacro(vtkBalloonRepresentation, TextProperty, vtkTextProperty);
vtkCxxSetObjectMacro(vtkBalloonRepresentation, FrameProperty, vtkProperty2D);
vtkCxxSetObjectMacro(vtkBalloonRepresentation, BalloonImage, vtkImageData);
vtkCxxSetObjectMacro(vtkBalloonRepresentation, ImageProperty, vtkProperty2D);

//------------------------------------------------------------------------------
vtkBalloonRepresentation::vtkBalloonRepresentation()
{
  // Initially we are not visible
  this->Visibility = 0;
  this->TextVisible = 0;
  this->ImageVisible = 0;

  // Balloon related
  this->BalloonText = nullptr;
  this->BalloonImage = nullptr;
  this->BalloonLayout = ImageTop;

  // Displaying the image in the balloon using texture. Create a quad polygon
  // and apply the texture on top of it.
  this->ImageSize[0] = 50;
  this->ImageSize[1] = 50;
  this->Texture = vtkTexture::New();
  this->TexturePolyData = vtkPolyData::New();
  this->TexturePoints = vtkPoints::New();
  this->TexturePoints->SetNumberOfPoints(4);
  this->TexturePolyData->SetPoints(this->TexturePoints);
  vtkCellArray* polys = vtkCellArray::New();
  polys->InsertNextCell(4);
  polys->InsertCellPoint(0);
  polys->InsertCellPoint(1);
  polys->InsertCellPoint(2);
  polys->InsertCellPoint(3);
  this->TexturePolyData->SetPolys(polys);
  polys->Delete();
  vtkFloatArray* tc = vtkFloatArray::New();
  tc->SetNumberOfComponents(2);
  tc->SetNumberOfTuples(4);
  tc->InsertComponent(0, 0, 0.0);
  tc->InsertComponent(0, 1, 0.0);
  tc->InsertComponent(1, 0, 1.0);
  tc->InsertComponent(1, 1, 0.0);
  tc->InsertComponent(2, 0, 1.0);
  tc->InsertComponent(2, 1, 1.0);
  tc->InsertComponent(3, 0, 0.0);
  tc->InsertComponent(3, 1, 1.0);
  this->TexturePolyData->GetPointData()->SetTCoords(tc);
  tc->Delete();
  this->TextureMapper = vtkPolyDataMapper2D::New();
  this->TextureMapper->SetInputData(this->TexturePolyData);
  this->TextureActor = vtkTexturedActor2D::New();
  this->TextureActor->SetMapper(this->TextureMapper);
  this->TextureActor->SetTexture(this->Texture);
  this->ImageProperty = vtkProperty2D::New();
  this->ImageProperty->SetOpacity(1.0);
  this->TextureActor->SetProperty(this->ImageProperty);

  // Controlling layout
  this->Padding = 5;
  this->Offset[0] = 15;
  this->Offset[1] = -30;

  // The text actor
  this->TextMapper = vtkTextMapper::New();
  this->TextActor = vtkActor2D::New();
  this->TextActor->SetMapper(this->TextMapper);
  this->TextProperty = vtkTextProperty::New();
  this->TextProperty->SetColor(0, 0, 0);
  this->TextProperty->SetFontSize(14);
  this->TextProperty->BoldOn();
  this->TextMapper->SetTextProperty(this->TextProperty);

  // The frame
  this->FramePoints = vtkPoints::New();
  this->FramePoints->SetNumberOfPoints(4);
  this->FramePolygon = vtkCellArray::New();
  this->FramePolygon->AllocateEstimate(1, 5);
  this->FramePolygon->InsertNextCell(4);
  this->FramePolygon->InsertCellPoint(0);
  this->FramePolygon->InsertCellPoint(1);
  this->FramePolygon->InsertCellPoint(2);
  this->FramePolygon->InsertCellPoint(3);
  this->FramePolyData = vtkPolyData::New();
  this->FramePolyData->SetPoints(this->FramePoints);
  this->FramePolyData->SetPolys(this->FramePolygon);
  this->FrameMapper = vtkPolyDataMapper2D::New();
  this->FrameMapper->SetInputData(this->FramePolyData);
  this->FrameActor = vtkActor2D::New();
  this->FrameActor->SetMapper(this->FrameMapper);
  this->FrameProperty = vtkProperty2D::New();
  this->FrameProperty->SetColor(1, 1, .882);
  this->FrameProperty->SetOpacity(0.5);
  this->FrameActor->SetProperty(this->FrameProperty);
}

//------------------------------------------------------------------------------
vtkBalloonRepresentation::~vtkBalloonRepresentation()
{
  delete[] this->BalloonText;

  if (this->BalloonImage)
  {
    this->BalloonImage->Delete();
  }

  this->Texture->Delete();
  this->TexturePolyData->Delete();
  this->TexturePoints->Delete();
  this->TextureMapper->Delete();
  this->TextureActor->Delete();
  this->ImageProperty->Delete();

  this->TextMapper->Delete();
  this->TextActor->Delete();
  this->TextProperty->Delete();

  // The frame
  this->FramePoints->Delete();
  this->FramePolygon->Delete();
  this->FramePolyData->Delete();
  this->FrameMapper->Delete();
  this->FrameActor->Delete();
  this->FrameProperty->Delete();
}

//------------------------------------------------------------------------------
void vtkBalloonRepresentation::StartWidgetInteraction(double e[2])
{
  this->StartEventPosition[0] = e[0];
  this->StartEventPosition[1] = e[1];
  this->VisibilityOn();
}

//------------------------------------------------------------------------------
void vtkBalloonRepresentation::EndWidgetInteraction(double vtkNotUsed(e)[2])
{
  this->VisibilityOff();
}

//------------------------------------------------------------------------------
void vtkBalloonRepresentation::AdjustImageSize(double imageSize[2])
{
  double r0 = this->ImageSize[0] / imageSize[0];
  double r1 = this->ImageSize[1] / imageSize[1];
  if (r0 > r1)
  {
    imageSize[0] *= r1;
    imageSize[1] *= r1;
  }
  else
  {
    imageSize[0] *= r0;
    imageSize[1] *= r0;
  }
}

//------------------------------------------------------------------------------
void vtkBalloonRepresentation::ScaleImage(double imageSize[2], double scale)
{
  imageSize[0] *= scale;
  imageSize[1] *= scale;
}

//------------------------------------------------------------------------------
void vtkBalloonRepresentation::BuildRepresentation()
{
  if (this->GetMTime() > this->BuildTime ||
    (this->Renderer && this->Renderer->GetVTKWindow() &&
      this->Renderer->GetVTKWindow()->GetMTime() > this->BuildTime))
  {
    this->TextVisible = 0;
    this->ImageVisible = 0;

    int size[2];
    size[0] = (this->Renderer->GetSize())[0];
    size[1] = (this->Renderer->GetSize())[1];
    int stringSize[2];
    stringSize[0] = stringSize[1] = 0;
    double imageSize[2];
    imageSize[0] = imageSize[1] = 0.0;
    double frameSize[2];
    frameSize[0] = frameSize[1] = 0.0;
    double io[2], so[2], fo[2];
    io[0] = 0.0;
    io[1] = 0.0;
    so[0] = 0.0;
    so[1] = 0.0;
    fo[0] = 0.0;
    fo[1] = 0.0;
    double e[2];
    e[0] = static_cast<double>(this->StartEventPosition[0] + this->Offset[0]);
    e[1] = static_cast<double>(this->StartEventPosition[1] + this->Offset[1]);

    // Determine the size of the text
    if (this->BalloonText)
    {
      // Start by getting the size of the text
      this->TextMapper->SetInput(this->BalloonText);
      this->TextMapper->GetSize(this->Renderer, stringSize);
      this->TextVisible = ((stringSize[0] > 0 && stringSize[1] > 0) ? 1 : 0);
    }

    // Determine the size of the image
    if (this->BalloonImage)
    {
      // this->BalloonImage->Update();
      if (this->BalloonImage->GetDataDimension() == 2)
      {
        int dims[3];
        this->BalloonImage->GetDimensions(dims);
        imageSize[0] = static_cast<double>(dims[0]);
        imageSize[1] = static_cast<double>(dims[1]);
        this->ImageVisible = ((imageSize[0] > 0.0 && imageSize[1] > 0.0) ? 1 : 0);
      }
    }

    // Layout the text and image
    if (this->TextVisible || this->ImageVisible)
    {
      if (this->TextVisible && !this->ImageVisible) // just text
      {
        frameSize[0] = static_cast<double>(stringSize[0] + 2 * this->Padding);
        frameSize[1] = static_cast<double>(stringSize[1] + 2 * this->Padding);
        fo[0] = 0.0;
        fo[1] = 0.0;
        so[0] = static_cast<double>(this->Padding);
        so[1] = static_cast<double>(this->Padding);
      }
      else if (this->ImageVisible && !this->TextVisible) // just image
      {
        this->AdjustImageSize(imageSize);
        io[0] = 0.0;
        io[1] = 0.0;
      }

      else // both image and text
      {
        this->AdjustImageSize(imageSize);
        if (this->BalloonLayout == ImageTop)
        {
          frameSize[1] = stringSize[1] + 2 * this->Padding;
          double length = (imageSize[0] > (stringSize[0] + 2 * this->Padding)
              ? imageSize[0]
              : (stringSize[0] + 2 * this->Padding));
          frameSize[0] = length;
          double scale = length / imageSize[0];
          this->ScaleImage(imageSize, scale);
          io[0] = 0.0;
          io[1] = frameSize[1];
          fo[0] = 0.0;
          fo[1] = 0.0;
          so[0] = length / 2.0 - stringSize[0] / 2.0;
          so[1] = this->Padding;
        }
        else if (this->BalloonLayout == ImageBottom)
        {
          frameSize[1] = stringSize[1] + 2 * this->Padding;
          double length = (imageSize[0] > (stringSize[0] + 2 * this->Padding)
              ? imageSize[0]
              : (stringSize[0] + 2 * this->Padding));
          frameSize[0] = length;
          double scale = length / imageSize[0];
          this->ScaleImage(imageSize, scale);
          io[0] = 0.0;
          io[1] = 0.0;
          fo[0] = 0.0;
          fo[1] = imageSize[1];
          so[0] = length / 2.0 - stringSize[0] / 2.0;
          so[1] = imageSize[1] + this->Padding;
        }
        else if (this->BalloonLayout == ImageLeft)
        {
          frameSize[0] = stringSize[0] + 2 * this->Padding;
          double length = (imageSize[1] > (stringSize[1] + 2 * this->Padding)
              ? imageSize[1]
              : (stringSize[1] + 2 * this->Padding));
          frameSize[1] = length;
          double scale = length / imageSize[1];
          this->ScaleImage(imageSize, scale);
          io[0] = 0.0;
          io[1] = 0.0;
          fo[0] = imageSize[0];
          fo[1] = 0.0;
          so[0] = imageSize[0] + this->Padding;
          so[1] = length / 2.0 - stringSize[1] / 2.0;
        }
        else if (this->BalloonLayout == ImageRight)
        {
          frameSize[0] = stringSize[0] + 2 * this->Padding;
          double length = (imageSize[1] > (stringSize[1] + 2 * this->Padding)
              ? imageSize[1]
              : (stringSize[1] + 2 * this->Padding));
          frameSize[1] = length;
          double scale = length / imageSize[1];
          this->ScaleImage(imageSize, scale);
          io[0] = frameSize[0];
          io[1] = 0.0;
          fo[0] = 0.0;
          fo[1] = 0.0;
          so[0] = this->Padding;
          so[1] = length / 2.0 - stringSize[1] / 2.0;
        }
      }

      // Reposition the origin of the balloon if it's off the renderer
      if (e[0] < 0)
      {
        e[0] = 0.0;
      }
      if (e[1] < 0)
      {
        e[1] = 0.0;
      }
      if ((e[0] + frameSize[0] + imageSize[0]) > size[0])
      {
        e[0] = size[0] - (frameSize[0] + imageSize[0]);
      }
      if ((e[1] + frameSize[1] + imageSize[1]) > size[1])
      {
        e[1] = size[1] - (frameSize[1] + imageSize[1]);
      }

      // Draw the text if visible
      if (this->TextVisible)
      {
        this->FramePoints->SetPoint(0, e[0] + fo[0], e[1] + fo[1], 0.0);
        this->FramePoints->SetPoint(1, e[0] + fo[0] + frameSize[0], e[1] + fo[1], 0.0);
        this->FramePoints->SetPoint(
          2, e[0] + fo[0] + frameSize[0], e[1] + fo[1] + frameSize[1], 0.0);
        this->FramePoints->SetPoint(3, e[0] + fo[0], e[1] + fo[1] + frameSize[1], 0.0);
        this->FramePoints->Modified();

        this->TextActor->SetPosition(e[0] + so[0], e[1] + so[1]);
      }

      // Place the texture
      if (this->ImageVisible)
      {
        this->Texture->SetInputData(this->BalloonImage);
        this->TexturePoints->SetPoint(0, e[0] + io[0], e[1] + io[1], 0.0);
        this->TexturePoints->SetPoint(1, e[0] + io[0] + imageSize[0], e[1] + io[1], 0.0);
        this->TexturePoints->SetPoint(
          2, e[0] + io[0] + imageSize[0], e[1] + io[1] + imageSize[1], 0.0);
        this->TexturePoints->SetPoint(3, e[0] + io[0], e[1] + io[1] + imageSize[1], 0.0);
        this->TexturePoints->Modified();
      }
    } // if something visible

    // Update the properties
    this->TextureActor->SetProperty(this->ImageProperty);
    this->FrameActor->SetProperty(this->FrameProperty);
    this->TextMapper->SetTextProperty(this->TextProperty);

    this->BuildTime.Modified();
  }
}

//------------------------------------------------------------------------------
void vtkBalloonRepresentation::ReleaseGraphicsResources(vtkWindow* w)
{
  this->Texture->ReleaseGraphicsResources(w);
  this->TextActor->ReleaseGraphicsResources(w);
  this->FrameActor->ReleaseGraphicsResources(w);
  this->TextureActor->ReleaseGraphicsResources(w);
}

//------------------------------------------------------------------------------
int vtkBalloonRepresentation::RenderOverlay(vtkViewport* v)
{
  int count = 0;
  this->BuildRepresentation();

  if (this->TextVisible)
  {
    count += this->FrameActor->RenderOverlay(v);
    count += this->TextActor->RenderOverlay(v);
  }

  if (this->ImageVisible)
  {
    vtkRenderer* ren = vtkRenderer::SafeDownCast(v);
    if (ren)
    {
      count += this->TextureActor->RenderOverlay(v);
    }
  }

  return count;
}

//------------------------------------------------------------------------------
int vtkBalloonRepresentation::ComputeInteractionState(int X, int Y, int)
{
  // Is it in the text region or the image region?
  double x0[3], x2[3];
  int origin[2] = { 0, 0 };
  if (this->Renderer)
  {
    origin[0] = (this->Renderer->GetOrigin())[0];
    origin[1] = (this->Renderer->GetOrigin())[1];
  }
  if (this->ImageVisible)
  {
    this->TexturePoints->GetPoint(0, x0);
    this->TexturePoints->GetPoint(2, x2);
    for (int i = 0; i < 2; ++i)
    {
      x0[i] += origin[i];
      x2[i] += origin[i];
    }
    if ((x0[0] <= X && X <= x2[0]) && (x0[1] <= Y && Y <= x2[1]))
    {
      return vtkBalloonRepresentation::OnImage;
    }
  }

  if (this->TextVisible)
  {
    this->FramePoints->GetPoint(0, x0);
    this->FramePoints->GetPoint(2, x2);
    for (int i = 0; i < 2; ++i)
    {
      x0[i] += origin[i];
      x2[i] += origin[i];
    }
    if ((x0[0] <= X && X <= x2[0]) && (x0[1] <= Y && Y <= x2[1]))
    {
      return vtkBalloonRepresentation::OnText;
    }
  }

  return vtkBalloonRepresentation::Outside;
}

//------------------------------------------------------------------------------
void vtkBalloonRepresentation::PrintSelf(ostream& os, vtkIndent indent)
{
  // Superclass typedef defined in vtkTypeMacro() found in vtkSetGet.h
  this->Superclass::PrintSelf(os, indent);

  os << indent << "Balloon Text: ";
  if (this->BalloonText)
  {
    os << this->BalloonText << "\n";
  }
  else
  {
    os << "(none)\n";
  }

  os << indent << "Balloon Image: ";
  if (this->BalloonImage)
  {
    os << this->BalloonImage << "\n";
  }
  else
  {
    os << "(none)\n";
  }

  os << indent << "Balloon Layout: ";
  switch (this->BalloonLayout)
  {
    case ImageLeft:
      os << "Image Left\n";
      break;
    case ImageRight:
      os << "Image Right\n";
      break;
    case ImageBottom:
      os << "Image Bottom\n";
      break;
    default:
      os << "Image Top\n";
  }

  os << indent << "Image Size: (" << this->ImageSize[0] << "," << this->ImageSize[1] << ")\n";
  os << indent << "Padding: " << this->Padding << "\n";
  os << indent << "Offset: (" << this->Offset[0] << "," << this->Offset[1] << ")\n";

  if (this->FrameProperty)
  {
    os << indent << "Frame Property:\n";
    this->FrameProperty->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << indent << "Frame Property: (none)\n";
  }

  if (this->ImageProperty)
  {
    os << indent << "Image Property:\n";
    this->ImageProperty->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << indent << "Image Property: (none)\n";
  }

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