#include "json_emit.h"

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

static const char B64[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static void b64_write(const uint8_t *src, size_t n) {
    size_t i = 0;
    while (i + 3 <= n) {
        uint32_t v = ((uint32_t)src[i] << 16) | ((uint32_t)src[i+1] << 8) | src[i+2];
        putchar(B64[(v >> 18) & 0x3F]);
        putchar(B64[(v >> 12) & 0x3F]);
        putchar(B64[(v >>  6) & 0x3F]);
        putchar(B64[ v        & 0x3F]);
        i += 3;
    }
    size_t rem = n - i;
    if (rem == 1) {
        uint32_t v = (uint32_t)src[i] << 16;
        putchar(B64[(v >> 18) & 0x3F]);
        putchar(B64[(v >> 12) & 0x3F]);
        putchar('=');
        putchar('=');
    } else if (rem == 2) {
        uint32_t v = ((uint32_t)src[i] << 16) | ((uint32_t)src[i+1] << 8);
        putchar(B64[(v >> 18) & 0x3F]);
        putchar(B64[(v >> 12) & 0x3F]);
        putchar(B64[(v >>  6) & 0x3F]);
        putchar('=');
    }
}

static void write_b64_field(const char *name, const uint8_t *bytes, size_t n) {
    printf("\"%s\":\"", name);
    b64_write(bytes, n);
    putchar('"');
}

static void write_xy(const char *name, int x, int y) {
    printf("\"%s\":[%d,%d]", name, x, y);
}

/* Bucket helpers. Room IDs and machine IDs are 1..255. */
typedef struct {
    int count;
    int cap;
    int *xs;
    int *ys;
} cellbuf_t;

static void cellbuf_push(cellbuf_t *b, int x, int y) {
    if (b->count == b->cap) {
        b->cap = b->cap ? b->cap * 2 : 16;
        b->xs = (int *)realloc(b->xs, b->cap * sizeof(int));
        b->ys = (int *)realloc(b->ys, b->cap * sizeof(int));
    }
    b->xs[b->count] = x;
    b->ys[b->count] = y;
    b->count++;
}

static void cellbuf_free(cellbuf_t *b) {
    free(b->xs);
    free(b->ys);
    b->xs = NULL; b->ys = NULL; b->count = 0; b->cap = 0;
}

static void write_cellbuf(const cellbuf_t *b) {
    putchar('[');
    for (int i = 0; i < b->count; i++) {
        if (i) putchar(',');
        printf("[%d,%d]", b->xs[i], b->ys[i]);
    }
    putchar(']');
}

void emit_grid_json(grid_t g, uint64_t seed, int depth) {
    const int N = DCOLS * DROWS;

    uint8_t *terrain    = (uint8_t *)malloc(N);
    uint8_t *liquid     = (uint8_t *)malloc(N);
    uint8_t *surface    = (uint8_t *)malloc(N);
    uint8_t *room_id    = (uint8_t *)malloc(N);
    uint8_t *machine_id = (uint8_t *)malloc(N);
    uint8_t *flags_le   = (uint8_t *)malloc(N * 4);

    int stairs_up_x = -1, stairs_up_y = -1;
    int stairs_down_x = -1, stairs_down_y = -1;

    cellbuf_t rooms[256] = {0};
    cellbuf_t mach_interior[256] = {0};
    int mach_gate_x[256], mach_gate_y[256];
    for (int i = 0; i < 256; i++) { mach_gate_x[i] = -1; mach_gate_y[i] = -1; }

    int idx = 0;
    for (int y = 0; y < DROWS; y++) {
        for (int x = 0; x < DCOLS; x++, idx++) {
            const cell_t *c = &g[y][x];
            terrain[idx]    = c->terrain;
            liquid[idx]     = c->liquid;
            surface[idx]    = c->surface;
            room_id[idx]    = c->room_id;
            machine_id[idx] = c->machine_id;
            /* Little-endian int32 of the uint16 flags (zero-extended). */
            uint32_t f = c->flags;
            flags_le[idx*4 + 0] = (uint8_t)( f        & 0xFF);
            flags_le[idx*4 + 1] = (uint8_t)((f >> 8)  & 0xFF);
            flags_le[idx*4 + 2] = (uint8_t)((f >> 16) & 0xFF);
            flags_le[idx*4 + 3] = (uint8_t)((f >> 24) & 0xFF);

            if (c->terrain == T_STAIRS_UP)   { stairs_up_x = x;   stairs_up_y = y; }
            if (c->terrain == T_STAIRS_DOWN) { stairs_down_x = x; stairs_down_y = y; }

            if (c->room_id > 0) {
                cellbuf_push(&rooms[c->room_id], x, y);
            }
            if (c->machine_id > 0) {
                cellbuf_push(&mach_interior[c->machine_id], x, y);
                if (c->flags & F_MACHINE_GATE) {
                    mach_gate_x[c->machine_id] = x;
                    mach_gate_y[c->machine_id] = y;
                }
            }
        }
    }

    putchar('{');
    printf("\"seed\":%llu,\"depth\":%d,\"width\":%d,\"height\":%d,",
           (unsigned long long)seed, depth, DCOLS, DROWS);

    write_b64_field("terrain", terrain, N); putchar(',');
    write_b64_field("liquid", liquid, N); putchar(',');
    write_b64_field("surface", surface, N); putchar(',');
    write_b64_field("room_id", room_id, N); putchar(',');
    write_b64_field("machine_id", machine_id, N); putchar(',');
    write_b64_field("flags", flags_le, N * 4); putchar(',');

    write_xy("stairs_up", stairs_up_x, stairs_up_y); putchar(',');
    write_xy("stairs_down", stairs_down_x, stairs_down_y); putchar(',');

    fputs("\"rooms\":[", stdout);
    int first = 1;
    for (int i = 1; i < 256; i++) {
        if (rooms[i].count == 0) continue;
        if (!first) putchar(',');
        first = 0;
        printf("{\"id\":%d,\"cells\":", i);
        write_cellbuf(&rooms[i]);
        putchar('}');
    }
    fputs("],", stdout);

    fputs("\"machines\":[", stdout);
    first = 1;
    for (int i = 1; i < 256; i++) {
        if (mach_interior[i].count == 0) continue;
        if (!first) putchar(',');
        first = 0;
        printf("{\"id\":%d,\"interior_cells\":", i);
        write_cellbuf(&mach_interior[i]);
        printf(",\"gate\":[%d,%d]}", mach_gate_x[i], mach_gate_y[i]);
    }
    fputs("]", stdout);

    fputs("}\n", stdout);

    free(terrain); free(liquid); free(surface);
    free(room_id); free(machine_id); free(flags_le);
    for (int i = 0; i < 256; i++) { cellbuf_free(&rooms[i]); cellbuf_free(&mach_interior[i]); }
}
