//
// Copyright 2024 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

#ifndef LIBANGLE_RENDERER_WGPU_WGPU_HELPERS_H_
#define LIBANGLE_RENDERER_WGPU_WGPU_HELPERS_H_

#include <stdint.h>
#include <webgpu/webgpu.h>
#include <algorithm>

#include "libANGLE/Error.h"
#include "libANGLE/ImageIndex.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/renderer/wgpu/ContextWgpu.h"
#include "libANGLE/renderer/wgpu/wgpu_utils.h"

namespace rx
{

class ContextWgpu;

namespace webgpu
{

// WebGPU requires copy buffers bytesPerRow to be aligned to 256.
// https://www.w3.org/TR/webgpu/#abstract-opdef-validating-gputexelcopybufferinfo
static const GLuint kCopyBufferAlignment = 256;

enum class UpdateSource
{
    Clear,
    Texture,
};

struct ClearUpdate
{
    ClearValues clearValues;
    bool hasDepth;
    bool hasStencil;
};

struct SubresourceUpdate
{
    SubresourceUpdate() {}
    ~SubresourceUpdate() {}

    SubresourceUpdate(UpdateSource targetUpdateSource,
                      gl::LevelIndex newTargetLevel,
                      BufferHandle targetBuffer,
                      const WGPUTexelCopyBufferLayout &targetBufferLayout)
    {
        updateSource      = targetUpdateSource;
        textureData       = targetBuffer;
        textureDataLayout = targetBufferLayout;
        targetLevel       = newTargetLevel;
    }

    SubresourceUpdate(UpdateSource targetUpdateSource,
                      gl::LevelIndex newTargetLevel,
                      uint32_t newLayerIndex,
                      uint32_t newLayerCount,
                      BufferHandle targetBuffer,
                      const WGPUTexelCopyBufferLayout &targetBufferLayout)
    {
        updateSource      = targetUpdateSource;
        textureData       = targetBuffer;
        textureDataLayout = targetBufferLayout;
        targetLevel       = newTargetLevel;
        layerIndex        = newLayerIndex;
        layerCount        = newLayerCount;
    }

    SubresourceUpdate(UpdateSource targetUpdateSource,
                      gl::LevelIndex newTargetLevel,
                      ClearValues clearValues,
                      bool hasDepth,
                      bool hasStencil)
    {
        updateSource          = targetUpdateSource;
        targetLevel           = newTargetLevel;
        clearData.clearValues = clearValues;
        clearData.hasDepth    = hasDepth;
        clearData.hasStencil  = hasStencil;
    }

    UpdateSource updateSource;
    ClearUpdate clearData;
    BufferHandle textureData;
    WGPUTexelCopyBufferLayout textureDataLayout;

    gl::LevelIndex targetLevel;
    uint32_t layerIndex = 0;
    uint32_t layerCount = 1;
};

WGPUTextureDimension ToWgpuTextureDimension(gl::TextureType glTextureType);

class ImageHelper : public angle::Subject
{
  public:
    ImageHelper();
    ~ImageHelper() override;

    angle::Result initImage(const DawnProcTable *wgpu,
                            angle::FormatID intendedFormatID,
                            angle::FormatID actualFormatID,
                            DeviceHandle device,
                            gl::LevelIndex firstAllocatedLevel,
                            WGPUTextureDescriptor textureDescriptor);
    angle::Result initExternal(const DawnProcTable *wgpu,
                               angle::FormatID intendedFormatID,
                               angle::FormatID actualFormatID,
                               webgpu::TextureHandle externalTexture);

    angle::Result flushStagedUpdates(ContextWgpu *contextWgpu);
    angle::Result flushSingleLevelUpdates(ContextWgpu *contextWgpu,
                                          gl::LevelIndex levelGL,
                                          ClearValuesArray *deferredClears,
                                          uint32_t deferredClearIndex);

    WGPUTextureDescriptor createTextureDescriptor(WGPUTextureUsage usage,
                                                  WGPUTextureDimension dimension,
                                                  WGPUExtent3D size,
                                                  WGPUTextureFormat format,
                                                  std::uint32_t mipLevelCount,
                                                  std::uint32_t sampleCount);

    angle::Result stageTextureUpload(ContextWgpu *contextWgpu,
                                     const webgpu::Format &webgpuFormat,
                                     GLenum type,
                                     const gl::Extents &glExtents,
                                     GLuint inputRowPitch,
                                     GLuint inputDepthPitch,
                                     uint32_t outputRowPitch,
                                     uint32_t outputDepthPitch,
                                     uint32_t allocationSize,
                                     const gl::ImageIndex &index,
                                     const uint8_t *pixels);

    void stageClear(gl::LevelIndex targetLevel,
                    ClearValues clearValues,
                    bool hasDepth,
                    bool hasStencil);

    void removeStagedUpdates(gl::LevelIndex levelToRemove);
    void removeSingleSubresourceStagedUpdates(gl::LevelIndex levelToRemove,
                                              uint32_t layerIndex,
                                              uint32_t layerCount);

    void resetImage();

    angle::Result CopyImage(ContextWgpu *contextWgpu,
                            ImageHelper *srcImage,
                            const gl::ImageIndex &dstIndex,
                            const gl::Offset &dstOffset,
                            gl::LevelIndex sourceLevelGL,
                            uint32_t sourceLayer,
                            const gl::Box &sourceBox,
                            WGPUTextureAspect aspect);

    angle::Result copyImageCpuReadback(const gl::Context *context,
                                       const gl::ImageIndex &index,
                                       const gl::Rectangle &sourceArea,
                                       const gl::Offset &dstOffset,
                                       const gl::Extents &dstExtent,
                                       const gl::InternalFormat &formatInfo,
                                       ImageHelper *srcImage,
                                       const gl::Extents &srcExtents);

    static angle::Result getReadPixelsParams(rx::ContextWgpu *contextWgpu,
                                             const gl::PixelPackState &packState,
                                             gl::Buffer *packBuffer,
                                             GLenum format,
                                             GLenum type,
                                             const gl::Rectangle &area,
                                             const gl::Rectangle &clippedArea,
                                             rx::PackPixelsParams *paramsOut,
                                             GLuint *skipBytesOut);

    angle::Result readPixels(rx::ContextWgpu *contextWgpu,
                             const gl::Rectangle &area,
                             const rx::PackPixelsParams &packPixelsParams,
                             webgpu::LevelIndex level,
                             uint32_t layer,
                             void *pixels);

    angle::Result createTextureViewSingleLevel(gl::LevelIndex targetLevel,
                                               uint32_t layerIndex,
                                               TextureViewHandle &textureViewOut,
                                               WGPUTextureAspect aspect,
                                               WGPUTextureFormat format);
    angle::Result createFullTextureView(TextureViewHandle &textureViewOut,
                                        WGPUTextureViewDimension desiredViewDimension);
    angle::Result createTextureView(gl::LevelIndex targetLevel,
                                    uint32_t levelCount,
                                    uint32_t layerIndex,
                                    uint32_t arrayLayerCount,
                                    TextureViewHandle &textureViewOut,
                                    Optional<WGPUTextureViewDimension> desiredViewDimension,
                                    WGPUTextureAspect aspect,
                                    WGPUTextureFormat format);
    LevelIndex toWgpuLevel(gl::LevelIndex levelIndexGl) const;
    gl::LevelIndex toGlLevel(LevelIndex levelIndexWgpu) const;
    bool isTextureLevelInAllocatedImage(gl::LevelIndex textureLevel) const;
    TextureHandle &getTexture() { return mTexture; }
    WGPUTextureFormat toWgpuTextureFormat() const { return mTextureDescriptor.format; }
    angle::FormatID getIntendedFormatID() const { return mIntendedFormatID; }
    angle::FormatID getActualFormatID() const { return mActualFormatID; }
    const WGPUTextureDescriptor &getTextureDescriptor() const { return mTextureDescriptor; }
    gl::LevelIndex getFirstAllocatedLevel() const { return mFirstAllocatedLevel; }
    gl::LevelIndex getLastAllocatedLevel() const;
    uint32_t getLevelCount() const { return mTextureDescriptor.mipLevelCount; }
    WGPUExtent3D getSize() const { return mTextureDescriptor.size; }
    WGPUExtent3D getLevelSize(LevelIndex wgpuLevel) const;
    uint32_t getSamples() const { return mTextureDescriptor.sampleCount; }
    WGPUTextureUsage getUsage() const { return mTextureDescriptor.usage; }
    bool isInitialized() const { return mInitialized; }

  private:
    void appendSubresourceUpdate(gl::LevelIndex level, SubresourceUpdate &&update);
    std::vector<SubresourceUpdate> *getLevelUpdates(gl::LevelIndex level);

    const DawnProcTable *mProcTable = nullptr;
    TextureHandle mTexture;
    WGPUTextureDescriptor mTextureDescriptor   = {};
    bool mInitialized                          = false;

    gl::LevelIndex mFirstAllocatedLevel = gl::LevelIndex(0);
    angle::FormatID mIntendedFormatID;
    angle::FormatID mActualFormatID;

    std::vector<std::vector<SubresourceUpdate>> mSubresourceQueue;
};
struct BufferMapState
{
    WGPUMapMode mode;
    size_t offset;
    size_t size;
};

enum class MapAtCreation
{
    No,
    Yes,
};

struct BufferReadback;

class BufferHelper : public angle::NonCopyable
{
  public:
    BufferHelper();
    ~BufferHelper();

    bool valid() const { return mBuffer.operator bool(); }
    void reset();

    angle::Result initBuffer(const DawnProcTable *wgpu,
                             webgpu::DeviceHandle device,
                             size_t size,
                             WGPUBufferUsage usage,
                             MapAtCreation mappedAtCreation);

    angle::Result mapImmediate(ContextWgpu *context, WGPUMapMode mode, size_t offset, size_t size);
    angle::Result unmap();

    uint8_t *getMapWritePointer(size_t offset, size_t size) const;
    const uint8_t *getMapReadPointer(size_t offset, size_t size) const;

    const std::optional<BufferMapState> &getMappedState() const;

    bool canMapForRead() const;
    bool canMapForWrite() const;

    bool isMappedForRead() const;
    bool isMappedForWrite() const;

    webgpu::BufferHandle getBuffer() const;
    uint64_t requestedSize() const;
    uint64_t actualSize() const;

    // Helper to copy data to a staging buffer and map it. Staging data is cleaned up by the
    // BufferReadback RAII object.
    angle::Result readDataImmediate(ContextWgpu *context,
                                    size_t offset,
                                    size_t size,
                                    webgpu::RenderPassClosureReason reason,
                                    BufferReadback *result);

  private:
    const DawnProcTable *mProcTable = nullptr;
    webgpu::BufferHandle mBuffer;
    size_t mRequestedSize = 0;

    std::optional<BufferMapState> mMappedState;
};

struct BufferReadback
{
    BufferHelper buffer;
    const uint8_t *data = nullptr;
};

}  // namespace webgpu
}  // namespace rx
#endif  // LIBANGLE_RENDERER_WGPU_WGPU_HELPERS_H_
