#define STB_IMAGE_IMPLEMENTATION
#include "VoxelGame.h"
#include <iostream>
#include <GL/glew.h>
#include <GL/glu.h>
#include "stb_image.h"
#include <cmath>
#include <unordered_set>
#include "imgui.h"
#include "GreedyMesher.h"

namespace {
    // --- Perlin Noise Helpers ---
    float fade_(float t) {
        return t * t * t * (t * (t * 6 - 15) + 10);
    }
    float lerp_(float a, float b, float t) {
        return a + t * (b - a);
    }
    float grad_(int hash, float x, float y) {
        int h = hash & 7;
        float u = h < 4 ? x : y;
        float v = h < 4 ? y : x;
        return ((h & 1) ? -u : u) + ((h & 2) ? -v : v);
    }
    float perlin(float x, float y) {
        static int p[512];
        static bool init = false;
        if (!init) {
            int perm[256] = {
                151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,
                140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,
                247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,
                57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,
                74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,
                60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,
                65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,
                200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,
                52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,
                207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,
                119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,
                129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,
                218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,
                81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,
                184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,
                222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
            };
            for (int i = 0; i < 256; i++) {
                p[i] = perm[i];
                p[i + 256] = perm[i];
            }
            init = true;
        }
        int X = int(floor(x)) & 255;
        int Y = int(floor(y)) & 255;
        x -= floor(x);
        y -= floor(y);
        float u = fade_(x);
        float v = fade_(y);
        int A = p[X] + Y, B = p[X + 1] + Y;
        return lerp_( v,
            lerp_( u, grad_(p[A],   x,   y), grad_(p[A+1],   x-1,   y) ),
            lerp_( u, grad_(p[B],   x, y-1), grad_(p[B+1], x-1, y-1) )
        );
    }
}

// Helper to safely query voxel (returns 0 if out of bounds)
inline int voxelAt(const Chunk& c, int x, int y, int z) {
    if (x<0||y<0||z<0||x>=16||y>=16||z>=16) return 0;
    return c.voxels[x][y][z];
}


VoxelGame::VoxelGame()
    : textureGrass(0), textureDirt(0), textureWood(0),
      totalChunksLoaded(0), totalChunksEverLoaded(0)
{
}

VoxelGame::~VoxelGame() {
    glDeleteTextures(1, &textureGrass);
    glDeleteTextures(1, &textureDirt);
    glDeleteTextures(1, &textureWood);
}


bool VoxelGame::init() {
    if (!loadTextures()) return false;
    // Initial camera position
    float cam[3] = {32,32,80};
    updateChunks(cam);
    // Build mesh for each chunk
    for (auto &p : chunks) {
        p.second.mesh = GreedyMesher::mesh(p.second.voxels);
    }
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    return true;
}

bool VoxelGame::loadTextures() {
    int w,h,n;
    unsigned char* data;
    data = stbi_load("grass.jpg",&w,&h,&n,4);
    if (!data) { std::cerr<<"grass.jpg load fail\n"; return false; }
    glGenTextures(1,&textureGrass); glBindTexture(GL_TEXTURE_2D,textureGrass);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
    glGenerateMipmap(GL_TEXTURE_2D);
    stbi_image_free(data);
    data = stbi_load("dirt.jpg",&w,&h,&n,4);
    if (!data) { std::cerr<<"dirt.jpg load fail\n"; return false; }
    glGenTextures(1,&textureDirt); glBindTexture(GL_TEXTURE_2D,textureDirt);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
    glGenerateMipmap(GL_TEXTURE_2D);
    stbi_image_free(data);
    data = stbi_load("wood.png",&w,&h,&n,4);
    if (!data) { std::cerr<<"wood.png load fail\n"; return false; }
    glGenTextures(1,&textureWood); glBindTexture(GL_TEXTURE_2D,textureWood);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
    glGenerateMipmap(GL_TEXTURE_2D);
    stbi_image_free(data);
    return true;
}

Chunk VoxelGame::generateChunk(int cx,int cz) {
    Chunk c; c.x=cx; c.z=cz;
    c.voxels.assign(16, std::vector<std::vector<int>>(16, std::vector<int>(16,0)));
    float scale=0.1f;
    for(int x=0;x<16;x++)for(int z=0;z<16;z++){
        float wx = cx*16 + x, wz = cz*16 + z;
        float n = perlin(wx*scale, wz*scale);
        int h = int(((n+1)*0.5f)*16);
        h = std::clamp(h,0,16);
        for(int y=0;y<h;y++){
            c.voxels[x][y][z] = (y==h-1?1:2);
        }
    }
    return c;
}

void VoxelGame::updateChunks(const float cam[3]) {
    int cx = int(floor(cam[0]/16)), cz=int(floor(cam[2]/16));
    int r=2;
    std::unordered_set<ChunkKey,ChunkKeyHash> keep;
    for(int dx=-r;dx<=r;dx++)for(int dz=-r;dz<=r;dz++){
        ChunkKey key{cx+dx,cz+dz};
        keep.insert(key);
        if(chunks.find(key)==chunks.end()){
            Chunk nc = generateChunk(key.x,key.z);
            nc.mesh = GreedyMesher::mesh(nc.voxels);
            chunks[key]=std::move(nc);
            totalChunksEverLoaded++;
        }
    }
    for(auto it=chunks.begin();it!=chunks.end();){
        if(!keep.count(it->first)) it=chunks.erase(it);
        else ++it;
    }
    totalChunksLoaded = chunks.size();
}

void VoxelGame::update(float dt,const float cam[3]){
    updateChunks(cam);
}

void VoxelGame::render(){
    for(auto &p:chunks){
        float ox = p.first.x*16, oz = p.first.z*16;
        for(auto &q:p.second.mesh){
            // shift by chunk offset
            float v0[3]={q.x+ox,q.y,q.z+oz},
                  v1[3]={q.x+q.du[0]+ox,q.y+q.du[1],q.z+q.du[2]+oz},
                  v2[3]={q.x+q.du[0]+q.dv[0]+ox,q.y+q.du[1]+q.dv[1],q.z+q.du[2]+q.dv[2]+oz},
                  v3[3]={q.x+q.dv[0]+ox,q.y+q.dv[1],q.z+q.dv[2]+oz};
            unsigned int tex = (q.textureID==1?textureGrass:(q.textureID==2?textureDirt:textureWood));
            glBindTexture(GL_TEXTURE_2D,tex);
            glBegin(GL_QUADS);
              glTexCoord2f(0,0); glVertex3fv(v0);
              glTexCoord2f(1,0); glVertex3fv(v1);
              glTexCoord2f(1,1); glVertex3fv(v2);
              glTexCoord2f(0,1); glVertex3fv(v3);
            glEnd();
        }
    }
}

void VoxelGame::debugUI(){
    ImGui::Begin("Debug");
    ImGui::Text("Chunks Loaded: %d", totalChunksLoaded);
    ImGui::Text("Chunks Ever Loaded: %d", totalChunksEverLoaded);
    static bool wf=false;
    if(ImGui::Checkbox("Wireframe",&wf))
        glPolygonMode(GL_FRONT_AND_BACK, wf?GL_LINE:GL_FILL);
    ImGui::End();
}
