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


namespace itk
{

template <typename TImageType>
PipelineMonitorImageFilter<TImageType>::PipelineMonitorImageFilter()
{
  m_NumberOfClearPipeline = 0;
  m_ClearPipelineOnGenerateOutputInformation = true;
  this->ClearPipelineSavedInformation();
}


template <typename TImageType>
bool
PipelineMonitorImageFilter<TImageType>::VerifyDownStreamFilterExecutedPropagation()
{
  bool ret = true;
  // we expect that the propagation is not going to be called
  // extra times
  if (m_OutputRequestedRegions.size() != this->GetNumberOfUpdates() ||
      m_InputRequestedRegions.size() != this->GetNumberOfUpdates())
  {
    itkWarningMacro("Down stream filter didn't execute PropagateRequestedRegion well");
    ret = false;
  }
  return ret;
}


template <typename TImageType>
bool
PipelineMonitorImageFilter<TImageType>::VerifyInputFilterExecutedStreaming(int expectedNumber)
{
  if (expectedNumber == 0)
  {
    return true;
  }
  else if (expectedNumber < 0 && static_cast<unsigned int>(-expectedNumber) <= this->GetNumberOfUpdates())
  {
    return true;
  }
  else if (expectedNumber == static_cast<int>(this->GetNumberOfUpdates()))
  {
    return true;
  }
  itkWarningMacro("Streamed pipeline was executed " << this->GetNumberOfUpdates()
                                                    << " times which was not the expected number " << expectedNumber
                                                    << " of times.");
  return false;
}


template <typename TImageType>
bool
PipelineMonitorImageFilter<TImageType>::VerifyInputFilterMatchedUpdateOutputInformation()
{
  InputImageConstPointer input = this->GetInput();
  if (input->GetSpacing() != m_UpdatedOutputSpacing)
  {
    itkWarningMacro("The input filter's Spacing does not match UpdateOutputInformation");
    return false;
  }
  if (input->GetOrigin() != m_UpdatedOutputOrigin)
  {
    itkWarningMacro("The input filter's Origin does not match UpdateOutputInformation");
    return false;
  }
  if (input->GetDirection() != m_UpdatedOutputDirection)
  {
    itkWarningMacro("The input filter's Direction does not match UpdateOutputInformation");
    return false;
  }
  if (input->GetLargestPossibleRegion() != m_UpdatedOutputLargestPossibleRegion)
  {
    itkWarningMacro("The input filter's LargestPossibleRegion does not match UpdateOutputInformation");
    itkWarningMacro("input: " << input->GetLargestPossibleRegion()
                              << "updated: " << m_UpdatedOutputLargestPossibleRegion);
    return false;
  }
  if (!m_UpdatedBufferedRegions.empty() &&
      !m_UpdatedOutputLargestPossibleRegion.IsInside(m_UpdatedBufferedRegions.back()))
  {
    itkWarningMacro("The input filter's BufferedRegion is not contained by LargestPossibleRegion");
    return false;
  }
  return true;
}


template <typename TImageType>
bool
PipelineMonitorImageFilter<TImageType>::VerifyInputFilterBufferedRequestedRegions()
{
  // we expect that the input filter's output image's buffered
  // region is going to match its requested region
  bool         ret = true;
  unsigned int i;
  for (i = 0; i < m_UpdatedBufferedRegions.size(); ++i)
  {
    if (m_UpdatedBufferedRegions[i] != m_UpdatedRequestedRegions[i])
    {
      itkWarningMacro("The input filter's updated buffered region was not the requested region");
      ret = false;
    }
  }
  return ret;
}


template <typename TImageType>
bool
PipelineMonitorImageFilter<TImageType>::VerifyInputFilterMatchedRequestedRegions()
{
  // we expect that the input filter's output image's buffered
  // region is going to match its requested region, which is going
  // to match the requested region at the end of propagation
  //
  bool   ret = true;
  size_t i = m_UpdatedBufferedRegions.size();
  size_t j = m_InputRequestedRegions.size();

  while (i != 0 && j != 0)
  {
    if (m_UpdatedBufferedRegions[--i] != m_InputRequestedRegions[--j])
    {
      itkWarningMacro("The input filter's updated buffer region was not the region we requested");
      ret = false;
    }
  }

  return ret;
}


template <typename TImageType>
bool
PipelineMonitorImageFilter<TImageType>::VerifyInputFilterRequestedLargestRegion()
{
  if (m_InputRequestedRegions.back() != m_UpdatedOutputLargestPossibleRegion)
  {
    itkWarningMacro("The input filter didn't set its output request to the largest region");
    return false;
  }
  return true;
}


template <typename TImageType>
bool
PipelineMonitorImageFilter<TImageType>::VerifyAllInputCanStream(int expectedNumber)
{
  return VerifyInputFilterExecutedStreaming(expectedNumber) && VerifyDownStreamFilterExecutedPropagation() &&
         VerifyInputFilterMatchedRequestedRegions() && VerifyInputFilterBufferedRequestedRegions() &&
         VerifyInputFilterMatchedUpdateOutputInformation();
}


template <typename TImageType>
bool
PipelineMonitorImageFilter<TImageType>::VerifyAllInputCanNotStream()
{
  return VerifyDownStreamFilterExecutedPropagation() && VerifyInputFilterRequestedLargestRegion() &&
         VerifyInputFilterBufferedRequestedRegions() && VerifyInputFilterMatchedUpdateOutputInformation();
}

template <typename TImageType>
bool
PipelineMonitorImageFilter<TImageType>::VerifyAllNoUpdate()
{
  return VerifyDownStreamFilterExecutedPropagation() && m_NumberOfUpdates == 0;
}


template <typename TImageType>
void
PipelineMonitorImageFilter<TImageType>::ClearPipelineSavedInformation()
{
  m_NumberOfUpdates = 0;
  m_OutputRequestedRegions.clear();
  m_InputRequestedRegions.clear();
  m_UpdatedBufferedRegions.clear();
  m_UpdatedRequestedRegions.clear();

  m_UpdatedOutputOrigin.Fill(-1);
  m_UpdatedOutputDirection.Fill(-1);
  m_UpdatedOutputSpacing.Fill(-1);

  ++m_NumberOfClearPipeline;
}


template <typename TImageType>
void
PipelineMonitorImageFilter<TImageType>::GenerateOutputInformation()
{
  if (m_ClearPipelineOnGenerateOutputInformation)
  {
    this->ClearPipelineSavedInformation();
  }

  Superclass::GenerateOutputInformation();

  InputImageConstPointer input = this->GetInput();
  m_UpdatedOutputOrigin = input->GetOrigin();
  m_UpdatedOutputDirection = input->GetDirection();
  m_UpdatedOutputSpacing = input->GetSpacing();
  m_UpdatedOutputLargestPossibleRegion = input->GetLargestPossibleRegion();
  itkDebugMacro("GenerateOutputInformation called");
}


template <typename TImageType>
void
PipelineMonitorImageFilter<TImageType>::PropagateRequestedRegion(DataObject * output)
{
  // call the superclass' implementation of this method
  Superclass::PropagateRequestedRegion(output);

  // record the regions after everything has executed
  itkDebugMacro("After PropagateRequestedRegion: " << this->GetInput()->GetRequestedRegion());
  this->m_InputRequestedRegions.push_back(this->GetInput()->GetRequestedRegion());
  this->m_OutputRequestedRegions.push_back(this->GetOutput()->GetRequestedRegion());
}


template <typename TImageType>
void
PipelineMonitorImageFilter<TImageType>::EnlargeOutputRequestedRegion(DataObject * output)
{
  // call the superclass' implementation of this method
  Superclass::EnlargeOutputRequestedRegion(output);

  itkDebugMacro("EnlargeOutputRequestRegion: " << this->GetOutput()->GetRequestedRegion());
}


template <typename TImageType>
void
PipelineMonitorImageFilter<TImageType>::GenerateInputRequestedRegion()
{
  // call the superclass' implementation of this method
  Superclass::GenerateInputRequestedRegion();

  // this is not very interesting as it's always going to be the
  // output requested region
  itkDebugMacro("GenerateInputRequestRegion: " << this->GetInput()->GetRequestedRegion());
}


template <typename TImageType>
void
PipelineMonitorImageFilter<TImageType>::GenerateData()
{
  // Get pointers to the input and output
  InputImagePointer output = this->GetOutput();
  InputImagePointer input = const_cast<TImageType *>(this->GetInput());

  // Graft the input Onto the output, so that we run "in-place"
  this->GraftOutput(input);

  itkDebugMacro("GenerateData Buffered: " << this->GetInput()->GetBufferedRegion()
                                          << " Requested:" << this->GetInput()->GetRequestedRegion());

  m_UpdatedBufferedRegions.push_back(this->GetInput()->GetBufferedRegion());
  m_UpdatedRequestedRegions.push_back(this->GetInput()->GetRequestedRegion());

  ++m_NumberOfUpdates;

  // We are finished with the input data, so release it. While this is
  // usually done in ReleaseInputs, it doesn't do any harm to release
  // it early
  input->ReleaseData();
}


template <typename TImageType>
void
PipelineMonitorImageFilter<TImageType>::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);

  os << indent
     << "ClearPipelineOnGenerateOutputInformation: " << (m_ClearPipelineOnGenerateOutputInformation ? "On" : "Off")
     << std::endl;
  os << indent << "NumberOfUpdates: " << m_NumberOfUpdates << std::endl;
  os << indent << "NumberOfClearPipeline: " << m_NumberOfClearPipeline << std::endl;

  os << indent << "OutputRequestedRegions: " << std::endl;
  for (auto i = m_OutputRequestedRegions.begin(); i != m_OutputRequestedRegions.end(); ++i)
  {
    i->Print(os, indent.GetNextIndent());
  }

  os << indent << "InputRequestedRegions: " << std::endl;
  for (auto i = m_InputRequestedRegions.begin(); i != m_InputRequestedRegions.end(); ++i)
  {
    i->Print(os, indent.GetNextIndent());
  }

  os << indent << "UpdatedBufferedRegions: " << std::endl;
  for (auto i = m_UpdatedBufferedRegions.begin(); i != m_UpdatedBufferedRegions.end(); ++i)
  {
    i->Print(os, indent.GetNextIndent());
  }

  os << indent << "UpdatedRequestedRegions: " << std::endl;
  for (auto i = m_UpdatedRequestedRegions.begin(); i != m_UpdatedRequestedRegions.end(); ++i)
  {
    i->Print(os, indent.GetNextIndent());
  }

  os << indent
     << "UpdatedOutputOrigin: " << static_cast<typename NumericTraits<PointType>::PrintType>(m_UpdatedOutputOrigin)
     << std::endl;
  os << indent << "UpdatedOutputDirection: "
     << static_cast<typename NumericTraits<DirectionType>::PrintType>(m_UpdatedOutputDirection) << std::endl;
  os << indent
     << "UpdatedOutputSpacing: " << static_cast<typename NumericTraits<SpacingType>::PrintType>(m_UpdatedOutputSpacing)
     << std::endl;
  os << indent << "UpdatedOutputLargestPossibleRegion: " << m_UpdatedOutputLargestPossibleRegion << std::endl;
}

} // namespace itk

#endif //  itkPipelineMonitorImageFilter_hxx
