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

#define SEED_PERCENT 55
#define ITERATIONS 5

static int count_n8(bitgrid_t g, int cx, int cy, int bx0, int by0, int bx1, int by1) {
    int n = 0;
    for (int dy = -1; dy <= 1; dy++) {
        for (int dx = -1; dx <= 1; dx++) {
            if (dx == 0 && dy == 0) continue;
            int x = cx + dx, y = cy + dy;
            if (x < bx0 || x >= bx1 || y < by0 || y >= by1) continue;
            if (g[y][x]) n++;
        }
    }
    return n;
}

static void step(bitgrid_t in, bitgrid_t out,
                 int bx0, int by0, int bx1, int by1) {
    memset(out, 0, sizeof(bitgrid_t));
    for (int y = by0; y < by1; y++) {
        for (int x = bx0; x < bx1; x++) {
            int n = count_n8(in, x, y, bx0, by0, bx1, by1);
            if (in[y][x]) {
                /* S45678 */
                out[y][x] = (n >= 4) ? 1 : 0;
            } else {
                /* B678 */
                out[y][x] = (n >= 6) ? 1 : 0;
            }
        }
    }
}

static int extract_largest_region(bitgrid_t g, int bx0, int by0, int bx1, int by1) {
    static int visited[DROWS][DCOLS];
    memset(visited, 0, sizeof(visited));
    int best_size = 0;
    int best_id = 0;
    int label = 0;

    static int stack_x[DCOLS * DROWS];
    static int stack_y[DCOLS * DROWS];

    static const int dx4[4] = {1, -1, 0, 0};
    static const int dy4[4] = {0, 0, 1, -1};

    for (int y = by0; y < by1; y++) {
        for (int x = bx0; x < bx1; x++) {
            if (!g[y][x] || visited[y][x]) continue;
            label++;
            int sp = 0;
            stack_x[sp] = x; stack_y[sp] = y; sp++;
            int size = 0;
            while (sp > 0) {
                sp--;
                int cx = stack_x[sp], cy = stack_y[sp];
                if (cx < bx0 || cx >= bx1 || cy < by0 || cy >= by1) continue;
                if (visited[cy][cx] || !g[cy][cx]) continue;
                visited[cy][cx] = label;
                size++;
                for (int i = 0; i < 4; i++) {
                    stack_x[sp] = cx + dx4[i];
                    stack_y[sp] = cy + dy4[i];
                    sp++;
                }
            }
            if (size > best_size) { best_size = size; best_id = label; }
        }
    }

    /* Zero out everything that isn't in best_id. */
    for (int y = by0; y < by1; y++) {
        for (int x = bx0; x < bx1; x++) {
            if (visited[y][x] != best_id) g[y][x] = 0;
        }
    }
    return best_size;
}

void ca_blob(bitgrid_t out, rng_t *rng, event_sink_t *sink,
             int x, int y, int w, int h) {
    int bx0 = x, by0 = y;
    int bx1 = x + w, by1 = y + h;
    if (bx0 < 0) bx0 = 0;
    if (by0 < 0) by0 = 0;
    if (bx1 > DCOLS) bx1 = DCOLS;
    if (by1 > DROWS) by1 = DROWS;

    memset(out, 0, sizeof(bitgrid_t));
    for (int j = by0; j < by1; j++) {
        for (int i = bx0; i < bx1; i++) {
            out[j][i] = rng_percent(rng, SEED_PERCENT) ? 1 : 0;
        }
    }

    bitgrid_t buf;
    for (int k = 0; k < ITERATIONS; k++) {
        if (k % 2 == 0) step(out, buf, bx0, by0, bx1, by1);
        else            step(buf, out, bx0, by0, bx1, by1);
        event_emit(sink, EV_CA_ITERATION, NULL, 0);
    }
    if (ITERATIONS % 2 == 1) memcpy(out, buf, sizeof(bitgrid_t));

    extract_largest_region(out, bx0, by0, bx1, by1);
}

int bitgrid_count(bitgrid_t g) {
    int n = 0;
    for (int y = 0; y < DROWS; y++)
        for (int x = 0; x < DCOLS; x++)
            if (g[y][x]) n++;
    return n;
}
