// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.

#pragma once

#include "Engine/Graphics/GPUResource.h"
#include "GPUTextureDescription.h"

class GPUContext;
class GPUTask;
class TextureMipData;
class TextureData;
template<typename T>
class DataContainer;
typedef DataContainer<byte> BytesContainer;
class Task;

/// <summary>
/// Defines a view for the <see cref="GPUTexture"/> surface, full resource or any of the sub-parts. Can be used to define a single subresource of the texture, volume texture or texture array. Used to render to the texture and/or use textures in the shaders.
/// </summary>
API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUTextureView : public GPUResourceView
{
    DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUTextureView);
protected:
    GPUResource* _parent = nullptr;
    PixelFormat _format = PixelFormat::Unknown;
    MSAALevel _msaa = MSAALevel::None;

    GPUTextureView()
        : GPUResourceView(SpawnParams(Guid::New(), TypeInitializer))
    {
    }

    FORCE_INLINE void Init(GPUResource* parent, PixelFormat format, MSAALevel msaa)
    {
        _parent = parent;
        _format = format;
        _msaa = msaa;
        if (parent)
            LastRenderTime = &parent->LastRenderTime;
    }

public:
    /// <summary>
    /// Gets parent GPU resource owning that view.
    /// </summary>
    API_PROPERTY() FORCE_INLINE GPUResource* GetParent() const
    {
        return _parent;
    }

    /// <summary>
    /// Gets the view format.
    /// </summary>
    API_PROPERTY() FORCE_INLINE PixelFormat GetFormat() const
    {
        return _format;
    }

    /// <summary>
    /// Gets view MSAA level.
    /// </summary>
    API_PROPERTY() FORCE_INLINE MSAALevel GetMSAA() const
    {
        return _msaa;
    }
};

/// <summary>
/// The GPU texture resource object. This class is able to create 2D/3D textures, volume textures and render targets.
/// </summary>
API_CLASS(Sealed) class FLAXENGINE_API GPUTexture : public GPUResource
{
    DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUTexture);
    static GPUTexture* Spawn(const SpawnParams& params);
    static GPUTexture* New();

protected:
    int32 _residentMipLevels;
    bool _sRGB, _isBlockCompressed;
    GPUTextureDescription _desc;

    GPUTexture();

public:
    /// <summary>
    /// Gets a value indicating whether this texture has any resided mip (data already uploaded to the GPU).
    /// </summary>
    API_PROPERTY() FORCE_INLINE bool HasResidentMip() const
    {
        return _residentMipLevels != 0;
    }

    /// <summary>
    /// Gets a value indicating whether this texture has been allocated.
    /// </summary>
    API_PROPERTY() FORCE_INLINE bool IsAllocated() const
    {
        return _desc.MipLevels > 0;
    }

    /// <summary>	
    /// Gets texture width (in texels).
    /// </summary>	
    API_PROPERTY() FORCE_INLINE int32 Width() const
    {
        return _desc.Width;
    }

    /// <summary>	
    /// Gets texture height (in texels).
    /// </summary>	
    API_PROPERTY() FORCE_INLINE int32 Height() const
    {
        return _desc.Height;
    }

    /// <summary>	
    /// Gets texture depth (in texels).
    /// </summary>	
    API_PROPERTY() FORCE_INLINE int32 Depth() const
    {
        return _desc.Depth;
    }

    /// <summary>	
    /// Gets number of textures in the array.
    /// </summary>	
    API_PROPERTY() FORCE_INLINE int32 ArraySize() const
    {
        return _desc.ArraySize;
    }

    /// <summary>	
    /// Gets multi-sampling parameters for the texture.
    /// </summary>	
    API_PROPERTY() FORCE_INLINE MSAALevel MultiSampleLevel() const
    {
        return _desc.MultiSampleLevel;
    }

    /// <summary>	
    /// Gets number of mipmap levels in the texture. 
    /// </summary>	
    API_PROPERTY() FORCE_INLINE int32 MipLevels() const
    {
        return _desc.MipLevels;
    }

    /// <summary>	
    /// Gets the number of resident mipmap levels in the texture (already uploaded to the GPU).
    /// </summary>	
    API_PROPERTY() FORCE_INLINE int32 ResidentMipLevels() const
    {
        return _residentMipLevels;
    }

    /// <summary>
    /// Gets the index of the highest resident mip map (may be equal to MipLevels if no mip has been uploaded). Note: mip=0 is the highest (top quality).
    /// </summary>
    API_PROPERTY() FORCE_INLINE int32 HighestResidentMipIndex() const
    {
        return MipLevels() - ResidentMipLevels();
    }

    /// <summary>	
    /// Gets texture data format.
    /// </summary>	
    API_PROPERTY() FORCE_INLINE PixelFormat Format() const
    {
        return _desc.Format;
    }

    /// <summary>	
    /// Gets flags of the texture.
    /// </summary>
    API_PROPERTY() FORCE_INLINE GPUTextureFlags Flags() const
    {
        return _desc.Flags;
    }

    /// <summary>
    /// Gets texture dimensions.
    /// </summary>
    API_PROPERTY() FORCE_INLINE TextureDimensions Dimensions() const
    {
        return _desc.Dimensions;
    }

    /// <summary>
    /// Gets texture description structure.
    /// </summary>
    API_PROPERTY() FORCE_INLINE const GPUTextureDescription& GetDescription() const
    {
        return _desc;
    }

public:
    /// <summary>
    /// Gets a value indicating whether this texture is a render target.
    /// </summary>
    FORCE_INLINE bool IsRenderTarget() const
    {
        return _desc.IsRenderTarget();
    }

    /// <summary>
    /// Gets a value indicating whether this texture is a shader resource.
    /// </summary>
    FORCE_INLINE bool IsShaderResource() const
    {
        return _desc.IsShaderResource();
    }

    /// <summary>
    /// Gets a value indicating whether this texture is a depth stencil.
    /// </summary>
    FORCE_INLINE bool IsDepthStencil() const
    {
        return _desc.IsDepthStencil();
    }

    /// <summary>
    /// Gets a value indicating whether this texture is a unordered access.
    /// </summary>
    FORCE_INLINE bool IsUnorderedAccess() const
    {
        return _desc.IsUnorderedAccess();
    }

    /// <summary>
    /// Gets a value indicating whether this instance has per mip level views.
    /// </summary>
    FORCE_INLINE bool HasPerMipViews() const
    {
        return _desc.HasPerMipViews();
    }

    /// <summary>
    /// Gets a value indicating whether this instance has per slice views.
    /// </summary>
    FORCE_INLINE bool HasPerSliceViews() const
    {
        return _desc.HasPerSliceViews();
    }

    /// <summary>
    /// Gets a value indicating whether this instance is a multi sample texture.
    /// </summary>
    FORCE_INLINE bool IsMultiSample() const
    {
        return _desc.IsMultiSample();
    }

    /// <summary>
    /// Gets a value indicating whether this instance is a cubemap texture.
    /// </summary>
    FORCE_INLINE bool IsCubeMap() const
    {
        return _desc.Dimensions == TextureDimensions::CubeTexture;
    }

    /// <summary>
    /// Gets a value indicating whether this instance is a volume texture.
    /// </summary>
    FORCE_INLINE bool IsVolume() const
    {
        return _desc.Dimensions == TextureDimensions::VolumeTexture;
    }

    /// <summary>
    /// Gets a value indicating whether this instance is an array texture.
    /// </summary>
    FORCE_INLINE bool IsArray() const
    {
        return _desc.ArraySize != 1;
    }

    /// <summary>
    /// Checks if texture contains sRGB colors data.
    /// </summary>
    FORCE_INLINE bool IsSRGB() const
    {
        return _sRGB;
    }

    /// <summary>
    /// Checks if texture is normal texture asset (not render target or unordered access or depth buffer or sth else).
    /// </summary>
    FORCE_INLINE bool IsRegularTexture() const
    {
        return _desc.Flags == GPUTextureFlags::ShaderResource;
    }

    /// <summary>
    /// Checks if texture is a staging buffer (supports direct CPU access).
    /// </summary>
    FORCE_INLINE bool IsStaging() const
    {
        return _desc.Usage == GPUResourceUsage::StagingUpload || _desc.Usage == GPUResourceUsage::StagingReadback;
    }

    /// <summary>
    /// Gets a boolean indicating whether this <see cref="GPUTexture"/> is a using a block compress format (BC1, BC2, BC3, BC4, BC5, BC6H, BC7, etc.).
    /// </summary>
    FORCE_INLINE bool IsBlockCompressed() const
    {
        return _isBlockCompressed;
    }

public:
    /// <summary>
    /// Gets the texture total size in pixels.
    /// </summary>
    API_PROPERTY() Float2 Size() const;

    /// <summary>
    /// Gets the texture total size in pixels (with depth).
    /// </summary>
    API_PROPERTY() Float3 Size3() const;

    /// <summary>
    /// Returns true if texture has size that is power of two.
    /// </summary>
    /// <returns>True if texture has size that is power of two.</returns>
    API_PROPERTY() bool IsPowerOfTwo() const;

    /// <summary>
    /// Gets the texture mip map dimensions.
    /// </summary>
    /// <param name="mipLevelIndex">Mip level index (zero-based where 0 is top texture surface).</param>
    /// <param name="mipWidth">The calculated mip level width (in pixels).</param>
    /// <param name="mipHeight">The calculated mip level height (in pixels).</param>
    API_FUNCTION() void GetMipSize(int32 mipLevelIndex, API_PARAM(Out) int32& mipWidth, API_PARAM(Out) int32& mipHeight) const;

    /// <summary>
    /// Gets the texture mip map dimensions.
    /// </summary>
    /// <param name="mipLevelIndex">Mip level index (zero-based where 0 is top texture surface).</param>
    /// <param name="mipWidth">The calculated mip level width (in pixels).</param>
    /// <param name="mipHeight">The calculated mip level height (in pixels).</param>
    /// <param name="mipDepth">The calculated mip level depth (in pixels).</param>
    API_FUNCTION() void GetMipSize(int32 mipLevelIndex, API_PARAM(Out) int32& mipWidth, API_PARAM(Out) int32& mipHeight, API_PARAM(Out) int32& mipDepth) const;

    /// <summary>
    /// Gets current texture size (uploaded to the GPU and in use).
    /// </summary>
    /// <param name="width">The current width (in pixels).</param>
    /// <param name="height">The current height (in pixels).</param>
    void GetResidentSize(int32& width, int32& height) const;

    /// <summary>
    /// Gets current texture size (uploaded to the GPU and in use).
    /// </summary>
    /// <param name="width">The current width (in pixels).</param>
    /// <param name="height">The current height (in pixels).</param>
    /// <param name="depth">The current depth (in pixels).</param>
    void GetResidentSize(int32& width, int32& height, int32& depth) const;

public:
    /// <summary>
    /// Calculates mip map row pitch (in bytes).
    /// </summary>
    /// <param name="mipIndex">Index of the mip.</param>
    /// <returns>Row pitch.</returns>
    uint32 RowPitch(int32 mipIndex = 0) const;

    /// <summary>
    /// Calculates mip map slice pitch (in bytes).
    /// </summary>
    /// <param name="mipIndex">Index of the mip.</param>
    /// <returns>Slice pitch.</returns>
    uint32 SlicePitch(int32 mipIndex = 0) const;

    /// <summary>
    /// Computes row and slice pitch of the mip map.
    /// </summary>
    /// <param name="mipIndex">Index of the mip.</param>
    /// <param name="rowPitch">The row pitch.</param>
    /// <param name="slicePitch">The slice pitch.</param>
    void ComputePitch(int32 mipIndex, uint32& rowPitch, uint32& slicePitch) const;

    /// <summary>
    /// Calculates the size of a particular mip.
    /// </summary>
    /// <param name="size">The size.</param>
    /// <param name="mipLevel">The mip level.</param>
    /// <returns>Mip size.</returns>
    int32 CalculateMipSize(int32 size, int32 mipLevel) const;

public:
    int32 ComputeSubresourceSize(int32 subresource, int32 rowAlign, int32 sliceAlign) const;
    int32 ComputeBufferOffset(int32 subresource, int32 rowAlign, int32 sliceAlign) const;
    int32 ComputeBufferTotalSize(int32 rowAlign, int32 sliceAlign) const;
    int32 ComputeSlicePitch(int32 mipLevel, int32 rowAlign) const;
    int32 ComputeRowPitch(int32 mipLevel, int32 rowAlign) const;

public:
    /// <summary>
    /// Gets the view to the first surface (only for 2D textures).
    /// </summary>
    /// <returns>The view to the main texture surface.</returns>
    API_FUNCTION() FORCE_INLINE GPUTextureView* View() const
    {
        return View(0);
    }

    /// <summary>
    /// Gets the view to the surface at index in an array.
    /// </summary>
    /// <remarks>
    /// To use per depth/array slice view you need to specify the <see cref="GPUTextureFlags.PerSliceViews"/> when creating the resource.
    /// </remarks>
    /// <param name="arrayOrDepthIndex">The index of the surface in an array (or depth slice index).</param>
    /// <returns>The view to the surface at index in an array.</returns>
    API_FUNCTION() virtual GPUTextureView* View(int32 arrayOrDepthIndex) const = 0;

    /// <summary>
    /// Gets the view to the mip map surface at index in an array.
    /// </summary>
    /// <remarks>
    /// To use per mip map view you need to specify the <see cref="GPUTextureFlags.PerMipViews"/> when creating the resource.
    /// </remarks>
    /// <param name="arrayOrDepthIndex">The index of the surface in an array (or depth slice index).</param>
    /// <param name="mipMapIndex">Index of the mip level.</param>
    /// <returns>The view to the surface at index in an array.</returns>
    API_FUNCTION() virtual GPUTextureView* View(int32 arrayOrDepthIndex, int32 mipMapIndex) const = 0;

    /// <summary>
    /// Gets the view to the array of surfaces 
    /// </summary>
    /// <remarks>
    /// To use array texture view you need to create render target as an array.
    /// </remarks>
    /// <returns>The view to the array of surfaces.</returns>
    API_FUNCTION() virtual GPUTextureView* ViewArray() const = 0;

    /// <summary>
    /// Gets the view to the volume texture (3D).
    /// </summary>
    /// <remarks>
    /// To use volume texture view you need to create render target as a volume resource (3D texture with Depth > 1).
    /// </remarks>
    /// <returns>The view to the volume texture.</returns>
    API_FUNCTION() virtual GPUTextureView* ViewVolume() const = 0;

    /// <summary>
    /// Gets the view to the texture as read-only depth/stencil buffer. Valid only if graphics device supports it and the texture uses depth/stencil.
    /// </summary>
    /// <returns>The view to the depth-stencil resource descriptor as read-only depth.</returns>
    API_FUNCTION() virtual GPUTextureView* ViewReadOnlyDepth() const = 0;

    /// <summary>
    /// Implicit conversion to the first surface (only for 2D textures).
    /// </summary>
    /// <returns>The view to the main texture surface.</returns>
    FORCE_INLINE operator GPUTextureView*() const
    {
        return View(0);
    }

public:
    /// <summary>
    /// Initializes a texture resource (allocates the GPU memory and performs the resource setup).
    /// </summary>
    /// <param name="desc">The texture description.</param>
    /// <returns>True if cannot create texture, otherwise false.</returns>
    API_FUNCTION() bool Init(API_PARAM(Ref) const GPUTextureDescription& desc);

    /// <summary>
    /// Creates new staging readback texture with the same dimensions and properties as a source texture (but without a data transferred; warning: caller must delete object).
    /// </summary>
    /// <returns>The staging readback texture.</returns>
    GPUTexture* ToStagingReadback() const;

    /// <summary>
    /// Creates new staging upload texture with the same dimensions and properties as a source texture (but without a data transferred; warning: caller must delete object).
    /// </summary>
    /// <returns>The staging upload texture.</returns>
    GPUTexture* ToStagingUpload() const;

    /// <summary>
    /// Resizes the texture. It must be created first.
    /// </summary>
    /// <param name="width">The width.</param>
    /// <param name="height">The height.</param>
    /// <param name="format">The new texture format. Use Unknown to remain texture format unchanged.</param>
    /// <returns>True if fails, otherwise false.</returns>
    API_FUNCTION() bool Resize(int32 width, int32 height, PixelFormat format = PixelFormat::Unknown)
    {
        const auto depth = IsAllocated() ? Depth() : 1;
        return Resize(width, height, depth, format);
    }

    /// <summary>
    /// Resizes the texture. It must be created first.
    /// </summary>
    /// <param name="width">The width.</param>
    /// <param name="height">The height.</param>
    /// <param name="depth">The depth.</param>
    /// <param name="format">The new texture format. Use Unknown to remain texture format unchanged.</param>
    /// <returns>True if fails, otherwise false.</returns>
    API_FUNCTION() bool Resize(int32 width, int32 height, int32 depth, PixelFormat format = PixelFormat::Unknown);

public:
    /// <summary>
    /// Gets the native pointer to the underlying resource. It's a low-level platform-specific handle.
    /// </summary>
    /// <returns>The pointer.</returns>
    API_PROPERTY() virtual void* GetNativePtr() const = 0;

    /// <summary>
    /// Uploads mip map data to the GPU. Creates async GPU task.
    /// </summary>
    /// <param name="data">Data to upload (it must be valid for the next a few frames due to GPU latency and async works executing)</param>
    /// <param name="mipIndex">Mip level index.</param>
    /// <param name="copyData">If true, the data will be copied to the async execution task instead of using the input pointer provided.</param>
    /// <returns>Created async task or null if cannot.</returns>
    GPUTask* UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, bool copyData = false);

    /// <summary>
    /// Uploads mip map data to the GPU. Creates async GPU task.
    /// </summary>
    /// <param name="data">Data to upload (it must be valid for the next a few frames due to GPU latency and async works executing)</param>
    /// <param name="mipIndex">Mip level index.</param>
    /// <param name="rowPitch">The data row pitch.</param>
    /// <param name="slicePitch">The data slice pitch.</param>
    /// <param name="copyData">If true, the data will be copied to the async execution task instead of using the input pointer provided.</param>
    /// <returns>Created async task or null if cannot.</returns>
    GPUTask* UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, int32 rowPitch, int32 slicePitch, bool copyData = false);

    /// <summary>
    /// Stops current thread execution to gather texture data from the GPU.
    /// </summary>
    /// <param name="result">The result data.</param>
    /// <returns>True if cannot download data, otherwise false.</returns>
    bool DownloadData(TextureData& result);

    /// <summary>
    /// Creates GPU async task that will gather texture data from the GPU.
    /// </summary>
    /// <param name="result">The result data.</param>
    /// <returns>Download data task (not started yet).</returns>
    Task* DownloadDataAsync(TextureData& result);

    /// <summary>
    /// Gets texture mipmap data (raw bytes). Can be used only with textures created with Staging flag.
    /// </summary>
    /// <param name="arrayIndex">Array slice index.</param>
    /// <param name="mipMapIndex">Mip map index.</param>
    /// <param name="data">Output mip data.</param>
    /// <param name="mipRowPitch">Output mip data row pitch to use. Use 0 to use the pitch from the internal GPU storage.</param>
    /// <returns>True if failed, otherwise false.</returns>
    virtual bool GetData(int32 arrayIndex, int32 mipMapIndex, TextureMipData& data, uint32 mipRowPitch = 0) = 0;

    /// <summary>
    /// Sets the number of resident mipmap levels in the texture (already uploaded to the GPU).
    /// </summary>
    API_PROPERTY() void SetResidentMipLevels(int32 count);

    /// <summary>
    /// Event called when texture residency gets changed. Texture Mip gets loaded into GPU memory and is ready to use.
    /// </summary>
    Delegate<GPUTexture*> ResidentMipsChanged;

protected:
    virtual bool OnInit() = 0;
    uint64 calculateMemoryUsage() const;
    virtual void OnResidentMipsChanged() = 0;

public:
    // [GPUResource]
    String ToString() const override;
    GPUResourceType GetResourceType() const final override;

protected:
    // [GPUResource]
    void OnReleaseGPU() override;
};
