#pragma once

#include <robin_hood.h>
#include "./Node.hpp"
#include "./EdittableChunk.hpp"

namespace world {

/// Chunk map with restricted access
struct ChunkContainer: robin_hood::unordered_map<chunk_pos, std::shared_ptr<EdittableChunk>> {
private:
    int radius;

public:
    /// Area radius in chunks
    constexpr inline int getRadius() const { return radius; }
    ChunkContainer(int radius) {
        resize(radius);
    }
    void resize(int radius) {
        assert(radius > 0 && radius < (1 << 22) / CHUNK_LENGTH);
        this->radius = radius;
    }
    bool inRange(const chunk_pos& pos) const;
    std::shared_ptr<EdittableChunk> findInRange(const chunk_pos &pos) const;
};

/// Multi region element (aka big group of Chunk)
class Area: public Element {
public:
    /// radius: size in chunk (length = radius * 2 + 1)
    Area(int radius): chunks(radius) { }

    void free(const node_id &i, Elements &l) override;
    aabb getBoundingBox(const Elements &l) const override;

    inline const ChunkContainer &getChunks() const { return chunks; }
    inline ChunkContainer &setChunks() { return chunks; }

    virtual std::optional<double> getCurvature() const = 0;

    struct params {
        int radius;
        std::optional<double> curvature;
    };

private:
    ChunkContainer chunks;
};

using area_id = generational::key<world::Area>;
using area_ref = generational::ref<world::Area>;

/// Absolute area region
struct area_region_pos {
    area_id area;
    region_pos region;
};
/// Absolute area region without generation
struct area_region_ref {
    area_region_ref(area_ref r, region_pos p): area(r), region(p) { }
    area_region_ref(const area_region_pos& p): area_region_ref(p.area, p.region) { }
    area_ref area;
    region_pos region;
};
/// Absolute area chunk
struct area_chunk_pos {
    area_id area;
    chunk_pos chunk;
    inline bool operator==(const area_chunk_pos& a) const {
        return area == a.area && chunk == a.chunk;
    }
};

/// Absolute area voxel
struct area_voxel_pos {
    area_id area;
    voxel_pos voxel;
};
/// Absolute chunk inside area or part
struct node_chunk_pos {
    node_id node;
    chunk_pos chunk;
    inline bool operator==(const node_chunk_pos& a) const {
        return node == a.node && chunk == a.chunk;
    }
};
/// Absolute chunk inside area or part without generation
struct node_chunk_ref {
    node_chunk_ref(node_ref r, chunk_pos p): node(r), chunk(p) { }
    node_chunk_ref(const node_chunk_pos& p): node_chunk_ref(p.node, p.chunk) { }
    node_ref node;
    chunk_pos chunk;
};
/// Absolute voxel inside area or part
struct node_voxel_pos {
    node_id node;
    voxel_pos voxel;
};
/// Absolute voxel inside area or part without generation
struct node_voxel_ref {
    node_voxel_ref(node_ref r, voxel_pos p): node(r), voxel(p) { }
    node_voxel_ref(const node_voxel_pos& p): node_voxel_ref(p.node, p.voxel) { }
    node_ref node;
    voxel_pos voxel;
};

}

namespace std {

template<>
struct hash<world::area_chunk_pos> {
    std::size_t operator()(const world::area_chunk_pos& a) const noexcept {
        std::size_t h1 = std::hash<generational::id>{}(a.area.val);
        std::size_t h2 = std::hash<world::chunk_pos>{}(a.chunk);
        return h1 ^ (h2 << 1);
    }
};

}