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

#include "itkLevelSetEvolutionComputeIterationThreader.h"

#include "itkImageRegionConstIteratorWithIndex.h"

namespace itk
{

template< typename TImage, typename TLevelSetEvolution >
void
LevelSetEvolutionComputeIterationThreader< LevelSetDenseImage< TImage >, ThreadedImageRegionPartitioner< TImage::ImageDimension >, TLevelSetEvolution >
::ThreadedExecution( const DomainType & imageSubRegion,
                     const ThreadIdType itkNotUsed(threadId) )
{
  typename LevelSetContainerType::Iterator levelSetContainerIt = this->m_Associate->m_LevelSetContainer->Begin();
  typename LevelSetType::Pointer levelSet = levelSetContainerIt->GetLevelSet();
  typename LevelSetImageType::ConstPointer levelSetImage = levelSet->GetImage();

  // Identify the level-set region
  OffsetType offset = levelSet->GetDomainOffset();
  IndexType index = imageSubRegion.GetIndex() - offset;
  RegionType subRegion;
  subRegion.SetSize( imageSubRegion.GetSize() );
  subRegion.SetIndex( index );

  ImageRegionConstIteratorWithIndex< LevelSetImageType > imageIt( levelSetImage, subRegion );
  imageIt.GoToBegin();

  if( this->m_Associate->m_LevelSetContainer->HasDomainMap() )
    {
    const IdListType * idList = this->m_Associate->m_IdListToProcessWhenThreading;

    // Avoid repeated map lookups.
    const size_t numberOfLevelSets = idList->size();
    std::vector< LevelSetImageType * > levelSetUpdateImages( numberOfLevelSets );
    std::vector< TermContainerType * > termContainers( numberOfLevelSets );
    auto idListIt = idList->begin();
    unsigned int idListIdx = 0;
    while( idListIt != idList->end() )
      {
      //! \todo Fix me for string identifiers
      LevelSetType * levelSetUpdate = this->m_Associate->m_UpdateBuffer->GetLevelSet( *idListIt - 1 );
      levelSetUpdateImages[idListIdx] = levelSetUpdate->GetModifiableImage();
      termContainers[idListIdx] = this->m_Associate->m_EquationContainer->GetEquation( *idListIt - 1 );
      ++idListIt;
      ++idListIdx;
      }

    while( !imageIt.IsAtEnd() )
      {
      const IndexType levelSetIndex = imageIt.GetIndex();
      const IndexType inputIndex = imageIt.GetIndex() + offset;
      for( idListIdx = 0; idListIdx < numberOfLevelSets; ++idListIdx )
        {
        LevelSetDataType characteristics;
        termContainers[idListIdx]->ComputeRequiredData( inputIndex, characteristics );
        LevelSetOutputRealType temp_update = termContainers[idListIdx]->Evaluate( inputIndex, characteristics );
        levelSetUpdateImages[idListIdx]->SetPixel( levelSetIndex, temp_update );
        }
      ++imageIt;
      }
    }
  else
    {
    // This is for single level set analysis, so we only process the first level
    // set.
    typename LevelSetContainerType::ConstIterator levelSetUpdateContainerIt = this->m_Associate->m_UpdateBuffer->Begin();
    typename LevelSetType::Pointer levelSetUpdate = levelSetUpdateContainerIt->GetLevelSet();
    typename LevelSetImageType::Pointer levelSetUpdateImage = levelSetUpdate->GetModifiableImage();

    typename EquationContainerType::Iterator equationContainerIt = this->m_Associate->m_EquationContainer->Begin();
    typename TermContainerType::Pointer termContainer = equationContainerIt->GetEquation();

    imageIt.GoToBegin();
    while( !imageIt.IsAtEnd() )
      {
      const IndexType levelSetIndex = imageIt.GetIndex();
      const IndexType inputIndex = imageIt.GetIndex() + offset;
      LevelSetDataType characteristics;
      termContainer->ComputeRequiredData( inputIndex, characteristics );
      LevelSetOutputRealType temp_update = termContainer->Evaluate( inputIndex, characteristics );
      levelSetUpdateImage->SetPixel( levelSetIndex, temp_update );
      ++imageIt;
      }
    }
}

template< typename TImage, typename TLevelSetEvolution >
void
LevelSetEvolutionComputeIterationThreader<
  LevelSetDenseImage< TImage >,
  ThreadedIteratorRangePartitioner< typename TLevelSetEvolution::DomainMapImageFilterType::DomainMapType::const_iterator >,
    TLevelSetEvolution >
::ThreadedExecution( const DomainType & imageSubDomain,
                     const ThreadIdType itkNotUsed(threadId) )
{
  typename InputImageType::ConstPointer inputImage = this->m_Associate->m_EquationContainer->GetInput();

  typename DomainType::IteratorType mapIt = imageSubDomain.Begin();
  while( mapIt != imageSubDomain.End() )
    {
    ImageRegionConstIteratorWithIndex< InputImageType > it( inputImage, *(mapIt->second.GetRegion()) );
    it.GoToBegin();

    while( !it.IsAtEnd() )
      {
      const IdListType idList = *(mapIt->second.GetIdList());

      //itkAssertInDebugOrThrowInReleaseMacro( !idList.empty() );

      for( auto idListIt = idList.begin(); idListIt != idList.end(); ++idListIt )
        {
        typename LevelSetType::Pointer levelSetUpdate = this->m_Associate->m_UpdateBuffer->GetLevelSet( *idListIt - 1 );

        OffsetType offset = levelSetUpdate->GetDomainOffset();
        IndexType levelSetIndex = it.GetIndex() - offset;

        LevelSetDataType characteristics;
        typename TermContainerType::Pointer termContainer = this->m_Associate->m_EquationContainer->GetEquation( *idListIt - 1 );
        termContainer->ComputeRequiredData( it.GetIndex(), characteristics );
        LevelSetOutputRealType tempUpdate = termContainer->Evaluate( it.GetIndex(), characteristics );

        LevelSetImageType * levelSetImage = levelSetUpdate->GetModifiableImage();
        levelSetImage->SetPixel( levelSetIndex, tempUpdate );
        }
      ++it;
      }
    ++mapIt;
    }
}

template< typename TOutput, unsigned int VDimension, typename TLevelSetEvolution >
void
LevelSetEvolutionComputeIterationThreader<
      WhitakerSparseLevelSetImage< TOutput, VDimension >,
      ThreadedIteratorRangePartitioner< typename WhitakerSparseLevelSetImage< TOutput, VDimension >::LayerConstIterator >,
      TLevelSetEvolution >
::BeforeThreadedExecution()
{
  const ThreadIdType numberOfThreads = this->GetNumberOfWorkUnitsUsed();
  this->m_NodePairsPerThread.resize( numberOfThreads );

  for( ThreadIdType ii = 0; ii < numberOfThreads; ++ii )
    {
    this->m_NodePairsPerThread[ii].clear();
    }
}

template< typename TOutput, unsigned int VDimension, typename TLevelSetEvolution >
void
LevelSetEvolutionComputeIterationThreader<
      WhitakerSparseLevelSetImage< TOutput, VDimension >,
      ThreadedIteratorRangePartitioner< typename WhitakerSparseLevelSetImage< TOutput, VDimension >::LayerConstIterator >,
      TLevelSetEvolution >
::ThreadedExecution( const DomainType & iteratorSubRange,
                     const ThreadIdType threadId )
{
  typename LevelSetContainerType::Iterator it = this->m_Associate->m_LevelSetContainerIteratorToProcessWhenThreading;
  typename LevelSetType::ConstPointer levelSet = it->GetLevelSet();

  LevelSetIdentifierType levelSetId = it->GetIdentifier();
  OffsetType offset = levelSet->GetDomainOffset();

  typename TermContainerType::Pointer termContainer = this->m_Associate->m_EquationContainer->GetEquation( levelSetId );

  typename LevelSetType::LayerConstIterator listIt = iteratorSubRange.Begin();

  while( listIt != iteratorSubRange.End() )
    {
    const LevelSetInputType levelsetIndex = listIt->first;
    LevelSetInputType inputIndex = listIt->first + offset;

    LevelSetDataType characteristics;

    termContainer->ComputeRequiredData( inputIndex, characteristics );

    const auto temp_update = static_cast< LevelSetOutputType >( termContainer->Evaluate( inputIndex, characteristics ) );

    this->m_NodePairsPerThread[threadId].push_back( NodePairType( levelsetIndex, temp_update ) );

    ++listIt;
    }
}

template< typename TOutput, unsigned int VDimension, typename TLevelSetEvolution >
void
LevelSetEvolutionComputeIterationThreader<
      WhitakerSparseLevelSetImage< TOutput, VDimension >,
      ThreadedIteratorRangePartitioner< typename WhitakerSparseLevelSetImage< TOutput, VDimension >::LayerConstIterator >,
      TLevelSetEvolution >
::AfterThreadedExecution()
{
  typename LevelSetContainerType::Iterator it = this->m_Associate->m_LevelSetContainerIteratorToProcessWhenThreading;
  LevelSetIdentifierType levelSetId = it->GetIdentifier();
  typename LevelSetEvolutionType::LevelSetLayerType * levelSetLayerUpdateBuffer = this->m_Associate->m_UpdateBuffer[ levelSetId ];

  const ThreadIdType numberOfThreads = this->GetNumberOfWorkUnitsUsed();
  for( ThreadIdType ii = 0; ii < numberOfThreads; ++ii )
    {
    typename std::vector< NodePairType >::const_iterator pairIt = this->m_NodePairsPerThread[ii].begin();
    while( pairIt != this->m_NodePairsPerThread[ii].end() )
      {
      levelSetLayerUpdateBuffer->insert( *pairIt );
      ++pairIt;
      }
    }
}

} // end namespace itk

#endif
