// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

#include "vtkSVGExporter.h"

#include "vtkAbstractMapper.h"
#include "vtkBrush.h"
#include "vtkCellArray.h"
#include "vtkContext2D.h"
#include "vtkContextItem.h"
#include "vtkContextScene.h"
#include "vtkContextView.h"
#include "vtkFloatArray.h"
#include "vtkImageData.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkOpenGLContextDevice2D.h"
#include "vtkPen.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkPoints2D.h"
#include "vtkPolyData.h"
#include "vtkPolyLine.h"
#include "vtkRTAnalyticSource.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkSmartPointer.h"
#include "vtkTestingInteractor.h"
#include "vtkTextProperty.h"
#include "vtkTransform2D.h"
#include "vtkTriangle.h"
#include "vtkUnsignedCharArray.h"

#include <string>

//------------------------------------------------------------------------------
class ContextSVGTest : public vtkContextItem
{
  void SetSpritePoint(int x, int y, vtkImageData* sprite);

public:
  static ContextSVGTest* New();
  vtkTypeMacro(ContextSVGTest, vtkContextItem);
  // Paint event for the chart, called whenever the chart needs to be drawn
  bool Paint(vtkContext2D* painter) override;
};

void drawPolyLinePolyData(vtkContext2D* painter)
{
  // Setup points
  vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
  points->InsertNextPoint(50.0, 0.0, 0.0);
  points->InsertNextPoint(0.0, 0.0, 0.0);
  points->InsertNextPoint(0.0, 50.0, 0.0);
  points->InsertNextPoint(50.0, 0.0, 0.0);

  // Define some colors
  unsigned char black[4] = { 0, 0, 0, 255 };

  // Setup the colors array
  vtkSmartPointer<vtkUnsignedCharArray> colors = vtkSmartPointer<vtkUnsignedCharArray>::New();
  colors->SetNumberOfComponents(4);
  colors->SetName("Colors");

  // Add the three colors we have created to the array
  colors->InsertNextTypedTuple(black);
  colors->InsertNextTypedTuple(black);
  colors->InsertNextTypedTuple(black);
  colors->InsertNextTypedTuple(black);

  vtkNew<vtkCellArray> polylines;
  vtkNew<vtkPolyLine> polyline;
  polyline->GetPointIds()->SetNumberOfIds(4);
  polyline->GetPointIds()->SetId(0, 0);
  polyline->GetPointIds()->SetId(1, 1);
  polyline->GetPointIds()->SetId(2, 2);
  polyline->GetPointIds()->SetId(3, 3);
  polylines->InsertNextCell(polyline);

  // Create a polydata object and add everything to it
  vtkSmartPointer<vtkPolyData> polydata = vtkSmartPointer<vtkPolyData>::New();
  polydata->SetPoints(points);
  polydata->SetLines(polylines);
  painter->GetPen()->SetWidth(2.0);
  painter->DrawPolyData(475, 200, polydata, colors, VTK_SCALAR_MODE_USE_POINT_DATA);
}

int TestSVGContextExport(int, char*[])
{
  // Set up a 2D context view, context test object and add it to the scene
  vtkNew<vtkContextView> view;
  view->GetRenderer()->SetBackground(1.0, 1.0, 0.5);
  view->GetRenderer()->SetBackground2(1.0, 0.5, 1.0);
  view->GetRenderer()->SetBackgroundAlpha(0.5);
  view->GetRenderer()->GradientBackgroundOn();
  view->GetRenderWindow()->SetSize(800, 600);
  vtkNew<ContextSVGTest> test;
  view->GetScene()->AddItem(test);

  // Force the use of the freetype based rendering strategy
  vtkOpenGLContextDevice2D::SafeDownCast(view->GetContext()->GetDevice())
    ->SetStringRendererToFreeType();

  view->GetRenderWindow()->SetMultiSamples(0);
  view->GetRenderWindow()->Render();

  std::string filename =
    vtkTestingInteractor::TempDirectory + std::string("/TestSVGContextExport.svg");

  vtkNew<vtkSVGExporter> exp;
  exp->SetRenderWindow(view->GetRenderWindow());
  exp->SetFileName(filename.c_str());
  exp->Write();

#if 0
  vtkNew<vtkRenderWindowInteractor> iren;
  iren->SetRenderWindow(view->GetRenderWindow());
  view->GetRenderWindow()->GetInteractor()->Initialize();
  view->GetRenderWindow()->GetInteractor()->Start();
#endif

  return EXIT_SUCCESS;
}

// Make our new derived class to draw a diagram
vtkStandardNewMacro(ContextSVGTest);

// This function aims to test the primitives provided by the 2D API.
bool ContextSVGTest::Paint(vtkContext2D* painter)
{
  // Reset painter state that we care about:
  painter->GetBrush()->SetTexture(nullptr);
  painter->GetBrush()->SetColor(0, 0, 0, 255);
  painter->GetPen()->SetColor(0, 0, 0, 255);
  painter->GetPen()->SetWidth(1.f);
  painter->GetTextProp()->SetUseTightBoundingBox(1);
  painter->GetTextProp()->SetOrientation(0.);
  painter->GetTextProp()->SetVerticalJustificationToCentered();
  painter->GetTextProp()->SetJustificationToCentered();
  painter->GetTextProp()->SetColor(0.0, 0.0, 0.0);
  painter->GetTextProp()->SetOpacity(1.);
  painter->GetTextProp()->SetFontSize(24);
  painter->GetTextProp()->SetBold(0);
  painter->GetTextProp()->SetItalic(0);
  painter->GetTextProp()->SetFontFamilyToArial();

  // Now to draw some points
  painter->GetPen()->SetColor(0, 0, 255);
  painter->GetPen()->SetWidth(5.0);
  painter->DrawPoint(10, 10);
  painter->DrawPoint(790, 10);
  painter->DrawPoint(10, 590);
  painter->DrawPoint(790, 590);

  // Test the string drawing functionality of the context
  painter->DrawString(400, 25, "SVG is used as a backend to the context.");
  painter->GetTextProp()->SetFontFamilyToTimes();
  painter->GetTextProp()->SetColor(1, 0.2, 0.1);
  painter->GetTextProp()->SetOpacity(0.5);
  painter->GetTextProp()->SetOrientation(-38.);
  painter->GetTextProp()->SetJustificationToRight();
  painter->GetTextProp()->SetVerticalJustificationToCentered();
  painter->DrawString(475, 250, "Testing multi-\nline justified\nand rotated text.");

  drawPolyLinePolyData(painter);

  // Draw some individual lines of different thicknesses.
  for (int i = 0; i < 10; ++i)
  {
    painter->GetPen()->SetColor(255, i * 25, 0);
    painter->GetPen()->SetWidth(1.f + static_cast<float>(i));
    painter->DrawLine(10, 50 + float(i) * 10, 60, 50 + float(i) * 10);
  }

  // Draw some individual lines of different thicknesses.
  painter->GetPen()->SetWidth(10);
  for (int i = 0; i < 10; ++i)
  {
    painter->GetPen()->SetLineType(i % (vtkPen::DENSE_DOT_LINE + 1));
    painter->GetPen()->SetColor(static_cast<unsigned char>(float(i) * 25.0), 255, 128);
    painter->DrawLine(10, 250 + float(i) * 10, 60, 250 + float(i) * 10);
  }
  painter->GetPen()->SetLineType(vtkPen::SOLID_LINE);

  // Use the draw lines function now to draw a shape.
  vtkSmartPointer<vtkPoints2D> points = vtkSmartPointer<vtkPoints2D>::New();
  points->SetNumberOfPoints(30);
  for (int i = 0; i < 30; ++i)
  {
    double point[2] = { float(i) * 25.0 + 10.0, sin(float(i) / 5.0) * 100.0 + 200.0 };
    points->SetPoint(i, point);
  }
  painter->GetPen()->SetColor(0, 255, 0);
  painter->GetPen()->SetWidth(5.0);
  painter->DrawPoly(points);

  // Test the markers
  float markerPoints[10 * 2];
  unsigned char markerColors[10 * 4];
  for (int i = 0; i < 10; ++i)
  {
    markerPoints[2 * i] = 500.0 + i * 30.0;
    markerPoints[2 * i + 1] = 20 * sin(markerPoints[2 * i]) + 375.0;

    markerColors[4 * i] = static_cast<unsigned char>(255 * i / 10.0);
    markerColors[4 * i + 1] = static_cast<unsigned char>(255 * (1.0 - i / 10.0));
    markerColors[4 * i + 2] = static_cast<unsigned char>(255 * (0.3));
    markerColors[4 * i + 3] = static_cast<unsigned char>(255 * (1.0 - ((i / 10.0) * 0.25)));
  }

  for (int style = VTK_MARKER_NONE + 1; style < VTK_MARKER_UNKNOWN; ++style)
  {
    // Increment the y values:
    for (int i = 0; i < 10; ++i)
    {
      markerPoints[2 * i + 1] += 35.0;
    }
    painter->GetPen()->SetWidth(style * 5 + 5);
    // Not highlighted:
    painter->DrawMarkers(style, false, markerPoints, 10, markerColors, 4);
    // Highlight the middle 4 points.
    painter->GetPen()->SetColorF(0.9, 0.8, 0.1, 0.5);
    painter->DrawMarkers(style, true, markerPoints + 3 * 2, 4);
  }

  // Draw some points of different widths.
  for (int i = 0; i < 10; ++i)
  {
    painter->GetPen()->SetColor(0, static_cast<unsigned char>(float(i) * 25.0), 255, 255);
    painter->GetPen()->SetWidth(1.0 + float(i));
    painter->DrawPoint(75, 50 + float(i) * 10);
  }

  painter->GetPen()->SetColor(0, 0, 255);
  painter->GetPen()->SetWidth(3.0);
  painter->DrawPoints(points);

  painter->GetPen()->SetColor(100, 200, 255);
  painter->GetPen()->SetWidth(3.0);
  painter->GetBrush()->SetColor(100, 255, 100);
  painter->DrawRect(100, 50, 200, 100);

  // Add in an arbitrary quad
  painter->GetPen()->SetColor(159, 0, 255);
  painter->GetPen()->SetWidth(1.0);
  painter->GetBrush()->SetColor(100, 55, 0, 200);
  painter->DrawQuad(350, 50, 375, 150, 525, 199, 666, 45);

  // Now to test out the transform...
  vtkNew<vtkTransform2D> transform;
  transform->Translate(145, 385);
  transform->Scale(0.25, 0.25);
  transform->Rotate(-45.);
  painter->PushMatrix();
  painter->SetTransform(transform);
  painter->GetPen()->SetColor(255, 0, 0);
  painter->GetPen()->SetWidth(6.0);
  painter->DrawPoly(points);

  transform->Identity();
  transform->Translate(0, 10);
  painter->AppendTransform(transform);
  painter->GetPen()->SetColor(0, 0, 200);
  painter->GetPen()->SetWidth(2.0);
  painter->DrawPoints(points);

  transform->Identity();
  transform->Translate(0, -20);
  painter->AppendTransform(transform);
  painter->GetPen()->SetColor(200, 0, 100);
  painter->GetPen()->SetWidth(5.0);
  painter->DrawPoints(points);

  transform->Identity();
  transform->Translate(20, 200);
  painter->SetTransform(transform);
  painter->GetPen()->SetColor(0, 0, 0);
  painter->GetPen()->SetWidth(1.0);
  painter->GetBrush()->SetColor(0, 0, 100, 69);
  // Draws smooth path (Full circle, testing oddball angles):
  painter->DrawEllipseWedge(100.0, 89.0, 20, 100, 15, 75, -26.23, 333.77);
  painter->DrawEllipseWedge(100.0, 89.0, 15, 15, 0, 0, -26.23, 333.77);
  painter->DrawEllipseWedge(125.0, 89.0, 20, 100, 0, 0, -26.23, 333.77);
  // Partial circle, more odd angles:
  painter->DrawEllipseWedge(150.0, 89.0, 20, 100, 15, 75, 403.f, 541.f);
  painter->DrawEllipseWedge(150.0, 89.0, 15, 75, 0, 0, 181.f, 403.f);
  // Smooth path:
  painter->DrawEllipticArc(100.0, 89.0, 20, 100, 0, 360);
  painter->DrawEllipticArc(100.0, 89.0, 15, 15, 0, 360);
  // Partial path:
  painter->DrawEllipticArc(150.0, 89.0, 20, 100, 43, 181);

  // Remove the transform:
  painter->PopMatrix();

  // Toss some images in:
  vtkNew<vtkRTAnalyticSource> imageSrc;
  imageSrc->SetWholeExtent(0, 49, 0, 49, 0, 0);
  imageSrc->SetMaximum(1.0);
  imageSrc->Update();
  vtkImageData* image = imageSrc->GetOutput();

  // convert to RGB bytes:
  vtkFloatArray* vals = static_cast<vtkFloatArray*>(image->GetPointData()->GetScalars());
  float imgRange[2];
  vals->GetValueRange(imgRange);
  float invRange = 1.f / (imgRange[1] - imgRange[0]);
  vtkUnsignedCharArray* scalars = vtkUnsignedCharArray::New();
  scalars->SetNumberOfComponents(3);
  scalars->SetNumberOfTuples(vals->GetNumberOfTuples());
  for (vtkIdType i = 0; i < vals->GetNumberOfTuples(); ++i)
  {
    float val = vals->GetValue(i);
    val = (val - imgRange[0]) * invRange; // normalize to (0, 1)
    scalars->SetComponent(i, 0, val * 255);
    scalars->SetComponent(i, 1, (1.f - val) * 255);
    scalars->SetComponent(i, 2, (val * val) * 255);
  }
  image->GetPointData()->SetScalars(scalars);
  scalars->Delete();
  painter->DrawImage(10, 525, image);
  painter->DrawImage(65, 500, 2.f, image);
  painter->DrawImage(vtkRectf(170, 537.5f, 25, 25), image);

  // Test transparent text over geometry:
  painter->GetTextProp()->SetOrientation(0.);
  painter->GetTextProp()->SetFontSize(175);
  painter->GetTextProp()->SetColor(1., 0., 0.);
  painter->GetTextProp()->SetOpacity(0.25);
  painter->GetTextProp()->SetBold(1);
  painter->GetTextProp()->SetJustificationToCentered();
  painter->GetTextProp()->SetVerticalJustificationToCentered();
  painter->DrawString(600, 450, "T");

  // Test text alignment:
  float alignX = 600.f;
  float alignY = 250.f;
  float alignW = 100.f * 0.5f;
  float alignH = 50.f * 0.5f;
  painter->GetPen()->SetWidth(1.f);
  painter->GetPen()->SetColor(0, 0, 0, 255);
  painter->DrawLine(alignX, alignY - alignH, alignX, alignY + alignH);
  painter->DrawLine(alignX - alignW, alignY, alignX + alignW, alignY);
  painter->GetTextProp()->SetFontSize(32);
  painter->GetTextProp()->SetJustificationToRight();
  painter->GetTextProp()->SetVerticalJustificationToBottom();
  painter->DrawString(alignX, alignY, "dag");
  painter->GetTextProp()->SetJustificationToLeft();
  painter->GetTextProp()->SetVerticalJustificationToBottom();
  painter->DrawString(alignX, alignY, "dig");
  painter->GetTextProp()->SetJustificationToRight();
  painter->GetTextProp()->SetVerticalJustificationToTop();
  painter->DrawString(alignX, alignY, "dog");
  painter->GetTextProp()->SetJustificationToLeft();
  painter->GetTextProp()->SetVerticalJustificationToTop();
  painter->DrawString(alignX, alignY, "dug");
  // Centering:
  float rect[4];
  const char* centerString = "Center";
  painter->ComputeStringBounds(centerString, rect);
  rect[0] += 350.f;
  rect[1] += 550.f;
  painter->GetBrush()->SetColor(0, 0, 0, 0);
  painter->DrawRect(rect[0], rect[1], rect[2], rect[3]);
  painter->GetTextProp()->SetJustificationToCentered();
  painter->GetTextProp()->SetVerticalJustificationToCentered();
  painter->DrawString(rect[0] + rect[2] * 0.5f, rect[1] + rect[3] * 0.5f, centerString);

  // Texturing:
  vtkNew<vtkImageData> pattern;
  pattern->SetDimensions(6, 6, 1);
  vtkNew<vtkUnsignedCharArray> patternScalars;
  patternScalars->SetNumberOfComponents(3);
  patternScalars->SetNumberOfTuples(36);
  patternScalars->FillValue(0);
  for (vtkIdType row = 0; row < 6; ++row)
  {
    for (vtkIdType col = 0; col < 6; ++col)
    {
      // Alternate red/blue:
      vtkIdType i = row * 6 + col;
      vtkIdType test = row + col;
      patternScalars->SetTypedComponent(i, (test % 2 == 0) ? 0 : 2, 255);
    }
  }
  pattern->GetPointData()->SetScalars(patternScalars);
  painter->GetBrush()->SetTexture(pattern);
  painter->GetBrush()->SetOpacity(0);

  // Stretching:
  painter->GetBrush()->SetTextureProperties(vtkBrush::Nearest | vtkBrush::Stretch);
  painter->DrawQuad(200, 485, 300, 400, 190, 420, 125, 390);

  // Tiling:
  painter->GetBrush()->SetTextureProperties(vtkBrush::Linear | vtkBrush::Repeat);
  painter->DrawQuad(300, 585, 400, 500, 290, 520, 230, 560);

  painter->GetBrush()->SetTexture(nullptr);

  // Some point sprites:
  vtkNew<vtkImageData> sprite;
  sprite->SetDimensions(25, 25, 1);
  vtkNew<vtkUnsignedCharArray> spriteScalars;
  spriteScalars->SetNumberOfComponents(3);
  spriteScalars->SetNumberOfTuples(25 * 25);
  spriteScalars->FillValue(0);
  sprite->GetPointData()->SetScalars(spriteScalars);
  std::vector<float> spritePoints;
  spritePoints.reserve(50);
  std::vector<unsigned char> spriteColors;
  spriteColors.reserve(100);
  for (int i = 0; i < 25; ++i)
  {
    this->SetSpritePoint(i, 0, sprite);
    this->SetSpritePoint(0, i, sprite);
    this->SetSpritePoint(i, i, sprite);
    this->SetSpritePoint(10, i, sprite);
    this->SetSpritePoint(i, 10, sprite);
    spritePoints.push_back(790.f);                                           // x
    spritePoints.push_back(50.f + i * 20);                                   // y
    spriteColors.push_back(static_cast<unsigned char>(127 + 128 / (i + 1))); // r
    spriteColors.push_back(static_cast<unsigned char>(255 - 128 / (i + 1))); // g
    spriteColors.push_back(static_cast<unsigned char>(64 + 128 / (i + 1)));  // b
    spriteColors.push_back(static_cast<unsigned char>(64 + 191 / (i + 1)));  // a
  }
  for (int i = 0; i < 10; ++i)
  {
    this->SetSpritePoint(24 - i, i, sprite);
  }
  painter->GetPen()->SetWidth(18);
  painter->DrawPointSprites(sprite, spritePoints.data(), 25, spriteColors.data(), 4);

  painter->GetPen()->SetColor(0, 255, 0, 64);
  painter->GetPen()->SetWidth(1);
  painter->DrawLine(781, 0, 781, 600);
  painter->DrawLine(799, 0, 799, 600);

  // Leaving this untested, since I can't find a viewer that seems to implement
  // clipPaths...webkit seems to not support userSpaceOnUse clipPath coords,
  // and forces objectBoundBox coords.
#if 0
  // Test clipping (why does Context2D not expose these directly??)
  vtkContextDevice2D *dev = painter->GetDevice();
  std::array<int, 4> clipRect = { 325, 385, 150, 100 }; // xywh
  painter->GetPen()->SetColor(0, 128, 255, 255);
  painter->GetBrush()->SetColor(0, 128, 255, 64);
  painter->DrawQuad(clipRect[0],               clipRect[1],
                    clipRect[0] + clipRect[2], clipRect[1],
                    clipRect[0] + clipRect[2], clipRect[1] + clipRect[3],
                    clipRect[0],               clipRect[1] + clipRect[3]);

  dev->SetClipping(clipRect.data());
  dev->EnableClipping(true);
  painter->GetBrush()->SetColor(0, 255, 128, 128);
  painter->DrawWedge(clipRect[0] + clipRect[2] / 2.f,
                     clipRect[1] + clipRect[3] / 2.f,
                     75.f, 50.f, 0.f, 360.f);
  dev->EnableClipping(false);
#endif

  return true;
}

//------------------------------------------------------------------------------
void ContextSVGTest::SetSpritePoint(int x, int y, vtkImageData* sprite)
{
  unsigned char* ptr = static_cast<unsigned char*>(sprite->GetScalarPointer(x, y, 0));
  std::fill(ptr, ptr + 3, 255);
}
