// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

#ifndef vtkWebGPUComputeTexture_h
#define vtkWebGPUComputeTexture_h

#include "vtkDataArray.h"             // for vtkDataArray used in SetData
#include "vtkLogger.h"                // for the logger
#include "vtkObject.h"                // for superclass
#include "vtkRenderingWebGPUModule.h" // For export macro
#include "vtkSetGet.h"                // for get/set macro
#include "vtk_wgpu.h"                 // for dawn classes

VTK_ABI_NAMESPACE_BEGIN

class vtkWebGPURenderer;

/**
 * Represents the set of parameters that will be used to create a compute shader texture on the
 * device when it will be added to a pipeline using vtkWebGPUComputePipeline::AddTexture()
 *
 * Some parameters have defaults for convenience:
 *
 * - Format defaults to RGBA
 * - Dimension defaults to 2D
 * - The depth of the texture (extents/size in the Z coordinate) defaults to 1
 * - The maximum number of mip levels defaults to 0
 */
class VTKRENDERINGWEBGPU_EXPORT vtkWebGPUComputeTexture : public vtkObject
{
public:
  vtkTypeMacro(vtkWebGPUComputeTexture, vtkObject);
  static vtkWebGPUComputeTexture* New();

  /**
   * RGBA: Uses RGB + alpha. Default.
   * R32_FLOAT: Only a 32 bit float red channel
   */
  enum TextureFormat
  {
    RGBA8_UNORM,
    R32_FLOAT
  };

  /**
   * How the texture data is arranged. Affects the type of sampler used in the compute shader
   *
   * DIMENSION_1D: 1D texture
   * DIMENSION_2D: 2D texture. Default.
   * DIMENSION_3D: 3D texture
   */
  enum TextureDimension
  {
    DIMENSION_1D,
    DIMENSION_2D,
    DIMENSION_3D
  };

  /**
   * How will the texture be used by the shader
   *
   * UNDEFINED: Texture mode not set. Default.
   * READ_ONLY: The compute shader can only read from the texture and a sampler can be used
   * WRITE_ONLY_STORAGE: The compute shader can only write to the texture and a sampler cannot be
   * used
   * READ_WRITE_STORAGE: The compute shader can read and write to the texture and a sampler
   * cannot be used
   */
  enum TextureMode
  {
    UNDEFINED,
    READ_ONLY,
    WRITE_ONLY_STORAGE,
    READ_WRITE_STORAGE
  };

  /**
   * Determines what kind of value is returned when reading from the texture in the compute shader
   *
   * FLOAT: Reading from the texture returns float values. Default.
   * UNFILTERABLE_FLOAT: Float but cannot be filtered by a sampler
   * DEPTH: Used for depth textures. The depth is returned as a float in the first channel of the
   * return vec4
   * SIGNED_INT: Signed integers. Used for textures containing signed integers data
   * UNSIGNED_INT: Unsigned integers. Used for textures containing unsigned integers data
   */
  enum TextureSampleType
  {
    FLOAT,
    UNFILTERABLE_FLOAT,
    DEPTH,
    SIGNED_INT,
    UNSIGNED_INT
  };

  ///@{
  /**
   * Get/set the size in bytes of the texture
   */
  vtkGetMacro(ByteSize, vtkIdType);
  vtkSetMacro(ByteSize, vtkIdType);
  ///@}

  /**
   * Number of bytes used per pixel
   */
  unsigned int GetBytesPerPixel();

  /**
   * Returns the number of components per pixel for the format of this texture. 1 for R32_FLOAT or
   * for RGBA8 for example.
   */
  unsigned int GetPixelComponentsCount();

  ///@{
  /**
   * Get/set the width in pixels of the texture
   */
  unsigned int GetWidth() { return Extents[0]; }
  void SetWidth(unsigned int width) { Extents[0] = width; }
  ///@}

  ///@{
  /**
   * Get/set the height in pixels of the texture
   */
  unsigned int GetHeight() { return Extents[1]; }
  void SetHeight(unsigned int height) { Extents[1] = height; }
  ///@}

  ///@{
  /**
   * Get/set the depth in pixels of the texture
   */
  unsigned int GetDepth() { return Extents[2]; }
  void SetDepth(unsigned int depth) { Extents[2] = depth; }
  ///@}

  ///@{
  /**
   * Get/set the size of the texture
   *
   * The Z coordinate can be omitted since vtkWebGPUComputeTexture use 1 by default (meaning that
   * there is no depth)
   */
  void GetSize(unsigned int& x, unsigned int& y, unsigned int& z);
  void GetSize(unsigned int& x, unsigned int& y);
  void GetSize(unsigned int* xyz);
  unsigned int* GetSize();

  void SetSize(unsigned int x, unsigned int y, unsigned int z);
  void SetSize(unsigned int x, unsigned int y);
  void SetSize(unsigned int* xyz);
  ///@}

  ///@{
  /**
   * Get/set the texture dimension
   */
  vtkGetEnumMacro(Dimension, TextureDimension);
  vtkSetEnumMacro(Dimension, TextureDimension);
  ///@}

  ///@{
  /**
   * Get/set the texture format
   */
  vtkGetEnumMacro(Format, TextureFormat);
  vtkSetEnumMacro(Format, TextureFormat);
  ///@}

  ///@{
  /**
   * Get/set the texture format
   */
  vtkGetEnumMacro(Mode, TextureMode);
  vtkSetEnumMacro(Mode, TextureMode);
  ///@}

  ///@{
  /**
   * Get/set the texture sample type
   */
  vtkGetEnumMacro(SampleType, TextureSampleType);
  vtkSetEnumMacro(SampleType, TextureSampleType);
  ///@}

  ///@{
  /**
   * Get/set the maximum mipmap levels used by the texture
   */
  vtkGetMacro(MipLevelCount, unsigned int);
  vtkSetMacro(MipLevelCount, unsigned int);
  ///@}

  /**
   * Sets the data that will be used by the texture.
   * Passing an empty vector to this method clears any previously given std::vector data, allowing
   * for the use of a vtkDataArray instead.
   *
   * @warning: This does not copy the data so the data given to this texture needs to stay valid
   * (i.e. not freed) until the texture is added to a compute pipeline using
   * vtkWebGPUComputePipeline::AddTexture().
   */
  template <typename T>
  void SetData(const std::vector<T>& data)
  {
    if (data.size() == 0)
    {
      // The user wants to clear the std::vector data

      this->DataPointer = nullptr;

      return;
    }

    if (this->DataArray != nullptr)
    {
      vtkLog(WARNING,
        "A vtkDataArray has already been set for this compute texture. You cannot have both "
        "std::vector and vtkDataArray data set at the same time. Use SetData() with a nullptr "
        "vtkDataArray if you wish to remove the vtkDataArray data from this compute texture. "
        "Nothing was changed in the texture by this call.");

      return;
    }

    this->DataPointer = data.data();
    this->ByteSize = data.size() * sizeof(T);
  }

  /**
   * Sets the data that will be used by the texture.
   * Passing a nullptr vtkDataArray clears any previously given vtkDataArray data allowing the use
   * of std::vector data instead.
   *
   * NOTE: This does not copy the data so the data given to this texture needs to stay valid (i.e.
   * not freed) until the texture is added to a compute pipeline using
   * vtkWebGPUComputePipeline::AddTexture().
   */
  void SetData(vtkDataArray* data);

  /**
   * The pointer to the std::vector<> data passed with SetData(std::vector)
   *
   * @warning: This pointer only points to a valid location as long as
   * the std::vector given with SetData() is alive.
   */
  const void* GetDataPointer() { return this->DataPointer; }

  /**
   * The pointer to the vtkDataArray passed with SetData(vtkDataArray)
   *
   * @warning: This pointer to the array is only valid as long as
   * the vtkDataArray given with SetData() is alive.
   */
  vtkDataArray* GetDataArray() { return this->DataArray; }

  ///@{
  /**
   * Get/set the label used for debugging in case of errors
   */
  const std::string& GetLabel() const { return this->Label; }
  vtkGetMacro(Label, std::string&);
  vtkSetMacro(Label, std::string);
  ///@}

private:
  // Total size of the texture in bytes
  vtkIdType ByteSize = -1;

  // Number of pixels in X, Y and Z direction.
  // Defaulting to 1 in the Z direction because 2D textures are assumed to be the common case.
  unsigned int Extents[3] = { 0, 0, 1 };

  // Is the texture 1D, 2D or 3D
  TextureDimension Dimension = TextureDimension::DIMENSION_2D;

  // The format of the texture: RGB or RGBA?
  TextureFormat Format = TextureFormat::RGBA8_UNORM;

  // The read/write mode of the texture
  TextureMode Mode = TextureMode::UNDEFINED;

  // The type of value produced when sampling the texture in the shader
  TextureSampleType SampleType = TextureSampleType::FLOAT;

  // Maximum number of mipmap levels supported by the texture
  unsigned int MipLevelCount = 1;

  // Pointer to the data that will be uploaded to the texture when a std::vector was given
  const void* DataPointer = nullptr;

  // Pointer to the vtkDataArray that will be uploaded to the texture
  vtkDataArray* DataArray = nullptr;

  // Label used for debugging.
  std::string Label = "Compute texture";
};

VTK_ABI_NAMESPACE_END

#endif