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

#include "itkFrameDifferenceVideoFilter.h"

#include "itkImageRegionConstIterator.h"
#include "itkImageRegionIterator.h"
#include "itkNumericTraits.h"

namespace itk
{

//
// Constructor
//
template <typename TInputVideoStream, typename TOutputVideoStream>
FrameDifferenceVideoFilter<TInputVideoStream, TOutputVideoStream>::FrameDifferenceVideoFilter()
{
  // Default to differencing adjacent frames
  this->TemporalProcessObject::m_UnitInputNumberOfFrames = 2;

  // Always output one frame
  this->TemporalProcessObject::m_UnitOutputNumberOfFrames = 1;

  // Use the frame number of the last frame in the stencil
  this->TemporalProcessObject::m_InputStencilCurrentFrameIndex = 0;

  // Move forward one frame of input for every frame of output
  this->TemporalProcessObject::m_FrameSkipPerOutput = 1;
}


//
// PrintSelf
//
template <typename TInputVideoStream, typename TOutputVideoStream>
void
FrameDifferenceVideoFilter<TInputVideoStream, TOutputVideoStream>::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);
  os << indent << "FrameOffset: " << this->TemporalProcessObject::m_UnitInputNumberOfFrames - 1 << std::endl;
}


//
// SetFrameOffset
//
template <typename TInputVideoStream, typename TOutputVideoStream>
void
FrameDifferenceVideoFilter<TInputVideoStream, TOutputVideoStream>::SetFrameOffset(SizeValueType numFrames)
{
  // Expand the input stencil
  this->TemporalProcessObject::m_UnitInputNumberOfFrames = numFrames + 1;

  // Mark modified
  this->Modified();
}


//
// GetFrameOffset
//
template <typename TInputVideoStream, typename TOutputVideoStream>
SizeValueType
FrameDifferenceVideoFilter<TInputVideoStream, TOutputVideoStream>::GetFrameOffset()
{
  return this->TemporalProcessObject::m_UnitInputNumberOfFrames - 1;
}


//
// ThreadedGenerateData
//
template <typename TInputVideoStream, typename TOutputVideoStream>
void
FrameDifferenceVideoFilter<TInputVideoStream, TOutputVideoStream>::ThreadedGenerateData(
  const OutputFrameSpatialRegionType & outputRegionForThread,
  int                                  itkNotUsed(threadId))
{
  // Get the input and output video streams
  const InputVideoStreamType * input = this->GetInput();
  OutputVideoStreamType *      output = this->GetOutput();
  SizeValueType                numFrames = this->TemporalProcessObject::m_UnitInputNumberOfFrames;

  // Get output frame number
  typename OutputVideoStreamType::TemporalRegionType outReqTempRegion = output->GetRequestedTemporalRegion();
  SizeValueType                                      outputFrameNumber = outReqTempRegion.GetFrameStart();

  typename InputVideoStreamType::TemporalRegionType inReqTempRegion = input->GetRequestedTemporalRegion();
  SizeValueType                                     inputStart = inReqTempRegion.GetFrameStart();
  SizeValueType                                     inputDuration = inReqTempRegion.GetFrameDuration();

  // Make sure we've got the right duration
  if (inputDuration != numFrames)
  {
    itkExceptionMacro("Incorrect number of input frames");
  }

  // Get iterators for the input frames
  using ConstIterType = ImageRegionConstIterator<InputFrameType>;
  OutputFrameSpatialRegionType inputRegion;
  inputRegion.SetSize(outputRegionForThread.GetSize());
  inputRegion.SetIndex(outputRegionForThread.GetIndex());
  ConstIterType I0Iter(input->GetFrame(inputStart), inputRegion);
  ConstIterType I1Iter(input->GetFrame(inputStart + numFrames - 1), inputRegion);

  // Get the output frame and its iterator
  OutputFrameType *                         outFrame = output->GetFrame(outputFrameNumber);
  itk::ImageRegionIterator<OutputFrameType> outIter(outFrame, outputRegionForThread);

  // Average the input frames at each pixel of the output region
  using InputPixelRealType = typename NumericTraits<InputPixelType>::RealType;
  while (!outIter.IsAtEnd())
  {
    // Compute the signed difference as a real value
    InputPixelRealType val =
      static_cast<InputPixelRealType>(I0Iter.Get()) - static_cast<InputPixelRealType>(I1Iter.Get());

    // Square it
    val *= val;

    // Set the output
    outIter.Set(static_cast<OutputPixelType>(val));

    // Update the iterators
    ++outIter;
    ++I0Iter;
    ++I1Iter;
  }
}


} // end namespace itk

#endif
