#ifndef CAVE_GENERATOR_HPP_
#define CAVE_GENERATOR_HPP_

#include <shared_mutex>
#include <mutex>
#include <unordered_map>

#include <Chunk/Chunk.hpp>
#include <GeneratorNode/GeneratorNode.hpp>

class SurvivalWorldGenerator;

/**
 * @brief Handles generating tunnels.
 */
class TunnelGenerator
{
private:
    struct TunnelSegment
    {
        double radius;
        glm::dvec3 curve;
    };

    struct Tunnel
    {
        glm::ivec3 startPos;
        glm::dvec3 startDir;
        int numSegments;
        TunnelSegment segments[128];
    };

    struct Region
    {
        std::vector<Tunnel> tunnels;
    };

    struct CaveGrid
    {
        std::vector<bool> data;
        std::shared_mutex mutex;

        CaveGrid();
        bool get(glm::ivec3 pos);
        void set(glm::ivec3 pos, bool hasCave);
        void carveSphere(glm::dvec3 center, double radius);
    };

    static constexpr int maxTunnelCount = 128;
    static constexpr int minTerrainForTunnel = 1;
    static constexpr double tunnelCurvature = 0.5;

    std::shared_mutex gridCacheMutex;
    std::unordered_map<int, std::unordered_map<int, std::unordered_map<int, std::shared_ptr<CaveGrid>>>> gridCache;

    SurvivalWorldGenerator &worldgen;
    GeneratorNode caveNode;

    void decodeSegments(TunnelSegment *outSegments, uint64_t word);
    void computeTunnelsAt(std::vector<Tunnel> &outTunnels, glm::ivec3 regionBase);
    void computeGridAt(CaveGrid &grid, int regionX, int regionY, int regionZ);
    CaveGrid& gridAt(int regionX, int regionY, int regionZ);

public:
    /**
     * @brief Construct a cave generator for the specified world generator.
     */
    TunnelGenerator(SurvivalWorldGenerator &worldgen);

    /**
     * @brief Returns true if the specified coordinates are inside a tunnel.
     */
    bool isInsideTunnel(glm::ivec3 pos);

    /**
     * @brief Generates tunnels on the specified chunk.
     */
    void generate(Chunk &chunk);
};

#endif