#include "grid_to_dict.h"

#include <godot_cpp/variant/packed_byte_array.hpp>
#include <godot_cpp/variant/packed_int32_array.hpp>
#include <godot_cpp/variant/array.hpp>
#include <godot_cpp/variant/typed_array.hpp>

using namespace godot;

static void build_rooms_and_machines(grid_t g, Array &rooms, Array &machines) {
    /* Bucket cells by room_id and machine_id. room_id and machine_id are 1..255. */
    struct Bucket { TypedArray<Vector2i> cells; Vector2i gate = Vector2i(-1, -1); };
    Bucket rbuckets[256];
    Bucket mbuckets[256];

    /* Also collect marker-per-cell for each machine. */
    Dictionary mmarkers[256];

    for (int y = 0; y < DROWS; y++) {
        for (int x = 0; x < DCOLS; x++) {
            const cell_t *c = &g[y][x];
            Vector2i p(x, y);
            if (c->room_id > 0) {
                rbuckets[c->room_id].cells.push_back(p);
            }
            if (c->machine_id > 0) {
                mbuckets[c->machine_id].cells.push_back(p);
                if (c->flags & F_MACHINE_GATE) {
                    mbuckets[c->machine_id].gate = p;
                }
                if (c->surface != MK_NONE) {
                    Dictionary &mk = mmarkers[c->machine_id];
                    int key = (int)c->surface;
                    TypedArray<Vector2i> list;
                    if (mk.has(key)) {
                        list = (TypedArray<Vector2i>)(Variant)mk[key];
                    }
                    list.push_back(p);
                    mk[key] = list;
                }
            }
        }
    }

    for (int i = 1; i < 256; i++) {
        if (rbuckets[i].cells.size() == 0) continue;
        Dictionary d;
        d["id"] = i;
        d["cells"] = rbuckets[i].cells;
        rooms.push_back(d);
    }
    for (int i = 1; i < 256; i++) {
        if (mbuckets[i].cells.size() == 0) continue;
        Dictionary d;
        d["id"] = i;
        d["interior_cells"] = mbuckets[i].cells;
        d["gate"] = mbuckets[i].gate;
        d["markers"] = mmarkers[i];
        machines.push_back(d);
    }
}

Dictionary godot::grid_to_dictionary(grid_t g, int seed, int depth, bool add_metadata) {
    const int N = DCOLS * DROWS;
    PackedByteArray terrain;    terrain.resize(N);
    PackedByteArray liquid;     liquid.resize(N);
    PackedByteArray room_id;    room_id.resize(N);
    PackedByteArray machine_id; machine_id.resize(N);
    PackedByteArray surface;    surface.resize(N);
    PackedInt32Array flags;     flags.resize(N);

    uint8_t *t  = terrain.ptrw();
    uint8_t *lq = liquid.ptrw();
    uint8_t *ri = room_id.ptrw();
    uint8_t *mi = machine_id.ptrw();
    uint8_t *sf = surface.ptrw();
    int32_t *fl = flags.ptrw();

    Vector2i stairs_up(-1, -1), stairs_down(-1, -1);
    int i = 0;
    for (int y = 0; y < DROWS; y++) {
        for (int x = 0; x < DCOLS; x++, i++) {
            const cell_t *c = &g[y][x];
            t[i]  = c->terrain;
            lq[i] = c->liquid;
            ri[i] = c->room_id;
            mi[i] = c->machine_id;
            sf[i] = c->surface;
            fl[i] = (int32_t)c->flags;
            if (c->terrain == T_STAIRS_UP)   stairs_up   = Vector2i(x, y);
            if (c->terrain == T_STAIRS_DOWN) stairs_down = Vector2i(x, y);
        }
    }

    Dictionary d;
    d["seed"]        = seed;
    d["depth"]       = depth;
    d["width"]       = DCOLS;
    d["height"]      = DROWS;
    d["terrain"]     = terrain;
    d["liquid"]      = liquid;
    d["flags"]       = flags;
    d["room_id"]     = room_id;
    d["machine_id"]  = machine_id;
    d["surface"]     = surface;
    d["stairs_up"]   = stairs_up;
    d["stairs_down"] = stairs_down;

    if (add_metadata) {
        Array rooms;
        Array machines;
        build_rooms_and_machines(g, rooms, machines);
        d["rooms"]    = rooms;
        d["machines"] = machines;
    }
    return d;
}

Dictionary godot::cell_to_dictionary(grid_t g, int x, int y) {
    Dictionary d;
    if (!grid_in_bounds(x, y)) return d;
    const cell_t *c = &g[y][x];
    d["x"]          = x;
    d["y"]          = y;
    d["terrain"]    = (int)c->terrain;
    d["liquid"]     = (int)c->liquid;
    d["surface"]    = (int)c->surface;
    d["flags"]      = (int)c->flags;
    d["room_id"]    = (int)c->room_id;
    d["machine_id"] = (int)c->machine_id;
    return d;
}
