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

#include "vtkGenericAdaptorCell.h"
#include "vtkGenericAttribute.h"
#include "vtkGenericAttributeCollection.h"
#include "vtkGenericDataSet.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include <cassert>
#include <iostream>

using std::cout;

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkGeometricErrorMetric);

//------------------------------------------------------------------------------
vtkGeometricErrorMetric::vtkGeometricErrorMetric()
{
  this->AbsoluteGeometricTolerance = 1.0; // arbitrary positive value
  this->Relative = 0;                     // GetError() will return the square absolute error.
  this->SmallestSize = 1;
}

//------------------------------------------------------------------------------
vtkGeometricErrorMetric::~vtkGeometricErrorMetric() = default;

//------------------------------------------------------------------------------
// Description :
// Set the geometric accuracy with an absolute value.
// Subdivision will be required if the square distance is greater than
// value. For instance 0.01 will give better result than 0.1.
// \pre positive_value: value>0
void vtkGeometricErrorMetric::SetAbsoluteGeometricTolerance(double value)
{
  assert("pre: positive_value" && value > 0);
  this->Relative = 0;
  if (this->AbsoluteGeometricTolerance != value)
  {
    this->AbsoluteGeometricTolerance = value;
    this->Modified();
  }
}

//------------------------------------------------------------------------------
// Description :
// Set the geometric accuracy with a value relative to the bounding box of
// the dataset. Internally compute the absolute tolerance.
// For instance 0.01 will give better result than 0.1.
// \pre valid_range_value: value>0 && value<1
// \pre ds_exists: ds!=0
void vtkGeometricErrorMetric::SetRelativeGeometricTolerance(double value, vtkGenericDataSet* ds)
{
  assert("pre: valid_range_value" && value > 0 && value < 1);
  assert("pre: ds_exists" && ds != nullptr);

  double bounds[6];
  ds->GetBounds(bounds);
  double smallest;
  double length;
  smallest = bounds[1] - bounds[0];
  length = bounds[3] - bounds[2];
  if (length < smallest || smallest == 0.0)
  {
    smallest = length;
  }
  length = bounds[5] - bounds[4];
  if (length < smallest || smallest == 0.0)
  {
    smallest = length;
  }
  length = ds->GetLength();
  if (length < smallest || smallest == 0.0)
  {
    smallest = length;
  }
  if (smallest == 0)
  {
    smallest = 1;
  }
  double tmp = value * smallest;
  this->SmallestSize = smallest;
  std::cout << "this->SmallestSize=" << this->SmallestSize << endl;
  this->Relative = 1;
  tmp = tmp * tmp;

  if (this->AbsoluteGeometricTolerance != tmp)
  {
    this->AbsoluteGeometricTolerance = tmp;
    this->Modified();
  }
}

#define VTK_DISTANCE_LINE_POINT

//------------------------------------------------------------------------------
int vtkGeometricErrorMetric::RequiresEdgeSubdivision(
  double* leftPoint, double* midPoint, double* rightPoint,
#ifdef VTK_DISTANCE_LINE_POINT
  double vtkNotUsed(alpha)
#else
  double alpha
#endif
)
{
  assert("pre: leftPoint_exists" && leftPoint != nullptr);
  assert("pre: midPoint_exists" && midPoint != nullptr);
  assert("pre: rightPoint_exists" && rightPoint != nullptr);
  //  assert("pre: clamped_alpha" && alpha>0 && alpha<1); // or else true
  if (this->GenericCell->IsGeometryLinear())
  {
    // don't need to do anything:
    return 0;
  }
  // distance between the line (leftPoint,rightPoint) and the point midPoint.
#ifdef VTK_DISTANCE_LINE_POINT
  return this->Distance2LinePoint(leftPoint, rightPoint, midPoint) >
    this->AbsoluteGeometricTolerance;
#else
  // Interpolated point
  double interpolatedPoint[3];
  int i = 0;
  while (i < 3)
  {
    interpolatedPoint[i] = leftPoint[i] + alpha * (rightPoint[i] - leftPoint[i]);
    ++i;
  }
  return vtkMath::Distance2BetweenPoints(midPoint, interpolatedPoint) >
    this->AbsoluteGeometricTolerance;
#endif
}

//------------------------------------------------------------------------------
// Description:
// Return the error at the mid-point. The type of error depends on the state
// of the concrete error metric. For instance, it can return an absolute
// or relative error metric.
// See RequiresEdgeSubdivision() for a description of the arguments.
// \post positive_result: result>=0
double vtkGeometricErrorMetric::GetError(double* leftPoint, double* midPoint, double* rightPoint,
#ifdef VTK_DISTANCE_LINE_POINT
  double vtkNotUsed(alpha)
#else
  double alpha
#endif
)
{
  assert("pre: leftPoint_exists" && leftPoint != nullptr);
  assert("pre: midPoint_exists" && midPoint != nullptr);
  assert("pre: rightPoint_exists" && rightPoint != nullptr);
  //  assert("pre: clamped_alpha" && alpha>0 && alpha<1); // or else true
  if (this->GenericCell->IsGeometryLinear())
  {
    // don't need to do anything:
    return 0;
  }
  // distance between the line (leftPoint,rightPoint) and the point midPoint.
#ifdef VTK_DISTANCE_LINE_POINT
  double squareAbsoluteError = this->Distance2LinePoint(leftPoint, rightPoint, midPoint);
#else
  // Interpolated point
  double interpolatedPoint[3];
  int i = 0;
  while (i < 3)
  {
    interpolatedPoint[i] = leftPoint[i] + alpha * (rightPoint[i] - leftPoint[i]);
    ++i;
  }
  double squareAbsoluteError = vtkMath::Distance2BetweenPoints(midPoint, interpolatedPoint);
#endif
  if (this->Relative)
  {
    return sqrt(squareAbsoluteError) / this->SmallestSize;
  }
  else
  {
    return squareAbsoluteError;
  }
}

//------------------------------------------------------------------------------
// Description:
// Return the type of output of GetError()
int vtkGeometricErrorMetric::GetRelative()
{
  return this->Relative;
}

//------------------------------------------------------------------------------
// Description:
// Square distance between a straight line (defined by points x and y)
// and a point z. Property: if x and y are equal, the line is a point and
// the result is the square distance between points x and z.
double vtkGeometricErrorMetric::Distance2LinePoint(double x[3], double y[3], double z[3])
{
  double u[3];
  double v[3];
  double w[3];

  u[0] = y[0] - x[0];
  u[1] = y[1] - x[1];
  u[2] = y[2] - x[2];

  vtkMath::Normalize(u);

  v[0] = z[0] - x[0];
  v[1] = z[1] - x[1];
  v[2] = z[2] - x[2];

  double dot = vtkMath::Dot(u, v);

  w[0] = v[0] - dot * u[0];
  w[1] = v[1] - dot * u[1];
  w[2] = v[2] - dot * u[2];

  return vtkMath::Dot(w, w);
}

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

  os << indent << "AbsoluteGeometricTolerance: " << this->AbsoluteGeometricTolerance << endl;
}
VTK_ABI_NAMESPACE_END
