/*=========================================================================
 *
 *  Copyright Insight Software Consortium
 *
 *  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
 *
 *         http://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_ASSIGN(LevelSetEquationContainer);

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

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

  /** Run-time type information */
  itkTypeMacro( LevelSetEquationContainer, Object );

  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 */
  itkSetObjectMacro( Input, InputImageType );
  itkGetModifiableObjectMacro(Input, InputImageType );

  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);
      }
    bool operator != (const Iterator& it) const
      {
      return (m_Iterator != it.m_Iterator);
      }
    bool operator == (const ConstIterator& it) const
      {
      return (m_Iterator == it.m_Iterator);
      }
    bool operator != (const ConstIterator& it) const
      {
      return (m_Iterator != it.m_Iterator);
      }
    LevelSetIdentifierType GetIdentifier() const
      {
      return m_Iterator->first;
      }

    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);
      }
    bool operator!=(const Iterator& it) const
      {
      return (m_Iterator!=it.m_Iterator);
      }
    bool operator==(const ConstIterator& it)const
      {
      return (m_Iterator == it.m_Iterator);
      }
    bool operator!=(const ConstIterator& it)const
      {
      return (m_Iterator != it.m_Iterator);
      }
    LevelSetIdentifierType GetIdentifier() const
      {
      return m_Iterator->first;
      }

    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;
};
}

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

#endif // itkLevelSetEquationContainer_h
