#pragma once

#include "foundation/result.h"

#include <cstddef>
#include <cstdint>
#include <memory>
#include <span>

namespace pp::renderer {

constexpr std::uint32_t max_texture_dimension = 32768;
constexpr std::uint32_t max_texture_mip_levels = 16;
constexpr std::uint32_t max_mesh_vertices = 16777216;
constexpr std::uint32_t max_texture_slots = 32;
constexpr std::uint64_t max_texture_bytes = 1024ULL * 1024ULL * 1024ULL;
constexpr std::size_t max_shader_source_bytes = 4ULL * 1024ULL * 1024ULL;
constexpr std::size_t max_shader_uniform_bytes = 64ULL * 1024ULL;
constexpr std::size_t max_trace_label_bytes = 256;
constexpr std::size_t max_resource_label_bytes = 256;

enum class TextureFormat : std::uint8_t {
    rgba8,
    r8,
    depth24_stencil8,
};

enum class TextureUsage : std::uint32_t {
    none = 0,
    sampled = 1U << 0U,
    render_target = 1U << 1U,
    upload_destination = 1U << 2U,
    readback_source = 1U << 3U,
    copy_source = 1U << 4U,
    copy_destination = 1U << 5U,
};

enum class TextureState : std::uint8_t {
    undefined,
    shader_read,
    render_target,
    upload_destination,
    copy_source,
    copy_destination,
    readback_source,
    present,
};

[[nodiscard]] constexpr TextureUsage operator|(TextureUsage lhs, TextureUsage rhs) noexcept
{
    return static_cast<TextureUsage>(
        static_cast<std::uint32_t>(lhs) | static_cast<std::uint32_t>(rhs));
}

[[nodiscard]] constexpr TextureUsage operator&(TextureUsage lhs, TextureUsage rhs) noexcept
{
    return static_cast<TextureUsage>(
        static_cast<std::uint32_t>(lhs) & static_cast<std::uint32_t>(rhs));
}

constexpr TextureUsage& operator|=(TextureUsage& lhs, TextureUsage rhs) noexcept
{
    lhs = lhs | rhs;
    return lhs;
}

struct Extent2D {
    std::uint32_t width = 0;
    std::uint32_t height = 0;
};

struct TextureDesc {
    Extent2D extent;
    TextureFormat format = TextureFormat::rgba8;
    std::uint32_t mip_levels = 1;
    TextureUsage usage = TextureUsage::sampled
        | TextureUsage::upload_destination
        | TextureUsage::readback_source
        | TextureUsage::copy_source
        | TextureUsage::copy_destination;
    const char* debug_name = "";
};

struct ReadbackRegion {
    std::uint32_t x = 0;
    std::uint32_t y = 0;
    std::uint32_t width = 0;
    std::uint32_t height = 0;
};

struct Viewport {
    std::int32_t x = 0;
    std::int32_t y = 0;
    std::uint32_t width = 0;
    std::uint32_t height = 0;
    float min_depth = 0.0F;
    float max_depth = 1.0F;
};

struct ScissorRect {
    bool enabled = false;
    std::int32_t x = 0;
    std::int32_t y = 0;
    std::uint32_t width = 0;
    std::uint32_t height = 0;
};

struct ClearColor {
    float r = 0.0F;
    float g = 0.0F;
    float b = 0.0F;
    float a = 0.0F;
};

struct RenderPassDesc {
    bool clear_color_enabled = true;
    ClearColor clear_color;
    bool clear_depth_enabled = false;
    float clear_depth = 1.0F;
    bool clear_stencil_enabled = false;
    std::uint8_t clear_stencil = 0;
};

enum class PrimitiveTopology : std::uint8_t {
    triangles,
    triangle_strip,
    lines,
};

enum class BlitFilter : std::uint8_t {
    nearest,
    linear,
};

enum class PaintFeedbackPath : std::uint8_t {
    none,
    framebuffer_fetch,
    ping_pong_textures,
};

enum class BlendFactor : std::uint8_t {
    zero,
    one,
    source_alpha,
    one_minus_source_alpha,
    destination_alpha,
    one_minus_destination_alpha,
};

enum class BlendOp : std::uint8_t {
    add,
    subtract,
    reverse_subtract,
};

enum class CompareOp : std::uint8_t {
    never,
    less,
    equal,
    less_or_equal,
    greater,
    not_equal,
    greater_or_equal,
    always,
};

enum class SamplerFilter : std::uint8_t {
    nearest,
    linear,
};

enum class SamplerAddressMode : std::uint8_t {
    clamp_to_edge,
    repeat,
    mirrored_repeat,
    clamp_to_border,
};

struct BlendState {
    bool enabled = false;
    BlendFactor source_color = BlendFactor::one;
    BlendFactor destination_color = BlendFactor::zero;
    BlendOp color_op = BlendOp::add;
    BlendFactor source_alpha = BlendFactor::one;
    BlendFactor destination_alpha = BlendFactor::zero;
    BlendOp alpha_op = BlendOp::add;
    bool write_r = true;
    bool write_g = true;
    bool write_b = true;
    bool write_a = true;
};

struct DepthState {
    bool test_enabled = false;
    bool write_enabled = false;
    CompareOp compare = CompareOp::less_or_equal;
};

struct SamplerDesc {
    SamplerFilter min_filter = SamplerFilter::linear;
    SamplerFilter mag_filter = SamplerFilter::linear;
    SamplerFilter mip_filter = SamplerFilter::linear;
    SamplerAddressMode address_u = SamplerAddressMode::clamp_to_edge;
    SamplerAddressMode address_v = SamplerAddressMode::clamp_to_edge;
    SamplerAddressMode address_w = SamplerAddressMode::clamp_to_edge;
};

struct MeshDesc {
    std::uint32_t vertex_count = 0;
    std::uint32_t index_count = 0;
    PrimitiveTopology topology = PrimitiveTopology::triangles;
    const char* debug_name = "";
};

struct DrawDesc {
    std::uint32_t first_vertex = 0;
    std::uint32_t vertex_count = 0;
    std::uint32_t first_index = 0;
    std::uint32_t index_count = 0;
    std::uint32_t instance_count = 1;
};

struct ShaderStageSource {
    const char* entry_point = "main";
    const char* source = nullptr;
    std::size_t source_size = 0;
};

struct ShaderProgramDesc {
    const char* debug_name = "";
    ShaderStageSource vertex;
    ShaderStageSource fragment;
};

struct RenderDeviceFeatures {
    bool framebuffer_fetch = false;
    bool explicit_texture_transitions = false;
    bool texture_copy = false;
    bool render_target_blit = false;
    bool frame_capture = false;
    bool float16_render_targets = false;
    bool float32_render_targets = false;
    bool float32_linear_filtering = false;
};

struct PaintFeedbackPlan {
    PaintFeedbackPath path = PaintFeedbackPath::none;
    TextureDesc target_desc {};
    TextureDesc auxiliary_desc {};
    std::uint64_t target_bytes = 0;
    bool complex_blend = false;
    bool reads_destination_color = false;
    bool requires_auxiliary_texture = false;
    bool requires_texture_copy = false;
    bool requires_render_target_blit = false;
    bool requires_explicit_transition = false;
};

class ITexture2D {
public:
    virtual ~ITexture2D() = default;
    [[nodiscard]] virtual TextureDesc desc() const noexcept = 0;
};

class IRenderTarget {
public:
    virtual ~IRenderTarget() = default;
    [[nodiscard]] virtual TextureDesc color_desc() const noexcept = 0;
};

class IShaderProgram {
public:
    virtual ~IShaderProgram() = default;
    [[nodiscard]] virtual const char* debug_name() const noexcept = 0;
};

class IMesh {
public:
    virtual ~IMesh() = default;
    [[nodiscard]] virtual MeshDesc desc() const noexcept = 0;
};

class IReadbackBuffer {
public:
    virtual ~IReadbackBuffer() = default;
    [[nodiscard]] virtual std::uint64_t size_bytes() const noexcept = 0;
};

class IRenderTrace {
public:
    virtual ~IRenderTrace() = default;
    [[nodiscard]] virtual pp::foundation::Status marker(const char* component, const char* name) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status begin_scope(const char* component, const char* name) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status end_scope() noexcept = 0;
};

class ICommandContext {
public:
    virtual ~ICommandContext() = default;
    [[nodiscard]] virtual pp::foundation::Status begin_render_pass(
        IRenderTarget& target,
        RenderPassDesc desc) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status set_viewport(Viewport viewport) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status set_scissor(ScissorRect scissor) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status set_blend_state(BlendState state) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status set_depth_state(DepthState state) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status bind_shader(IShaderProgram& shader) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status set_shader_uniform(
        const char* name,
        std::span<const std::byte> bytes) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status bind_texture(
        std::uint32_t slot,
        ITexture2D& texture) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status bind_sampler(
        std::uint32_t slot,
        SamplerDesc sampler) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status bind_mesh(IMesh& mesh) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status draw(DrawDesc desc) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status read_texture(
        ITexture2D& texture,
        ReadbackRegion region,
        IReadbackBuffer& destination) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status upload_texture(
        ITexture2D& texture,
        ReadbackRegion region,
        std::span<const std::byte> rgba_or_channel_bytes) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status generate_mipmaps(
        ITexture2D& texture) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status transition_texture(
        ITexture2D& texture,
        TextureState before,
        TextureState after) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status copy_texture(
        ITexture2D& source,
        ReadbackRegion source_region,
        ITexture2D& destination,
        ReadbackRegion destination_region) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status capture_frame(
        IRenderTarget& target,
        IReadbackBuffer& destination) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Status blit_render_target(
        IRenderTarget& source,
        ReadbackRegion source_region,
        IRenderTarget& destination,
        ReadbackRegion destination_region,
        BlitFilter filter) noexcept = 0;
    virtual void end_render_pass() noexcept = 0;
};

class IRenderDevice {
public:
    virtual ~IRenderDevice() = default;
    [[nodiscard]] virtual const char* backend_name() const noexcept = 0;
    [[nodiscard]] virtual RenderDeviceFeatures features() const noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Result<std::unique_ptr<ITexture2D>> create_texture(
        TextureDesc desc) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Result<std::unique_ptr<IRenderTarget>> create_render_target(
        TextureDesc color_desc) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Result<std::unique_ptr<IShaderProgram>> create_shader_program(
        ShaderProgramDesc desc) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Result<std::unique_ptr<IMesh>> create_mesh(
        MeshDesc desc) noexcept = 0;
    [[nodiscard]] virtual pp::foundation::Result<std::unique_ptr<IReadbackBuffer>> create_readback_buffer(
        std::uint64_t size_bytes) noexcept = 0;
    [[nodiscard]] virtual ICommandContext& immediate_context() noexcept = 0;
    [[nodiscard]] virtual IRenderTrace* trace() noexcept = 0;
};

[[nodiscard]] std::uint32_t bytes_per_pixel(TextureFormat format) noexcept;
[[nodiscard]] std::uint32_t max_mip_levels_for_extent(Extent2D extent) noexcept;
[[nodiscard]] bool has_texture_usage(TextureUsage usage, TextureUsage required) noexcept;
[[nodiscard]] pp::foundation::Status validate_extent(Extent2D extent) noexcept;
[[nodiscard]] pp::foundation::Status validate_texture_usage(TextureUsage usage) noexcept;
[[nodiscard]] pp::foundation::Status validate_resource_label(const char* label) noexcept;
[[nodiscard]] pp::foundation::Status validate_texture_desc(TextureDesc desc) noexcept;
[[nodiscard]] pp::foundation::Status validate_viewport(Viewport viewport, Extent2D target_extent) noexcept;
[[nodiscard]] pp::foundation::Status validate_scissor(ScissorRect scissor, Extent2D target_extent) noexcept;
[[nodiscard]] pp::foundation::Status validate_render_pass_desc(RenderPassDesc desc) noexcept;
[[nodiscard]] pp::foundation::Status validate_blend_factor(BlendFactor factor) noexcept;
[[nodiscard]] pp::foundation::Status validate_blend_op(BlendOp op) noexcept;
[[nodiscard]] pp::foundation::Status validate_blend_state(BlendState state) noexcept;
[[nodiscard]] pp::foundation::Status validate_compare_op(CompareOp op) noexcept;
[[nodiscard]] pp::foundation::Status validate_depth_state(DepthState state) noexcept;
[[nodiscard]] pp::foundation::Status validate_sampler_filter(SamplerFilter filter) noexcept;
[[nodiscard]] pp::foundation::Status validate_sampler_address_mode(SamplerAddressMode mode) noexcept;
[[nodiscard]] pp::foundation::Status validate_sampler_desc(SamplerDesc desc) noexcept;
[[nodiscard]] pp::foundation::Status validate_mesh_desc(MeshDesc desc) noexcept;
[[nodiscard]] pp::foundation::Status validate_draw_desc(MeshDesc mesh, DrawDesc draw) noexcept;
[[nodiscard]] pp::foundation::Status validate_texture_slot(std::uint32_t slot) noexcept;
[[nodiscard]] pp::foundation::Status validate_shader_program_desc(ShaderProgramDesc desc) noexcept;
[[nodiscard]] pp::foundation::Status validate_shader_uniform_write(
    const char* name,
    std::span<const std::byte> bytes) noexcept;
[[nodiscard]] pp::foundation::Status validate_trace_label(const char* component, const char* name) noexcept;
[[nodiscard]] pp::foundation::Result<std::uint64_t> texture_byte_size(TextureDesc desc) noexcept;
[[nodiscard]] pp::foundation::Result<std::uint64_t> readback_byte_size(
    TextureDesc desc,
    ReadbackRegion region) noexcept;
[[nodiscard]] pp::foundation::Result<std::uint64_t> frame_capture_byte_size(TextureDesc desc) noexcept;
[[nodiscard]] pp::foundation::Status validate_readback_region(TextureDesc desc, ReadbackRegion region) noexcept;
[[nodiscard]] pp::foundation::Status validate_texture_copy_descs(
    TextureDesc source,
    ReadbackRegion source_region,
    TextureDesc destination,
    ReadbackRegion destination_region) noexcept;
[[nodiscard]] pp::foundation::Status validate_mipmap_generation_desc(TextureDesc desc) noexcept;
[[nodiscard]] pp::foundation::Status validate_texture_state(TextureState state) noexcept;
[[nodiscard]] pp::foundation::Status validate_texture_transition_desc(
    TextureDesc desc,
    TextureState before,
    TextureState after) noexcept;
[[nodiscard]] pp::foundation::Status validate_blit_filter(BlitFilter filter) noexcept;
[[nodiscard]] pp::foundation::Status validate_blit_descs(
    TextureDesc source,
    TextureDesc destination) noexcept;
[[nodiscard]] pp::foundation::Result<PaintFeedbackPlan> plan_paint_feedback(
    RenderDeviceFeatures features,
    TextureDesc target_desc,
    bool complex_blend) noexcept;
[[nodiscard]] const char* texture_format_name(TextureFormat format) noexcept;
[[nodiscard]] const char* texture_state_name(TextureState state) noexcept;
[[nodiscard]] const char* primitive_topology_name(PrimitiveTopology topology) noexcept;
[[nodiscard]] const char* blit_filter_name(BlitFilter filter) noexcept;
[[nodiscard]] const char* paint_feedback_path_name(PaintFeedbackPath path) noexcept;
[[nodiscard]] const char* blend_factor_name(BlendFactor factor) noexcept;
[[nodiscard]] const char* blend_op_name(BlendOp op) noexcept;
[[nodiscard]] const char* compare_op_name(CompareOp op) noexcept;
[[nodiscard]] const char* sampler_filter_name(SamplerFilter filter) noexcept;
[[nodiscard]] const char* sampler_address_mode_name(SamplerAddressMode mode) noexcept;

}
