/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkRawVideoFrame.h

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
/**
 * @class   vtkRawVideoFrame
 * @brief   abstract class for a raw video frame that leaves out implementation
 *          of storage for the subclasses.
 *
 * vtkRawVideoFrame associates an un-compressed video frame with
 * key parameters such as width, height, luminance-chroma pitches, offsets,
 * pixel format and slice order.
 *
 * @sa vtkOpenGLVideoFrame
 */

#ifndef vtkRawVideoFrame_h
#define vtkRawVideoFrame_h

#include "vtkObject.h"

#include "vtkPixelFormatTypes.h"    // for pixel type enum
#include "vtkSmartPointer.h"        // for return value
#include "vtkStreamingCoreModule.h" // for export macro
#include "vtkUnsignedCharArray.h"   // for return value

class vtkRenderWindow;

class VTKSTREAMINGCORE_EXPORT vtkRawVideoFrame : public vtkObject
{
public:
  vtkTypeMacro(vtkRawVideoFrame, vtkObject);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  enum SliceOrderType
  {
    TopDown,  // <- Video codec expected storage format.
    BottomUp, // <- OpenGL pixels, BMP
  };

  ///@{
  /**
   * Set to true if this frame is meant to be a key frame in the video
   * encoding algorithm.
   */
  vtkGetMacro(IsKeyFrame, bool);
  vtkSetMacro(IsKeyFrame, bool);
  ///@}

  ///@{
  /**
   * Set/Get width of the frame.
   * @warning StorageWidth may not always be equal to the provided width.
   */
  void SetWidth(int) noexcept;
  int GetWidth() const noexcept;
  int GetStorageWidth() const noexcept;
  ///@}

  ///@{
  /**
   * Set/Get height of the frame.
   * Storage height is the number of rows in this image.
   * @warning StorageHeight may not always be equal to the provided height.
   */
  void SetHeight(int) noexcept;
  int GetHeight() const noexcept;
  int GetStorageHeight() const noexcept;
  ///@}

  ///@{
  /**
   * Set/Get the format of the pixels. Ex: RGB24, RGBA32, IYUV, NV12
   */
  void SetPixelFormat(VTKPixelFormatType) noexcept;
  VTKPixelFormatType GetPixelFormat() const noexcept;
  ///@}

  ///@{
  /**
   * Set/Get the slice order of the image.
   *
   * The slice order is the memory representation of an image. It can be either top-down
   * or bottom-up. In a top-down ordering, the first block in memory corresponds to the pixel
   * in top-left corner, whereas, in a bottom-up ordering, the first block in memory corresponds
   * to a pixel in the bottom-left corner.
   *
   * Video encoders and decoders work with a top-down memory ordering, whereas
   * OpenGL framebuffers and textures are represented in bottom-up memory order.
   */
  void SetSliceOrderType(SliceOrderType) noexcept;
  SliceOrderType GetSliceOrderType() const noexcept;
  ///@}

  ///@{
  /**
   * Set/Get the strides and use them to interpret the underlying data.
   * Refer https://docs.microsoft.com/en-us/windows/win32/medfound/image-stride for an
   * excellent description of planar image strides.
   * This class uses 8-byte alignment.
   */
  void ComputeDefaultStrides();
  int* GetStrides() VTK_SIZEHINT(3) { return this->Strides; }
  int GetPlanePointerIdx(int planeIdx);
  ///@}

  ///@{
  /**
   * Concrete subclasses can implement pixel read/write operation
   * from/to the given render window.
   * This is an opportunity for subclasses with GPU-storage to
   * avoid GPU->CPU transfer during the read/write operation.
   */
  virtual void Capture(vtkRenderWindow* window) = 0;
  virtual void Render(vtkRenderWindow* window) = 0;
  ///@}

  /**
   * Convenient method that writes out underlying memory to a file.
   */
  void Save(const char* filename);

  /**
   * Copy data from another memory address into this frame.
   */
  void CopyData(unsigned char* from, int rowsize, int numrows);
  void CopyPlanarData(unsigned char* from, int rowsize, int numrows, int plane);

  /**
   * Get a pointer to underlying data. Caller is responsible
   * for memory management of `data`.
   *
   * @note Please use the other `vtkSmartPointer<vtkUnsignedCharArray> GetData()`
   *       when integrating with vtk image writers. It sets up the correct number
   *       of components and indirectly calls the first method. A second copy is not made.
   */
  unsigned int GetData(unsigned char*& data);
  vtkSmartPointer<vtkUnsignedCharArray> GetData();

  ///@{
  /**
   * Copy the pixel data from an `vtkUnsignedCharArray`.
   */
  void CopyData(vtkUnsignedCharArray* from, int rowsize, int numrows);
  void CopyPlanarData(vtkUnsignedCharArray* from, int rowsize, int numrows, int plane);
  ///@}

  ///@{
  /**
   * Methods to copy members and the pixels.
   * ShallowCopy must **not** raise an exception.
   */
  virtual void ShallowCopy(vtkRawVideoFrame* from) noexcept;
  virtual void DeepCopy(vtkRawVideoFrame* from);
  ///@}

  /**
   * Get the actual size instead of estimated size. Often, the data can be
   * larger than our estimated size due to presence of extra padded bytes.
   */
  virtual unsigned int GetActualSize() const = 0;

  /**
   * Allocates internal storage based on the estimated size of this frame.
   */
  virtual void AllocateDataStore() = 0;

  /**
   * Derived instances can return device pointer here.
   * ex: glTexture/cuda/d3d handles.
   * It can be anything you want it to be to help your encoder/decoder implementation.
   */
  virtual void* GetResourceHandle() noexcept = 0;

  ///@{
  /**
   * Luminance/chrominance utilities.
   * Get pitch of the frame.
   * Get chroma pitch of the frame.
   * Get chroma plane offsets of the frame.
   */
  static unsigned int AlignUp(int value, int bytes) noexcept;
  static unsigned int GetWidthBytes(int width, VTKPixelFormatType pixelFormat) noexcept;
  static unsigned int GetNumberOfChromaPlanes(VTKPixelFormatType pixelFormat) noexcept;
  static unsigned int GetChromaHeight(int height, VTKPixelFormatType pixelFormat) noexcept;
  static unsigned int GetEstimatedSize(
    int width, int height, VTKPixelFormatType pixelFormat, int* strides = nullptr) noexcept;
  static unsigned int GetPitch(int width, VTKPixelFormatType pixelFormat) noexcept;
  static unsigned int GetChromaPitch(int width, VTKPixelFormatType pixelFormat) noexcept;
  static std::vector<unsigned int> GetChromaOffsets(
    int width, int height, VTKPixelFormatType pixelFormat) noexcept;
  ///@}

protected:
  vtkRawVideoFrame();
  ~vtkRawVideoFrame() override;

  int DisplayWidth = 0;
  int DisplayHeight = 0;
  int Strides[3] = {};
  int StorageWidth = 0;
  int StorageHeight = 0;
  bool IsKeyFrame = false;
  VTKPixelFormatType PixelFormat = VTKPixelFormatType::VTKPF_NV12;
  SliceOrderType SliceOrder = SliceOrderType::TopDown;

  virtual void CopyDataInternal(unsigned char* from, int rowsize, int numrows) = 0;
  virtual void CopyPlanarDataInternal(unsigned char* from, int rowsize, int numrows, int plane) = 0;
  virtual unsigned int GetDataInternal(unsigned char*& data) = 0;

private:
  vtkRawVideoFrame(const vtkRawVideoFrame&) = delete;
  void operator=(const vtkRawVideoFrame&) = delete;
};

#endif // vtkRawVideoFrame
