#pragma once

#include <array>
#include <compare>
#include <cstddef>
#include <cstdint>
#include <memory>

namespace renderive {

enum class Frame_3D_Control_Mode : std::uint8_t {
    Manual,
    Low_Latency,
    Playback
};

struct Extent_2D {
    std::uint32_t width{};
    std::uint32_t height{};

    [[nodiscard]] constexpr bool empty() const noexcept { return width == 0 || height == 0; }
    bool operator==(const Extent_2D&) const = default;
};

struct Vec_3 {
    float x{};
    float y{};
    float z{};
    bool operator==(const Vec_3&) const = default;
};

struct Rgba_8 {
    std::uint8_t red{255};
    std::uint8_t green{255};
    std::uint8_t blue{255};
    std::uint8_t alpha{255};
    bool operator==(const Rgba_8&) const = default;
};

struct Clear_Color {
    float red{};
    float green{};
    float blue{};
    float alpha{1.0F};
    bool operator==(const Clear_Color&) const = default;
};

struct Matrix_4x4 {
    std::array<float, 16> values{
        1.0F, 0.0F, 0.0F, 0.0F,
        0.0F, 1.0F, 0.0F, 0.0F,
        0.0F, 0.0F, 1.0F, 0.0F,
        0.0F, 0.0F, 0.0F, 1.0F};
    bool operator==(const Matrix_4x4&) const = default;
};

enum class Camera_Projection : std::uint8_t {
    Perspective,
    Orthographic
};

struct Camera_3D {
    Vec_3 eye{0.0F, 0.0F, 4.0F};
    Vec_3 target{};
    Vec_3 up{0.0F, 1.0F, 0.0F};
    Camera_Projection projection{Camera_Projection::Perspective};
    float vertical_field_of_view_radians{0.785398163F};
    float near_clip{0.01F};
    float far_clip{1000.0F};
    float orthographic_height{2.0F};
    bool operator==(const Camera_3D&) const = default;
};

struct Sphere_3D {
    Vec_3 position{};
    float radius{1.0F};
    Rgba_8 color{};
    Matrix_4x4 transform{};
    bool visible{true};
    bool operator==(const Sphere_3D&) const = default;
};

struct Object_3D_Id {
    std::uint64_t value{};

    [[nodiscard]] explicit constexpr operator bool() const noexcept { return value != 0; }
    auto operator<=>(const Object_3D_Id&) const = default;
};

class Render_3D final {
public:
    class Builder final {
    public:
        Builder& frame_control(Frame_3D_Control_Mode mode, double maximum_frames_per_second = 60.0);
        Builder& viewport(Extent_2D extent);
        Builder& frame_slots(std::size_t count);
        Builder& gpu(std::uint32_t index, bool validation_enabled = false);
        Builder& camera(Camera_3D value);
        Builder& clear_color(Clear_Color value);
        Builder& scale(float device_x, float device_y, float render, float user);
        [[nodiscard]] std::unique_ptr<Render_3D> build() const;

    private:
        Frame_3D_Control_Mode frame_control_mode_{Frame_3D_Control_Mode::Low_Latency};
        double maximum_frames_per_second_{60.0};
        Extent_2D viewport_{800, 600};
        std::size_t frame_slot_count_{2};
        std::uint32_t gpu_index_{};
        bool validation_enabled_{};
        Camera_3D camera_;
        Clear_Color clear_color_;
        float device_scale_x_{1.0F};
        float device_scale_y_{1.0F};
        float render_scale_{1.0F};
        float user_scale_{1.0F};
    };

    ~Render_3D();
    Render_3D(const Render_3D&) = delete;
    Render_3D& operator=(const Render_3D&) = delete;
    Render_3D(Render_3D&&) = delete;
    Render_3D& operator=(Render_3D&&) = delete;

    void resize(Extent_2D extent);
    void set_camera(Camera_3D camera);
    void set_clear_color(Clear_Color color);
    void set_scale(float device_x, float device_y, float render, float user);

    [[nodiscard]] Object_3D_Id add_sphere(Sphere_3D sphere);
    [[nodiscard]] bool replace_sphere(Object_3D_Id id, Sphere_3D sphere);
    [[nodiscard]] bool erase_object(Object_3D_Id id);

    [[nodiscard]] bool prepare_frame();
    [[nodiscard]] bool refresh_manual_frame();
    [[nodiscard]] bool discard_pending_frame();
    [[nodiscard]] bool render_prepared_frame();
    void wait_for_render();

private:
    struct Configuration;
    struct Impl;

    explicit Render_3D(const Configuration& configuration);
    std::unique_ptr<Impl> impl_;
};

} // namespace renderive
