#pragma once

#include "../../core/world/forward.h"
#include "../../core/geometry/IBox.hpp"
#include "../../core/data/math.hpp"

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

    public:
        /// Area radius in chunks
        constexpr inline int getRadius() const { return radius; }
        ChunkContainer(int radius): radius(radius) {
            assert(radius > 0 && radius < (1 << 22) / CHUNK_LENGTH);
        }
        inline bool inRange(const chunk_pos& pos) const {
            return glm::abs(pos.x) < radius && glm::abs(pos.y) < radius && glm::abs(pos.z) < radius;
        }
        std::optional<std::shared_ptr<Chunk>> findInRange(const chunk_pos &pos) const {
            const auto it = find(pos);
            return it != end() ? std::make_optional(it->second) : std::nullopt;
        }
    };

    /// Area (aka big group of Chunk)
    struct Area {
    public:
        /// radius: size in chunk (length = radius * 2 + 1)
        Area(voxel_pos center, int radius): center(center), chunks(radius) { }

        inline const area_pos &getOffset() const { return center; }
        inline geometry::IBox getBounding() const {
            const auto c = center.as_voxel();
            return geometry::IBox(c - voxel_pos(CHUNK_LENGTH * (chunks.getRadius() - 1)),
                                  c + voxel_pos(CHUNK_LENGTH * chunks.getRadius()));
        }
        /// Move offset return if chunk_change
        bool inline move(const area_pos::offset_t &offset) {
            const auto prev = glm::divide(center.as_voxel());
            center = center + offset;
            return prev != glm::divide(center.as_voxel());
        }

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

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

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

    protected:
        area_pos center;

    private:
        ChunkContainer chunks;
    };
}
