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

#include "itkUpdateShiSparseLevelSet.h"
#include "itkConnectedImageNeighborhoodShape.h"

namespace itk
{

template< unsigned int VDimension, typename TEquationContainer >
UpdateShiSparseLevelSet< VDimension, TEquationContainer >
::UpdateShiSparseLevelSet() :
  m_CurrentLevelSetId( NumericTraits< IdentifierType >::ZeroValue() ),
  m_RMSChangeAccumulator( NumericTraits< LevelSetOutputRealType >::ZeroValue() )
{
  this->m_Offset.Fill( 0 );
  this->m_OutputLevelSet = LevelSetType::New();
}

template< unsigned int VDimension, typename TEquationContainer >
void
UpdateShiSparseLevelSet< VDimension, TEquationContainer >
::Update()
{
  if( this->m_InputLevelSet.IsNull() )
    {
    itkGenericExceptionMacro( <<"m_InputLevelSet is nullptr" );
    }

  this->m_Offset = this->m_InputLevelSet->GetDomainOffset();

  TermContainerPointer termContainer = this->m_EquationContainer->GetEquation( this->m_CurrentLevelSetId );

  this->m_OutputLevelSet->SetLayer( LevelSetType::MinusOneLayer(), this->m_InputLevelSet->GetLayer( LevelSetType::MinusOneLayer() ) );
  this->m_OutputLevelSet->SetLayer( LevelSetType::PlusOneLayer(), this->m_InputLevelSet->GetLayer( LevelSetType::PlusOneLayer() ) );

  this->m_OutputLevelSet->SetLabelMap( this->m_InputLevelSet->GetModifiableLabelMap() );
  this->m_OutputLevelSet->SetDomainOffset( this->m_Offset );

  using LabelMapToLabelImageFilterType = LabelMapToLabelImageFilter<LevelSetLabelMapType, LabelImageType>;
  typename LabelMapToLabelImageFilterType::Pointer labelMapToLabelImageFilter = LabelMapToLabelImageFilterType::New();
  labelMapToLabelImageFilter->SetInput( this->m_InputLevelSet->GetLabelMap() );
  labelMapToLabelImageFilter->Update();

  this->m_InternalImage = labelMapToLabelImageFilter->GetOutput();
  this->m_InternalImage->DisconnectPipeline();

  // neighborhood iterator
  ZeroFluxNeumannBoundaryCondition< LabelImageType > spNBC;

  typename NeighborhoodIteratorType::RadiusType radius;
  radius.Fill( 1 );

  NeighborhoodIteratorType neighIt( radius, this->m_InternalImage,
                                    this->m_InternalImage->GetLargestPossibleRegion() );

  neighIt.OverrideBoundaryCondition( &spNBC );
  neighIt.ActivateOffsets(
    Experimental::GenerateConnectedImageNeighborhoodShapeOffsets<ImageDimension, 1, false>());

  // Step 2.1.1
  this->UpdateLayerPlusOne();

 // Step 2.1.2 - for each point x in L_out
  LevelSetLayerType & listIn = this->m_OutputLevelSet->GetLayer( LevelSetType::MinusOneLayer() );

  auto nodeIt = listIn.begin();
  auto nodeEnd = listIn.end();

  LevelSetInputType inputIndex;
  while( nodeIt != nodeEnd )
    {
    const LevelSetInputType currentIndex = nodeIt->first;
    inputIndex = currentIndex + this->m_Offset;

    neighIt.SetLocation( currentIndex );

    bool toBeDeleted = true;

    for( typename NeighborhoodIteratorType::Iterator
         i = neighIt.Begin(); !i.IsAtEnd(); ++i )
      {
      LevelSetOutputType tempValue = i.Get();
      if ( tempValue > NumericTraits< LevelSetOutputType >::ZeroValue() )
        {
        toBeDeleted = false;
        break;
        }
      }
    if( toBeDeleted )
      {
      const LevelSetOutputType oldValue = LevelSetType::MinusOneLayer();
      const LevelSetOutputType newValue = LevelSetType::MinusThreeLayer();

      this->m_InternalImage->SetPixel( currentIndex, newValue );

      auto tempIt = nodeIt;
      ++nodeIt;
      listIn.erase( tempIt );

      termContainer->UpdatePixel( inputIndex, oldValue, newValue );
      }
    else
      {
      ++nodeIt;
      }
    }

//     Step 2.1.3 - for each point x in L_in
  this->UpdateLayerMinusOne();

//     Step 2.1.4
  LevelSetLayerType & listOut = this->m_OutputLevelSet->GetLayer( LevelSetType::PlusOneLayer() );

  nodeIt = listOut.begin();
  nodeEnd = listOut.end();

  while( nodeIt != nodeEnd )
    {
    const LevelSetInputType currentIndex = nodeIt->first;

    neighIt.SetLocation( currentIndex );

    bool toBeDeleted = true;

    for( typename NeighborhoodIteratorType::Iterator i = neighIt.Begin(); !i.IsAtEnd(); ++i )
      {
      LevelSetOutputType tempValue = i.Get();
      if ( tempValue < NumericTraits< LevelSetOutputType >::ZeroValue() )
        {
        toBeDeleted = false;
        break;
        }
      }
    if( toBeDeleted )
      {
      const LevelSetOutputType oldValue = LevelSetType::PlusOneLayer();
      const LevelSetOutputType newValue = LevelSetType::PlusThreeLayer();
      this->m_InternalImage->SetPixel( currentIndex, newValue );

      auto tempIt = nodeIt;
      ++nodeIt;
      listOut.erase( tempIt );

      termContainer->UpdatePixel( inputIndex, oldValue, newValue );
      }
    else
      {
      ++nodeIt;
      }
    }

  using LabelImageToLabelMapFilterType = LabelImageToLabelMapFilter< LabelImageType, LevelSetLabelMapType>;
  typename LabelImageToLabelMapFilterType::Pointer labelImageToLabelMapFilter = LabelImageToLabelMapFilterType::New();
  labelImageToLabelMapFilter->SetInput( this->m_InternalImage );
  labelImageToLabelMapFilter->SetBackgroundValue( LevelSetType::PlusThreeLayer() );
  labelImageToLabelMapFilter->Update();

  LevelSetLabelMapPointer outputLabelMap = this->m_OutputLevelSet->GetModifiableLabelMap( );
  outputLabelMap->Graft( labelImageToLabelMapFilter->GetOutput() );
}

template< unsigned int VDimension, typename TEquationContainer >
void
UpdateShiSparseLevelSet< VDimension, TEquationContainer >
::UpdateLayerPlusOne()
{
  TermContainerPointer termContainer = this->m_EquationContainer->GetEquation( this->m_CurrentLevelSetId );

  LevelSetLayerType & listOut  = this->m_OutputLevelSet->GetLayer( LevelSetType::PlusOneLayer() );
  LevelSetLayerType & listIn   = this->m_OutputLevelSet->GetLayer( LevelSetType::MinusOneLayer() );

  ZeroFluxNeumannBoundaryCondition< LabelImageType > spNBC;

  typename NeighborhoodIteratorType::RadiusType radius;
  radius.Fill( 1 );

  NeighborhoodIteratorType neighIt( radius, this->m_InternalImage,
                                    this->m_InternalImage->GetLargestPossibleRegion() );

  neighIt.OverrideBoundaryCondition( &spNBC );
  neighIt.ActivateOffsets(
    Experimental::GenerateConnectedImageNeighborhoodShapeOffsets<ImageDimension, 1, false>());

  LevelSetLayerType insertListIn;
  LevelSetLayerType insertListOut;

  auto nodeIt   = listOut.begin();
  auto nodeEnd  = listOut.end();

  LevelSetInputType inputIndex;

  // for each point in Lz
  while( nodeIt != nodeEnd )
    {
    bool erased = false;
    const LevelSetInputType   currentIndex = nodeIt->first;
    const LevelSetOutputType  currentValue = nodeIt->second;
    inputIndex = currentIndex + this->m_Offset;

    // update the level set
    LevelSetOutputRealType update = termContainer->Evaluate( inputIndex );

    if( update < NumericTraits< LevelSetOutputRealType >::ZeroValue() )
      {
      if( Con( currentIndex, currentValue , update ) )
        {
        // CheckIn
        insertListIn.insert(
              NodePairType( currentIndex, LevelSetType::MinusOneLayer() ) );

        auto tempIt = nodeIt;
        ++nodeIt;
        listOut.erase( tempIt );
        erased = true;

        neighIt.SetLocation( currentIndex );

        for( typename NeighborhoodIteratorType::Iterator
            i = neighIt.Begin();
            !i.IsAtEnd(); ++i )
          {
          LevelSetOutputType tempValue = i.Get();

          if ( tempValue == LevelSetType::PlusThreeLayer() )
            {
            LevelSetInputType tempIndex =
                neighIt.GetIndex( i.GetNeighborhoodOffset() );

            insertListOut.insert(
                  NodePairType( tempIndex, LevelSetType::PlusOneLayer() ) );
            }
          }
        }
      }
    if( !erased )
      {
      ++nodeIt;
      }
    }

  nodeIt   = insertListOut.begin();
  nodeEnd  = insertListOut.end();

  // for each point in Lz
  while( nodeIt != nodeEnd )
    {
    listOut.insert( *nodeIt );

    this->m_InternalImage->SetPixel( nodeIt->first, LevelSetType::PlusOneLayer() );
    termContainer->UpdatePixel( nodeIt->first + this->m_Offset , LevelSetType::PlusThreeLayer(), LevelSetType::PlusOneLayer() );

    ++nodeIt;
    }

  nodeIt = insertListIn.begin();
  nodeEnd = insertListIn.end();

  while( nodeIt != nodeEnd )
    {
    listIn.insert( *nodeIt );

    this->m_InternalImage->SetPixel( nodeIt->first, LevelSetType::MinusOneLayer() );
    termContainer->UpdatePixel( nodeIt->first + this->m_Offset, LevelSetType::PlusOneLayer(), LevelSetType::MinusOneLayer() );
    ++nodeIt;
    }
}

template< unsigned int VDimension, typename TEquationContainer >
void
UpdateShiSparseLevelSet< VDimension, TEquationContainer >
::UpdateLayerMinusOne()
{
  TermContainerPointer termContainer = this->m_EquationContainer->GetEquation( this->m_CurrentLevelSetId );

  LevelSetLayerType & listOut  = this->m_OutputLevelSet->GetLayer( LevelSetType::PlusOneLayer() );
  LevelSetLayerType & listIn   = this->m_OutputLevelSet->GetLayer( LevelSetType::MinusOneLayer() );

  ZeroFluxNeumannBoundaryCondition< LabelImageType > spNBC;

  typename NeighborhoodIteratorType::RadiusType radius;
  radius.Fill( 1 );

  NeighborhoodIteratorType neighIt( radius,
                                    this->m_InternalImage,
                                    this->m_InternalImage->GetLargestPossibleRegion() );

  neighIt.OverrideBoundaryCondition( &spNBC );
  neighIt.ActivateOffsets(
    Experimental::GenerateConnectedImageNeighborhoodShapeOffsets<ImageDimension, 1, false>());

  LevelSetLayerType insertListIn;
  LevelSetLayerType insertListOut;

  auto nodeIt   = listIn.begin();
  auto nodeEnd  = listIn.end();

  // for each point in Lz
  while( nodeIt != nodeEnd )
    {
    bool erased = false;
    const LevelSetInputType   currentIndex = nodeIt->first;
    const LevelSetOutputType  currentValue = nodeIt->second;
    const LevelSetInputType inputIndex = currentIndex + this->m_Offset;

    // update for the current level set
    LevelSetOutputRealType update = termContainer->Evaluate( inputIndex );

    if( update > NumericTraits< LevelSetOutputRealType >::ZeroValue() )
      {
      if( Con( currentIndex, currentValue , update ) )
        {
        // CheckOut
        insertListOut.insert(
              NodePairType( currentIndex, LevelSetType::PlusOneLayer() ) );

        auto tempIt = nodeIt;
        ++nodeIt;
        listIn.erase( tempIt );

        erased = true;

        neighIt.SetLocation( currentIndex );

        for( typename NeighborhoodIteratorType::Iterator
            i = neighIt.Begin(); !i.IsAtEnd(); ++i )
          {
          LevelSetOutputType tempValue = i.Get();

          if ( tempValue == LevelSetType::MinusThreeLayer() )
            {
            LevelSetInputType tempIndex = neighIt.GetIndex( i.GetNeighborhoodOffset() );

            insertListIn.insert( NodePairType( tempIndex, LevelSetType::MinusOneLayer() ) );
            }
          }
        }
      }
    if( !erased )
      {
      ++nodeIt;
      }
    }

  nodeIt   = insertListIn.begin();
  nodeEnd  = insertListIn.end();

  // for each point in insertListIn
  while( nodeIt != nodeEnd )
    {
    listIn.insert( *nodeIt );
    this->m_InternalImage->SetPixel( nodeIt->first, LevelSetType::MinusOneLayer() );
    termContainer->UpdatePixel( nodeIt->first + this->m_Offset, LevelSetType::MinusThreeLayer(), LevelSetType::MinusOneLayer() );
    ++nodeIt;
    }

  nodeIt   = insertListOut.begin();
  nodeEnd  = insertListOut.end();

  // for each point in insertListOut
  while( nodeIt != nodeEnd )
    {
    listOut.insert( *nodeIt );
    this->m_InternalImage->SetPixel( nodeIt->first, LevelSetType::PlusOneLayer() );
    termContainer->UpdatePixel( nodeIt->first + this->m_Offset, LevelSetType::MinusOneLayer(), LevelSetType::PlusOneLayer() );
    ++nodeIt;
    }
}


template< unsigned int VDimension, typename TEquationContainer >
bool
UpdateShiSparseLevelSet< VDimension, TEquationContainer >
::Con( const LevelSetInputType& idx, const LevelSetOutputType& currentStatus,
       const LevelSetOutputRealType& currentUpdate ) const
{
  TermContainerPointer termContainer = this->m_EquationContainer->GetEquation( this->m_CurrentLevelSetId );

  ZeroFluxNeumannBoundaryCondition< LabelImageType > spNBC;

  typename NeighborhoodIteratorType::RadiusType radius;
  radius.Fill( 1 );

  NeighborhoodIteratorType neighIt( radius, this->m_InternalImage,
                                    this->m_InternalImage->GetLargestPossibleRegion() );

  neighIt.OverrideBoundaryCondition( &spNBC );
  neighIt.ActivateOffsets(
    Experimental::GenerateConnectedImageNeighborhoodShapeOffsets<ImageDimension, 1, false>());
  neighIt.SetLocation( idx );

  const LevelSetOutputType oppositeStatus = ( currentStatus == LevelSetType::PlusOneLayer() ) ?
        LevelSetType::MinusOneLayer() : LevelSetType::PlusOneLayer();

  for( typename NeighborhoodIteratorType::Iterator i = neighIt.Begin(); !i.IsAtEnd(); ++i )
    {
    LevelSetOutputType tempValue = i.Get();

    if ( tempValue == oppositeStatus )
      {
      LevelSetInputType tempIdx = neighIt.GetIndex( i.GetNeighborhoodOffset() );

      LevelSetOutputRealType neighborUpdate = termContainer->Evaluate( tempIdx + this->m_Offset );

      if ( neighborUpdate * currentUpdate > NumericTraits< LevelSetOutputType >::ZeroValue() )
        {
        return true;
        }
      }
    }
 return false;
}

} // end namespace itk

#endif // itkUpdateShiSparseLevelSet_hxx
