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

#include <deque>
#include "itkLightObject.h"
#include "itkLabelObjectLine.h"
#include "itkWeakPointer.h"
#include "itkObjectFactory.h"

namespace itk
{
/**
 * \class LabelObject
 *  \brief The base class for the representation of a labeled binary object in an image.
 *
 * LabelObject is the base class to represent a labeled object in an image.
 * It should be used associated with the LabelMap.
 *
 * LabelObject store mainly 2 things: the label of the object, and a set of lines
 * which are part of the object.
 * No attribute is available in that class, so this class can be used as a base class
 * to implement a label object with attribute, or when no attribute is needed (see the
 * reconstruction filters for an example. If a simple attribute is needed,
 * AttributeLabelObject can be used directly.
 *
 * All the subclasses of LabelObject have to reimplement the CopyAttributesFrom() and CopyAllFrom() method.
 * No need to reimplement CopyLinesFrom() since all derived class share the same type line data members.
 *
 * The pixels locations belonging to the LabelObject can be obtained using:
   \code
   for(unsigned int pixelId = 0; pixelId < labelObject->Size(); pixelId++)
     {
     std::cout << labelObject->GetIndex(pixelId);
     }
   \endcode
 *
 * \author Gaetan Lehmann. Biologie du Developpement et de la Reproduction, INRA de Jouy-en-Josas, France.
 *
 * This implementation was taken from the Insight Journal paper:
 * https://www.insight-journal.org/browse/publication/176
 *
 * \sa LabelMapFilter, AttributeLabelObject
 * \ingroup DataRepresentation
 * \ingroup LabeledImageObject
 * \ingroup ITKLabelMap
 */
template <typename TLabel, unsigned int VImageDimension>
class ITK_TEMPLATE_EXPORT LabelObject : public LightObject
{
public:
  ITK_DISALLOW_COPY_AND_MOVE(LabelObject);

  /** Standard class type aliases */
  using Self = LabelObject;
  using Superclass = LightObject;
  using LabelObjectType = Self;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;
  using ConstWeakPointer = WeakPointer<const Self>;

  /** Method for creation through the object factory. */
  itkNewMacro(Self);

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

  static constexpr unsigned int ImageDimension = VImageDimension;

  using IndexType = Index<VImageDimension>;
  using OffsetType = Offset<VImageDimension>;
  using LabelType = TLabel;
  using LineType = LabelObjectLine<VImageDimension>;
  using LengthType = typename LineType::LengthType;
  using AttributeType = unsigned int;
  using SizeValueType = itk::SizeValueType;

  static constexpr AttributeType LABEL = 0;

  static AttributeType
  GetAttributeFromName(const std::string & s);

  static std::string
  GetNameFromAttribute(const AttributeType & a);

  /**
   * Set/Get the label associated with the object.
   */
  const LabelType &
  GetLabel() const;

  void
  SetLabel(const LabelType & label);

  /**
   * Return true if the object contain the given index and false otherwise.
   * Worst case complexity is O(L) where L is the number of lines in the object.
   */
  bool
  HasIndex(const IndexType & idx) const;

  /**
   * Add an index to the object. If the index is already in the object, the index can
   * be found several time in the object.
   */
  void
  AddIndex(const IndexType & idx);

  /**
   * Remove an index to the object. Depending on the configuration, it can either
   * reduce the size of the corresponding line, add one more line, remove the line
   * from the line container.
   */
  bool
  RemoveIndex(const IndexType & idx);

  /**
   * Add a new line to the object, without any check.
   */
  void
  AddLine(const IndexType & idx, const LengthType & length);

  /**
   * Add a new line to the object, without any check.
   */
  void
  AddLine(const LineType & line);

  SizeValueType
  GetNumberOfLines() const;

  const LineType &
  GetLine(SizeValueType i) const;

  LineType &
  GetLine(SizeValueType i);

  /**
   * Returns the number of pixels contained in the object.
   *
   * \warning To get an accurate result, you need to make sure
   * there is no duplication in the line container. One way to
   * ensure this (at a cost) is to call the Optimize method.
   */
  SizeValueType
  Size() const;

  /**
   * Returns true if there no line in the container (and thus no pixel in
   * the object.
   */
  bool
  Empty() const;

  void
  Clear();

  /**
   * Get the index of the ith pixel associated with the object.
   * Valid indices are from 0 to LabelObject->GetSize() - 1.
   */
  IndexType
  GetIndex(SizeValueType offset) const;

  /** Copy the lines of another node to this one */
  template <typename TSourceLabelObject>
  void
  CopyLinesFrom(const TSourceLabelObject * src);

  /** Copy the label and the attributes of another node to this one */
  template <typename TSourceLabelObject>
  void
  CopyAttributesFrom(const TSourceLabelObject * src);

  /** Copy the lines, the label and the attributes from another node. */
  template <typename TSourceLabelObject>
  void
  CopyAllFrom(const TSourceLabelObject * src);

  /** Reorder the lines, merge the touching lines and ensure that no
   * pixel is covered by two lines
   */
  void
  Optimize();

  /** Shift the object position */
  void
  Shift(OffsetType offset);

  /**
   * \class ConstLineIterator
   * \brief A forward iterator over the lines of a LabelObject
   * \ingroup ITKLabelMap
   */
  class ConstLineIterator
  {
  public:
    ConstLineIterator() = default;

    ConstLineIterator(const Self * lo)
    {
      m_Begin = lo->m_LineContainer.begin();
      m_End = lo->m_LineContainer.end();
      m_Iterator = m_Begin;
    }

    ConstLineIterator(const ConstLineIterator & iter)
    {
      m_Iterator = iter.m_Iterator;
      m_Begin = iter.m_Begin;
      m_End = iter.m_End;
    }

    ConstLineIterator &
    operator=(const ConstLineIterator & iter)
    {
      m_Iterator = iter.m_Iterator;
      m_Begin = iter.m_Begin;
      m_End = iter.m_End;
      return *this;
    }

    const LineType &
    GetLine() const
    {
      return *m_Iterator;
    }

    ConstLineIterator
    operator++(int)
    {
      ConstLineIterator tmp = *this;
      ++(*this);
      return tmp;
    }

    ConstLineIterator &
    operator++()
    {
      ++m_Iterator;
      return *this;
    }

    bool
    operator==(const ConstLineIterator & iter) const
    {
      return m_Iterator == iter.m_Iterator && m_Begin == iter.m_Begin && m_End == iter.m_End;
    }

    ITK_UNEQUAL_OPERATOR_MEMBER_FUNCTION(ConstLineIterator);

    void
    GoToBegin()
    {
      m_Iterator = m_Begin;
    }

    bool
    IsAtEnd() const
    {
      return m_Iterator == m_End;
    }

  private:
    using LineContainerType = typename std::deque<LineType>;
    using InternalIteratorType = typename LineContainerType::const_iterator;
    InternalIteratorType m_Iterator;
    InternalIteratorType m_Begin;
    InternalIteratorType m_End;
  };

  /**
   * \class ConstLineIterator
   * \brief A forward iterator over the indexes of a LabelObject
   * \ingroup ITKLabelMap
   */
  class ConstIndexIterator
  {
  public:
    ConstIndexIterator()
      : m_Iterator()
      , m_Begin()
      , m_End()
    {
      m_Index.Fill(0);
    }

    ConstIndexIterator(const Self * lo)
    {
      m_Begin = lo->m_LineContainer.begin();
      m_End = lo->m_LineContainer.end();
      GoToBegin();
    }

    ConstIndexIterator(const ConstIndexIterator & iter)
    {
      m_Iterator = iter.m_Iterator;
      m_Index = iter.m_Index;
      m_Begin = iter.m_Begin;
      m_End = iter.m_End;
    }

    ConstIndexIterator &
    operator=(const ConstIndexIterator & iter)
    {
      m_Iterator = iter.m_Iterator;
      m_Index = iter.m_Index;
      m_Begin = iter.m_Begin;
      m_End = iter.m_End;
      return *this;
    }

    const IndexType &
    GetIndex() const
    {
      return m_Index;
    }

    ConstIndexIterator &
    operator++()
    {
      m_Index[0]++;
      if (m_Index[0] >= m_Iterator->GetIndex()[0] + (OffsetValueType)m_Iterator->GetLength())
      {
        // we've reached the end of the line - go to the next one
        ++m_Iterator;
        NextValidLine();
      }
      return *this;
    }

    ConstIndexIterator
    operator++(int)
    {
      ConstIndexIterator tmp = *this;
      ++(*this);
      return tmp;
    }

    bool
    operator==(const ConstIndexIterator & iter) const
    {
      return m_Index == iter.m_Index && m_Iterator == iter.m_Iterator && m_Begin == iter.m_Begin && m_End == iter.m_End;
    }

    ITK_UNEQUAL_OPERATOR_MEMBER_FUNCTION(ConstIndexIterator);

    void
    GoToBegin()
    {
      m_Iterator = m_Begin;
      m_Index.Fill(0);
      NextValidLine();
    }

    bool
    IsAtEnd() const
    {
      return m_Iterator == m_End;
    }

  private:
    using LineContainerType = typename std::deque<LineType>;
    using InternalIteratorType = typename LineContainerType::const_iterator;
    void
    NextValidLine()
    {
      // search for the next valid position
      while (m_Iterator != m_End && m_Iterator->GetLength() == 0)
      {
        ++m_Iterator;
      }
      if (m_Iterator != m_End)
      {
        m_Index = m_Iterator->GetIndex();
      }
    }

    InternalIteratorType m_Iterator;
    InternalIteratorType m_Begin;
    InternalIteratorType m_End;
    IndexType            m_Index;
  };

protected:
  LabelObject();
  void
  PrintSelf(std::ostream & os, Indent indent) const override;

private:
  using LineContainerType = typename std::deque<LineType>;

  LineContainerType m_LineContainer{};
  LabelType         m_Label{};
};
} // end namespace itk

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

#endif
