// Copyright (c) Wojciech Figat. All rights reserved.

#pragma once

#if COMPILE_WITH_TEXTURE_TOOL

#include "Engine/Render2D/SpriteAtlas.h"
#include "Engine/Graphics/Textures/Types.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Core/ISerializable.h"

class JsonWriter;

/// <summary>
/// Textures importing, processing and exporting utilities.
/// </summary>
API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool
{
    DECLARE_SCRIPTING_TYPE_MINIMAL(TextureTool);

    /// <summary>
    /// Texture import options.
    /// </summary>
    API_STRUCT(Attributes="HideInEditor") struct FLAXENGINE_API Options : public ISerializable
    {
        DECLARE_SCRIPTING_TYPE_MINIMAL(Options);

        // Texture format type.
        API_FIELD(Attributes="EditorOrder(0)")
        TextureFormatType Type = TextureFormatType::ColorRGB;

        // True if texture should be imported as a texture atlas (with sprites).
        API_FIELD(Attributes="EditorOrder(10)")
        bool IsAtlas = false;

        // True if disable dynamic texture streaming.
        API_FIELD(Attributes="EditorOrder(20)")
        bool NeverStream = false;

        // True if texture should be compressed.
        API_FIELD(Attributes="EditorOrder(30)")
        bool Compress = true;

        // True if texture channels have independent data (for compression methods).
        API_FIELD(Attributes="EditorOrder(40)")
        bool IndependentChannels = false;

        // If checked, indicates that input file should be loaded as an sRGB image. Common for color maps and diffuse/albedo textures.
        API_FIELD(Attributes="EditorOrder(50), EditorDisplay(null, \"sRGB\")")
        bool sRGB = false;

        // True if generate mip maps chain for the texture.
        API_FIELD(Attributes="EditorOrder(60)")
        bool GenerateMipMaps = true;

        // True if flip Y coordinate of the texture (Flips over X axis).
        API_FIELD(Attributes="EditorOrder(70)")
        bool FlipY = false;

        // True if flip X coordinate of the texture (Flips over Y axis).
        API_FIELD(Attributes="EditorOrder(71)")
        bool FlipX = false;

        // Invert the red channel.
        API_FIELD(Attributes="EditorOrder(72), EditorDisplay(\"Invert Channels\")")
        bool InvertRedChannel = false;

        // Invert the green channel. Good for OpenGL to DirectX conversion.
        API_FIELD(Attributes="EditorOrder(73), EditorDisplay(\"Invert Channels\")")
        bool InvertGreenChannel = false;

        // Invert the blue channel.
        API_FIELD(Attributes="EditorOrder(74), EditorDisplay(\"Invert Channels\")")
        bool InvertBlueChannel = false;

        // Invert the alpha channel.
        API_FIELD(Attributes="EditorOrder(75), EditorDisplay(\"Invert Channels\")")
        bool InvertAlphaChannel = false;

        // Rebuild Z (blue) channel assuming X/Y are normals.
        API_FIELD(Attributes="EditorOrder(76)")
        bool ReconstructZChannel = false;

        // Texture size scale. Allows increasing or decreasing the imported texture resolution. Default is 1.
        API_FIELD(Attributes="EditorOrder(80), Limit(0.0001f, 1000.0f, 0.01f)")
        float Scale = 1.0f;

        // Maximum size of the texture (for both width and height). Higher resolution textures will be resized during importing process. Used to clip textures that are too big.
        API_FIELD(Attributes="HideInEditor")
        int32 MaxSize = 8192;

        // True if resize texture on import. Use SizeX/SizeY properties to define texture width and height. Texture scale property will be ignored.
        API_FIELD(Attributes="EditorOrder(100)")
        bool Resize = false;

        // Keeps the aspect ratio when resizing.
        API_FIELD(Attributes="EditorOrder(101), VisibleIf(nameof(Resize))")
        bool KeepAspectRatio = false;

        // The width of the imported texture. If Resize property is set to true then texture will be resized during the import to this value during the import, otherwise it will be ignored.
        API_FIELD(Attributes="HideInEditor")
        int32 SizeX = 1024;

        // The height of the imported texture. If Resize property is set to true then texture will be resized during the import to this value during the import, otherwise it will be ignored.
        API_FIELD(Attributes="HideInEditor")
        int32 SizeY = 1024;

        // Check to preserve alpha coverage in generated mips for alpha test reference. Scales mipmap alpha values to preserve alpha coverage based on an alpha test reference value.
        API_FIELD(Attributes="EditorOrder(200)")
        bool PreserveAlphaCoverage = false;

        // The reference value for the alpha coverage preserving.
        API_FIELD(Attributes="EditorOrder(210), VisibleIf(\"PreserveAlphaCoverage\")")
        float PreserveAlphaCoverageReference = 0.5f;

        // The texture group for streaming (negative if unused). See Streaming Settings.
        API_FIELD(Attributes="EditorOrder(300), CustomEditorAlias(\"FlaxEditor.CustomEditors.Dedicated.TextureGroupEditor\")")
        int32 TextureGroup = -1;

        // The sprites for the sprite sheet import mode.
        API_FIELD(Attributes="HideInEditor")
        Array<Sprite> Sprites;

        // The custom format to use. Can be used to override default format from texture Type.
        API_FIELD(Attributes="HideInEditor")
        PixelFormat InternalFormat = PixelFormat::Unknown;

        // Function used for fast importing textures used by internal parts of the engine
        Function<bool(TextureData&)> InternalLoad;

    public:
        String ToString() const;

        // [ISerializable]
        void Serialize(SerializeStream& stream, const void* otherObj) override;
        void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
    };

public:
#if USE_EDITOR
    /// <summary>
    /// Checks whenever the given texture file contains alpha channel data with values different from solid fill of 1 (non fully opaque).
    /// </summary>
    /// <param name="path">The file path.</param>
    /// <returns>True if has alpha channel, otherwise false.</returns>
    static bool HasAlpha(const StringView& path);
#endif

    /// <summary>
    /// Imports the texture.
    /// </summary>
    /// <param name="path">The file path.</param>
    /// <param name="textureData">The output data.</param>
    /// <returns>True if fails, otherwise false.</returns>
    static bool ImportTexture(const StringView& path, TextureData& textureData);

    /// <summary>
    /// Imports the texture.
    /// </summary>
    /// <param name="path">The file path.</param>
    /// <param name="textureData">The output data.</param>
    /// <param name="options">The import options.</param>
    /// <param name="errorMsg">The error message container.</param>
    /// <returns>True if fails, otherwise false.</returns>
    static bool ImportTexture(const StringView& path, TextureData& textureData, Options options, String& errorMsg);

    /// <summary>
    /// Exports the texture.
    /// </summary>
    /// <param name="path">The output file path.</param>
    /// <param name="textureData">The output data.</param>
    /// <returns>True if fails, otherwise false.</returns>
    static bool ExportTexture(const StringView& path, const TextureData& textureData);

    /// <summary>
    /// Converts the specified source texture data into another format.
    /// </summary>
    /// <param name="dst">The destination data.</param>
    /// <param name="src">The source data.</param>
    /// <param name="dstFormat">The destination data format. Must be other than source data type.</param>
    /// <returns>True if fails, otherwise false.</returns>
    static bool Convert(TextureData& dst, const TextureData& src, const PixelFormat dstFormat);

    /// <summary>
    /// Resizes the specified source texture data into another dimensions.
    /// </summary>
    /// <param name="dst">The destination data.</param>
    /// <param name="src">The source data.</param>
    /// <param name="dstWidth">The destination data width.</param>
    /// <param name="dstHeight">The destination data height.</param>
    /// <returns>True if fails, otherwise false.</returns>
    static bool Resize(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight);

    /// <summary>
    /// Updates the texture data. Supports transcoding source data (eg. Basis).
    /// </summary>
    /// <param name="context">The GPU context.</param>
    /// <param name="texture">The destination GPU texture. It has to be allocated.</param>
    /// <param name="arrayIndex">The destination surface index in the texture array.</param>
    /// <param name="mipIndex">The absolute index of the mip map to update.</param>
    /// <param name="data">The buffer with texture the data.</param>
    /// <param name="rowPitch">The row pitch (in bytes) of the input data.</param>
    /// <param name="slicePitch">The slice pitch (in bytes) of the input data.</param>
    /// <param name="dataFormat">The format of the data. If different then GPU texture format then runtime conversion might happen.</param>
    static bool UpdateTexture(GPUContext* context, GPUTexture* texture, int32 arrayIndex, int32 mipIndex, Span<byte> data, uint32 rowPitch, uint32 slicePitch, PixelFormat dataFormat);

    /// <summary>
    /// Calculates the runtime format for the texture pixels based on the texture type and input texture data (format and size). Checks the current GPUDevice formats support.
    /// </summary>
    /// <param name="textureType">Type fo the texture that hints it's usage.</param>
    /// <param name="dataFormat">The existing texture data format (ideally to preserve to avoid conversions).</param>
    /// <param name="width">Width of the texture (in pixels).</param>
    /// <param name="height">Height of the texture (in pixels).</param>
    /// <param name="sRGB">Indicates that texture data is stored in sRGB color space.</param>
    /// <returns>Resolved pixel format for the GPU Texture.</returns>
    static PixelFormat GetTextureFormat(TextureFormatType textureType, PixelFormat dataFormat, int32 width, int32 height, bool sRGB);

public:
    static PixelFormat ToPixelFormat(TextureFormatType format, int32 width, int32 height, bool canCompress = true);
#if USE_EDITOR
    static bool WriteTextureData(BytesContainer& result, const TextureData& textureData, int32 mipIndex);
#endif

private:
    enum class ImageType
    {
        DDS,
        TGA,
        PNG,
        BMP,
        GIF,
        TIFF,
        JPEG,
        HDR,
        RAW,
        EXR,
        Internal,
    };

    static bool GetImageType(const StringView& path, ImageType& type);
    static bool Transform(TextureData& texture, const Function<void(Color&)>& transformation);

#if COMPILE_WITH_DIRECTXTEX
    static bool ExportTextureDirectXTex(ImageType type, const StringView& path, const TextureData& textureData);
    static bool ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha);
    static bool ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha);
    static bool ConvertDirectXTex(TextureData& dst, const TextureData& src, const PixelFormat dstFormat);
    static bool ResizeDirectXTex(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight);
#endif
#if COMPILE_WITH_STB
    static bool ExportTextureStb(ImageType type, const StringView& path, const TextureData& textureData);
    static bool ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha);
    static bool ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha);
    static bool ConvertStb(TextureData& dst, const TextureData& src, const PixelFormat dstFormat);
    static bool ResizeStb(PixelFormat format, TextureMipData& dstMip, const TextureMipData& srcMip, int32 dstMipWidth, int32 dstMipHeight);
    static bool ResizeStb(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight);
#endif
#if COMPILE_WITH_ASTC
    static bool ConvertAstc(TextureData& dst, const TextureData& src, PixelFormat dstFormat);
#endif
#if COMPILE_WITH_BASISU
    static bool ConvertBasisUniversal(TextureData& dst, const TextureData& src);
    static bool UpdateTextureBasisUniversal(GPUContext* context, GPUTexture* texture, int32 arrayIndex, int32 mipIndex, Span<byte> data, uint32 rowPitch, uint32 slicePitch, PixelFormat dataFormat);
#endif
};

#endif
