#pragma once

#include <variant>
#include "forward.h"
#include "Voxel.hpp"
#include "position.h"
#include "../geometry/Ray.hpp"
#include "../geometry/Shapes.hpp"

namespace world {
    /// Whole abstract universe container
    class Universe {
    public:
        virtual ~Universe() {}

        /// Distance management
        struct options {
            /// Radius in chunks to load if missing
            uint16_t loadDistance = 5;
            /// Radius in chunks to keep in memory
            uint16_t keepDistance = 6;
        };

        /// Universe voxel ray intersection
        struct ray_target {
            ray_target(area_<voxel_pos> pos, Voxel value, voxel_pos offset):
                pos(pos), value(value), offset(offset) { }
            area_<voxel_pos> pos;
            Voxel value;
            voxel_pos offset;
        };
        /// Universe unloaded chunk ray intersection
        struct ray_out_of_range {
            ray_out_of_range(area_<chunk_pos> pos, voxel_pos offset):
                pos(pos), offset(offset) { }
            area_<chunk_pos> pos;
            voxel_pos offset;
        };
        /// Universe ray intersection
        /// @return target voxel, unloaded chunk or none
        using ray_result = std::variant<std::monostate, ray_target, ray_out_of_range>;

        /// Get nearest voxel colliding ray
        /// @note ray in world scale
        virtual ray_result raycast(const geometry::Ray &ray) const = 0;

        /// Check for entity in shape
        /// MAYBE: multiarea
        virtual bool isAreaFree(const area_<voxel_pos> &pos, geometry::Shape shape, uint16_t radius) const = 0;

        /// Check for collision on movement
        bool collide(const glm::ifvec3 &pos, const glm::vec3 &vel, int density, float radius = 0) const;
        /// Move with collision check
        /// @note must remove velocity after colision
        bool move(glm::ifvec3 &pos, const glm::vec3 &vel, int density, float radius = 0) const;

        /// Entities commun properties
        struct Entity {
            /// Linear cube of Chunk
            /// Size: 1+divide(size)
            using area_t = std::vector<std::shared_ptr<Chunk>>;
            /// render::Model::Data serialized
            using model_t = std::vector<char>;

            Entity(const glm::usvec3& size = glm::usvec3(1), const glm::vec3& scale = glm::vec3(1), bool permanant = true):
                size(size), scale(scale), permanant(permanant) { }
            Entity(const model_t& model, const glm::usvec3& size = glm::usvec3(1), const glm::vec3& scale = glm::vec3(1), bool permanant = true):
                size(size), scale(scale), permanant(permanant) { shape = model; }

            std::variant<area_t, model_t> shape;
            glm::usvec3 size;
            glm::vec3 scale;
            bool permanant;
            struct Instance {
                glm::ifvec3 pos;
                glm::vec3 velocity;
            };
            data::generational::vector<Instance> instances;
        };
    };
}