#pragma once

#include "common.hpp"
#include <optional>
#include <vector>
#include <cmath>

namespace render {

/// Abstract raw image
class Image {
public:
    virtual ~Image() { }

    struct frame {
        uint32_t height;
        uint32_t width;
    };
    // NOTE: matches VkFormat
    enum class Format {
        /// DXT3 RGBA SRGB
        BC2 = 136,
        BC2_UNORM = 135,
        /// DXT5  RGBA SRGB
        BC3 = 138,
        BC3_UNORM = 137,
        // MAYBE: R8G8B8A8
        // MAYBE: For HDR BC6H_SFLOAT = 144,
    };

    // NOTE: matches VkImageLayout
    enum class Layout {
        UNDEFINED = 0,
        GENERAL = 1,
        COLOR_ATTACHMENT = 2,
        DEPTH_STENCIL_ATTACHMENT = 3,
        DEPTH_STENCIL_READ_ONLY = 4,
        SHADER_READ_ONLY = 5,
        TRANSFER_SRC = 6,
        TRANSFER_DST = 7,
        PREINITIALIZED = 8,
        DEPTH_READ_ONLY_STENCIL_ATTACHMENT = 1000117000,
        DEPTH_ATTACHMENT_STENCIL_READ_ONLY = 1000117001,
        DEPTH_ATTACHMENT = 1000241000,
        DEPTH_READ_ONLY = 1000241001,
        STENCIL_ATTACHMENT = 1000241002,
        STENCIL_READ_ONLY = 1000241003
    };
    // NOTE: matches VkImageUsageFlags
    enum class Usage {
        TRANSFER_SRC = 0x00000001,
        TRANSFER_DST = 0x00000002,
        SAMPLED = 0x00000004,
        STORAGE = 0x00000008,
        COLOR_ATTACHMENT = 0x00000010,
        DEPTH_STENCIL_ATTACHMENT = 0x00000020,
        TRANSIENT_ATTACHMENT = 0x00000040,
        INPUT_ATTACHMENT = 0x00000080,
    };
    friend inline Usage operator|(Usage a, Usage b) {
        return static_cast<Usage>(static_cast<int>(a) | static_cast<int>(b));
    }
    //NOTE: matches VkImageAspectFlags
    enum class Aspect {
        COLOR = 0x00000001,
        DEPTH = 0x00000002,
        STENCIL = 0x00000004,
        METADATA = 0x00000008
    };

    struct properties {
        frame size;
        uint32_t mipmapLevels;
        Format format;
    };
    struct requirement: properties {
        requirement(const properties& props, Layout layout, Usage usage, Aspect aspect,
            int samples = 1, uint32_t layers = 1, bool cube = false, bool optimal = true):
            properties(props), layout(layout), usage(usage), aspect(aspect), samples(samples),
            layers(layers), cube(cube), optimal(optimal)
        {
            assert(samples > 0 && (std::ceil(std::log2(samples)) == std::floor(std::log2(samples))) && "Samples must be pow2");
            assert(!cube || layers == 6);
        }
        Layout layout;
        Usage usage;
        Aspect aspect;
        //NOTE: matches VkSampleCountFlagBits
        int samples;
        uint32_t layers;
        bool cube;
        bool optimal;

        static requirement Texture(const properties &props, uint32_t arraySize = 1, bool cube = false) {
            return requirement(props, Layout::SHADER_READ_ONLY, Usage::SAMPLED, Aspect::COLOR, 1, arraySize, cube); }
    };

    static std::optional<properties> Read(const std::string&, std::vector<unsigned char>& data, bool srgb = true);
};

/// Const image (single texture2D) with sampler
class Texture: public Image {
public:
    // NOTE: matches VkSamplerAddressMode
    enum class Wrap {
        REPEAT = 0,
        MIRRORED_REPEAT = 1,
        CLAMP_TO_EDGE = 2,
        CLAMP_TO_BORDER = 3,
    };
    struct sampling {
        bool magLinear = true;
        bool minLinear = true;
        Wrap wrap = Wrap::MIRRORED_REPEAT;
        int anisotropy = 0;
        bool mipmap = true;
        float mipmapLod = 0;
    };

    /// Only supports dds files
    /// DXT3(BC2) DXT5(BC3)
    static _FORCE_INLINE_ std::unique_ptr<Texture> LoadFromFile(const std::string &path, const sampling &props) {
        assert(loadFunc != nullptr && "Uninitialized renderer");
        return loadFunc(path, props);
    }

protected:
    static std::unique_ptr<Texture> (*loadFunc)(const std::string&, const sampling&);
};

/// Const 6 dept image (single textureCube) with sampler
class TextureCube: public Image {
public:
    /// Only supports dds files
    /// DXT3(BC2) DXT5(BC3)
    static _FORCE_INLINE_ std::unique_ptr<TextureCube> LoadFromFiles(const std::array<std::string, 6> &paths, const Texture::sampling &props) {
        assert(loadFunc != nullptr && "Uninitialized renderer");
        return loadFunc(paths, props);
    }

protected:
    static std::unique_ptr<TextureCube> (*loadFunc)(const std::array<std::string, 6>&, const Texture::sampling&);
};

/// Const image array (textureArray) with sampler
class TextureArray: public Image {
public:
    /// Only supports dds files
    /// DXT3(BC2) DXT5(BC3)
    static _FORCE_INLINE_ std::unique_ptr<TextureArray> LoadFromFiles(const std::vector<std::string> &paths, const Texture::sampling &props) {
        assert(loadFunc != nullptr && "Uninitialized renderer");
        return loadFunc(paths, props);
    }

    constexpr uint32_t getSize() const { return size; }

protected:
    TextureArray(uint32_t size): size(size) { }
    uint32_t size;
    static std::unique_ptr<TextureArray> (*loadFunc)(const std::vector<std::string>&, const Texture::sampling&);
};
}