#pragma once

#include <string>
#include <thread>
#include "../../core/world/Universe.hpp"
#include "../../core/world/actions.hpp"
#include "../../core/data/math.hpp"
#include "../../core/data/safe_queue.hpp"
#include "../../core/data/safe_priority_queue.hpp"
#include "../../core/data/file.hpp"
#include "../../core/data/mem.hpp"
#include "../net/Server.hpp"
#include "Area.hpp"

namespace buffer {
    class Abstract;
}

using namespace data;
/// Universe data
namespace world::server {
    class Chunk;

    /// Whole universe container in abstract server
    class Universe: public world::Universe {
    public:
        /// Server config
        struct options: world::Universe::options {
            /// Storage path
            std::string folderPath = "world";

            net::exposure connection = net::exposure{"", 4242, 1, "content/key.pem", "content/cert.pem"};

            /// Iteration count in part break search
            size_t floodFillLimit = CHUNK_SIZE;
        };

        Universe(const options &);
        virtual ~Universe();

        /// Update physics
        void update(float deltaTime);
        /// Send-Receive packets
        //NOTE: triggers onPacket, onConnect & onDisconnect
        void pull();
        /// Apply new options
        void setOptions(const options &);

        /// Set volume of voxel with pos as center
        /// MAYBE: allow set multi area
        ItemList set(const area_<voxel_pos> &pos, int radius, action::Shape shape, const Voxel &val);

        /// Instante entity
        entity_instance_id addEntity(entity_id type, const Entity::Instance &instance);
        Entity::Instance* findEntity(entity_id type, entity_id id);

        /// Move player
        bool movePlayer(data::generational::id id, glm::ifvec3 pos);

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

        bool isAreaFree(const area_<voxel_pos> &pos, geometry::Shape shape, uint16_t radius) const override;

        /// Check for collision on destination
        bool collide_end(const glm::ifvec3 &pos, const glm::vec3 &vel, int density, float radius) const;
        /// Check for collision at position
        bool collide_point(const glm::ifvec3 &pos, const glm::vec3 &vel, int density) const;

    protected:
        /// Save all chunks (saveThread uses virtual calls)
        void saveAll(bool remove);

        /// Handle networking requests
        std::optional<uint16_t> onConnect(net::server::Peer*);
        bool onDisconnect(net::server::Peer*, bool, uint16_t);
        bool onPacket(net::server::Peer*, const data::out_view&, net::PacketFlags);
        void broadcastAreas();
        data::out_buffer serializeChunk(area_<chunk_pos>, const std::shared_ptr<Chunk>&);
        virtual void broadcastMessage(const std::string &);
        virtual void broadcastEntities();

        using area_map = robin_hood::unordered_map<area_id, std::shared_ptr<Area>>;

        virtual std::shared_ptr<Chunk> createChunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd) const;
        virtual std::shared_ptr<Chunk> createChunk(std::istream &str) const;
        virtual std::shared_ptr<Chunk> createChunk() const;

        virtual void updateChunk(area_map::iterator&, world::ChunkContainer::iterator&, chunk_pos, float deltaTime);
        virtual void loadChunk(area_<chunk_pos>, chunk_pos, const world::ChunkContainer &);

        robin_hood::unordered_set<data::generational::id> movedPlayers;
        voxel_pos spawnPoint;

        /// Alive areas containing chunks
        area_map areas;

        using area_it_t = robin_hood::pair<area_id, std::shared_ptr<Area>>;
        /// Dead areas
        data::generational::vector<Area::params> far_areas;
        void saveAreas() const;

        data::generational::vector<Entity> entities;

        bool running = true;
        std::vector<std::thread> workers;
        safe_priority_queue_map<area_<chunk_pos>, std::shared_ptr<Area>, int, area_hash> loadQueue; //NOTE: consider Area const (getRegion uses mutex)
        safe_queue<robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>>> loadedQueue;

        using save_task_t = std::pair<area_it_t, robin_hood::pair<chunk_pos, std::shared_ptr<world::server::Chunk>>>;
        data::safe_queue<save_task_t> saveQueue; //NOTE: consider Area and Chunk const

        uint16_t loadDistance;
        uint16_t keepDistance;
        std::string folderPath;
        size_t floodFillLimit;

        net::server::Server host;

        data::file_content dict_content;
        zstd::dict_set dicts;
        zstd::write_ctx dict_write_ctx;
    };
}