#include <vector>
#include <cstdint>
#include <cstring>
#include <algorithm>
#include "common.hpp"
#include <vector>
#include <cstdint>
#include <array>

#ifndef SERVER
#ifndef LEGACY_GL

MeshData GenerateChunkMesh(ChunkRenderData chunkRenderData, Chunk chunk, int chunkWorldX, int chunkWorldY, uint8_t sideMask)
{
    MeshData out = {0};
    
    out.vertexCount = 0; out.indexCount = 0;

    std::vector<Vector3> positions;
    std::vector<Vector3> normals;
    std::vector<Vector2> uvs;
    std::vector<Color> colors;
    std::vector<unsigned short> indices;
    
    int face = 0;
    for (int x = 0; x < CHUNK_SIZE; ++x)
    for (int y = 0; y < WORLD_HEIGHT; ++y)
    for (int z = 0; z < CHUNK_SIZE; ++z)
    {
        int index = GetIndexChunk(x,y,z);
        uint8_t crdata = chunkRenderData.sides[index];
        if (crdata == 0) continue;

        int this_block = fetch_block(chunk, x,y,z);
        
        BlockDef definition = BLOCKS[this_block];

        UVCorners UVside = GetUVTex(definition.textureSide);
        UVCorners  UVtop = GetUVTex(definition.textureTop);
        UVCorners  UVbtm = GetUVTex(definition.textureBottom);

        bool left, right, up, down, back, front;
        unpack6(crdata & sideMask, &left, &right, &up, &down, &back, &front);
        
        bool shaded = chunk.highestBlock[GetIndexChunk2D(x, z)] != y;
        Color color = {255, 255, 255, 255};
        Color colorShaded = {128, 128, 172, 255};
        
        float wx = (float)(chunkWorldX * CHUNK_SIZE + x);
        float wy = (float)(y);
        float wz = (float)(chunkWorldY * CHUNK_SIZE + z);
        /*if(GetCloud(wx, wz)) {
            color = ColorLerp(color, colorShaded, 0.5f);
        }*/
        if (left) {
            Vector3 verts[4] = {
                {wx - 0.5f, wy - 0.5f, wz - 0.5f}, // bottom-left
                {wx - 0.5f, wy - 0.5f, wz + 0.5f}, // bottom-right
                {wx - 0.5f, wy + 0.5f, wz + 0.5f}, // top-right
                {wx - 0.5f, wy + 0.5f, wz - 0.5f}  // top-left
            };
            Vector3 normal = {-1.0f, 0.0f, 0.0f};
            Vector2 corners[4] = {
                UVside.corner0,
                UVside.corner1,
                UVside.corner2,
                UVside.corner3
            };
            positions.push_back(verts[0]);
            positions.push_back(verts[1]);
            positions.push_back(verts[2]);
            positions.push_back(verts[3]);

            normals.push_back(normal);
            colors.push_back(colorShaded);

            uvs.push_back(corners[0]);
            uvs.push_back(corners[1]);
            uvs.push_back(corners[2]);
            uvs.push_back(corners[3]);

            unsigned short indbase = face*4;
            unsigned short tri[6] = {
                indbase + 0, indbase + 1, indbase + 2,
                indbase + 0, indbase + 2, indbase + 3
            };

            indices.insert(indices.end(), tri, tri + 6);

            face++;
        }

        // Right face (+X)
        if (right) {
            Vector3 verts[4] = {
                {wx + 0.5f, wy - 0.5f, wz + 0.5f}, // bottom-left (from +X side)
                {wx + 0.5f, wy - 0.5f, wz - 0.5f}, // bottom-right
                {wx + 0.5f, wy + 0.5f, wz - 0.5f}, // top-right
                {wx + 0.5f, wy + 0.5f, wz + 0.5f}  // top-left
            };
            Vector3 normal = {1.0f, 0.0f, 0.0f};
            Vector2 corners[4] = {
                UVside.corner0,
                UVside.corner1,
                UVside.corner2,
                UVside.corner3
            };
            positions.push_back(verts[0]);
            positions.push_back(verts[1]);
            positions.push_back(verts[2]);
            positions.push_back(verts[3]);

            normals.push_back(normal);
            colors.push_back(colorShaded);

            uvs.push_back(corners[0]);
            uvs.push_back(corners[1]);
            uvs.push_back(corners[2]);
            uvs.push_back(corners[3]);

            unsigned short indbase = face*4;
            unsigned short tri[6] = {
                indbase + 0, indbase + 1, indbase + 2,
                indbase + 0, indbase + 2, indbase + 3
            };

            indices.insert(indices.end(), tri, tri + 6);

            face++;
        }

        // Down (-Y)
        if (down) {
            Vector3 verts[4] = {
                {wx - 0.5f, wy - 0.5f, wz - 0.5f}, // bottom-left
                {wx + 0.5f, wy - 0.5f, wz - 0.5f}, // bottom-right
                {wx + 0.5f, wy - 0.5f, wz + 0.5f}, // top-right
                {wx - 0.5f, wy - 0.5f, wz + 0.5f}  // top-left
            };
            Vector3 normal = {0.0f, -1.0f, 0.0f};
            Vector2 corners[4] = {
                UVbtm.corner0,
                UVbtm.corner1,
                UVbtm.corner2,
                UVbtm.corner3
            };
            positions.push_back(verts[0]);
            positions.push_back(verts[1]);
            positions.push_back(verts[2]);
            positions.push_back(verts[3]);

            normals.push_back(normal);
            colors.push_back(colorShaded);

            uvs.push_back(corners[0]);
            uvs.push_back(corners[1]);
            uvs.push_back(corners[2]);
            uvs.push_back(corners[3]);

            unsigned short indbase = face*4;
            unsigned short tri[6] = {
                indbase + 0, indbase + 1, indbase + 2,
                indbase + 0, indbase + 2, indbase + 3
            };

            indices.insert(indices.end(), tri, tri + 6);

            face++;
        }

        // Up (+Y)
        if (up) {
            Vector3 verts[4] = {
                {wx - 0.5f, wy + 0.5f, wz + 0.5f}, // bottom-left (from +Y side)
                {wx + 0.5f, wy + 0.5f, wz + 0.5f}, // bottom-right
                {wx + 0.5f, wy + 0.5f, wz - 0.5f}, // top-right
                {wx - 0.5f, wy + 0.5f, wz - 0.5f}  // top-left
            };
            Vector3 normal = {0.0f, 1.0f, 0.0f};
            Vector2 corners[4] = {
                UVtop.corner0,
                UVtop.corner1,
                UVtop.corner2,
                UVtop.corner3
            };
            positions.push_back(verts[0]);
            positions.push_back(verts[1]);
            positions.push_back(verts[2]);
            positions.push_back(verts[3]);

            normals.push_back(normal);
            if(shaded) {
                colors.push_back(colorShaded);
            }
            else {
                colors.push_back(color);
            }

            uvs.push_back(corners[0]);
            uvs.push_back(corners[1]);
            uvs.push_back(corners[2]);
            uvs.push_back(corners[3]);

            unsigned short indbase = face*4;
            unsigned short tri[6] = {
                indbase + 0, indbase + 1, indbase + 2,
                indbase + 0, indbase + 2, indbase + 3
            };

            indices.insert(indices.end(), tri, tri + 6);

            face++;
        }

        // Front (+Z)
        if (front) {
            Vector3 verts[4] = {
                {wx - 0.5f, wy - 0.5f, wz + 0.5f}, // bottom-left
                {wx + 0.5f, wy - 0.5f, wz + 0.5f}, // bottom-right
                {wx + 0.5f, wy + 0.5f, wz + 0.5f}, // top-right
                {wx - 0.5f, wy + 0.5f, wz + 0.5f}  // top-left
            };
            Vector3 normal = {0.0f, 0.0f, 1.0f};
            Vector2 corners[4] = {
                UVside.corner0,
                UVside.corner1,
                UVside.corner2,
                UVside.corner3
            };
            positions.push_back(verts[0]);
            positions.push_back(verts[1]);
            positions.push_back(verts[2]);
            positions.push_back(verts[3]);

            normals.push_back(normal);
            colors.push_back(colorShaded);

            uvs.push_back(corners[0]);
            uvs.push_back(corners[1]);
            uvs.push_back(corners[2]);
            uvs.push_back(corners[3]);

            unsigned short indbase = face*4;
            unsigned short tri[6] = {
                indbase + 0, indbase + 1, indbase + 2,
                indbase + 0, indbase + 2, indbase + 3
            };

            indices.insert(indices.end(), tri, tri + 6);

            face++;
        }

        // Back (-Z)
        if (back) {
            Vector3 verts[4] = {
                {wx + 0.5f, wy - 0.5f, wz - 0.5f}, // bottom-left (from -Z side)
                {wx - 0.5f, wy - 0.5f, wz - 0.5f}, // bottom-right
                {wx - 0.5f, wy + 0.5f, wz - 0.5f}, // top-right
                {wx + 0.5f, wy + 0.5f, wz - 0.5f}  // top-left
            };
            Vector3 normal = {0.0f, 0.0f, -1.0f};
            Vector2 corners[4] = {
                UVside.corner0,
                UVside.corner1,
                UVside.corner2,
                UVside.corner3
            };
            positions.push_back(verts[0]);
            positions.push_back(verts[1]);
            positions.push_back(verts[2]);
            positions.push_back(verts[3]);

            normals.push_back(normal);
            colors.push_back(colorShaded);

            uvs.push_back(corners[0]);
            uvs.push_back(corners[1]);
            uvs.push_back(corners[2]);
            uvs.push_back(corners[3]);

            unsigned short indbase = face*4;
            unsigned short tri[6] = {
                indbase + 0, indbase + 1, indbase + 2,
                indbase + 0, indbase + 2, indbase + 3
            };

            indices.insert(indices.end(), tri, tri + 6);

            face++;
        }
    }

    out.positions = (float*)malloc(sizeof(float) * 3 * positions.size());
    out.uvs = (float*)malloc(sizeof(float) * 2 * uvs.size());
    out.indices = (unsigned short*)malloc(sizeof(unsigned short) * indices.size());
    out.normals = (float*)malloc(sizeof(float) * 3 * 4 * normals.size());
    out.colors = (unsigned char*)malloc(sizeof(unsigned char) * 4 * 4 * colors.size());

    int s = 0;
    for (Vector3 v : positions)
    {
        out.positions[s]   = v.x;
        out.positions[s+1] = v.y;
        out.positions[s+2] = v.z;
        s += 3;
    }
    s = 0;
    for (Vector3 n : normals)
    {
        for (size_t _ = 0; _ < 4; _++)
        {
            out.normals[s]   = n.x;
            out.normals[s+1] = n.y;
            out.normals[s+2] = n.z;
            s += 3;
        }
    }
    s = 0;
    for (Color c : colors)
    {
        for (size_t _ = 0; _ < 4; _++)
        {
            out.colors[s]   = c.r;
            out.colors[s+1] = c.g;
            out.colors[s+2] = c.b;
            out.colors[s+3] = c.a;
            s += 4;
        }
    }
    s = 0;
    for (Vector2 uv : uvs)
    {
        out.uvs[s]   = uv.x;
        out.uvs[s+1] = uv.y;
        s += 2;
    }
    s = 0;
    for (unsigned short i : indices)
    {
        out.indices[s] = i;
        s += 1;
    }
    out.vertexCount = positions.size();
    out.indexCount = indices.size();
    positions.clear();
    normals.clear();
    uvs.clear();
    indices.clear();
    return out;
}


MeshData MergeMeshData(const MeshData &a, const MeshData &b)
{
    MeshData out = {0};

    // initial counts
    int aPosCount = a.positions ? (a.vertexCount * 3) : 0;
    int aNormCount = a.normals ? (a.vertexCount * 3) : 0;
    int aUvCount = a.uvs ? (a.vertexCount * 2) : 0;
    int aColCount = a.colors ? (a.vertexCount * 4) : 0;
    int aIdxCount = a.indices ? (a.indexCount) : 0;

    int bPosCount = b.positions ? (b.vertexCount * 3) : 0;
    int bNormCount = b.normals ? (b.vertexCount * 3) : 0;
    int bUvCount = b.uvs ? (b.vertexCount * 2) : 0;
    int bColCount = b.colors ? (b.vertexCount * 4) : 0;
    int bIdxCount = b.indices ? (b.indexCount) : 0;

    // allocate combined buffers
    int totalVert = a.vertexCount + b.vertexCount;
    int totalPos = totalVert * 3;
    int totalNorm = totalVert * 3;
    int totalUv = totalVert * 2;
    int totalCol = totalVert * 4;
    int totalIdx = a.indexCount + b.indexCount;

    if (totalPos > 0) {
        out.positions = (float*)malloc(totalPos * sizeof(float));
        memcpy(out.positions, a.positions ? a.positions : nullptr, aPosCount * sizeof(float));
    } else out.positions = nullptr;

    if (totalNorm > 0) {
        out.normals = (float*)malloc(totalNorm * sizeof(float));
        memcpy(out.normals, a.normals ? a.normals : nullptr, aNormCount * sizeof(float));
    } else out.normals = nullptr;

    if (totalUv > 0) {
        out.uvs = (float*)malloc(totalUv * sizeof(float));
        memcpy(out.uvs, a.uvs ? a.uvs : nullptr, aUvCount * sizeof(float));
    } else out.uvs = nullptr;

    if (totalCol > 0) {
        out.colors = (unsigned char*)malloc(totalCol * sizeof(unsigned char));
        memcpy(out.colors, a.colors ? a.colors : nullptr, aColCount * sizeof(unsigned char));
    } else out.colors = nullptr;

    if (totalIdx > 0) {
        out.indices = (unsigned short*)malloc(totalIdx * sizeof(unsigned short));
        if (a.indices && aIdxCount > 0)
            memcpy(out.indices, a.indices, aIdxCount * sizeof(unsigned short));
    } else out.indices = nullptr;

    // copy B data appending with index offset
    // positions
    if (bPosCount > 0) {
        memcpy(out.positions + aPosCount, b.positions, bPosCount * sizeof(float));
    }
    // normals
    if (bNormCount > 0) {
        memcpy(out.normals + aNormCount, b.normals, bNormCount * sizeof(float));
    }
    // uvs
    if (bUvCount > 0) {
        memcpy(out.uvs + aUvCount, b.uvs, bUvCount * sizeof(float));
    }
    // colors
    if (bColCount > 0) {
        memcpy(out.colors + aColCount, b.colors, bColCount * sizeof(unsigned char));
    }
    // indices (need to offset b indices by a.vertexCount)
    if (bIdxCount > 0) {
        unsigned short idxOffset = (unsigned short)(a.vertexCount);
        for (int i = 0; i < bIdxCount; ++i) {
            out.indices[aIdxCount + i] = (unsigned short)(b.indices[i] + idxOffset);
        }
    }

    out.vertexCount = totalVert;
    out.indexCount = totalIdx;

    return out;
}

MeshData CreateCubeMeshData(const BlockDef &def, float cx, float cy, float cz)
{
    MeshData out = {0};
    // 4 verts * 6 faces = 24 vertices
    const int vertsPerFace = 4;
    const int faces = 6;
    const int totalVerts = vertsPerFace * faces;
    const int totalPos = totalVerts * 3;
    const int totalNorm = totalVerts * 3;
    const int totalUv = totalVerts * 2;
    const int totalCol = totalVerts * 4;
    const int totalIdx = faces * 6;

    out.vertexCount = totalVerts;
    out.indexCount = totalIdx;

    out.positions = (float*)malloc(totalPos * sizeof(float));
    out.normals   = (float*)malloc(totalNorm * sizeof(float));
    out.uvs       = (float*)malloc(totalUv * sizeof(float));
    out.colors    = (unsigned char*)malloc(totalCol * sizeof(unsigned char));
    out.indices   = (unsigned short*)malloc(totalIdx * sizeof(unsigned short));

    int p = 0, n = 0, u = 0, c = 0, ix = 0;
    Color baseColor = {255,255,255,255};

    auto pushPos = [&](float x, float y, float z) {
        out.positions[p++] = x; out.positions[p++] = y; out.positions[p++] = z;
    };
    auto pushNorm = [&](float x, float y, float z) {
        out.normals[n++] = x; out.normals[n++] = y; out.normals[n++] = z;
    };
    auto pushUV = [&](float uu, float vv) {
        out.uvs[u++] = uu; out.uvs[u++] = vv;
    };
    auto pushCol = [&](unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
        out.colors[c++] = r; out.colors[c++] = g; out.colors[c++] = b; out.colors[c++] = a;
    };
    auto pushTriIdx = [&](unsigned short a, unsigned short b, unsigned short c2) {
        out.indices[ix++] = a; out.indices[ix++] = b; out.indices[ix++] = c2;
    };

    // get UV corners for faces
    UVCorners UVside = GetUVTex(def.textureSide);
    UVCorners UVtop  = GetUVTex(def.textureTop);
    UVCorners UVbtm  = GetUVTex(def.textureBottom);

    // face builder: verts in CCW for the outside-facing side
    auto buildFace = [&](const Vector3 faceVerts[4], const Vector3 &normal, const Vector2 faceUVs[4]) {
        unsigned short base = (unsigned short)((p/3)); // current vertex index
        Color clr = baseColor;

        for (int i=0;i<4;i++) {
            pushPos(faceVerts[i].x, faceVerts[i].y, faceVerts[i].z);
            pushNorm(normal.x, normal.y, normal.z);
            pushUV(faceUVs[i].x, faceUVs[i].y);
            pushCol(clr.r, clr.g, clr.b, clr.a);
        }
        // two tris (0,1,2) (0,2,3)
        pushTriIdx(base + 0, base + 1, base + 2);
        pushTriIdx(base + 0, base + 2, base + 3);
    };

    float hx = 0.5f, hy = 0.5f, hz = 0.5f;
    // Left (-X)
    {
        Vector3 verts[4] = {
            {cx - hx, cy - hy, cz - hz},
            {cx - hx, cy - hy, cz + hz},
            {cx - hx, cy + hy, cz + hz},
            {cx - hx, cy + hy, cz - hz}
        };
        Vector2 uvs[4] = { UVside.corner0, UVside.corner1, UVside.corner2, UVside.corner3 };
        Vector3 normal = {-1,0,0};
        buildFace(verts, normal, uvs);
    }
    // Right (+X)
    {
        Vector3 verts[4] = {
            {cx + hx, cy - hy, cz + hz},
            {cx + hx, cy - hy, cz - hz},
            {cx + hx, cy + hy, cz - hz},
            {cx + hx, cy + hy, cz + hz}
        };
        Vector2 uvs[4] = { UVside.corner0, UVside.corner1, UVside.corner2, UVside.corner3 };
        Vector3 normal = {1,0,0};
        buildFace(verts, normal, uvs);
    }
    // Down (-Y)
    {
        Vector3 verts[4] = {
            {cx - hx, cy - hy, cz - hz},
            {cx + hx, cy - hy, cz - hz},
            {cx + hx, cy - hy, cz + hz},
            {cx - hx, cy - hy, cz + hz}
        };
        Vector2 uvs[4] = { UVbtm.corner0, UVbtm.corner1, UVbtm.corner2, UVbtm.corner3 };
        Vector3 normal = {0,-1,0};
        buildFace(verts, normal, uvs);
    }
    // Up (+Y)
    {
        Vector3 verts[4] = {
            {cx - hx, cy + hy, cz + hz},
            {cx + hx, cy + hy, cz + hz},
            {cx + hx, cy + hy, cz - hz},
            {cx - hx, cy + hy, cz - hz}
        };
        Vector2 uvs[4] = { UVtop.corner0, UVtop.corner1, UVtop.corner2, UVtop.corner3 };
        Vector3 normal = {0,1,0};
        buildFace(verts, normal, uvs);
    }
    // Front (+Z)
    {
        Vector3 verts[4] = {
            {cx - hx, cy - hy, cz + hz},
            {cx + hx, cy - hy, cz + hz},
            {cx + hx, cy + hy, cz + hz},
            {cx - hx, cy + hy, cz + hz}
        };
        Vector2 uvs[4] = { UVside.corner0, UVside.corner1, UVside.corner2, UVside.corner3 };
        Vector3 normal = {0,0,1};
        buildFace(verts, normal, uvs);
    }
    // Back (-Z)
    {
        Vector3 verts[4] = {
            {cx + hx, cy - hy, cz - hz},
            {cx - hx, cy - hy, cz - hz},
            {cx - hx, cy + hy, cz - hz},
            {cx + hx, cy + hy, cz - hz}
        };
        Vector2 uvs[4] = { UVside.corner0, UVside.corner1, UVside.corner2, UVside.corner3 };
        Vector3 normal = {0,0,-1};
        buildFace(verts, normal, uvs);
    }

    return out;
}


Mesh GenChunkMesh(MeshData meshData)
{
    Mesh mesh = { 0 };
    mesh.indices = meshData.indices;
    mesh.vertexCount = meshData.vertexCount;
    mesh.triangleCount = meshData.indexCount / 3;
    mesh.vertices = meshData.positions;
    mesh.texcoords = meshData.uvs;
    mesh.normals = meshData.normals;
    mesh.colors = meshData.colors;

    UploadMesh(&mesh, false);
    
    return mesh;
}
#endif
#endif