#include "face_mesh.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* ---- per-group dynamic array -------------------------------------------- */

static int group_reserve(mesh_group_t *g, size_t extra) {
    size_t need = g->vert_count + extra;
    if (need <= g->capacity) return 1;
    size_t cap = g->capacity ? g->capacity : 256;
    while (cap < need) cap *= 2;
    mesh_vertex_t *nv = (mesh_vertex_t *)realloc(g->verts, cap * sizeof(*nv));
    if (!nv) return 0;
    g->verts = nv;
    g->capacity = cap;
    return 1;
}

static void push_v(mesh_group_t *g,
                   float px, float py, float pz,
                   float nx, float ny, float nz,
                   float u, float v) {
    mesh_vertex_t *mv = &g->verts[g->vert_count++];
    mv->px = px; mv->py = py; mv->pz = pz;
    mv->nx = nx; mv->ny = ny; mv->nz = nz;
    mv->u = u;   mv->v = v;
}

/* Emit a quad as two triangles with outward normal = (nx,ny,nz). Vertices are
   passed in CCW order when viewed from the normal side. UV is a simple
   0..1 square mapped corner-to-corner. */
static int emit_quad(mesh_group_t *g,
                     float x0, float y0, float z0,
                     float x1, float y1, float z1,
                     float x2, float y2, float z2,
                     float x3, float y3, float z3,
                     float nx, float ny, float nz) {
    if (!group_reserve(g, 6)) return 0;
    push_v(g, x0, y0, z0, nx, ny, nz, 0.0f, 0.0f);
    push_v(g, x1, y1, z1, nx, ny, nz, 1.0f, 0.0f);
    push_v(g, x2, y2, z2, nx, ny, nz, 1.0f, 1.0f);
    push_v(g, x0, y0, z0, nx, ny, nz, 0.0f, 0.0f);
    push_v(g, x2, y2, z2, nx, ny, nz, 1.0f, 1.0f);
    push_v(g, x3, y3, z3, nx, ny, nz, 0.0f, 1.0f);
    return 1;
}

/* ---- material selection -------------------------------------------------- */

static int floor_material(const cell3d_t *c) {
    switch (c->floor) {
        case FT_DOOR_SILL:  return MAT_DOOR_FLOOR;
        case FT_STAIR_UP:   return MAT_STAIR_UP;
        case FT_STAIR_DOWN: return MAT_STAIR_DOWN;
        case FT_BRIDGE:     return MAT_BRIDGE;
        case FT_WATER:      return MAT_WATER;
        case FT_LAVA:       return MAT_LAVA;
        default:            break;
    }
    return (c->style == S_CAVE) ? MAT_CAVE_FLOOR : MAT_STONE_FLOOR;
}
static int ceiling_material(const cell3d_t *c) {
    return (c->style == S_CAVE) ? MAT_CAVE_CEILING : MAT_STONE_CEILING;
}
static int wall_material(const cell3d_t *c) {
    return (c->style == S_CAVE) ? MAT_CAVE_WALL : MAT_STONE_WALL;
}

/* ---- builder ------------------------------------------------------------- */

mesh_build_t *mesh_build_from_dungeon(const dungeon_t *d, float cell_size) {
    mesh_build_t *b = (mesh_build_t *)calloc(1, sizeof(*b));
    if (!b) return NULL;
    b->cell_size = cell_size;
    b->n_levels = d->n_levels;
    for (int m = 0; m < MAT_COUNT; m++) b->groups[m].material = m;

    const float s = cell_size;

    for (int lvl = 0; lvl < d->n_levels; lvl++) {
        /* Level N sits at world-Y in [ -(lvl+1)*s , -lvl*s ]. Level 0 is at
           the top; descending increases lvl and decreases Y. */
        const float y_ceil  = -(float)lvl * s;
        const float y_floor = y_ceil - s;

        for (int y = 0; y < BL_DROWS; y++) {
            for (int x = 0; x < BL_DCOLS; x++) {
                const cell3d_t *c = dungeon_cell_c(d, lvl, x, y);
                if (c->floor == FT_VOID) continue;

                const float x0 = (float)x * s;
                const float x1 = x0 + s;
                const float z0 = (float)y * s;
                const float z1 = z0 + s;

                /* Floor (skip chasms \u2014 open drop to level below). */
                if (c->floor != FT_CHASM) {
                    mesh_group_t *g = &b->groups[floor_material(c)];
                    /* Normal +Y; CCW viewed from +Y means corners in order
                       (x0,z0) (x1,z0) (x1,z1) (x0,z1). */
                    emit_quad(g,
                        x0, y_floor, z0,
                        x1, y_floor, z0,
                        x1, y_floor, z1,
                        x0, y_floor, z1,
                        0.0f, 1.0f, 0.0f);
                }

                /* Ceiling (skip when open to above \u2014 chasm-mirrored cell). */
                if (c->ceiling != C_OPEN) {
                    mesh_group_t *g = &b->groups[ceiling_material(c)];
                    /* Normal \u2212Y; CCW viewed from \u2212Y = reverse winding. */
                    emit_quad(g,
                        x0, y_ceil, z1,
                        x1, y_ceil, z1,
                        x1, y_ceil, z0,
                        x0, y_ceil, z0,
                        0.0f, -1.0f, 0.0f);
                }

                /* Wall faces. Each wall face sits at the cell boundary with
                   its visible normal pointing INTO the cell (so the party
                   standing in the cell sees it). Winding CCW from +normal. */
                mesh_group_t *gw = &b->groups[wall_material(c)];

                /* FACE_N (\u2212Z / north): wall plane at z = z0, normal +Z. */
                if (c->walls[FACE_N] != W_NONE) {
                    emit_quad(gw,
                        x0, y_floor, z0,
                        x0, y_ceil,  z0,
                        x1, y_ceil,  z0,
                        x1, y_floor, z0,
                        0.0f, 0.0f, 1.0f);
                }
                /* FACE_E (+X / east): wall plane at x = x1, normal \u2212X. */
                if (c->walls[FACE_E] != W_NONE) {
                    emit_quad(gw,
                        x1, y_floor, z0,
                        x1, y_ceil,  z0,
                        x1, y_ceil,  z1,
                        x1, y_floor, z1,
                        -1.0f, 0.0f, 0.0f);
                }
                /* FACE_S (+Z / south): wall plane at z = z1, normal \u2212Z. */
                if (c->walls[FACE_S] != W_NONE) {
                    emit_quad(gw,
                        x1, y_floor, z1,
                        x1, y_ceil,  z1,
                        x0, y_ceil,  z1,
                        x0, y_floor, z1,
                        0.0f, 0.0f, -1.0f);
                }
                /* FACE_W (\u2212X / west): wall plane at x = x0, normal +X. */
                if (c->walls[FACE_W] != W_NONE) {
                    emit_quad(gw,
                        x0, y_floor, z1,
                        x0, y_ceil,  z1,
                        x0, y_ceil,  z0,
                        x0, y_floor, z0,
                        1.0f, 0.0f, 0.0f);
                }
            }
        }
    }
    return b;
}

void mesh_build_destroy(mesh_build_t *b) {
    if (!b) return;
    for (int m = 0; m < MAT_COUNT; m++) free(b->groups[m].verts);
    free(b);
}

static const char *material_name(int m) {
    switch (m) {
        case MAT_STONE_FLOOR:   return "stone_floor";
        case MAT_STONE_CEILING: return "stone_ceiling";
        case MAT_STONE_WALL:    return "stone_wall";
        case MAT_DOOR_FLOOR:    return "door_floor";
        case MAT_STAIR_UP:      return "stair_up";
        case MAT_STAIR_DOWN:    return "stair_down";
        case MAT_WATER:         return "water";
        case MAT_LAVA:          return "lava";
        case MAT_BRIDGE:        return "bridge";
        case MAT_CAVE_FLOOR:    return "cave_floor";
        case MAT_CAVE_CEILING:  return "cave_ceiling";
        case MAT_CAVE_WALL:     return "cave_wall";
        default:                return "unknown";
    }
}

int mesh_build_write_obj(const mesh_build_t *b, const char *path) {
    FILE *f = fopen(path, "w");
    if (!f) return 0;
    fprintf(f, "# brogue-genesis blobber dungeon mesh\n");
    size_t v_offset = 1; /* .obj uses 1-based indices */
    for (int m = 0; m < MAT_COUNT; m++) {
        const mesh_group_t *g = &b->groups[m];
        if (g->vert_count == 0) continue;
        fprintf(f, "\no %s\n", material_name(m));
        for (size_t i = 0; i < g->vert_count; i++) {
            const mesh_vertex_t *v = &g->verts[i];
            fprintf(f, "v %.4f %.4f %.4f\n", v->px, v->py, v->pz);
        }
        for (size_t i = 0; i < g->vert_count; i++) {
            const mesh_vertex_t *v = &g->verts[i];
            fprintf(f, "vn %.4f %.4f %.4f\n", v->nx, v->ny, v->nz);
        }
        for (size_t i = 0; i < g->vert_count; i++) {
            const mesh_vertex_t *v = &g->verts[i];
            fprintf(f, "vt %.4f %.4f\n", v->u, v->v);
        }
        /* Face list: every 3 verts = one triangle. */
        for (size_t i = 0; i + 2 < g->vert_count; i += 3) {
            size_t a = v_offset + i;
            size_t b2 = v_offset + i + 1;
            size_t c = v_offset + i + 2;
            fprintf(f, "f %zu/%zu/%zu %zu/%zu/%zu %zu/%zu/%zu\n",
                    a, a, a, b2, b2, b2, c, c, c);
        }
        v_offset += g->vert_count;
    }
    fclose(f);
    return 1;
}
