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

#include "vtkCellArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPolyData.h"
#include "vtkSpherePuzzle.h"

#include <cmath>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkSpherePuzzleArrows);

//------------------------------------------------------------------------------
// Construct a new puzzle.
vtkSpherePuzzleArrows::vtkSpherePuzzleArrows()
{
  int idx;

  for (idx = 0; idx < 32; ++idx)
  {
    this->Permutation[idx] = idx;
  }

  this->Radius = 0.51;

  this->SetNumberOfInputPorts(0);
}

//------------------------------------------------------------------------------
// Destruct the puzzle.
vtkSpherePuzzleArrows::~vtkSpherePuzzleArrows() = default;

//------------------------------------------------------------------------------
void vtkSpherePuzzleArrows::SetPermutationComponent(int comp, int val)
{
  if (this->Permutation[comp] == val)
  {
    return;
  }

  this->Permutation[comp] = val;
  this->Modified();
}

//------------------------------------------------------------------------------
int vtkSpherePuzzleArrows::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
{
  // get the info object
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

  // get the output
  vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkPoints* pts = vtkPoints::New();
  vtkCellArray* polys = vtkCellArray::New();
  int idx;

  for (idx = 0; idx < 32; ++idx)
  {
    if (this->CheckAbort())
    {
      break;
    }
    if (this->Permutation[idx] != idx)
    {
      // this->AppendArrow(idx, this->Permutation[idx], pts, polys);
      this->AppendArrow(this->Permutation[idx], idx, pts, polys);
    }
  }
  output->SetPoints(pts);
  output->SetPolys(polys);
  pts->Delete();
  polys->Delete();

  return 1;
}

//------------------------------------------------------------------------------
// Draw an arrow for piece with id1 to piece with id2.
void vtkSpherePuzzleArrows::AppendArrow(int id1, int id2, vtkPoints* pts, vtkCellArray* polys)
{
  double phi1, phi2, dPhi;
  double theta1, theta2, dTheta;
  double phi = 0, theta = 0, phiOff, thetaOff;
  double length;
  double x, y, z;
  int num, idx;
  vtkIdType ptId1, ptId2, ptId3, ptId4, ptId5;

  // Convert the start and end piece ids into sphere coordinates.
  phi1 = vtkMath::Pi() * ((id1 / 8) + 0.5) / 4.0;
  theta1 = vtkMath::Pi() * ((id1 % 8) + 0.5) / 4.0;
  phi2 = vtkMath::Pi() * ((id2 / 8) + 0.5) / 4.0;
  theta2 = vtkMath::Pi() * ((id2 % 8) + 0.5) / 4.0;
  dPhi = phi2 - phi1;
  dTheta = theta2 - theta1;
  // Take the short way around.
  while (dPhi > vtkMath::Pi())
  {
    dPhi -= 2 * vtkMath::Pi();
  }
  while (dPhi < -vtkMath::Pi())
  {
    dPhi += 2 * vtkMath::Pi();
  }
  while (dTheta > vtkMath::Pi())
  {
    dTheta -= 2 * vtkMath::Pi();
  }
  while (dTheta < -vtkMath::Pi())
  {
    dTheta += 2 * vtkMath::Pi();
  }
  theta2 = theta1 + dTheta;
  phi2 = phi1 + dPhi;

  // Compute the length (world coords).
  length = dTheta * sin(0.5 * (phi1 + phi2));
  length = sqrt(length * length + dPhi * dPhi);
  // How many division do we need.
  num = (int)(length / 0.1);

  // Compute the perpendicular phi theta step.
  thetaOff = dPhi;
  phiOff = -dTheta;
  // Normalize (sphere coords).
  length = sqrt(thetaOff * thetaOff + phiOff * phiOff);
  phiOff = 0.08 * phiOff / length;
  thetaOff = 0.08 * thetaOff / length;

  // First point.
  x = cos(theta1 + thetaOff) * sin(phi1 + phiOff);
  y = sin(theta1 + thetaOff) * sin(phi1 + phiOff);
  z = cos(phi1 + phiOff);
  ptId1 = pts->InsertNextPoint(this->Radius * x, this->Radius * y, this->Radius * z);
  x = cos(theta1 + 2 * thetaOff) * sin(phi1 + 2 * phiOff);
  y = sin(theta1 + 2 * thetaOff) * sin(phi1 + 2 * phiOff);
  z = cos(phi1 + 2 * phiOff);
  ptId2 = pts->InsertNextPoint(this->Radius * x, this->Radius * y, this->Radius * z);
  for (idx = 1; idx < num; ++idx)
  {
    // Interpolate angles.
    theta = theta1 + ((double)(idx) / (double)(num)) * dTheta;
    phi = phi1 + ((double)(idx) / (double)(num)) * (phi2 - phi1);
    x = cos(theta + thetaOff) * sin(phi + phiOff);
    y = sin(theta + thetaOff) * sin(phi + phiOff);
    z = cos(phi + phiOff);
    ptId3 = pts->InsertNextPoint(this->Radius * x, this->Radius * y, this->Radius * z);
    x = cos(theta + 2 * thetaOff) * sin(phi + 2 * phiOff);
    y = sin(theta + 2 * thetaOff) * sin(phi + 2 * phiOff);
    z = cos(phi + 2 * phiOff);
    ptId4 = pts->InsertNextPoint(this->Radius * x, this->Radius * y, this->Radius * z);
    // Create the rectangle.
    polys->InsertNextCell(4);
    polys->InsertCellPoint(ptId1);
    polys->InsertCellPoint(ptId2);
    polys->InsertCellPoint(ptId4);
    polys->InsertCellPoint(ptId3);
    // Initialize the next step.
    ptId1 = ptId3;
    ptId2 = ptId4;
  }
  // Now create the arrow.
  x = cos(theta) * sin(phi);
  y = sin(theta) * sin(phi);
  z = cos(phi);
  ptId3 = pts->InsertNextPoint(this->Radius * x, this->Radius * y, this->Radius * z);
  x = cos(theta + 3 * thetaOff) * sin(phi + 3 * phiOff);
  y = sin(theta + 3 * thetaOff) * sin(phi + 3 * phiOff);
  z = cos(phi + 3 * phiOff);
  ptId4 = pts->InsertNextPoint(this->Radius * x, this->Radius * y, this->Radius * z);
  x = cos(theta2 + 1.5 * thetaOff) * sin(phi2 + 1.5 * phiOff);
  y = sin(theta2 + 1.5 * thetaOff) * sin(phi2 + 1.5 * phiOff);
  z = cos(phi2 + 1.5 * phiOff);
  ptId5 = pts->InsertNextPoint(this->Radius * x, this->Radius * y, this->Radius * z);
  polys->InsertNextCell(5);
  polys->InsertCellPoint(ptId5);
  polys->InsertCellPoint(ptId4);
  polys->InsertCellPoint(ptId2);
  polys->InsertCellPoint(ptId1);
  polys->InsertCellPoint(ptId3);
}

//------------------------------------------------------------------------------
void vtkSpherePuzzleArrows::SetPermutation(vtkSpherePuzzle* puz)
{
  this->SetPermutation(puz->GetState());
}

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

  os << indent << "Permutation: ";
  for (i = 0; i < 32; ++i)
  {
    os << this->Permutation[i] << " ";
  }
  os << endl;
}
VTK_ABI_NAMESPACE_END
