#ifndef CHUNK_H
#define CHUNK_H

#include <cstdint>
#include <unordered_map>
#include <memory>
#include <array>
#include <algorithm>
#include <iostream>

#include <glm/glm.hpp>

#include "Mesh.h"

enum class VoxelKind : uint8_t {
    Air,
    Dirt,
    Grass,
    Stone
};

const int CHUNK_SIZE = 32;

// Hash function for glm::ivec3 to use with unordered_set
// Still used in World.h
namespace std {
    template <>
    struct hash<glm::ivec3> {
        size_t operator()(const glm::ivec3& v) const {
            // Combine hash values of x, y, z components
            size_t h1 = hash<int>()(v.x);
            size_t h2 = hash<int>()(v.y);
            size_t h3 = hash<int>()(v.z);

            // Use a simple hash combination algorithm
            return h1 ^ (h2 << 1) ^ (h3 << 2);
        }
    };
}

class Chunk {
  public:

    Chunk();

    Mesh* getMesh();  // Returns pointer, can be null if not generated yet

    void generateMesh();

    // Accessors
    inline VoxelKind get(int x, int y, int z) const {
        return data[index(x, y, z)];
    }
    inline VoxelKind get(glm::ivec3 pos) const {
        // std::cout << "Getting Pos (" << pos.x << ", " << pos.y << ", " << pos.z << ")" << std::endl;
        return data[index(pos.x, pos.y, pos.z)];
    }

    // Mutators
    inline void set(int x, int y, int z, VoxelKind v) {
        data[index(x, y, z)] = v;
        empty_checked = false;  // Invalidate cache
        mesh_dirty = true;  // Mark mesh as needing regeneration
    }
    inline void set(glm::ivec3 pos, VoxelKind v) {
        // std::cout << "Setting Pos (" << pos.x << ", " << pos.y << ", " << pos.z << ")" << std::endl;
        data[index(pos.x, pos.y, pos.z)] = v;
        empty_checked = false;  // Invalidate cache
        mesh_dirty = true;  // Mark mesh as needing regeneration
    }

    // Other
    inline bool isEmpty() const {
        if (!empty_checked) {
            is_empty = std::all_of(data.begin(), data.end(), [](VoxelKind k){
                return k == VoxelKind::Air;
            });
            empty_checked = true;
        }
        return is_empty;
    }

  private:
    static constexpr int SIZE = CHUNK_SIZE;
    static constexpr int TOTAL = SIZE * SIZE * SIZE;
    std::array<VoxelKind, TOTAL> data;

    std::unique_ptr<Mesh> mesh;  // Nullable mesh
    mutable bool is_empty = true;  // Cache empty state
    mutable bool empty_checked = false;  // Track if we've checked
    bool mesh_dirty = true;  // Track if mesh needs regeneration

    static inline constexpr int index(int x, int y, int z) noexcept {
        return x + (y * SIZE) + (z * SIZE * SIZE);
    }
};

#endif // CHUNK_H
