#ifndef TERRAIN_GENERATOR_HPP_
#define TERRAIN_GENERATOR_HPP_

#include <DevCommand/TerrainDumpCommand/TerrainDumpCommand.hpp>
#include <vector>
#include <shared_mutex>
#include <Chunk/Chunk.hpp>
#include <GeneratorNode/GeneratorNode.hpp>
#include <Perlin2D/Perlin2D.hpp>

class SurvivalWorldGenerator;

/**
 * @brief Handle terrain generation.
 */
class TerrainGenerator
{
public:
    struct Cell
    {
        int height;
        float temperature;
        float humidity;
        float fertility;
        float bumpiness;
        int topID;
        int groundID;
        int underLakeID;
        int fluidBlockID;
    };

private:
    static constexpr uint32_t temperatureOrder = 5;
    static constexpr uint32_t fertilityOrder = 2;
    static constexpr uint32_t humidityOrder = 5;
    static constexpr uint32_t biasOrder = 1;

    static std::vector<std::string> clayColorBands;
    static constexpr int clayBandHeight = 3;

    struct TerrainGrid
    {
        std::vector<Cell> data;

        TerrainGrid();
    };

    SurvivalWorldGenerator &worldgen;
    GeneratorNode permutationNode;

    Perlin2D temperatureNoise;
    Perlin2D humidityNoise;
    Perlin2D fertilityNoise;
    Perlin2D bumpinessNoise;
    Perlin2D steepnessNoise;
    Perlin2D subtleSteepnessNoise;
    Perlin2D terrainNoise;
    Perlin2D biasNoise;

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

    void computeGridAt(TerrainGrid &grid, int regionX, int regionY);
    TerrainGrid& gridAt(int regionX, int regionY);
    float erode(float height, float steepness, float scale);

public:
    /**
     * @brief Construct the terrain generator.
     * 
     * @param worldgen The world generator.
     */
    TerrainGenerator(SurvivalWorldGenerator &worldgen);

    Cell& getCell(int x, int y);

    /**
     * @brief Get the highest Z of the terrain (where a block is still placed).
     */
    int getTopZ(int x, int y);

    /**
     * @brief Generate terrain in the specified chunk, if applicable.
     */
    void generate(Chunk &chunk);

    friend class TerrainDumpCommand;
};

#endif