/*=========================================================================
 *
 *  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 "itkListSample.h"
#include "itkSubsample.h"

int
itkSubsampleTest2(int, char *[])
{
  std::cout << "Subsample Test \n \n";

  using MeasurementVectorType = itk::Array<float>;

  using SampleType = itk::Statistics::ListSample<MeasurementVectorType>;

  constexpr SampleType::MeasurementVectorSizeType measurementVectorSize{ 3 };
  std::cerr << "Measurement vector size: " << measurementVectorSize << std::endl;

  constexpr unsigned int sampleSize{ 10 };

  auto sample = SampleType::New();

  sample->SetMeasurementVectorSize(measurementVectorSize);

  MeasurementVectorType mv(measurementVectorSize);
  for (unsigned int i = 0; i < sampleSize; ++i)
  {
    for (unsigned int j = 0; j < measurementVectorSize; ++j)
    {
      mv[j] = j + i * measurementVectorSize;
    }
    std::cout << "Adding measurement vector: " << mv << std::endl;
    sample->PushBack(mv);
  }

  // tests begin
  using SubsampleType = itk::Statistics::Subsample<SampleType>;
  auto subSample = SubsampleType::New();
  subSample->SetSample(sample);

  // Add measurement vectors in sample with even id number to subSample
  for (unsigned int i = 0; i < sample->Size(); i = i + 2)
  {
    subSample->AddInstance(i);
    std::cout << "Adding instance: " << i << " to subSample" << std::endl;
  }

  if (subSample->Size() != 5)
  {
    std::cerr << "Size of the subsample container should be 5" << std::endl;
    return EXIT_FAILURE;
  }

  for (unsigned int i = 0; i < subSample->Size(); ++i)
  {
    std::cout << "Measurement Vector: " << i << '\t' << subSample->GetMeasurementVector(i) << std::endl;

    if (subSample->GetMeasurementVector(i) != sample->GetMeasurementVector(i * 2))
    {
      std::cerr << "Subsampling is not correctly done!" << std::endl;
      return EXIT_FAILURE;
    }
  }

  using CascadedSubsampleType = itk::Statistics::Subsample<SubsampleType>;
  auto subSample2 = CascadedSubsampleType::New();
  subSample2->SetSample(subSample);

  // Add measurement vectors in subsample with even id number to subSample2
  for (unsigned int i = 0; i < subSample->Size(); i = i + 2)
  {
    std::cout << "Adding instance: " << i << " to subSample2" << std::endl;
    subSample2->AddInstance(i);
  }

  if (subSample2->Size() != 3)
  {
    std::cerr << "Size of the subsample2 container should be 3" << std::endl;
    return EXIT_FAILURE;
  }

  subSample2->Swap(0, 2);

  // swap back
  subSample2->Swap(2, 0);

  for (unsigned int i = 0; i < subSample2->Size(); ++i)
  {
    std::cout << "Measurement Vector: " << i << '\t' << subSample2->GetMeasurementVector(i) << std::endl;

    if (subSample2->GetMeasurementVector(i) != sample->GetMeasurementVector(i * 4))
    {
      std::cerr << "Subsampling of a subsample is not correctly done!" << std::endl;
      return EXIT_FAILURE;
    }
  }

  std::cout << subSample2->GetSample() << std::endl;

  using IteratorType = CascadedSubsampleType::Iterator;

  IteratorType iter = subSample2->Begin();
  while (iter != subSample2->End())
  {
    std::cout << iter.GetInstanceIdentifier() << ' ';
    std::cout << iter.GetMeasurementVector() << ' ';
    std::cout << iter.GetFrequency() << std::endl;
    ++iter;
  }

  using ConstIteratorType = CascadedSubsampleType::ConstIterator;

  IteratorType citer = subSample2->Begin();
  while (citer != subSample2->End())
  {
    std::cout << citer.GetInstanceIdentifier() << ' ';
    std::cout << citer.GetMeasurementVector() << ' ';
    std::cout << citer.GetFrequency() << std::endl;
    ++citer;
  }

  const IteratorType iter1 = subSample2->Begin();
  const IteratorType iter2 = subSample2->Begin();

  citer = iter1;

  if (citer != iter1)
  {
    std::cerr << "Error in iterator != operator " << std::endl;
    return EXIT_FAILURE;
  }


  if (!(citer == iter1))
  {
    std::cerr << "Error in iterator == operator " << std::endl;
    return EXIT_FAILURE;
  }


  if (iter1 != iter2)
  {
    std::cerr << "Error in iterator != operator " << std::endl;
    return EXIT_FAILURE;
  }


  if (!(iter1 == iter2))
  {
    std::cerr << "Error in iterator == operator " << std::endl;
    return EXIT_FAILURE;
  }


  //
  // Additional iterator tests
  //

  // Testing methods specific to Iterators
  {
    const IteratorType iter7 = subSample2->Begin();
    IteratorType       iter8 = subSample2->End();

    iter8 = iter7;
    if (iter8 != iter7)
    {
      std::cerr << "Iterator operator=() failed" << std::endl;
      return EXIT_FAILURE;
    }

    IteratorType iter3 = subSample2->Begin();
    if (iter3 != subSample2->Begin())
    {
      std::cerr << "Iterator constructor from sample failed" << std::endl;
      return EXIT_FAILURE;
    }

    unsigned int counter = 0;
    while (iter3 != subSample2->End())
    {
      ++iter3;
      counter++;
    }

    if (counter != subSample2->Size())
    {
      std::cerr << "Iterator walk failed" << std::endl;
      return EXIT_FAILURE;
    }

    const IteratorType iter4(iter8);
    if (iter4 != iter8)
    {
      std::cerr << "Iterator copy constructor failed" << std::endl;
      return EXIT_FAILURE;
    }

    const IteratorType iter5 = iter8;
    if (iter5 != iter8)
    {
      std::cerr << "Iterator operator= failed" << std::endl;
      return EXIT_FAILURE;
    }

    IteratorType           iter6(subSample2);
    constexpr unsigned int targetEntry{ 2 };
    for (unsigned int kk = 0; kk < targetEntry; ++kk)
    {
      std::cout << "GetInstanceIdentifier() = " << iter6.GetInstanceIdentifier() << std::endl;
      ++iter6;
    }

    if (iter6.GetInstanceIdentifier() != targetEntry)
    {
      std::cerr << "Iterator Constructor with instance identifier failed" << std::endl;
      std::cerr << "Expected identifier = " << targetEntry << std::endl;
      std::cerr << "identifier returned = " << iter6.GetInstanceIdentifier() << std::endl;
      return EXIT_FAILURE;
    }
  }

  // Testing methods specific to ConstIterators
  {
    const ConstIteratorType iter11 = subSample2->Begin();
    ConstIteratorType       iter12 = subSample2->End();

    iter12 = iter11;

    if (iter12 != iter11)
    {
      std::cerr << "ConstIterator operator!=() or operator=() failed" << std::endl;
      return EXIT_FAILURE;
    }

    if (!(iter12 == iter11))
    {
      std::cerr << "ConstIterator operator==() failed" << std::endl;
      return EXIT_FAILURE;
    }

    const ConstIteratorType iter3(iter12);
    if (iter3 != iter12)
    {
      std::cerr << "ConstIterator copy constructor failed" << std::endl;
      return EXIT_FAILURE;
    }

    const CascadedSubsampleType * constSample = subSample2.GetPointer();

    const ConstIteratorType iter4(constSample->Begin());
    const ConstIteratorType iter5(subSample2->Begin());
    if (iter4 != iter5)
    {
      std::cerr << "Constructor from const container Begin() differs from non-const Begin() " << std::endl;
      return EXIT_FAILURE;
    }

    const ConstIteratorType iter6(constSample);
    const ConstIteratorType iter7(subSample2);
    if (iter6 != iter7)
    {
      std::cerr << "ConstIterator Constructor from const container differs from non-const container" << std::endl;
      return EXIT_FAILURE;
    }

    const ConstIteratorType iter8(subSample2);
    if (iter8.GetInstanceIdentifier() != 0)
    {
      std::cerr << "Constructor with instance identifier 0 failed" << std::endl;
      return EXIT_FAILURE;
    }


    ConstIteratorType      iter9(subSample2);
    constexpr unsigned int targetEntry{ 2 };
    for (unsigned int kk = 0; kk < targetEntry; ++kk)
    {
      std::cout << "Instance identifier = " << iter9.GetInstanceIdentifier() << std::endl;
      ++iter9;
    }

    std::cout << "Instance identifier = " << iter9.GetInstanceIdentifier() << std::endl;
    MeasurementVectorType vector9a = iter9.GetMeasurementVector();
    MeasurementVectorType vector9b = subSample2->GetMeasurementVector(targetEntry);
    for (unsigned int kitr = 0; kitr < measurementVectorSize; ++kitr)
    {
      if (itk::Math::Absolute(vector9b[kitr] - vector9a[kitr]))
      {
        std::cerr << "Constructor with container followed by increments failed" << std::endl;
        std::cerr << "Expected " << vector9b << std::endl;
        std::cerr << "Received " << vector9a << std::endl;
        return EXIT_FAILURE;
      }
    }

    unsigned int      counter = 0;
    ConstIteratorType iter10(constSample);
    if (iter10 != constSample->Begin())
    {
      std::cerr << "ConstIterator constructor from sample failed" << std::endl;
      return EXIT_FAILURE;
    }


    while (iter10 != constSample->End())
    {
      ++iter10;
      counter++;
    }

    if (counter != constSample->Size())
    {
      std::cerr << "Iterator walk failed" << std::endl;
      return EXIT_FAILURE;
    }
  }

  return EXIT_SUCCESS;
}
