#pragma once

#include <thread>
#include "core/world/Universe.hpp"
#include "core/world/actions.hpp"
#include "core/queue/safe_priority_queue.hpp"
#include "core/queue/safe_queue.hpp"
#include "core/world/server_handle.hpp"
#include "./Elements.hpp"
#include "./Chunk.hpp"

/// Universe data
namespace world::server {
class Serializer;
class ChunkFactory;

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

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

        /// Invert probability of random tick
        uint randomTick = 256; // each ~30s
    };

    Universe(const options&, Serializer *const, world::server_handle *const = nullptr);
    virtual ~Universe();

#ifdef STANDALONE_SERVER
    const Elements* getElements() const { return &elements; }
    Elements* setElements() { return &elements; }
#else
    mutex::shared_guard<Elements>::shared_handle getElements() const { return elements.lock_shared(); }
    mutex::shared_guard<Elements>::handle setElements() { return elements.lock(); }
#endif

    /// Update physics
    void update(float deltaTime);
    /// Save and load from disk
    void upgrade();

    /// Try to extract chunk from area or part
    std::shared_ptr<Chunk> findChunk(const node_chunk_pos&) const;
    const Node* getPlayer(node_id) const;
    Node* setPlayer(node_id);
    const Elements::hierarchy_entry* findHierarchy(node_ref r) const {
        return getElements()->hierarchy.get(r);
    }

    bool isRangeFree(const node_voxel_ref &center, const geometry::Volume& volume) const {
        return IsRangeFree(*getElements(), center, volume);
    }
    void fill(const action::FillShape &shape, fill_edits *const edits = nullptr);

    node_id addPlayer(std::optional<relative_pos> spawn = std::nullopt);
    /// Save and delete
    void removePlayer(node_id);
    bool movePlayer(node_id, const relative_pos&);

    /// Set volume of voxel with pos as center
    //TODO: ItemList set(const cell_pos &pos, int radius, action::Shape shape, const Voxel &val);
    //FIXME: ItemList set(const area_voxel_pos &pos, int radius, action::Shape shape, const Voxel &val);

private:
    void loadIndex();
    void writeIndex();

    world::server_handle *const handle;
    const options& opts;

    /// Create custom chunks
    const std::unique_ptr<ChunkFactory> chunkFactory;

    /// All element in universe
#ifdef STANDALONE_SERVER
    Elements elements;
#else
    mutex::shared_guard<Elements> elements;
#endif

    bool running = true;
    std::vector<std::thread> workers;
    using load_queue_t = data::safe_priority_queue_map<area_chunk_pos, std::shared_ptr<Area>, int>;
    load_queue_t loadAreaQueue; //NOTE: consider Area const (getRegion uses mutex)
    data::safe_queue<std::pair<part_id, std::shared_ptr<Part>>> loadPartQueue;
    data::safe_queue<std::pair<node_chunk_pos, std::shared_ptr<Chunk>>> loadedQueue;

    using area_it_t = std::pair<area_id, std::shared_ptr<Area>>;
    using save_task_t = std::pair<area_it_t, std::pair<chunk_pos, std::shared_ptr<world::server::Chunk>>>;
    data::safe_queue<save_task_t> saveAreaQueue; //NOTE: consider Area and Chunk const
    data::safe_queue<std::pair<part_id, std::shared_ptr<Part>>> savePartQueue;

    relative_pos spawnPoint{generational::id(), world::world_pos()};
};

}