#ifndef CHUNK_HPP_
#define CHUNK_HPP_

#include <memory>
#include <tuple>
#include <vector>
#include <atomic>
#include <string>
#include <Protocol/Protocol.hpp>
#include <Utilities/Extern.hpp>
#include <Frustum/Frustum.hpp>
#include <AudioSource/AudioSource.hpp>
#include <GraphicsContext/GraphicsContext.hpp>
#include <DataUpdatePlanner/DataUpdatePlanner.hpp>
#include <Utilities/DenseArray.hpp>

class Machine;
class Block;
class World;
class WorldDisplay;
class WorldGridNode;
class ChunkEncoder;
class ChunkWindow;

/**
 * @brief Represents a single chunk of a world.
 */
class Chunk final : public std::enable_shared_from_this<Chunk>
{
private:
    static constexpr size_t maxVariantsInPalette = 128;

    DenseArray variantPalette;
    DenseArray blockGrid;

    std::atomic<bool> loadingInProgress;
    std::atomic<bool> knownReady;

    std::weak_ptr<WorldGridNode> node;
    std::string gridFilePath;

    std::shared_ptr<ChunkEncoder> encoder;
    std::atomic<bool> gridGenerated;

    void regenerateGrid();

public:
    static const std::string gridFileBaseName;

    const glm::ivec3 base;
    std::atomic<bool> dirty;
    std::optional<uint64_t> lastFlushGeneration;
    bool needsSaving = false;
    int retainCount = 0;
    int bookKeepSeqNo = 0;
    bool isInPurgeQueue = false;
    bool isInWindowLoadingQueue = false;
    std::vector<glm::ivec3> monitorPositions;
    bool purged = false;

    /**
     * @brief Construct an empty chunk.
     */
    Chunk(std::shared_ptr<WorldGridNode> node, glm::ivec3 base);

    /**
     * @brief Copy-constructor.
     * 
     * Necessary because of the atomics.
     */
    Chunk(const Chunk &other);

    std::tuple<int32_t, uint8_t> getBlockAndMeta(glm::ivec3 offset);
    void setBlockAndMeta(glm::ivec3 offset, int32_t blockId, uint8_t meta);

    void toNetworkPayload(std::vector<uint8_t> &bytes);
    void fromNetworkPayload(const uint8_t *bytes, size_t size);

    /**
     * @brief Indicate that chunk loading is complete.
     * 
     * @param world The world in which this chunk is placed.
     */
    void markLoadingComplete();

    void deserializeGrid(std::vector<uint8_t> &bytes);

    /**
     * @brief Purge the chunk's voxels.
     * 
     * Returns true if after this call no voxel operations are in progress.
     */
    bool purgeVoxels();

    /**
     * @brief Get the level 0 world grid node for this chunk.
     * 
     * Returns `nullptr` if we are currently detached.
     */
    std::shared_ptr<WorldGridNode> getNode();

    /**
     * @brief Save this chunk via the specified planner.
     */
    void save(DataUpdatePlanner &planner);

    /**
     * @brief Save this chunk to the end of the specified data buffer.
     */
    void saveInto(std::vector<uint8_t> &data);

    /**
     * @brief Returns the path to the grid file.
     */
    const std::string& getGridFilePath();

    /**
     * @brief Returns true if the loading of this chunk is complete.
     */
    bool isReady();

    /**
     * @brief Dump the components walking up the tree of this chunk.
     */
    void dumpComponents();

    void beginGridGeneration(ChunkWindow &window);
    bool tryEncodeIntoSVO();
    void onBlockUpdated(glm::ivec3 offset);
    void refreshBlock(glm::ivec3 offset);
    void detach();
    void updateEffects(ChunkWindow &window);
    bool hasBlockEffects();
    bool isEncoding();
};

#endif