#pragma once

#include "../../core/world/forward.h"
#include "../../core/geometry/Frustum.hpp"
#include "../../core/geometry/Ray.hpp"
#include "../../core/geometry/Faces.hpp"
#include "../../core/flags.hpp"
#include <glm/gtc/matrix_transform.hpp>

namespace render {
    class Model;
    class LodModel;
}
/// Mesh creation (from world to render)
namespace contouring {
    /// Generating mesh from world data
    class Abstract {
    public:
        Abstract() { }
        virtual ~Abstract() { }

        /// Each frame ping.
        /// Mostly used for cleanup and to flush buffers data using main thread
        virtual void update(const voxel_pos &pos, const world::client::area_map &areas) = 0;

        /// Chunk data change
        /// @param offset priority position offset
        virtual void onUpdate(const area_<chunk_pos> &pos, const chunk_pos &offset, const world::ChunkContainer &data, geometry::Faces neighbors) = 0;
        /// Chunk existante ping
        /// @note notify for chunks entering view while moving
        virtual void onNotify(const area_<chunk_pos> &pos, const chunk_pos &offset, const world::ChunkContainer &data) = 0;
        /// Display ImGui config
        virtual void onGui() = 0;
        /// Register entity from model
        virtual void onEntityLoad(size_t id, std::istream&) = 0;
        /// Register entity from area
        virtual void onEntityLoad(size_t id, const glm::ucvec3& size, const std::vector<std::shared_ptr<world::Chunk>>&) = 0;
        /// Free entity model
        virtual void onEntityUnload(size_t id) = 0;
        /// Get options
        virtual std::string getOptions() const = 0;
        /// Get camera recommended far_dist range
        virtual std::pair<float, float> getFarRange() const = 0;

        /// Get pending elements
        virtual size_t getQueueSize() = 0;

        using area_info = std::tuple<area_pos, voxel_pos::value_type, float>;
        using draw_call = const std::function<void(glm::mat4, render::LodModel *const, const area_info&, const voxel_pos&)> &;

        /// Get buffers in frustum with model matrices
        /// @note buffers invalidated after update
        virtual void getModels(draw_call draw, const std::optional<geometry::Frustum>& frustum, const glm::llvec3& offset, int density, bool solid) = 0;
        /// Get buffers hitting occlusion rays with model matrices
        /// @note buffers invalidated after update
        virtual void getModels(draw_call draw, const glm::ifvec3 &from, float far_dist, const std::vector<glm::vec3> &occlusion, const glm::llvec3 &offset, int density, bool solid) = 0;

        /// Get buffer corresponding to entity idx
        virtual render::Model* getEntityModel(size_t) = 0;
        /// Get entity instance model if in frustum
        static _FORCE_INLINE_ bool CullEntity(glm::mat4& out, const glm::vec3& size, const glm::vec3& scale, const glm::ifvec3& pos,
            const std::optional<geometry::Frustum>& frustum, const glm::llvec3& offset, int density)
        {
            const glm::vec3 fPos = (glm::vec3(pos.raw_as_long() - offset * glm::llvec3(density)) + pos.offset) / glm::vec3(density);
            if (!frustum.has_value() || frustum.value().contains(geometry::Box::fromMin(fPos, size))) {
                out = glm::scale(glm::translate(glm::mat4(1), fPos * (float)density), scale);
                return true;
            }
            return false;
        };
    };
}