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

#include "itkCastImageFilter.h"

namespace itk
{

template <typename TInputImage, typename TKernelImage, typename TOutputImage, typename TInternalPrecision>
IterativeDeconvolutionImageFilter<TInputImage, TKernelImage, TOutputImage, TInternalPrecision>::
  IterativeDeconvolutionImageFilter()
{
  m_NumberOfIterations = 1;
  m_Iteration = 0;
  m_StopIteration = false;
  m_TransferFunction = nullptr;
  m_CurrentEstimate = nullptr;
  m_InputMTime = 0L;
  m_KernelMTime = 0L;
}

template <typename TInputImage, typename TKernelImage, typename TOutputImage, typename TInternalPrecision>
IterativeDeconvolutionImageFilter<TInputImage, TKernelImage, TOutputImage, TInternalPrecision>::
  ~IterativeDeconvolutionImageFilter()
{
  m_TransferFunction = nullptr;
  m_CurrentEstimate = nullptr;
}

template <typename TInputImage, typename TKernelImage, typename TOutputImage, typename TInternalPrecision>
void
IterativeDeconvolutionImageFilter<TInputImage, TKernelImage, TOutputImage, TInternalPrecision>::Initialize(
  ProgressAccumulator * progress,
  float                 progressWeight,
  float                 itkNotUsed(iterationProgressWeight))
{
  // Generate an estimate if there is none or if the input has changed.
  if (!this->m_CurrentEstimate || m_InputMTime != this->GetInput()->GetMTime())
  {
    this->PadInput(this->GetInput(), m_CurrentEstimate, progress, 0.5f * progressWeight);
    m_CurrentEstimate->DisconnectPipeline();

    m_InputMTime = this->GetInput()->GetMTime();
  }

  // Generate the transfer function if there is none or if the kernel
  // input has changed.
  if (!this->m_TransferFunction || m_KernelMTime != this->GetKernelImage()->GetMTime())
  {
    this->PrepareKernel(this->GetKernelImage(), m_TransferFunction, progress, 0.5f * progressWeight);
    m_TransferFunction->DisconnectPipeline();

    m_KernelMTime = this->GetKernelImage()->GetMTime();
  }
}

template <typename TInputImage, typename TKernelImage, typename TOutputImage, typename TInternalPrecision>
void
IterativeDeconvolutionImageFilter<TInputImage, TKernelImage, TOutputImage, TInternalPrecision>::Finish(
  ProgressAccumulator * progress,
  float                 progressWeight)
{
  this->CropOutput(m_CurrentEstimate, progress, progressWeight);

  m_CurrentEstimate = nullptr;
  m_TransferFunction = nullptr;
}

template <typename TInputImage, typename TKernelImage, typename TOutputImage, typename TInternalPrecision>
void
IterativeDeconvolutionImageFilter<TInputImage, TKernelImage, TOutputImage, TInternalPrecision>::
  GenerateInputRequestedRegion()
{
  // Request the largest possible region for both input images.
  if (this->GetInput())
  {
    typename InputImageType::Pointer imagePtr = const_cast<InputImageType *>(this->GetInput());
    imagePtr->SetRequestedRegionToLargestPossibleRegion();
  }

  if (this->GetKernelImage())
  {
    // Input kernel is an image, cast away the constness so we can set
    // the requested region.
    typename KernelImageType::Pointer kernelPtr = const_cast<KernelImageType *>(this->GetKernelImage());
    kernelPtr->SetRequestedRegionToLargestPossibleRegion();
  }
}

template <typename TInputImage, typename TKernelImage, typename TOutputImage, typename TInternalPrecision>
void
IterativeDeconvolutionImageFilter<TInputImage, TKernelImage, TOutputImage, TInternalPrecision>::GenerateData()
{
  // Create a process accumulator for tracking the progress of this minipipeline
  auto progress = ProgressAccumulator::New();
  progress->SetMiniPipelineFilter(this);

  typename Superclass::InputImageConstPointer inputPtr = this->GetInput();
  typename Superclass::OutputImagePointer     outputPtr = this->GetOutput(0);

  outputPtr->SetRequestedRegion(inputPtr->GetRequestedRegion());
  outputPtr->SetBufferedRegion(inputPtr->GetBufferedRegion());
  outputPtr->SetLargestPossibleRegion(inputPtr->GetLargestPossibleRegion());
  outputPtr->Allocate();

  // Set up progress tracking
  float iterationWeight = 0.8f / static_cast<float>(m_NumberOfIterations);
  this->Initialize(progress, 0.1f, iterationWeight);

  for (m_Iteration = 0; m_Iteration < m_NumberOfIterations; ++m_Iteration)
  {
    this->InvokeEvent(IterationEvent());
    if (m_StopIteration)
    {
      break;
    }

    this->Iteration(progress, iterationWeight);
  }

  this->Finish(progress, 0.1f);
}

template <typename TInputImage, typename TKernelImage, typename TOutputImage, typename TInternalPrecision>
void
IterativeDeconvolutionImageFilter<TInputImage, TKernelImage, TOutputImage, TInternalPrecision>::PrintSelf(
  std::ostream & os,
  Indent         indent) const
{
  this->Superclass::PrintSelf(os, indent);

  os << indent << "NumberOfIterations: " << m_NumberOfIterations << std::endl;
  os << indent << "Iteration: " << m_Iteration << std::endl;
  os << indent << "StopIteration: " << m_StopIteration << std::endl;
  os << indent << "InputMTime: " << m_InputMTime << std::endl;
  os << indent << "KernelMTime: " << m_KernelMTime << std::endl;
}

} // end namespace itk

#endif
