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

int
itkListSampleTest(int argc, char * argv[])
{
  std::cerr << "ListSample Test \n \n";
  if (argc < 2)
  {
    std::cerr << "itkListSampleTest LengthOfMeasurementVector" << std::endl;
  }

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

  SampleType::MeasurementVectorSizeType measurementVectorSize = std::stoi(argv[1]);
  std::cerr << "Measurement vector size: " << measurementVectorSize << std::endl;

  unsigned int sampleSize = 25;

  auto sample = SampleType::New();

  sample->SetMeasurementVectorSize(measurementVectorSize);

  MeasurementVectorType mv(measurementVectorSize);

  std::cerr << "Sample length = " << sample->GetMeasurementVectorSize() << std::endl;
  std::cerr << "Vector length = " << itk::NumericTraits<MeasurementVectorType>::GetLength(mv) << std::endl;

  for (unsigned int i = 0; i < sampleSize; ++i)
  {
    for (unsigned int j = 0; j < measurementVectorSize; ++j)
    {
      mv[j] = rand() / (RAND_MAX + 1.0);
    }
    sample->PushBack(mv);
  }

  // Try to push a measurement vector size different from what is set
  MeasurementVectorType mvLargerSize(measurementVectorSize + 1);

  for (unsigned int j = 0; j <= measurementVectorSize; ++j)
  {
    mvLargerSize[j] = rand() / (RAND_MAX + 1.0);
  }

  try
  {
    sample->PushBack(mvLargerSize);
    std::cerr
      << "Exception was expected since the vector that was added to the list has size different from what is set"
      << std::endl;
    return EXIT_FAILURE;
  }
  catch (const itk::ExceptionObject & excp)
  {
    std::cerr << "Caught expected exception: " << excp << std::endl;
  }

  // tests begin

  //
  // general interface
  //
  std::cerr << "General interface..." << std::endl;
  std::cerr << "Trying Size()...";
  if (sampleSize != sample->Size())
  {
    std::cerr << "FAILED" << std::endl;
    return EXIT_FAILURE;
  }
  else
  {
    std::cerr << "PASSED" << std::endl;
  }
  std::cerr << "Trying GetMeasurementVectorSize()...";
  if (sample->GetMeasurementVectorSize() != measurementVectorSize)
  {
    std::cerr << "GetMeasurementVectorSize() failed" << std::endl;
    return EXIT_FAILURE;
  }
  else
  {
    std::cerr << "PASSED" << std::endl;
  }

  // get and set measurements
  std::cerr << "Trying GetMeasurementVector(4)..." << std::endl;
  mv = sample->GetMeasurementVector(4);
  if (mv != sample->GetMeasurementVector(4))
  {
    std::cerr << "FAILED" << std::endl;
    return EXIT_FAILURE;
  }
  else
  {
    std::cerr << "PASSED" << std::endl;
  }

  std::cerr << "Trying SetMeasurementVector(4,mv)...";
  float tmp = mv[0];
  mv[0] += 1.0;
  sample->SetMeasurementVector(4, mv);
  if (mv != sample->GetMeasurementVector(4))
  {
    std::cerr << "FAILED" << std::endl;
    return EXIT_FAILURE;
  }
  else
  {
    std::cerr << "PASSED" << std::endl;
  }

  std::cerr << "Trying SetMeasurement(4,0,tmp)...";
  mv[0] = tmp;
  sample->SetMeasurement(4, 0, tmp);
  if (mv != sample->GetMeasurementVector(4))
  {
    std::cerr << "FAILED" << std::endl;
    return EXIT_FAILURE;
  }
  else
  {
    std::cerr << "PASSED" << std::endl;
  }

  // frequency
  std::cerr << "Trying GetTotalFrequency...";
  if (sample->GetTotalFrequency() != sampleSize)
  {
    std::cerr << "FAILED" << std::endl;
    return EXIT_FAILURE;
  }
  else
  {
    std::cerr << "PASSED" << std::endl;
  }

  //
  // iterator tests
  //
  std::cerr << "Iterators..." << std::endl;
  {
    // forward iterator
    std::cerr << "Trying Iterator::Copy Constructor...";
    using IteratorType = SampleType::Iterator;

    IteratorType s_iter = sample->Begin();

    // copy constructor
    IteratorType bs_iter(s_iter);
    if (bs_iter != s_iter)
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }

    // assignment operator
    std::cerr << "Trying Iterator::assignment operator...";
    IteratorType assignment_iter(bs_iter);
    assignment_iter = s_iter;
    if (assignment_iter != s_iter)
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }

    SampleType::InstanceIdentifier id = 0;
    while (s_iter != sample->End())
    {
      std::cerr << "Trying Iterator::GetMeasurementVector (forward)...";
      if (sample->GetMeasurementVector(id) != s_iter.GetMeasurementVector())
      {
        std::cerr << "FAILED" << std::endl;
        return EXIT_FAILURE;
      }
      else
      {
        std::cerr << "PASSED" << std::endl;
      }
      std::cerr << "Trying Iterator::GetInstanceIdentifier (forward)...";
      if (id != s_iter.GetInstanceIdentifier())
      {
        std::cerr << "FAILED" << std::endl;
        return EXIT_FAILURE;
      }
      else
      {
        std::cerr << "PASSED" << std::endl;
      }
      std::cerr << "Trying Iterator::GetFrequency (forward)...";
      if (s_iter.GetFrequency() != 1)
      {
        std::cerr << "FAILED" << std::endl;
        return EXIT_FAILURE;
      }
      else
      {
        std::cerr << "PASSED" << std::endl;
      }
      std::cerr << "Trying GetFrequency (forward)...";
      if (sample->GetFrequency(id) != 1)
      {
        std::cerr << "FAILED" << std::endl;
        return EXIT_FAILURE;
      }
      else
      {
        std::cerr << "PASSED" << std::endl;
      }
      ++id;
      ++s_iter;
    }

    std::cerr << "Trying Iterator::End (forward)...";
    if (s_iter != sample->End())
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }
  }

  // ConstIterator test
  std::cerr << "Const Iterators..." << std::endl;
  {
    std::cerr << "Trying Iterator::Copy Constructor (from const)...";
    // forward iterator
    using ConstIteratorType = SampleType::ConstIterator;

    ConstIteratorType s_iter = sample->Begin();

    // copy constructor
    ConstIteratorType bs_iter(s_iter);
    if (bs_iter != s_iter)
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }

    // assignment operator
    std::cerr << "Trying Const Iterator::operator= ()...";
    ConstIteratorType assignment_iter(bs_iter);
    assignment_iter = s_iter;
    if (assignment_iter != s_iter)
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }

    // copy from non-const iterator
    std::cerr << "Trying Iterator::Copy Constructor (from non-const)...";
    SampleType::Iterator      nonconst_iter = sample->Begin();
    SampleType::ConstIterator s2_iter(nonconst_iter);
    if (s2_iter != s_iter)
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }
    // assignment from non-const iterator
    std::cerr << "Trying Iterator::assignment (from non-const)...";
    s2_iter = nonconst_iter;
    if (s2_iter != s_iter)
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }

    SampleType::InstanceIdentifier id = 0;
    while (s_iter != sample->End())
    {
      std::cerr << "Trying Iterator::GetMeasurementVector (forward)...";
      if (sample->GetMeasurementVector(id) != s_iter.GetMeasurementVector())
      {
        std::cerr << "FAILED" << std::endl;
        return EXIT_FAILURE;
      }
      else
      {
        std::cerr << "PASSED" << std::endl;
      }

      std::cerr << "Trying Iterator::GetInstanceIdentifier (forward)...";
      if (id != s_iter.GetInstanceIdentifier())
      {
        std::cerr << "FAILED" << std::endl;
        return EXIT_FAILURE;
      }
      else
      {
        std::cerr << "PASSED" << std::endl;
      }
      std::cerr << "Trying Iterator::GetFrequency (forward)...";
      if (s_iter.GetFrequency() != 1)
      {
        std::cerr << "FAILED" << std::endl;
        return EXIT_FAILURE;
      }
      else
      {
        std::cerr << "PASSED" << std::endl;
      }
      ++id;
      ++s_iter;
    }

    std::cerr << "Trying Iterator::End (forward)...";
    if (s_iter != sample->End())
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }
  }


  //
  // resizing
  std::cerr << "Trying Clear()...";
  sample->Clear();
  if (sample->Size() != 0)
  {
    std::cerr << "FAILED" << std::endl;
    return EXIT_FAILURE;
  }
  else
  {
    std::cerr << "PASSED" << std::endl;
  }

  std::cerr << "Trying Resize()...";
  sample->Resize(sampleSize);
  if (sample->Size() != sampleSize)
  {
    std::cerr << "FAILED" << std::endl;
    return EXIT_FAILURE;
  }
  else
  {
    std::cerr << "PASSED" << std::endl;
  }


  // Test a VariableSizeVector
  using VariableSizeMeasurementVectorType = itk::VariableLengthVector<float>;

  using VariableSizeListSampleType = itk::Statistics::ListSample<VariableSizeMeasurementVectorType>;

  auto variableSizeSample = VariableSizeListSampleType::New();

  constexpr unsigned int initialSize = 19;
  variableSizeSample->SetMeasurementVectorSize(initialSize);

  unsigned int returnedSize = variableSizeSample->GetMeasurementVectorSize();

  if (initialSize != returnedSize)
  {
    std::cerr << "Error in Get/SetMeasurementVectorSize() " << std::endl;
    return EXIT_FAILURE;
  }

  VariableSizeMeasurementVectorType variableLengthVector;
  constexpr unsigned int            newsize = 42;
  variableLengthVector.SetSize(newsize);

  variableSizeSample->Clear();
  variableSizeSample->SetMeasurementVectorSize(newsize);
  variableSizeSample->PushBack(variableLengthVector);

  // Attempt to resize a non-empty ListSample should throw an exception.
  try
  {
    variableSizeSample->SetMeasurementVectorSize(initialSize);
    std::cerr << "Failed to throw expected exception in SetMeasurementVectorSize() " << std::endl;
    return EXIT_FAILURE;
  }
  catch (const itk::ExceptionObject & excp)
  {
    std::cerr << "Caught expected exception: " << excp << std::endl;
  }


  // Now, verify that it can be changed
  constexpr unsigned int initialSize2 = 37;
  variableSizeSample->Clear();
  variableSizeSample->SetMeasurementVectorSize(initialSize2);

  const unsigned int returnedSize2 = variableSizeSample->GetMeasurementVectorSize();

  if (initialSize2 != returnedSize2)
  {
    std::cerr << "Error in Get/SetMeasurementVectorSize() " << std::endl;
    std::cerr << "expected " << initialSize2 << std::endl;
    std::cerr << "but got  " << returnedSize2 << std::endl;
    return EXIT_FAILURE;
  }


  // Exercise the exception throwing when requesting
  // an element id that is outside the list range.
  const unsigned int largestId = sample->Size();
  bool               exceptionWorks = false;
  try
  {
    MeasurementVectorType measurement = sample->GetMeasurementVector(largestId + 10);
    std::cerr << measurement << std::endl;
  }
  catch (const itk::ExceptionObject & excp)
  {
    std::cerr << "Caught expected exception: " << excp << std::endl;
    exceptionWorks = true;
  }

  if (!exceptionWorks)
  {
    std::cerr << "GetMeasurementVector() exception failed !";
    std::cerr << std::endl;
    return EXIT_FAILURE;
  }

  const double outOfRangeFrequency = sample->GetFrequency(largestId + 10);

  if (outOfRangeFrequency != 0.0)
  {
    std::cerr << "GetFrequency() failed for out of range Id";
    std::cerr << std::endl;
    return EXIT_FAILURE;
  }

  //
  // Additional iterator tests
  //
  sample->Clear();
  MeasurementVectorType mvt(measurementVectorSize);
  for (unsigned int i = 0; i < sampleSize; ++i)
  {
    for (unsigned int j = 0; j < measurementVectorSize; ++j)
    {
      mvt[j] = j + i * i;
    }
    sample->PushBack(mv);
  }


  // Testing methods specific to Iterators
  {
    using IteratorType = SampleType::Iterator;
    IteratorType iter = sample->Begin();
    IteratorType iter2 = sample->Begin();

    std::cerr << "Trying Iterator operator=()...";
    iter2 = iter;
    if (iter2 != iter)
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }

    std::cerr << "Trying Iterator constructor from sample...";
    IteratorType iter3 = sample->Begin();
    if (iter3 != sample->Begin())
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }

    std::cerr << "Trying Iterator walk...";
    unsigned int counter = 0;
    while (iter3 != sample->End())
    {
      ++iter3;
      counter++;
    }

    if (counter != sample->Size())
    {
      std::cerr << "FAILED. counter: " << counter << " != sample->Size(): " << sample->Size() << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }

    std::cerr << "Trying Iterator copy constructor...";
    IteratorType iter4(iter2);
    if (iter4 != iter2)
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }

    std::cerr << "Trying Iterator operator=...";
    IteratorType iter5 = iter2;
    if (iter5 != iter2)
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }

    std::cerr << "Trying Iterator Constructor with instance identifier 7...";
    IteratorType iter6(sample);
    for (unsigned int kk = 0; kk < 7; ++kk)
    {
      ++iter6;
    }

    if (iter6.GetInstanceIdentifier() != 7)
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }
  }

  // Testing methods specific to ConstIterators
  {
    std::cerr << "Trying ConstIterator operator!=() and operator=()...";
    using ConstIteratorType = SampleType::ConstIterator;
    ConstIteratorType iter = sample->Begin();
    ConstIteratorType iter2 = sample->End();

    iter2 = iter;

    if (iter2 != iter)
    {
      std::cerr << "ConstIterator operator!=() or operator=() FAILED" << std::endl;
      return EXIT_FAILURE;
    }

    if (!(iter2 == iter))
    {
      std::cerr << "ConstIterator operator==() FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }

    std::cerr << "Trying ConstIterator copy constructor...";
    ConstIteratorType iter3(iter2);
    if (iter3 != iter2)
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }

    std::cerr << "Trying Constructor from const container Begin() differs from non-const Begin()...";
    const SampleType * constSample = sample.GetPointer();

    ConstIteratorType iter4(constSample->Begin());
    ConstIteratorType iter5(sample->Begin());
    if (iter4 != iter5)
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }

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

    std::cerr << "Trying Constructor with instance identifier 0...";
    ConstIteratorType iter8(sample);
    if (iter8.GetInstanceIdentifier() != 0)
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }


    ConstIteratorType iter9(sample);
    std::cerr << "Trying Instance identifier = " << iter9.GetInstanceIdentifier() << "...";
    for (unsigned int kk = 0; kk < 7; ++kk)
    {
      ++iter9;
    }

    MeasurementVectorType vector9a = iter9.GetMeasurementVector();
    MeasurementVectorType vector9b = sample->GetMeasurementVector(7);
    for (unsigned int kitr = 0; kitr < measurementVectorSize; ++kitr)
    {
      if (itk::Math::abs(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;
      }
    }
    std::cerr << "PASSED" << std::endl;

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


    std::cerr << "Trying Iterator walk...";
    while (iter10 != constSample->End())
    {
      ++iter10;
      counter++;
    }

    if (counter != constSample->Size())
    {
      std::cerr << "FAILED" << std::endl;
      return EXIT_FAILURE;
    }
    else
    {
      std::cerr << "PASSED" << std::endl;
    }
  }

  std::cerr << "Test passed." << std::endl;
  return EXIT_SUCCESS;
}
