#include "chokepoints.h"
#include <stddef.h>
#include <string.h>

/* Port of Brogue's chokepoint analysis (Architect.c:analyzeMap), simplified:
   - Identify chokepoint cells — narrow passages where all 4 cardinal arcs
     change state and one axis is fully blocked.
   - For each chokepoint, flood-fill from its adjacent open neighbors while
     pretending the chokepoint is blocked. The flood count is the "pocket
     size" that depends on this chokepoint. Every cell in that pocket gets
     min(current value, flood count) in the choke map.
   - The chokepoint next to the smallest pocket it guards is flagged as the
     pocket's gate site. */

static const int CDIRS[8][2] = {
    { 1,  0}, { 1, -1}, { 0, -1}, {-1, -1},
    {-1,  0}, {-1,  1}, { 0,  1}, { 1,  1},
};

static int pass(grid_t g, int x, int y) {
    if (!grid_in_bounds(x, y)) return 0;
    return terrain_is_passable(g[y][x].terrain);
}

/* Rotating around 8 neighbors, count transitions between pass/not-pass. */
static int narrow_passage(grid_t g, int x, int y) {
    int arcs = 0;
    for (int d = 0; d < 8; d++) {
        int old_x = x + CDIRS[(d + 7) % 8][0];
        int old_y = y + CDIRS[(d + 7) % 8][1];
        int new_x = x + CDIRS[d][0];
        int new_y = y + CDIRS[d][1];
        int a = pass(g, new_x, new_y);
        int b = pass(g, old_x, old_y);
        if (a != b) {
            if (++arcs > 2) {
                int horiz_blocked = !pass(g, x - 1, y) && !pass(g, x + 1, y);
                int vert_blocked  = !pass(g, x, y - 1) && !pass(g, x, y + 1);
                return horiz_blocked || vert_blocked;
            }
        }
    }
    return 0;
}

/* 4-connected flood from (sx,sy) through passable cells, but treat
   (block_x, block_y) as impassable. Marks visited cells in out[y][x] and
   returns the count. */
static int flood_blocked(grid_t g, int sx, int sy,
                         int block_x, int block_y,
                         uint8_t out[DROWS][DCOLS]) {
    memset(out, 0, (size_t)DROWS * DCOLS);
    if (!pass(g, sx, sy)) return 0;
    if (sx == block_x && sy == block_y) return 0;
    static int qx[DCOLS * DROWS];
    static int qy[DCOLS * DROWS];
    int head = 0, tail = 0;
    out[sy][sx] = 1;
    qx[tail] = sx; qy[tail] = sy; tail++;
    int count = 0;
    static const int d4[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
    while (head < tail) {
        int cx = qx[head], cy = qy[head]; head++;
        count++;
        for (int i = 0; i < 4; i++) {
            int nx = cx + d4[i][0];
            int ny = cy + d4[i][1];
            if (!grid_in_bounds(nx, ny)) continue;
            if (out[ny][nx]) continue;
            if (nx == block_x && ny == block_y) continue;
            if (!pass(g, nx, ny)) continue;
            out[ny][nx] = 1;
            qx[tail] = nx; qy[tail] = ny; tail++;
        }
    }
    return count;
}

void compute_chokepoints(grid_t g, event_sink_t *sink,
                         choke_map_t choke_out,
                         choke_flag_t is_chokepoint,
                         choke_flag_t is_gate_site) {
    /* Init */
    for (int y = 0; y < DROWS; y++)
        for (int x = 0; x < DCOLS; x++) {
            choke_out[y][x] = CHOKE_UNREACHABLE;
            is_chokepoint[y][x] = 0;
            is_gate_site[y][x] = 0;
        }

    /* Pass 1: flag chokepoints. */
    for (int y = 1; y < DROWS - 1; y++) {
        for (int x = 1; x < DCOLS - 1; x++) {
            if (!pass(g, x, y)) continue;
            if (narrow_passage(g, x, y)) is_chokepoint[y][x] = 1;
        }
    }

    /* Pass 2: for each chokepoint, flood through each open cardinal neighbor
       with the chokepoint treated as blocked. Update choke_out for the
       flooded pocket. Track gate sites. */
    static uint8_t pocket[DROWS][DCOLS];
    static const int d4[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
    for (int cy = 0; cy < DROWS; cy++) {
        for (int cx = 0; cx < DCOLS; cx++) {
            if (!is_chokepoint[cy][cx]) continue;
            int best_flood = CHOKE_UNREACHABLE;
            for (int i = 0; i < 4; i++) {
                int nx = cx + d4[i][0];
                int ny = cy + d4[i][1];
                if (!pass(g, nx, ny)) continue;
                if (is_chokepoint[ny][nx]) continue;
                int n = flood_blocked(g, nx, ny, cx, cy, pocket);
                if (n < 4) continue; /* too tiny to matter */
                for (int py = 0; py < DROWS; py++) {
                    for (int px = 0; px < DCOLS; px++) {
                        if (!pocket[py][px]) continue;
                        if (n < choke_out[py][px]) {
                            choke_out[py][px] = (int16_t)n;
                            is_gate_site[py][px] = 0;
                        }
                    }
                }
                if (n < best_flood) best_flood = n;
                if (n < choke_out[cy][cx]) {
                    choke_out[cy][cx] = (int16_t)n;
                    is_gate_site[cy][cx] = 1;
                }
            }
            if (best_flood < CHOKE_UNREACHABLE)
                event_emit(sink, EV_CHOKEPOINT_FOUND, NULL, 0);
        }
    }
}
