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

#include "gen/grid.h"
#include "gen/rng.h"
#include "gen/room_types.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 "gen/dijkstra.h"
#include "json_emit.h"

static int g_verbose = 0;
static int g_quiet = 0;
static int g_mark_unreached = 0;
static int g_emit_json = 0;

static void print_ascii(grid_t g) {
    dist_map_t dist;
    if (g_mark_unreached) {
        int sx = -1, sy = -1;
        for (int y = 0; y < DROWS && sx < 0; y++)
            for (int x = 0; x < DCOLS && sx < 0; x++) {
                uint8_t t = g[y][x].terrain;
                if (t == T_FLOOR || t == T_CORRIDOR || t == T_DOOR || t == T_BRIDGE) {
                    sx = x; sy = y;
                }
            }
        if (sx >= 0) bfs_from(g, sx, sy, dist, -1, -1);
    }
    for (int y = 0; y < DROWS; y++) {
        for (int x = 0; x < DCOLS; x++) {
            char ch;
            uint8_t t = g[y][x].terrain;
            int passable = (t == T_FLOOR || t == T_CORRIDOR || t == T_DOOR || t == T_BRIDGE);
            if (g_mark_unreached && passable && dist[y][x] == DIST_UNREACHABLE) {
                ch = '!';
            } else switch (t) {
                case T_FLOOR:    ch = (g[y][x].flags & F_WREATH) ? ';' : '.'; break;
                case T_CORRIDOR: ch = ','; break;
                case T_DOOR:     ch = '+'; break;
                case T_WALL:     ch = (g[y][x].flags & F_WREATH) ? ':' : '#'; break;
                case T_LIQUID:
                    switch (g[y][x].liquid) {
                        case L_LAVA:      ch = '^'; break;
                        case L_CHASM:     ch = 'v'; break;
                        case L_BRIMSTONE: ch = '%'; break;
                        case L_WATER:
                        default:          ch = '~'; break;
                    }
                    break;
                case T_BRIDGE:       ch = '='; break;
                case T_STAIRS_UP:    ch = '<'; break;
                case T_STAIRS_DOWN:  ch = '>'; break;
                default:         ch = ' '; break;
            }
            putchar(ch);
        }
        putchar('\n');
    }
}

static void stderr_sink(void *ctx, const gen_event_t *ev) {
    (void)ctx;
    if (!g_verbose) return;
    fprintf(stderr, "[ev %4u] %s\n",
            ev->step, event_kind_name((event_kind_t)ev->kind));
}

typedef struct {
    int counts[EV_KIND_COUNT];
} sink_ctx_t;

static void counting_sink(void *ctx, const gen_event_t *ev) {
    sink_ctx_t *s = (sink_ctx_t *)ctx;
    s->counts[ev->kind]++;
    if (g_verbose) stderr_sink(NULL, ev);
}

typedef struct {
    int rooms, loops, lakes_proposed, lakes_rejected, lakes_placed, bridges;
    int machines_proposed, machines_placed;
    int floor_cells;
    int connected;
    int unreached;
} run_stats_t;

static void run_once(uint64_t seed, int depth, grid_t *gp, run_stats_t *out, int verify) {
    cell_t (*g)[DCOLS] = *gp;
    rng_t rng;
    rng_seed(&rng, seed);
    grid_fill(g, T_NOTHING);

    sink_ctx_t sctx = {0};
    event_sink_t sink = { .fn = counting_sink, .ctx = &sctx, .step = 0 };

    event_emit(&sink, EV_GEN_BEGIN, NULL, 0);

    accrete_rooms(g, &rng, &sink);
    if (verify && !is_connected(g, NULL))
        fprintf(stderr, "!! seed %llu: disconnected after accretion\n", (unsigned long long)seed);
    add_loops(g, &sink);
    if (verify && !is_connected(g, NULL))
        fprintf(stderr, "!! seed %llu: disconnected after loops\n", (unsigned long long)seed);
    place_lakes(g, &rng, &sink, depth);
    if (verify && !is_connected(g, NULL))
        fprintf(stderr, "!! seed %llu: disconnected after lakes\n", (unsigned long long)seed);
    apply_wreaths(g, &sink);
    place_bridges(g, &sink);
    finish_walls(g, &sink);
    place_stairs(g, &sink);

    /* Chokepoint analysis + machine placement. */
    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);

    out->rooms          = sctx.counts[EV_ROOM_PLACED];
    out->loops          = sctx.counts[EV_LOOP_ACCEPTED];
    out->lakes_proposed = sctx.counts[EV_LAKE_PROPOSED];
    out->lakes_rejected = sctx.counts[EV_LAKE_REJECTED_IMPASSABLE];
    out->lakes_placed   = sctx.counts[EV_LAKE_PLACED];
    out->bridges        = sctx.counts[EV_BRIDGE_PLACED];
    out->machines_proposed = sctx.counts[EV_MACHINE_PROPOSED];
    out->machines_placed   = sctx.counts[EV_MACHINE_ACCEPTED];
    out->floor_cells    = grid_count_floor(g);
    out->connected      = is_connected(g, &out->unreached);
}

static void print_summary(uint64_t seed, int depth, const run_stats_t *s) {
    fprintf(stderr,
            "seed=%llu depth=%d rooms=%d loops=%d "
            "lakes_proposed=%d lakes_rejected=%d lakes_placed=%d "
            "bridges=%d machines=%d/%d floor=%d connected=%d unreached=%d\n",
            (unsigned long long)seed, depth,
            s->rooms, s->loops,
            s->lakes_proposed, s->lakes_rejected, s->lakes_placed,
            s->bridges, s->machines_placed, s->machines_proposed,
            s->floor_cells, s->connected, s->unreached);
}

static void usage(FILE *f, const char *prog) {
    fprintf(f,
        "usage: %s [options]\n"
        "\n"
        "Generates a single Brogue-style 2D level and prints an ASCII map.\n"
        "\n"
        "options:\n"
        "  --seed N            RNG seed (default 1234)\n"
        "  --depth N           dungeon depth 1..26, biases lakes/machines (default 1)\n"
        "  --verify            assert connectivity after each pipeline stage\n"
        "  --mark-unreached    print '!' on passable cells BFS cannot reach from start\n"
        "  --verbose           stream generator events to stderr\n"
        "  --quiet             suppress ASCII map (summary only)\n"
        "  --emit=json, --json emit grid JSON to stdout instead of ASCII\n"
        "  --stress N          run N seeds (from --stress-base) and report failures\n"
        "  --stress-base N     starting seed for --stress (default 1)\n"
        "  -h, --help          show this help and exit\n",
        prog);
}

static int cmd_stress(int n, uint64_t base_seed, int depth) {
    grid_t g;
    int failures = 0;
    for (int i = 0; i < n; i++) {
        uint64_t seed = base_seed + (uint64_t)i;
        run_stats_t s = {0};
        run_once(seed, depth, &g, &s, 0);
        if (!s.connected) {
            fprintf(stderr, "FAIL seed=%llu unreached=%d\n",
                    (unsigned long long)seed, s.unreached);
            failures++;
        }
    }
    fprintf(stderr, "stress: %d seeds, %d failures\n", n, failures);
    return failures == 0 ? 0 : 1;
}

int main(int argc, char **argv) {
    uint64_t seed = 1234;
    int depth = 1;
    int verify = 0;
    int stress_n = 0;
    uint64_t stress_base = 1;

    for (int i = 1; i < argc; i++) {
        if (!strcmp(argv[i], "--seed") && i + 1 < argc) {
            seed = strtoull(argv[++i], NULL, 10);
        } else if (!strcmp(argv[i], "--depth") && i + 1 < argc) {
            depth = atoi(argv[++i]);
            if (depth < 1) depth = 1;
            if (depth > 26) depth = 26;
        } else if (!strcmp(argv[i], "--verbose")) {
            g_verbose = 1;
        } else if (!strcmp(argv[i], "--quiet")) {
            g_quiet = 1;
        } else if (!strcmp(argv[i], "--verify")) {
            verify = 1;
        } else if (!strcmp(argv[i], "--stress") && i + 1 < argc) {
            stress_n = atoi(argv[++i]);
        } else if (!strcmp(argv[i], "--stress-base") && i + 1 < argc) {
            stress_base = strtoull(argv[++i], NULL, 10);
        } else if (!strcmp(argv[i], "--mark-unreached")) {
            g_mark_unreached = 1;
        } else if (!strcmp(argv[i], "--emit=json") || !strcmp(argv[i], "--json")) {
            g_emit_json = 1;
        } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
            usage(stdout, argv[0]);
            return 0;
        } else {
            fprintf(stderr, "unknown arg: %s\n", argv[i]);
            usage(stderr, argv[0]);
            return 2;
        }
    }

    if (stress_n > 0) return cmd_stress(stress_n, stress_base, depth);

    grid_t g;
    run_stats_t s = {0};
    run_once(seed, depth, &g, &s, verify);
    if (g_emit_json) {
        emit_grid_json(g, seed, depth);
        if (verify && !s.connected) return 1;
        return 0;
    }
    print_summary(seed, depth, &s);
    if (!g_quiet) print_ascii(g);

    if (verify && !s.connected) return 1;
    return 0;
}
