#include "brogue_gen.h"

#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/packed_byte_array.hpp>
#include <godot_cpp/variant/packed_int32_array.hpp>
#include <godot_cpp/variant/vector2i.hpp>
#include <godot_cpp/variant/typed_array.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

extern "C" {
#include "gen/grid.h"
#include "gen/rng.h"
#include "gen/events.h"
#include "gen/accretion.h"
#include "gen/loops.h"
#include "gen/lakes.h"
#include "gen/walls.h"
#include "gen/stairs.h"
#include "gen/chokepoints.h"
#include "gen/machines.h"
#include "blobber/pipeline.h"
#include "blobber/dungeon.h"
#include "mesh/face_mesh.h"
}

#include "grid_to_dict.h"
#include "dungeon_to_dict.h"

using namespace godot;

BrogueGen::BrogueGen() = default;
BrogueGen::~BrogueGen() = default;

void BrogueGen::_bind_methods() {
    ClassDB::bind_method(D_METHOD("generate", "seed", "depth"), &BrogueGen::generate);
    ClassDB::bind_method(D_METHOD("generate_dungeon", "seed", "num_levels", "depth"),
                         &BrogueGen::generate_dungeon);
    ClassDB::bind_method(D_METHOD("generate_2d_slices", "seed", "num_levels", "depth"),
                         &BrogueGen::generate_2d_slices);
}

Dictionary BrogueGen::generate(int seed, int depth) {
    grid_t g;
    event_sink_t sink = {nullptr, nullptr, 0};
    rng_t rng;
    rng_seed(&rng, (uint64_t)seed);
    grid_fill(g, T_NOTHING);

    event_emit(&sink, EV_GEN_BEGIN, NULL, 0);
    accrete_rooms(g, &rng, &sink);
    add_loops(g, &sink);
    place_lakes(g, &rng, &sink, depth);
    apply_wreaths(g, &sink);
    place_bridges(g, &sink);
    finish_walls(g, &sink);
    place_stairs(g, &sink);

    static choke_map_t choke;
    static choke_flag_t is_choke, is_gate;
    compute_chokepoints(g, &sink, choke, is_choke, is_gate);
    place_machines(g, &rng, depth, &sink, choke, is_gate);
    event_emit(&sink, EV_GEN_END, NULL, 0);

    Dictionary d = grid_to_dictionary(g, seed, depth, /*add_metadata=*/true);

    TypedArray<Vector2i> chokes, gates;
    for (int y = 0; y < DROWS; y++) {
        for (int x = 0; x < DCOLS; x++) {
            if (is_choke[y][x]) chokes.push_back(Vector2i(x, y));
            if (is_gate[y][x])  gates.push_back(Vector2i(x, y));
        }
    }
    d["chokepoints"] = chokes;
    d["gate_sites"]  = gates;
    return d;
}

Dictionary BrogueGen::generate_dungeon(int seed, int num_levels, int depth) {
    if (num_levels < 1) num_levels = 1;
    dungeon_t *dng = blobber_generate((uint64_t)seed, num_levels, depth);
    if (!dng) return Dictionary();

    mesh_build_t *mb = mesh_build_from_dungeon(dng, /*cell_size=*/3.0f);
    if (!mb) {
        dungeon_destroy(dng);
        return Dictionary();
    }

    Dictionary d = dungeon_to_dictionary(dng, mb, seed, depth, num_levels);

    mesh_build_destroy(mb);
    dungeon_destroy(dng);
    return d;
}

TypedArray<Dictionary> BrogueGen::generate_2d_slices(int seed, int num_levels, int depth) {
    if (num_levels < 1) num_levels = 1;
    dungeon_t *dng = blobber_generate((uint64_t)seed, num_levels, depth);
    if (!dng) return TypedArray<Dictionary>();
    TypedArray<Dictionary> slices = dungeon_to_2d_slices(dng);
    dungeon_destroy(dng);
    return slices;
}
