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

#include "itkMath.h"
#include "itkLevelSetEquationTermContainer.h"
#include "itkObject.h"

namespace itk
{
// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::LevelSetEquationTermContainer()
{
  this->m_CurrentLevelSetId = LevelSetIdentifierType();
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
typename LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::Iterator
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::Begin()
{
  return Iterator( this->m_Container.begin() );
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
typename LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::Iterator
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::End()
{
  return Iterator( this->m_Container.end() );
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
typename LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::ConstIterator
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::Begin() const
{
  return ConstIterator( this->m_Container.begin() );
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
typename LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::ConstIterator
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::End() const
{
  return ConstIterator( this->m_Container.end() );
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
void
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::AddTerm( const TermIdType& iId, TermType* iTerm )
{
  if( iTerm )
    {
    if( ! iTerm->GetInput() )
      {
      if( m_Input.IsNotNull() )
        {
        iTerm->SetInput( m_Input );
        }
      else
        {
        itkGenericExceptionMacro( <<"m_Input and iTerm->GetInput are nullptr" );
        }
      }
    iTerm->SetCurrentLevelSetId( this->m_CurrentLevelSetId );

    if( this->m_LevelSetContainer.IsNotNull() )
      {
      iTerm->SetLevelSetContainer( this->m_LevelSetContainer );
      }
    else
      {
      if( ! iTerm->GetLevelSetContainer() )
        {
        itkGenericExceptionMacro( <<"m_LevelSetContainer and iTerm->GetLevelSetContainer() are nullptr" );
        }
      }

    m_Container[iId] = iTerm;
    m_TermContribution[iId] = NumericTraits< LevelSetOutputPixelType >::ZeroValue();
    m_NameContainer[ iTerm->GetTermName() ] = iTerm;

    RequiredDataType termRequiredData = iTerm->GetRequiredData();

    typename RequiredDataType::const_iterator dIt   = termRequiredData.begin();
    typename RequiredDataType::const_iterator dEnd  = termRequiredData.end();

    while( dIt != dEnd )
      {
      m_RequiredData.insert( *dIt );
      ++dIt;
      }

    this->Modified();
    }
  else
    {
    itkGenericExceptionMacro( <<"Term supplied is null" );
    }
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
void
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::PushTerm( TermType* iTerm )
{
  if( iTerm )
    {
    if( ! iTerm->GetInput() )
      {
      if( m_Input.IsNotNull() )
        {
        iTerm->SetInput( m_Input );
        }
      else
        {
        itkGenericExceptionMacro( <<"m_Input and iTerm->GetInput are nullptr" );
        }
      }

    iTerm->SetCurrentLevelSetId( this->m_CurrentLevelSetId );

    if( this->m_LevelSetContainer.isNotNull() )
      {
      iTerm->SetLevelSetContainer( this->m_LevelSetContainer );
      }
    else
      {
      if( ! iTerm->GetLevelSetContainer() )
        {
        itkGenericExceptionMacro( <<"m_LevelSetContainer and iTerm->GetLevelSetContainer() are nullptr" );
        }
      }

    TermIdType id = ( m_Container.rbegin() )->first;
    ++id;

    m_Container[ id ] = iTerm;
    m_TermContribution[ id ] = NumericTraits< LevelSetOutputPixelType >::ZeroValue();
    m_NameContainer[ iTerm->GetTermName() ] = iTerm;

    RequiredDataType termRequiredData = iTerm->GetRequiredData();

    typename RequiredDataType::const_iterator dIt = termRequiredData.begin();
    typename RequiredDataType::const_iterator dEnd = termRequiredData.end();

    while( dIt != dEnd )
      {
      m_RequiredData.insert( *dIt );
      ++dIt;
      }

    this->Modified();
    }
  else
    {
    itkGenericExceptionMacro( <<"Term supplied is null" );
    }
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
typename LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >::TermType*
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >::
GetTerm( const std::string& iName )
{
  MapTermContainerIteratorType it = m_Container.find( iName );

  if( it == m_Container.end() )
    {
    itkGenericExceptionMacro( <<"the term " << iName.c_str() << " is not present in the container" );
    }

  return it->second;
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
typename LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >::TermType*
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >::
GetTerm( const TermIdType& iId )
{
  auto it = m_Container.find( iId );

  if( it == m_Container.end() )
    {
    itkGenericExceptionMacro( <<"the term " << iId << " is not present in the container" );
    }

  return it->second;
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
void
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::Initialize( const LevelSetInputIndexType& iP )
{
  auto term_it  = m_Container.begin();
  auto term_end = m_Container.end();

  while( term_it != term_end )
    {
    ( term_it->second )->Initialize( iP );
    ++term_it;
    }
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
void
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::UpdatePixel( const LevelSetInputIndexType& iP,
               const LevelSetOutputRealType & oldValue,
               const LevelSetOutputRealType & newValue )
{
  auto term_it = m_Container.begin();
  auto term_end = m_Container.end();

  while( term_it != term_end )
    {
    ( term_it->second )->UpdatePixel( iP, oldValue, newValue );
    ++term_it;
    }
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
void
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::InitializeParameters()
{
  auto term_it = m_Container.begin();
  auto term_end = m_Container.end();

  while( term_it != term_end )
    {
    ( term_it->second )->InitializeParameters();
    ++term_it;
    }
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
typename LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >::LevelSetOutputRealType
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::Evaluate( const LevelSetInputIndexType& iP )
{
  auto term_it  = m_Container.begin();
  auto term_end = m_Container.end();

  auto cfl_it = m_TermContribution.begin();

  LevelSetOutputRealType oValue = NumericTraits< LevelSetOutputRealType >::ZeroValue();

  while( term_it != term_end )
    {
    LevelSetOutputRealType temp_val = ( term_it->second )->Evaluate( iP );

    cfl_it->second = std::max( itk::Math::abs( temp_val ), cfl_it->second );

    oValue += temp_val;
    ++term_it;
    ++cfl_it;
    }

  return oValue;
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
typename LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >::LevelSetOutputRealType
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::Evaluate( const LevelSetInputIndexType& iP, const LevelSetDataType& iData )
{
  auto term_it  = m_Container.begin();
  auto term_end = m_Container.end();

  auto cfl_it = m_TermContribution.begin();

  LevelSetOutputRealType oValue = NumericTraits< LevelSetOutputRealType >::ZeroValue();

  while( term_it != term_end )
    {
    LevelSetOutputRealType temp_val = ( term_it->second )->Evaluate( iP, iData );

    cfl_it->second = std::max( itk::Math::abs( temp_val ), cfl_it->second );

    oValue += temp_val;
    ++term_it;
    ++cfl_it;
    }

  return oValue;
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
void
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::Update()
{
  auto term_it = m_Container.begin();
  auto term_end = m_Container.end();

  auto cfl_it = m_TermContribution.begin();

  while( term_it != term_end )
    {
    ( term_it->second )->Update();
    ( cfl_it->second )= NumericTraits< LevelSetOutputPixelType >::ZeroValue();
    ++term_it;
    ++cfl_it;
    }
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
typename LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >::LevelSetOutputRealType
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::ComputeCFLContribution() const
{
  auto term_it = m_Container.begin();
  auto term_end = m_Container.end();

  auto cfl_it = m_TermContribution.begin();

  LevelSetOutputRealType oValue = NumericTraits< LevelSetOutputRealType >::ZeroValue();

  while( term_it != term_end )
    {
    LevelSetOutputRealType cfl = ( term_it->second )->GetCFLContribution();

    if( Math::AlmostEquals( cfl, NumericTraits< LevelSetOutputRealType >::ZeroValue() ) )
      {
      cfl = ( cfl_it->second );
      }

    oValue += cfl;
    ++term_it;
    ++cfl_it;
    }

  return oValue;
}

// ----------------------------------------------------------------------------
template< typename TInputImage, typename TLevelSetContainer >
void
LevelSetEquationTermContainer< TInputImage, TLevelSetContainer >
::ComputeRequiredData( const LevelSetInputIndexType& iP, LevelSetDataType& ioData )
{
  typename RequiredDataType::const_iterator dIt = m_RequiredData.begin();
  typename RequiredDataType::const_iterator dEnd = m_RequiredData.end();

  auto tIt = m_Container.begin();

  LevelSetPointer levelset = ( tIt->second )->GetModifiableCurrentLevelSetPointer();

  while( dIt != dEnd )
    {
    if( *dIt == "Value" )
      {
      levelset->Evaluate( iP, ioData );
      }
    if( *dIt == "Gradient" )
      {
      levelset->EvaluateGradient( iP, ioData );
      }
    if( *dIt == "Hessian" )
      {
      levelset->EvaluateHessian( iP, ioData );
      }
    if( *dIt == "Laplacian" )
      {
      levelset->EvaluateLaplacian( iP, ioData );
      }
    if( *dIt == "GradientNorm" )
      {
      levelset->EvaluateGradientNorm( iP, ioData );
      }
    if( *dIt == "MeanCurvature" )
      {
      levelset->EvaluateMeanCurvature( iP, ioData );
      }
    if( *dIt == "ForwardGradient" )
      {
      levelset->EvaluateForwardGradient( iP, ioData );
      }
    if( *dIt == "BackwardGradient" )
      {
      levelset->EvaluateBackwardGradient( iP, ioData );
      }
    // here add new characteristics
    ++dIt;
    }
}

}
#endif // itkLevelSetEquationTermContainer_hxx
