#pragma once

#include "Abstract.hpp"
#include "surrounding.hpp"

#include "../../core/data/safe_queue.hpp"
#include "../../core/data/safe_priority_queue.hpp"
#include "../render/api/Models.hpp"
#include "../../core/data/math.hpp"
#include <thread>

using namespace data;
namespace contouring {
    /// Dual Marching Cube 1:1 contouring
    class FlatDualMC final: public Abstract {
    public:
        FlatDualMC(const std::string&);
        virtual ~FlatDualMC();

        void update(const voxel_pos&, const world::client::area_map&) override;

        void onGui() override;

        std::string getOptions() const override;
        std::pair<float, float> getFarRange() const override;
        size_t getQueueSize() override;

        /// Chunk data change
        void onUpdate(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &, geometry::Faces) override;
        /// Chunk existante ping
        /// @note notify for chunks entering view while moving
        void onNotify(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &) override;

        /// Register entity from model
        void onEntityLoad(size_t id, std::istream&) override;
        /// Register entity from area
        void onEntityLoad(size_t id, const glm::ucvec3& size, const std::vector<std::shared_ptr<world::Chunk>>&) override;
        /// Free entity model
        void onEntityUnload(size_t id) override;

        /// Get buffers in frustum with model matrices
        /// @note buffers invalidated after update
        void getModels(draw_call draw, const std::optional<geometry::Frustum> &frustum, const glm::llvec3 &offset, int density, bool solid) override;
        /// Get buffers hitting occlusion rays with model matrices
        /// @note buffers invalidated after update
        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) override;

        render::Model* getEntityModel(size_t) override;

    protected:
        //FIXME: use unique_ptr
        robin_hood::unordered_map<area_id, robin_hood::pair<area_info, robin_hood::unordered_map<chunk_pos, std::pair<render::LodModel *, render::LodModel *>>>> buffers;

        data::generational::view_vector<std::unique_ptr<render::Model>> entities;

        safe_priority_queue_map<area_<chunk_pos>, surrounding::corners, int, area_hash> loadQueue;
        safe_queue<std::pair<area_<chunk_pos>, std::pair<render::LodModel::LodData, render::LodModel::LodData>>> loadedQueue;
        struct entity_area_job_t { size_t id; glm::ucvec3 size; std::vector<std::shared_ptr<world::Chunk>> area; };
        safe_queue<entity_area_job_t> entityLoadQueue;
        safe_queue<std::pair<size_t, render::Model::Data>> entityLoadedQueue;

        bool running = true;
        std::vector<std::thread> workers;

        void enqueue(const area_<chunk_pos> &, const chunk_pos &offset, const world::ChunkContainer &);

        uint16_t loadDistance = 3;
        uint16_t keepDistance = 4;
        bool transparency = false;
        float iso = .1f;
        bool manifold = true;
        bool reordering = true;
        float lod_strength = .15;
        float lod_quality = 0;
        std::deque<bool> lod_levels;

        std::vector<std::pair<float, float>> loadedLevels;

        enum class Layer {
            Solid = 1,
            Transparent = 2,
            Both = Solid | Transparent,
        };
        friend inline bool operator&&(Layer a, Layer b) {
            return static_cast<int>(a) & static_cast<int>(b);
        }

        void render(const surrounding::corners &surrounding, render::Model::Data::indices_t& idx, std::vector<render::VertexData>& ver, Layer layer) const;
        void render(const surrounding::corners &surrounding, render::LodModel::LodData& out, std::vector<render::VertexData>& tmp, Layer layer) const;
    };
}
