/*=========================================================================
 *
 *  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.
 *
 *=========================================================================*/

#include "itkNumericTraits.h"
#include "itkTimeProbesCollectorBase.h"
#include "itkImage.h"

#include <iostream>
#include <fstream>

template <typename T>
void
TestTransformIndexToPhysicalPoint(T * image)
{
  typename T::IndexType index3D;
  typename T::PointType point3D;

  for (int k = 0; k < 10; ++k)
  {
    index3D[2] = k;
    for (int j = 0; j < 1000; ++j)
    {
      index3D[1] = j;
      for (int i = 0; i < 1000; ++i)
      {
        index3D[0] = i;
        image->TransformIndexToPhysicalPoint(index3D, point3D);
      }
    }
    if (k == 5)
    {
      std::cout << point3D << std::endl;
    }
  }
}
template <typename T>
void
TestTransformPhysicalPointToIndex(T * image)
{
  typename T::IndexType index3D;
  typename T::PointType point3D;

  for (int k = 0; k < 10; ++k)
  {
    point3D[2] = static_cast<typename itk::NumericTraits<typename T::PointType>::ValueType>(k);
    for (int j = 0; j < 1000; ++j)
    {
      point3D[1] = static_cast<typename itk::NumericTraits<typename T::PointType>::ValueType>(j);
      for (int i = 0; i < 1000; ++i)
      {
        point3D[0] = static_cast<typename itk::NumericTraits<typename T::PointType>::ValueType>(i);
        index3D = image->TransformPhysicalPointToIndex(point3D);
      }
    }
    if (k == 5)
    {
      std::cout << point3D << std::endl;
    }
  }
}
//-------------------------
//
//   This file test the interface to the TimeProbe classes
//
//-------------------------
int
itkTimeProbesTest(int, char *[])
{

  itk::TimeProbesCollectorBase collector;

  const unsigned int N = 1000L;
  const unsigned int M = 10000L;

  constexpr unsigned int iteration = 3;

  for (unsigned int it = 0; it < iteration; ++it)
  {
    collector.Start("Loop1"); // label that identify the range of the probe

    // Do slow stuff here...
    {
      for (unsigned int i = 0; i < N; ++i)
      {
        auto * dummy = new unsigned int[M];
        for (unsigned int j = 0; j < M; ++j)
        {
          dummy[j] = j;
        }
        delete[] dummy;
      }
    }
    collector.Stop("Loop1");

    collector.Start("Loop2"); // label that identify the range of the probe

    // Do other slow stuff here...
    {
      for (unsigned int i = 0; i < M; ++i)
      {
        auto * dummy = new unsigned int[N];
        for (unsigned int j = 0; j < N; ++j)
        {
          dummy[j] = j;
        }
        delete[] dummy;
      }
    }
    collector.Stop("Loop2");

    using Image3DType = itk::Image<float, 3>;

    auto image3D = Image3DType::New();
    auto orientedImage3D = Image3DType::New();

    using Region3DType = itk::ImageRegion<3>;
    using Size3DType = Region3DType::SizeType;
    Region3DType region3D;
    Size3DType   size3D = { { 1000, 1000, 1000 } };

    region3D.SetSize(size3D);
    collector.Start("i:TransformIndexToPhysicalPoint");
    TestTransformIndexToPhysicalPoint<Image3DType>(image3D);
    collector.Stop("i:TransformIndexToPhysicalPoint");

    collector.Start("i:TransformPhysicalPointToIndex");
    TestTransformPhysicalPointToIndex<Image3DType>(image3D);
    collector.Stop("i:TransformPhysicalPointToIndex");

    collector.Start("o:TransformIndexToPhysicalPoint");
    TestTransformIndexToPhysicalPoint<Image3DType>(orientedImage3D);
    collector.Stop("o:TransformIndexToPhysicalPoint");

    collector.Start("o:TransformPhysicalPointToIndex");
    TestTransformPhysicalPointToIndex<Image3DType>(orientedImage3D);
    collector.Stop("o:TransformPhysicalPointToIndex");
  }

  // Print a regular report (including nameOfProbe, Iteration, Total, Min, Mean, Max, and STD)
  std::cout << std::endl << "Print normal reports from all probes" << std::endl;
  collector.Report();

  // Print a regular report (including nameOfProbe, Iteration, Total, Min, Mean, Max, and STD)
  // of the probe of "Loop1"
  std::cout << std::endl << "Print a normal report of a specific probe" << std::endl;
  collector.Report("Loop1");

  // Print an expanded report (including nameOfProbe, Iteration, Total, Min, Mean-Min
  //                          Mean/Min *100 (%), Mean, Max, Max- Mean, Max/Mean(%),
  //                          Total Diff(:Max - Min) and STD)
  std::cout << std::endl << "Print expanded reports from all probes" << std::endl;
  collector.ExpandedReport();

  // Print an expanded report (including nameOfProbe, Iteration, Total, Min, Mean-Min
  //                          Mean/Min *100 (%), Mean, Max, Max- Mean, Max/Mean(%),
  //                          Total Diff(:Max - Min) and STD)
  std::cout << std::endl << "Print an expanded report of a specific probe" << std::endl;
  collector.ExpandedReport("o:TransformPhysicalPointToIndex");

  std::cout << std::endl << "Print a JSON report from all probes" << std::endl;
  collector.JSONReport();

  std::cout << std::endl << "Print a JSON report of a specific probe" << std::endl;
  collector.JSONReport("o:TransformPhysicalPointToIndex");

  // Test writing to an ostream
  std::ofstream logfile;
  logfile.open("itkTimeProbesTest.txt");
  collector.Report(logfile);
  logfile.close();

  std::ofstream jsonFile;
  jsonFile.open("itkTimeProbesTest.json");
  collector.JSONReport(jsonFile);
  jsonFile.close();

  // Print to the standard error
  std::cout << std::endl << "Print normal reports from all probes to the standard error" << std::endl;
  collector.Report(std::cerr);

  // Use tabs on the output
  collector.Report(std::cout, true, true, true);
  collector.ExpandedReport(std::cout, true, true, true);

  return EXIT_SUCCESS;
}
