/*=========================================================================
 *
 *  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.
 *
 *=========================================================================*/
/*=========================================================================
 *
 *  Portions of this file are subject to the VTK Toolkit Version 3 copyright.
 *
 *  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
 *
 *  For complete copyright, license and disclaimer of warranty information
 *  please refer to the NOTICE file at the top of the ITK source tree.
 *
 *=========================================================================*/
#ifndef itkInPlaceImageFilter_hxx
#define itkInPlaceImageFilter_hxx

#include "itkInPlaceImageFilter.h"

namespace itk
{

template <typename TInputImage, typename TOutputImage>
void
InPlaceImageFilter<TInputImage, TOutputImage>::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);
  os << indent << "InPlace: " << (m_InPlace ? "On" : "Off") << std::endl;
  if (this->CanRunInPlace())
  {
    os << indent << "The input and output to this filter are the same type. The filter can be run in place."
       << std::endl;
  }
  else
  {
    os << indent << "The input and output to this filter are different types. The filter cannot be run in place."
       << std::endl;
  }
}

template <typename TInputImage, typename TOutputImage>
void
InPlaceImageFilter<TInputImage, TOutputImage>::InternalAllocateOutputs(const TrueType &)
{
  // Use ProcessObject's GetInput method to get a DataObject pointer,
  // then perform a dynamic_cast to the expected InputImageType. This
  // may fail and that is an expected likely hood, if inputPtr is nullptr
  // then this filter will not run in-place.
  const auto *      inputPtr = dynamic_cast<const InputImageType *>(this->ProcessObject::GetInput(0));
  OutputImageType * outputPtr = this->GetOutput();

  // if told to run in place and the types support it,
  // additionally the buffered and requested regions of the input and
  // output must match.
  bool rMatch = true;
  if (inputPtr != nullptr && (unsigned int)InputImageDimension == (unsigned int)OutputImageDimension)
  {
    for (unsigned int i = 0; i < (unsigned int)InputImageDimension; i++)
    {
      if (inputPtr->GetBufferedRegion().GetIndex(i) != outputPtr->GetRequestedRegion().GetIndex(i))
      {
        rMatch = false;
      }
      if (inputPtr->GetBufferedRegion().GetSize(i) != outputPtr->GetRequestedRegion().GetSize(i))
      {
        rMatch = false;
      }
    }
  }
  else
  {
    rMatch = false;
  }
  if (inputPtr != nullptr && this->GetInPlace() && this->CanRunInPlace() && rMatch)
  {
    // Graft this first input to the output.  Later, we'll need to
    // remove the input's hold on the bulk data.
    //
    OutputImagePointer inputAsOutput = nullptr;
    if (IsSame<TInputImage, TOutputImage>())
    {
      inputAsOutput = reinterpret_cast<TOutputImage *>(const_cast<TInputImage *>(inputPtr));
    }
    itkAssertOrThrowMacro(inputAsOutput.IsNotNull(), "Unable to convert input image to output image as expected!");

    this->GraftOutput(inputAsOutput);
    this->m_RunningInPlace = true;

    using ImageBaseType = ImageBase<OutputImageDimension>;

    // If there are more than one outputs, allocate the remaining outputs
    for (unsigned int i = 1; i < this->GetNumberOfIndexedOutputs(); i++)
    {
      // Check whether the output is an image of the appropriate
      // dimension (use ProcessObject's version of the GetInput()
      // method since it returns the input as a pointer to a
      // DataObject as opposed to the subclass version which
      // static_casts the input to an TInputImage).
      typename ImageBaseType::Pointer nthOutputPtr = dynamic_cast<ImageBaseType *>(this->ProcessObject::GetOutput(i));

      if (nthOutputPtr)
      {
        nthOutputPtr->SetBufferedRegion(nthOutputPtr->GetRequestedRegion());
        nthOutputPtr->Allocate();
      }
      // if the output is not of similar type then it is assumed the
      // the derived class allocated the output if needed.
    }
  }
  else
  {
    this->m_RunningInPlace = false;
    Superclass::AllocateOutputs();
  }
}

template <typename TInputImage, typename TOutputImage>
bool
InPlaceImageFilter<TInputImage, TOutputImage>::CanRunInPlace() const
{
  return IsSame<TInputImage, TOutputImage>();
}

template <typename TInputImage, typename TOutputImage>
void
InPlaceImageFilter<TInputImage, TOutputImage>::ReleaseInputs()
{
  // if told to run in place and the types support it,
  if (this->m_RunningInPlace)
  {
    // Release any input where the ReleaseData flag has been set
    ProcessObject::ReleaseInputs();

    // Release input 0 by default since we overwrote it
    auto * ptr = const_cast<TInputImage *>(this->GetInput());
    if (ptr)
    {
      ptr->ReleaseData();
    }

    this->m_RunningInPlace = false;
  }
  else
  {
    Superclass::ReleaseInputs();
  }
}

} // end namespace itk

#endif
