/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/

#ifndef ITK_LEGACY_REMOVE
#  define ITK_LEGACY_TEST
#endif
#include <iostream>
#include "itkBoundingBox.h"
#include "itkMath.h"

int
itkBoundingBoxTest(int, char *[])
{
  // Test out the bounding box code

  using BB = itk::BoundingBox<unsigned long, 1, double>;
  auto myBox = BB::New();

  const BB::PointsContainerPointer Points = BB::PointsContainer::New();

  itk::Point<double, 1> P;

  std::cout << "Testing Bounding Box" << std::endl;
  {
    const BB::BoundsArrayType & bounds = myBox->GetBounds();
    for (unsigned int i = 0; i < bounds.Size(); ++i)
    {
      if (itk::Math::NotExactlyEquals(bounds[i], BB::CoordinateType{}))
      {
        std::cerr << "Bounding Box initialization test failed" << std::endl;
        std::cerr << bounds << std::endl;
        return EXIT_FAILURE;
      }
    }
  }
  std::cout << "Null GetBoundingBox test passed" << std::endl;

  {
    BB::PointType center = myBox->GetCenter();
    for (unsigned int i = 0; i < 1; ++i)
    {
      if (itk::Math::NotExactlyEquals(center[i], BB::CoordinateType{}))
      {
        std::cerr << "Empty Box GetCenter initialization test failed" << std::endl;
        return EXIT_FAILURE;
      }
    }
    std::cout << "Null GetCenter test passed" << std::endl;
  }


  if (itk::Math::NotExactlyEquals(myBox->GetDiagonalLength2(), 0.0))
  {
    return EXIT_FAILURE;
  }
  std::cout << "Null GetDiagonalLength2 test passed" << std::endl;

  if (myBox->GetPoints())
  {
    return EXIT_FAILURE;
  }
  std::cout << "Null GetPoints test passed" << std::endl;


  for (unsigned int i = 0; i < 10; ++i)
  {
    P[0] = static_cast<double>(i);
    Points->InsertElement(i, P);
  }
  std::cout << "Insert points passed" << std::endl;

  myBox->SetPoints(Points);
  if (!myBox->ComputeBoundingBox())
  {
    return EXIT_FAILURE;
  }
  std::cout << "Compute Bounding Box passed" << std::endl;

  // Now we should have something
  {
    const BB::BoundsArrayType & bounds = myBox->GetBounds();
    if ((bounds[0] != 0.0) || (bounds[1] != 9.0))
    {
      std::cerr << "Bounding Box initialization test failed" << std::endl;
      std::cerr << bounds << std::endl;
      return EXIT_FAILURE;
    }
    std::cout << "GetBoundingBox passed" << std::endl;
  }


  {
    BB::PointType center = myBox->GetCenter();
    for (unsigned int i = 0; i < 1; ++i)
    {
      if (center[i] != 4.5)
      {
        std::cerr << "Empty Box GetCenter initialization test failed" << std::endl;
        return EXIT_FAILURE;
      }
    }
    std::cout << "Null GetCenter test passed" << std::endl;
  }

  const itk::NumericTraits<double>::AccumulateType diagonal = myBox->GetDiagonalLength2();
  if (diagonal != 81.0)
  {
    return EXIT_FAILURE;
  }
  std::cout << "GetDiagonalLength2 passed" << std::endl;

  const BB::PointsContainerConstPointer NewPoints = myBox->GetPoints();

  // End with a Print.
  myBox->Print(std::cout);


  // Test the IsInside method in 3D
  std::cout << " Some Testing in 3D " << std::endl;

  using CC = itk::BoundingBox<unsigned long, 3, double>;
  auto my3DBox = CC::New();

  const CC::PointsContainerPointer Points3D = CC::PointsContainer::New();

  constexpr CC::PointType::ValueType qval1[3]{ -1.0f, -1.0f, -1.0f };
  CC::PointType                      Q = qval1;
  Points3D->InsertElement(0, Q);

  CC::PointType::ValueType qval2[3] = { 1.0f, 1.0f, 1.0f };
  Q = qval2;
  Points3D->InsertElement(1, Q);
  std::cout << "Insert points passed" << std::endl;

  my3DBox->SetPoints(Points3D);
  if (!my3DBox->ComputeBoundingBox())
  {
    return EXIT_FAILURE;
  }
  std::cout << "Compute Bounding Box passed" << std::endl;

  CC::PointType::ValueType qval3[3] = { 0.0f, 0.0f, 0.0f };
  Q = qval3;
  if (!my3DBox->IsInside(Q))
  {
    std::cerr << "Point " << Q << " Should be reported inside " << std::endl;
    return EXIT_FAILURE;
  }

  CC::PointType::ValueType qval4[3] = { 2.0f, 0.0f, 0.0f };
  Q = qval4;
  if (my3DBox->IsInside(Q))
  {
    std::cerr << "Point " << Q << " Should be reported outside " << std::endl;
    return EXIT_FAILURE;
  }

  // Testing the corners
  std::cout << "Testing ComputeCorners() : ";
  const auto   corners = my3DBox->ComputeCorners();
  auto         it = corners.begin();
  unsigned int j = 0;
  while (it != corners.end())
  {
    for (unsigned int i = 0; i < 3; ++i)
    {
      if ((*it)[i] !=
          std::pow(-1.0, static_cast<double>(j / (static_cast<int>(std::pow(2.0, static_cast<double>(i)))))))
      {
        std::cout << "[FAILED]" << std::endl;
        return EXIT_FAILURE;
      }
    }
    j++;
    ++it;
  }
  std::cout << "[PASSED]" << std::endl;

  // Testing the DeepCopy method
  {
    constexpr double            tolerance{ 1e-10 };
    const CC::Pointer           clone = my3DBox->DeepCopy();
    const CC::BoundsArrayType & originalBounds = my3DBox->GetBounds();
    const CC::BoundsArrayType & clonedbounds = clone->GetBounds();
    for (unsigned int i = 0; i < originalBounds.Size(); ++i)
    {
      if (itk::Math::Absolute(originalBounds[i] - clonedbounds[i]) > tolerance)
      {
        std::cerr << "Cloning test failed" << std::endl;
        std::cerr << originalBounds << std::endl;
        std::cerr << clonedbounds << std::endl;
        return EXIT_FAILURE;
      }
    }
  }

#ifndef ITK_LEGACY_REMOVE
  // Expect four corners for a two-dimensional box:
  const auto boundingBox_2D = itk::BoundingBox<itk::IdentifierType, 2>::New();
  if (boundingBox_2D->GetCorners()->size() != 4)
  {
    std::cerr << "Wrong number of corners for 2D box (expects 4):" << boundingBox_2D->GetCorners()->size() << std::endl;
    return EXIT_FAILURE;
  }

  // Expect eight corners for a three-dimensional box:
  const auto boundingBox_3D = itk::BoundingBox<itk::IdentifierType, 3>::New();
  if (boundingBox_3D->GetCorners()->size() != 8)
  {
    std::cerr << "Wrong number of corners for 3D box (expects 8):" << boundingBox_3D->GetCorners()->size() << std::endl;
    return EXIT_FAILURE;
  }
#endif
  std::cout << "BoundingBox test PASSED ! " << std::endl;
  return EXIT_SUCCESS;
}
