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

#include "itkProcessObject.h"
#include "itkTemporalRegion.h"
#include "ITKVideoCoreExport.h"

namespace itk
{

// Forward reference because of circular dependencies
class ITK_FORWARD_EXPORT TemporalDataObject;

/**
 * \class TemporalProcessObject
 * \brief TemporalProcessObject implements a ProcessObject for the itk pipeline
 *        with the notion of a temporal region
 *
 * TemporalProcessObject acts as a pass-through in the inheritance tree in
 * order to require that subclasses properly implement handling of temporal
 * regions. The key parameters of a temporal process object are:
 *
 * m_UnitInputNumberOfFrames
 * m_UnitOutputNumberOfFrames
 * m_FrameSkipPerOutput
 * m_InputStencilCurrentFrameIndex
 *
 * These parameters are protected by default so that the filter writer can
 * choose which to expose and which to keep constant. For a full explanation of
 * each parameter, see the individual comments with their declarations.
 * Subclasses of TemporalProcessObject must implement one of the following:
 *
 * GenerateData()
 * TemporalStreamingGenerateData()
 *
 * By default, GenerateData will split the requested temporal region of the
 * input and output temporal data objects into sub-requests of unit input and
 * unit output size. For example, if the output requests 3 frames and the
 * filter has unit input size of 2 and unit output size of 1, GenerateData will
 * produce 3 sub-requests that each require the input to provide 2 frames.
 * Using this default behavior, TemporalStreamingGenerateData must be
 * implemented and can assume that the RequestedTemporalRegion on the input and
 * output are unit size. If the temporal streaming behavior is not desired,
 * GenerateData can be implemented to directly handle the full output requested
 * temporal region in a single pass.
 *
 * \ingroup ITKVideoCore
 */
class ITKVideoCore_EXPORT TemporalProcessObject : public ProcessObject
{
public:
  ITK_DISALLOW_COPY_AND_MOVE(TemporalProcessObject);

  /*-TYPEDEFS----------------------------------------------------------------*/

  /** Standard class type aliases */
  using Self = TemporalProcessObject;
  using Superclass = ProcessObject;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;

  /** \see LightObject::GetNameOfClass() */
  itkOverrideGetNameOfClassMacro(TemporalProcessObject);

  /*-PUBLIC METHODS----------------------------------------------------------*/

  /** Override EnlargeOutputRequestedRegion, GenerateOutputRequestedRegion, and
   * GenerateInputRequestedRegion to handle temporal regions */
  void
  EnlargeOutputRequestedRegion(DataObject * output) override;

  void
  GenerateOutputRequestedRegion(DataObject * output) override;

  void
  GenerateInputRequestedRegion() override;

  /** Get the number of frames of input required to produce output. We don't
   * provide a Set method because we want some filters to be able to hold this
   * as a constant. */
  itkGetMacro(UnitInputNumberOfFrames, SizeValueType);

  /** Get the number of frames of output produced for a single set of input
   * frames. We don't provide a Set method because we want some filters to be
   * able to hold this as a constant. */
  itkGetMacro(UnitOutputNumberOfFrames, SizeValueType);

  /** The default implementation of UpdateOutputInformation to handle temporal
   * regions will compute the proper size of the output largest possible
   * temporal region based on the largest possible temporal region of the input,
   * the unit input/output sizes for the process, and the number of frames
   * skipped per output*/
  void
  UpdateOutputInformation() override;

  /** Override ProcessObject's implementation of UpdateOutputData. This is
   * necessary because by default ProcessObject propagates the call to its
   * inputs before calling GenerateData. This doesn't work here because we need
   * to adjust the requested temporal region of our inputs before calling their
   * UpdateOutputData methods
   *
   * NOTE: Subclasses that don't use the temporal streaming process must
   * override this function to call the version implemented in ProcessObject
   *
   * Code: this->ProcessObject::UpdateOutputData( output )
   */
  void
  UpdateOutputData(DataObject * output) override;

  /** Override GenerateData to do temporal region streaming. This is analogous
   * to the ThreadedGenerateData system implemented in ImageSource, but it
   * functions slightly differently. Since most temporal processes are going to
   * to need more input frames than they produce output frames for a single
   * operation, we cannot use the same model as spatial streaming which assumes
   * that the input requested region is fully populated before producing any
   * output. Instead, once we have split the requested output region, we reset
   * the requested temporal region of the input to each input requested
   * temporal sub-region (in sequence) and re-propagate the temporal region
   * request up the pipeline. */
  void
  GenerateData() override;

  /** TemporalStreamingGenerateData is in charge of producing output for a
   * single portion of the output requested temporal region. This is where
   * the body of the process will take place. Subclasses that handle spatial
   * data (such as video frames) may instead use this function to split the
   * requested spatial region and process the spatial sub-regions using the
   * mechanism implemented in ImageBase for multithreading. */
  virtual void
  TemporalStreamingGenerateData();

protected:
  /*-PROTECTED METHODS-------------------------------------------------------*/

  /** Constructor that initializes members */
  TemporalProcessObject();

  /** Empty Destructor */
  ~TemporalProcessObject() override = default;

  /** ITK print mechanism */
  void
  PrintSelf(std::ostream & os, Indent indent) const override;

  /** Explicitly handle temporal regions in EnlargeRequestedRegion. The default
   * implementation makes sure that the output requested temporal region is
   * contained in the buffered temporal region or has a duration that is a
   * multiple of the unit output number of frames */
  virtual void
  EnlargeOutputRequestedTemporalRegion(TemporalDataObject * output);

  /** Explicitly handle temporal regions in GenerateOutputRegion. The default
   * implementation checks to see if a request is currently set and if not, it
   * sets the requested temporal region to match the largest possible temporal
   * region
   *
   * Note: If the largest possible temporal duration has infinite duration, the
   * requested temporal region will be set to have duration 1. */
  virtual void
  GenerateOutputRequestedTemporalRegion(TemporalDataObject * output);

  /** Explicitly handle temporal regions in GenerateInputRequestedRegion. The
   * default implementation sets the requested temporal region on the input to
   * start at m_FrameSkipPerOutput times the start frame of the requested output
   * temporal region with the duration needed to produce the entire requested
   * output.
   *
   * NOTE: This default propagation will be overwritten during the
   * UpdateOutputData phase by the temporal streaming mechanism if a subclass
   * implements TemporalStreamingGenerateData, but this propagation is provided
   * so that subclasses which directly implement GenerateData will work
   * correctly. */
  virtual void
  GenerateInputRequestedTemporalRegion();

  /** Split the output's RequestedTemporalRegion into the proper number of
   * sub-regions. By default it is assumed that each sub-region processed
   * should be generated using the set of input frames starting one frame
   * forward in time from the previous sub-region. To change this, set
   * FrameSkipPerOutput to something other than 1. Positive indicates forward
   * in time while negative indicates backward in time.
   * The set of returned TemporalRegion objects is the set of temporal regions
   * that are needed as input to generate the entire output requested region */
  virtual std::vector<TemporalRegion>
  SplitRequestedTemporalRegion();

  /** Method that gets called by GenerateData before
   * TemporalStreamingGenerateData. Subclasses can override this method to
   * provide pre-processing for the data before splitting up the requested
   * output temporal region. */
  virtual void
  BeforeTemporalStreamingGenerateData()
  {}

  /** Method that gets called by GenerateData after
   * TemporalStreamingGenerateData. Subclasses can override this method to
   * provide post-processing for the data after producing the requested output
   * temporal region. */
  virtual void
  AfterTemporalStreamingGenerateData()
  {}

  /** Generate a default LargestPossibleRegion. This is used by temporal
   * process objects that have no input. The default implementation starts at
   * frame 0 and has infinite duration. */
  virtual TemporalRegion
  GenerateDefaultLargestPossibleTemporalRegion();

  itkSetMacro(UnitInputNumberOfFrames, SizeValueType);
  itkSetMacro(UnitOutputNumberOfFrames, SizeValueType);
  itkSetMacro(FrameSkipPerOutput, OffsetValueType);
  itkSetMacro(InputStencilCurrentFrameIndex, SizeValueType);
  itkGetMacro(InputStencilCurrentFrameIndex, SizeValueType);

  /*-PROTECTED MEMBERS-------------------------------------------------------*/

  /** Members to indicate the number of input frames and output frames required
   * to perform a single pass through the processing. */
  SizeValueType m_UnitInputNumberOfFrames{ 1 };
  SizeValueType m_UnitOutputNumberOfFrames{ 1 };

  /** Number of frames to move in order to produce each set of output frames.
   * There is no public API for this member, but subclasses can set it
   * internally (or provide access to it) if they wish. */
  OffsetValueType m_FrameSkipPerOutput{ 1 };

  /** Member to indicate the location of the "current frame" in the minimum set
   * of input frames. For example, if the unit number of input frames is 6 and
   * m_InputStencilCurrentFrameIndex = 4, this indicates that for output frame
   * n, frames n-4 through n+1 are required, whereas if
   * m_InputStencilCurrentFrameIndex = 0, frames n through n+5 are required. */
  SizeValueType m_InputStencilCurrentFrameIndex{ 0 };
}; // end class TemporalProcessObject

} // end namespace itk

#endif
