#include "lakes.h"
#include "ca.h"
#include "dijkstra.h"
#include <string.h>
#include <stddef.h>

static const int LAKE_BOXES[][2] = {
    {30, 15}, {30, 15}, {28, 14}, {24, 12}, {22, 11}, {20, 10},
};
#define LAKE_COUNT (int)(sizeof(LAKE_BOXES) / sizeof(LAKE_BOXES[0]))

static int is_passable(uint8_t t) {
    return t == T_FLOOR || t == T_CORRIDOR || t == T_DOOR || t == T_BRIDGE;
}

/* Returns 1 if the proposed lake preserves connectivity of all remaining
   passable cells on the map. */
static int passability_test(grid_t g, bitgrid_t lake) {
    /* Find first passable cell not in lake. */
    int sx = -1, sy = -1;
    int total = 0;
    for (int y = 0; y < DROWS; y++) {
        for (int x = 0; x < DCOLS; x++) {
            if (lake[y][x]) continue;
            if (is_passable(g[y][x].terrain)) {
                if (sx < 0) { sx = x; sy = y; }
                total++;
            }
        }
    }
    if (sx < 0) return 1; /* no passable cells — trivially fine */

    /* BFS, treating lake as impassable. */
    static int visited[DROWS][DCOLS];
    memset(visited, 0, sizeof(visited));
    int qx[DCOLS * DROWS], qy[DCOLS * DROWS];
    int head = 0, tail = 0;
    qx[tail] = sx; qy[tail] = sy; tail++;
    visited[sy][sx] = 1;
    int reached = 0;
    static const int dx4[4] = {1, -1, 0, 0};
    static const int dy4[4] = {0, 0, 1, -1};
    while (head < tail) {
        int cx = qx[head], cy = qy[head]; head++;
        reached++;
        for (int i = 0; i < 4; i++) {
            int nx = cx + dx4[i], ny = cy + dy4[i];
            if (!grid_in_bounds(nx, ny)) continue;
            if (visited[ny][nx]) continue;
            if (lake[ny][nx]) continue;
            if (!is_passable(g[ny][nx].terrain)) continue;
            visited[ny][nx] = 1;
            qx[tail] = nx; qy[tail] = ny; tail++;
        }
    }
    return reached == total;
}

static void paint_lake(grid_t g, bitgrid_t lake, liquid_t kind) {
    for (int y = 0; y < DROWS; y++) {
        for (int x = 0; x < DCOLS; x++) {
            if (!lake[y][x]) continue;
            g[y][x].terrain = T_LIQUID;
            g[y][x].liquid = (uint8_t)kind;
            g[y][x].flags &= (uint16_t)~F_IN_ROOM;
            g[y][x].room_id = 0;
        }
    }
}

/* Brogue-ish weighted selection by depth. Water dominates shallow; lava,
   chasm, brimstone phase in as depth grows. */
static liquid_t pick_liquid(rng_t *rng, int depth) {
    int w_water     = (depth >= 1  && depth <= 14) ? 20 : 0;
    int w_lava      = (depth >= 3  && depth <= 17) ? (depth - 2) : 0;
    int w_chasm     = (depth >= 7  && depth <= 24) ? (depth - 6) : 0;
    int w_brimstone = (depth >= 14 && depth <= 26) ? (depth - 13) : 0;
    int total = w_water + w_lava + w_chasm + w_brimstone;
    if (total <= 0) return L_WATER;
    int roll = rng_range(rng, 0, total - 1);
    if ((roll -= w_water)     < 0) return L_WATER;
    if ((roll -= w_lava)      < 0) return L_LAVA;
    if ((roll -= w_chasm)     < 0) return L_CHASM;
    return L_BRIMSTONE;
}

static void mark_proposed(grid_t g, bitgrid_t lake, int set) {
    for (int y = 0; y < DROWS; y++) {
        for (int x = 0; x < DCOLS; x++) {
            if (!lake[y][x]) continue;
            if (set) g[y][x].flags |= F_PROPOSED_LAKE;
            else     g[y][x].flags &= (uint16_t)~F_PROPOSED_LAKE;
        }
    }
}

int place_lakes(grid_t g, rng_t *rng, event_sink_t *sink, int depth) {
    (void)depth; /* used in Phase A.2 for liquid type selection */
    int placed = 0;
    static bitgrid_t lake;

    for (int i = 0; i < LAKE_COUNT; i++) {
        int bw = LAKE_BOXES[i][0];
        int bh = LAKE_BOXES[i][1];
        if (bw >= DCOLS - 2) bw = DCOLS - 3;
        if (bh >= DROWS - 2) bh = DROWS - 3;

        int max_bx = DCOLS - bw - 1;
        int max_by = DROWS - bh - 1;
        if (max_bx < 1) max_bx = 1;
        if (max_by < 1) max_by = 1;
        int bx = rng_range(rng, 1, max_bx);
        int by = rng_range(rng, 1, max_by);

        ca_blob(lake, rng, sink, bx, by, bw, bh);
        if (bitgrid_count(lake) < 5) continue;

        /* Mark proposal so the recorder + viewer can show what's being
           tested. The flag is cleared before the outcome event. */
        mark_proposed(g, lake, 1);
        event_emit(sink, EV_LAKE_PROPOSED, NULL, 0);

        if (!passability_test(g, lake)) {
            mark_proposed(g, lake, 0);
            event_emit(sink, EV_LAKE_REJECTED_IMPASSABLE, NULL, 0);
            continue;
        }
        mark_proposed(g, lake, 0);
        paint_lake(g, lake, pick_liquid(rng, depth));
        event_emit(sink, EV_LAKE_PLACED, NULL, 0);
        placed++;
    }
    return placed;
}

void apply_wreaths(grid_t g, event_sink_t *sink) {
    for (int y = 0; y < DROWS; y++) {
        for (int x = 0; x < DCOLS; x++) {
            if (g[y][x].terrain != T_LIQUID) continue;
            for (int dy = -1; dy <= 1; dy++) {
                for (int dx = -1; dx <= 1; dx++) {
                    if (dx == 0 && dy == 0) continue;
                    int qx = x + dx, qy = y + dy;
                    if (!grid_in_bounds(qx, qy)) continue;
                    if (g[qy][qx].terrain == T_LIQUID) continue;
                    g[qy][qx].flags |= F_WREATH;
                }
            }
        }
    }
    event_emit(sink, EV_WREATH_APPLIED, NULL, 0);
}

static int try_bridge_run(grid_t g, int x, int y, int dx, int dy, event_sink_t *sink) {
    /* (x,y) must be passable floor; the next cells must be liquid for 1..5
       steps, terminating at another passable cell. Water and chasms support
       bridges; lava and brimstone don't. */
    if (!is_passable(g[y][x].terrain)) return 0;
    int k = 0;
    int nx = x + dx, ny = y + dy;
    while (grid_in_bounds(nx, ny) && g[ny][nx].terrain == T_LIQUID && k < 5) {
        uint8_t l = g[ny][nx].liquid;
        if (l != L_WATER && l != L_CHASM) return 0;
        k++;
        nx += dx; ny += dy;
    }
    if (k == 0) return 0;
    if (!grid_in_bounds(nx, ny)) return 0;
    if (!is_passable(g[ny][nx].terrain)) return 0;

    for (int i = 1; i <= k; i++) {
        int bx = x + i * dx, by = y + i * dy;
        g[by][bx].terrain = T_BRIDGE;
        g[by][bx].liquid = L_NONE;
        g[by][bx].flags |= F_BRIDGE;
    }
    event_emit(sink, EV_BRIDGE_PLACED, NULL, 0);
    return 1;
}

int place_bridges(grid_t g, event_sink_t *sink) {
    int n = 0;
    for (int y = 1; y < DROWS - 1; y++) {
        for (int x = 1; x < DCOLS - 1; x++) {
            if (!is_passable(g[y][x].terrain)) continue;
            n += try_bridge_run(g, x, y, 1, 0, sink);
            n += try_bridge_run(g, x, y, 0, 1, sink);
        }
    }
    return n;
}
