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

#include "itkObject.h"
#include "itkObjectFactory.h"

namespace itk
{
/**
 *  \class LevelSetEquationContainer
 *  \brief Class for holding a set of level set equations (PDEs).
 *
 *  \tparam TTermContainer Container holding the terms in a level set equation
 *
 *  Evolving level-set functions \f$\left\{ \phi_j \right\}_{j=1}^{M}\f$
 *  can be expressed as follows:
 *  \f{eqnarray*}{
 *  \frac{\partial \phi_1(p)}{\partial \tau} &=& \sum\limits_{i=1}^{N_1} \alpha_{i1}
 *  \cdot \omega_{i1}(p) \\
 *  \frac{\partial \phi_2(p)}{\partial \tau} &=& \sum\limits_{i=1}^{N_2} \alpha_{i2}
 *  \cdot \omega_{i2}(p) \\
 *  & \vdots & \\
 *  \frac{\partial \phi_M(p)}{\partial \tau} &=& \sum\limits_{i=1}^{N_M} \alpha_{iM}
 *  \cdot \omega_{iM}(p)
 *  \f}
 *  where \f$\omega_{iM}\f$ is a term which could depend on any of the level-set
 *  functions \f$\left\{ \phi_j \right\}_{j=1}^{M}\f$ , the input image,
 *  and \f$\alpha_{iM}\f$ is a weight to balance the contribution of each term
 *  in the PDE.
 *
 *  Each equation of this system of equation (PDE) is referred as an equation in
 *  the level-set framework. Each equation \f$ equation_{j} \f$ contributes to the
 *  evolution of the level-set function \f$ \phi_j \f$.
 *
 *  \sa LevelSetEquationTerm
 *
 *  \ingroup ITKLevelSetsv4
 */
template <typename TTermContainer>
class ITK_TEMPLATE_EXPORT LevelSetEquationContainer : public Object
{
public:
  ITK_DISALLOW_COPY_AND_MOVE(LevelSetEquationContainer);

  using Self = LevelSetEquationContainer;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;
  using Superclass = Object;

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

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

  using TermContainerType = TTermContainer;
  using TermContainerPointer = typename TermContainerType::Pointer;

  using InputImageType = typename TermContainerType::InputImageType;
  using InputImagePointer = typename TermContainerType::InputImagePointer;

  using LevelSetOutputRealType = typename TermContainerType::LevelSetOutputRealType;
  using LevelSetInputIndexType = typename TermContainerType::LevelSetInputIndexType;

  using LevelSetIdentifierType = typename TermContainerType::LevelSetIdentifierType;
  using LevelSetContainerType = typename TermContainerType::LevelSetContainerType;
  using LevelSetContainerPointer = typename TermContainerType::LevelSetContainerPointer;

  /** Add a equation to the system of equations in the EquationContainer map */
  void
  AddEquation(const LevelSetIdentifierType & iId, TermContainerType * iEquation);

  /** Return a pointer to the equation of given id */
  TermContainerType *
  GetEquation(const LevelSetIdentifierType & iId) const;

  /** Update the equation container recursively by calling update on individual equations */
  void
  UpdateInternalEquationTerms();

  /** Supply the update at a given pixel index to update the terms */
  void
  UpdatePixel(const LevelSetInputIndexType & iP,
              const LevelSetOutputRealType & oldValue,
              const LevelSetOutputRealType & newValue);

  /** Initialize parameters in the terms of all the equations */
  void
  InitializeParameters();

  /** Returns the Courant-Friedrichs-Lewy (CFL) contribution
   * for all the equations */
  LevelSetOutputRealType
  ComputeCFLContribution() const;

  /** Set/Get the input speed or feature image */
  /** @ITKStartGrouping */
  itkSetObjectMacro(Input, InputImageType);
  itkGetModifiableObjectMacro(Input, InputImageType);
  /** @ITKEndGrouping */
  itkSetObjectMacro(LevelSetContainer, LevelSetContainerType);
  itkGetModifiableObjectMacro(LevelSetContainer, LevelSetContainerType);

protected:
  using MapContainerType = std::map<LevelSetIdentifierType, TermContainerPointer>;
  using MapContainerIterator = typename MapContainerType::iterator;
  using MapContainerConstIterator = typename MapContainerType::const_iterator;

public:
  class Iterator;
  friend class Iterator;

  class ConstIterator
  {
  public:
    ConstIterator() = default;
    ConstIterator(const MapContainerConstIterator & it)
      : m_Iterator(it)
    {}
    ~ConstIterator() = default;
    ConstIterator(const Iterator & it)
      : m_Iterator(it.m_Iterator)
    {}
    ConstIterator &
    operator*()
    {
      return *this;
    }
    ConstIterator *
    operator->()
    {
      return this;
    }
    ConstIterator &
    operator++()
    {
      ++m_Iterator;
      return *this;
    }
    ConstIterator
    operator++(int)
    {
      ConstIterator tmp(*this);
      ++(*this);
      return tmp;
    }
    ConstIterator &
    operator--()
    {
      --m_Iterator;
      return *this;
    }
    ConstIterator
    operator--(int)
    {
      ConstIterator tmp(*this);
      --(*this);
      return tmp;
    }
    bool
    operator==(const Iterator & it) const
    {
      return m_Iterator == it.m_Iterator;
    }

    ITK_UNEQUAL_OPERATOR_MEMBER_FUNCTION(Iterator);

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

    ITK_UNEQUAL_OPERATOR_MEMBER_FUNCTION(ConstIterator);

    [[nodiscard]] LevelSetIdentifierType
    GetIdentifier() const
    {
      return m_Iterator->first;
    }

    [[nodiscard]] TermContainerType *
    GetEquation() const
    {
      return m_Iterator->second;
    }

  private:
    MapContainerConstIterator m_Iterator;
    friend class Iterator;
  };

  class Iterator
  {
  public:
    Iterator() = default;
    Iterator(const MapContainerIterator & it)
      : m_Iterator(it)
    {}
    Iterator(const ConstIterator & it)
      : m_Iterator(it.m_Iterator)
    {}
    ~Iterator() = default;

    Iterator &
    operator*()
    {
      return *this;
    }
    Iterator *
    operator->()
    {
      return this;
    }

    Iterator &
    operator++()
    {
      ++m_Iterator;
      return *this;
    }
    Iterator
    operator++(int)
    {
      Iterator tmp(*this);
      ++(*this);
      return tmp;
    }
    Iterator &
    operator--()
    {
      --m_Iterator;
      return *this;
    }
    Iterator
    operator--(int)
    {
      Iterator tmp(*this);
      --(*this);
      return tmp;
    }

    bool
    operator==(const Iterator & it) const
    {
      return m_Iterator == it.m_Iterator;
    }

    ITK_UNEQUAL_OPERATOR_MEMBER_FUNCTION(Iterator);

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

    ITK_UNEQUAL_OPERATOR_MEMBER_FUNCTION(ConstIterator);

    [[nodiscard]] LevelSetIdentifierType
    GetIdentifier() const
    {
      return m_Iterator->first;
    }

    [[nodiscard]] TermContainerType *
    GetEquation() const
    {
      return m_Iterator->second;
    }

  private:
    MapContainerIterator m_Iterator;
    friend class ConstIterator;
  };

  Iterator
  Begin();
  Iterator
  End();

  ConstIterator
  Begin() const;
  ConstIterator
  End() const;

protected:
  LevelSetEquationContainer() = default;
  ~LevelSetEquationContainer() override = default;

  LevelSetContainerPointer m_LevelSetContainer{};
  MapContainerType         m_Container{};
  InputImagePointer        m_Input{};
};
} // namespace itk

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

#endif // itkLevelSetEquationContainer_h
