/*=========================================================================
 *
 *  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 itkSubsample_h
#define itkSubsample_h

#include "itkSample.h"
#include "itkMacro.h"
#include "itkObjectFactory.h"

namespace itk::Statistics
{
/**
 * \class Subsample
 * \brief This class stores a subset of instance identifiers from another sample
 * object. You can create a subsample out of another sample object or another
 * subsample object. The class is useful when storing or extracting a portion
 * of a sample object. Note that when the elements of a subsample are sorted,
 * the instance identifiers of the subsample are sorted without changing the
 * original source sample. Most Statistics algorithms (that derive from
 * StatisticsAlgorithmBase accept Subsample objects as inputs).
 *
 * \ingroup ITKStatistics
 */
template <typename TSample>
class ITK_TEMPLATE_EXPORT Subsample : public TSample
{
public:
  ITK_DISALLOW_COPY_AND_MOVE(Subsample);

  /** Standard class type aliases */
  using Self = Subsample;
  using Superclass = TSample;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;

  /** \see LightObject::GetNameOfClass() */
  itkOverrideGetNameOfClassMacro(Subsample);

  /** standard New() method support */
  itkNewMacro(Self);

  /** Smart pointer to the actual sample data holder */
  using SamplePointer = typename TSample::Pointer;

  /** Typedefs for Measurement vector, measurement, Instance Identifier,
   * frequency, size, size element value from the template argument TSample */
  using MeasurementVectorType = typename TSample::MeasurementVectorType;
  using MeasurementType = typename TSample::MeasurementType;
  using InstanceIdentifier = typename TSample::InstanceIdentifier;
  using ValueType = MeasurementVectorType;

  using AbsoluteFrequencyType = typename TSample::AbsoluteFrequencyType;
  using TotalAbsoluteFrequencyType = typename TSample::TotalAbsoluteFrequencyType;

  /** Type of the storage for instances that belong to the class
   * represented by a Subsample object. A Subsample object stores
   * only the InstanceIdentifiers. The actual data is still in the Sample
   * object */
  using InstanceIdentifierHolder = std::vector<InstanceIdentifier>;

  /** Get the Id Holder */
  const InstanceIdentifierHolder &
  GetIdHolder() const
  {
    return this->m_IdHolder;
  }

  /** Plug in the actual sample data */
  void
  SetSample(const TSample * sample);

  const TSample *
  GetSample() const;

  /** Initialize the subsample with all instances of the sample */
  void
  InitializeWithAllInstances();

  /** Add instance to the subsample */
  void
  AddInstance(InstanceIdentifier id);

  /** returns SizeType object whose each element is the number of
   * elements in each dimension */
  InstanceIdentifier
  Size() const override;

  /** Clear the subsample */
  void
  Clear();

  /** returns the measurement of the instance which is identified
   * by the 'id' */
  const MeasurementVectorType &
  GetMeasurementVector(InstanceIdentifier id) const override;

  /** returns the frequency of the instance which is identified by the 'id' */
  AbsoluteFrequencyType
  GetFrequency(InstanceIdentifier id) const override;

  /** returns the total frequency for the 'd' dimension */
  TotalAbsoluteFrequencyType
  GetTotalFrequency() const override;

  void
  Swap(unsigned int index1, unsigned int index2);

  InstanceIdentifier
  GetInstanceIdentifier(unsigned int index);

  const MeasurementVectorType &
  GetMeasurementVectorByIndex(unsigned int index) const;

  AbsoluteFrequencyType
  GetFrequencyByIndex(unsigned int index) const;

  /** Method to graft another sample */
  void
  Graft(const DataObject * thatObject) override;

  class ConstIterator
  {
    friend class Subsample;

  public:
    ConstIterator(const Self * sample) { *this = sample->Begin(); }

    ConstIterator(const ConstIterator & iter)
      : m_Iter(iter.m_Iter)
      , m_Subsample(iter.m_Subsample)
      , m_Sample(iter.m_Sample)
    {}

    ConstIterator &
    operator=(const ConstIterator & iter) = default;

    bool
    operator==(const ConstIterator & it) const
    {
      return m_Iter == it.m_Iter;
    }

    ITK_UNEQUAL_OPERATOR_MEMBER_FUNCTION(ConstIterator);

    ConstIterator &
    operator++()
    {
      ++m_Iter;
      return *this;
    }

    [[nodiscard]] AbsoluteFrequencyType
    GetFrequency() const
    {
      return m_Sample->GetFrequency(*m_Iter);
    }

    [[nodiscard]] const MeasurementVectorType &
    GetMeasurementVector() const
    {
      return m_Sample->GetMeasurementVector(*m_Iter);
    }

    [[nodiscard]] InstanceIdentifier
    GetInstanceIdentifier() const
    {
      return m_Iter - m_Subsample->GetIdHolder().begin();
    }

  protected:
    // Purposely not implemented
    ConstIterator();

    // Only to be called from the Subsample
    ConstIterator(typename InstanceIdentifierHolder::const_iterator iter, const Self * classSample)
      : m_Iter(iter)
      , m_Subsample(classSample)
      , m_Sample(classSample->GetSample())
    {}

    // ConstIterator pointing to ImageToListSampleAdaptor
    typename InstanceIdentifierHolder::const_iterator m_Iter;

    // Pointer to Subsample object
    const Self *    m_Subsample;
    const TSample * m_Sample;

  private:
  };

  class Iterator : public ConstIterator
  {
    friend class Subsample;

  public:
    Iterator(Self * sample)
      : ConstIterator(sample)
    {}

    Iterator(const Iterator & iter)
      : ConstIterator(iter)
    {}

    Iterator &
    operator=(const Iterator & iter)
    {
      this->ConstIterator::operator=(iter);
      return *this;
    }

  protected:
    // To ensure const-correctness these method must not be in the public API.
    // The are purposely not implemented, since they should never be called.
    Iterator();
    Iterator(const Self * sample);
    Iterator(typename InstanceIdentifierHolder::const_iterator iter, const Self * classSample);
    Iterator(const ConstIterator & it);
    ConstIterator &
    operator=(const ConstIterator & it);

    // Only to be called from the Subsample
    Iterator(typename InstanceIdentifierHolder::iterator iter, Self * classSample)
      : ConstIterator(iter, classSample)
    {}

  private:
  };

  /** This method returns an iterator to the beginning of the
      measurement vectors */
  Iterator
  Begin()
  {
    const Iterator iter(m_IdHolder.begin(), this);

    return iter;
  }

  /** This method returns an iterator to the beginning of the
      measurement vectors */
  Iterator
  End()
  {
    const Iterator iter(m_IdHolder.end(), this);

    return iter;
  }

  ConstIterator
  Begin() const
  {
    const ConstIterator iter(m_IdHolder.begin(), this);

    return iter;
  }

  ConstIterator
  End() const
  {
    const ConstIterator iter(m_IdHolder.end(), this);

    return iter;
  }

protected:
  Subsample();
  ~Subsample() override = default;
  void
  PrintSelf(std::ostream & os, Indent indent) const override;

private:
  const TSample *            m_Sample{};
  InstanceIdentifierHolder   m_IdHolder{};
  unsigned int               m_ActiveDimension{};
  TotalAbsoluteFrequencyType m_TotalFrequency{};
}; // end of class
} // namespace itk::Statistics

#ifndef ITK_MANUAL_INSTANTIATION
#  include "itkSubsample.hxx"
#endif

#endif
