#ifndef UNDERGROUND_GENERATOR_HPP_
#define UNDERGROUND_GENERATOR_HPP_

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

class SurvivalWorldGenerator;

/**
 * @brief Handles the generation of the stone underground, and ores.
 */
class UndergroundGenerator
{
private:
    struct Ore
    {
        GeneratorNode node;
        int depth;
        int minClusterCount;
        int maxClusterCount;
        int minClusterSize;
        int maxClusterSize;
        std::string blockName;
    };

    struct Cluster
    {
        int blockID;
        int size;
        glm::ivec3 base;
        GeneratorNode node;
    };

    struct ClusterBuildState
    {
        int blocksLeft;
        std::bitset<80 * 80 * 80> occupyBitmap;
    };

    SurvivalWorldGenerator &worldgen;
    GeneratorNode oreNode;
    std::vector<Ore> ores;

    /**
     * @brief Push clusters that start in the given chunk to the `clusters` vector.
     */
    void getClustersBasedInChunk(glm::ivec3 chunkBase, std::vector<Cluster> &clusters);

    /**
     * @brief Generate the ore clusters for a given chunk.
     */
    void generateOres(Chunk &chunk);

    /**
     * @brief Checks if the specified position is occupied according to the state.
     */
    bool isOccupiedByOre(Chunk &chunk, glm::ivec3 pos, ClusterBuildState &state);

    /**
     * @brief Marks the specified position as occupied in the state.
     */
    void markOccupiedByOre(Chunk &chunk, glm::ivec3 pos, ClusterBuildState &state);

    /**
     * @brief Generate a layer of a cluster with a given radius.
     */
    void generateClusterLayer(Chunk &chunk, const Cluster &cluster, ClusterBuildState &state, int radius);

    /**
     * @brief Generate a cluster of a block on the specified chunk.
     * 
     * @param chunk The chunk.
     * @param cluster The cluster.
     */
    void generateCluster(Chunk &chunk, const Cluster &cluster);

    /**
     * @brief Add an ore to the ore list.
     * 
     * The node ID is used to select a subnode of the generator for this ore. This should be
     * a randomly-chosen value, but kept stable and unchanged once chosen. It must be different
     * for each ore.
     * 
     * @param nodeID The node ID (see above).
     * @param depth The maximum Z value for this ore.
     * @param minClusterCount Min number of clusters per chunk.
     * @param maxClusterCount Max number of clusters per chunk.
     * @param minClusterSize Minimum number of blocks in a cluster.
     * @param maxClusterSize Maximum number of blocks in a cluster.
     * @param blockName The block name.
     */
    void registerOre(
        GeneratorNode::ID nodeID,
        int depth,
        int minClusterCount, int maxClusterCount,
        int minClusterSize, int maxClusterSize,
        std::string blockName
    );

public:
    /**
     * @brief Construct this underground generator.
     */
    UndergroundGenerator(SurvivalWorldGenerator &worldgen);

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

#endif