/* NetHack 3.7	selvar.c	$NHDT-Date: 1709677544 2024/03/05 22:25:44 $  $NHDT-Branch: keni-mdlib-followup $:$NHDT-Revision: 1.360 $ */
/* Copyright (c) 2024 by Pasi Kallinen */
/* NetHack may be freely redistributed.  See license for details. */

#include "hack.h"
#include "selvar.h"
#include "sp_lev.h"

staticfn boolean sel_flood_havepoint(coordxy, coordxy, coordxy *, coordxy *,
                                    int);
staticfn long line_dist_coord(long, long, long, long, long, long);

/* selection */
struct selectionvar *
selection_new(void)
{
    struct selectionvar *tmps = (struct selectionvar *) alloc(sizeof *tmps);

    tmps->wid = COLNO;
    tmps->hei = ROWNO;
    tmps->bounds_dirty = FALSE;
    tmps->bounds.lx = COLNO;
    tmps->bounds.ly = ROWNO;
    tmps->bounds.hx = tmps->bounds.hy = 0;
    tmps->map = (char *) alloc((COLNO * ROWNO) + 1);
    (void) memset(tmps->map, 1, (COLNO * ROWNO));
    tmps->map[(COLNO * ROWNO)] = '\0';

    return tmps;
}

void
selection_free(struct selectionvar *sel, boolean freesel)
{
    if (sel) {
        if (sel->map)
            free(sel->map);
        sel->map = NULL;
        if (freesel)
            free((genericptr_t) sel);
        else
            (void) memset((genericptr_t) sel, 0, sizeof *sel);
    }
}

/* clear selection, setting all locations to value val */
void
selection_clear(struct selectionvar *sel, int val)
{
    (void) memset(sel->map, 1 + val, (COLNO * ROWNO));
    if (val) {
        sel->bounds.lx = 0;
        sel->bounds.ly = 0;
        sel->bounds.hx = COLNO - 1;
        sel->bounds.hy = ROWNO - 1;
    } else {
        sel->bounds.lx = COLNO;
        sel->bounds.ly = ROWNO;
        sel->bounds.hx = sel->bounds.hy = 0;
    }
    sel->bounds_dirty = FALSE;
}

struct selectionvar *
selection_clone(struct selectionvar *sel)
{
    struct selectionvar *tmps = (struct selectionvar *) alloc(sizeof *tmps);

    *tmps = *sel;
    tmps->map = dupstr(sel->map);

    return tmps;
}

/* get boundary rect of selection sel into b */
void
selection_getbounds(struct selectionvar *sel, NhRect *b)
{
    if (!sel || !b)
        return;

    selection_recalc_bounds(sel);

    if (sel->bounds.lx >= sel->wid) {
        b->lx = 0;
        b->ly = 0;
        b->hx = COLNO - 1;
        b->hy = ROWNO - 1;
    } else {
        b->lx = sel->bounds.lx;
        b->ly = sel->bounds.ly;
        b->hx = sel->bounds.hx;
        b->hy = sel->bounds.hy;
    }
}

/* recalc the boundary of selection, if necessary */
void
selection_recalc_bounds(struct selectionvar *sel)
{
    coordxy x, y;
    NhRect r;

    if (!sel->bounds_dirty)
        return;

    sel->bounds.lx = COLNO;
    sel->bounds.ly = ROWNO;
    sel->bounds.hx = sel->bounds.hy = 0;

    r.lx = r.ly = r.hx = r.hy = -1;

    /* left */
    for (x = 0; x < sel->wid; x++) {
        for (y = 0; y < sel->hei; y++) {
            if (selection_getpoint(x, y, sel)) {
                r.lx = x;
                break;
            }
        }
        if (r.lx > -1)
            break;
    }

    if (r.lx > -1) {
        /* right */
        for (x = sel->wid-1; x >= r.lx; x--) {
            for (y = 0; y < sel->hei; y++) {
                if (selection_getpoint(x, y, sel)) {
                    r.hx = x;
                    break;
                }
            }
            if (r.hx > -1)
                break;
        }

        /* top */
        for (y = 0; y < sel->hei; y++) {
            for (x = r.lx; x <= r.hx; x++) {
                if (selection_getpoint(x, y, sel)) {
                    r.ly = y;
                    break;
                }
            }
            if (r.ly > -1)
                break;
        }

        /* bottom */
        for (y = sel->hei-1; y >= r.ly; y--) {
            for (x = r.lx; x <= r.hx; x++) {
                if (selection_getpoint(x, y, sel)) {
                    r.hy = y;
                    break;
                }
            }
            if (r.hy > -1)
                break;
        }
        sel->bounds = r;
    }

    sel->bounds_dirty = FALSE;
}

coordxy
selection_getpoint(
    coordxy x, coordxy y,
    struct selectionvar *sel)
{
    if (!sel || !sel->map)
        return 0;
    if (x < 0 || y < 0 || x >= sel->wid || y >= sel->hei)
        return 0;

    return (sel->map[sel->wid * y + x] - 1);
}

void
selection_setpoint(
    coordxy x, coordxy y,
    struct selectionvar *sel,
    int c)
{
    if (!sel || !sel->map)
        return;
    if (x < 0 || y < 0 || x >= sel->wid || y >= sel->hei)
        return;

    if (c && !sel->bounds_dirty) {
        if (sel->bounds.lx > x) sel->bounds.lx = x;
        if (sel->bounds.ly > y) sel->bounds.ly = y;
        if (sel->bounds.hx < x) sel->bounds.hx = x;
        if (sel->bounds.hy < y) sel->bounds.hy = y;
    } else {
        sel->bounds_dirty = TRUE;
    }

    sel->map[sel->wid * y + x] = (char) (c + 1);
}

struct selectionvar *
selection_not(struct selectionvar *s)
{
    int x, y;
    NhRect tmprect = cg.zeroNhRect;

    for (x = 0; x < s->wid; x++)
        for (y = 0; y < s->hei; y++)
            selection_setpoint(x, y, s, selection_getpoint(x, y, s) ? 0 : 1);
    selection_getbounds(s, &tmprect);
    return s;
}

struct selectionvar *
selection_filter_percent(
    struct selectionvar *ov,
    int percent)
{
    int x, y;
    struct selectionvar *ret;
    NhRect rect = cg.zeroNhRect;

    if (!ov)
        return NULL;

    ret = selection_new();

    selection_getbounds(ov, &rect);

    for (x = rect.lx; x <= rect.hx; x++)
        for (y = rect.ly; y <= rect.hy; y++)
            if (selection_getpoint(x, y, ov) && (rn2(100) < percent))
                selection_setpoint(x, y, ret, 1);

    return ret;
}

struct selectionvar *
selection_filter_mapchar(struct selectionvar *ov,  xint16 typ, int lit)
{
    int x, y;
    struct selectionvar *ret;
    NhRect rect = cg.zeroNhRect;

    if (!ov)
        return NULL;

    ret = selection_new();

    selection_getbounds(ov, &rect);

    for (x = rect.lx; x <= rect.hx; x++)
        for (y = rect.ly; y <= rect.hy; y++)
            if (selection_getpoint(x, y, ov)
                && match_maptyps(typ, levl[x][y].typ)) {
                switch (lit) {
                default:
                case -2:
                    selection_setpoint(x, y, ret, 1);
                    break;
                case -1:
                    selection_setpoint(x, y, ret, rn2(2));
                    break;
                case 0:
                case 1:
                    if (levl[x][y].lit == (unsigned int) lit)
                        selection_setpoint(x, y, ret, 1);
                    break;
                }
            }
    return ret;
}

int
selection_rndcoord(
    struct selectionvar *ov,
    coordxy *x, coordxy *y,
    boolean removeit)
{
    int idx = 0;
    int c;
    int dx, dy;
    NhRect rect = cg.zeroNhRect;

    selection_getbounds(ov, &rect);

    for (dx = rect.lx; dx <= rect.hx; dx++)
        for (dy = rect.ly; dy <= rect.hy; dy++)
            if (selection_getpoint(dx, dy, ov))
                idx++;

    if (idx) {
        c = rn2(idx);
        for (dx = rect.lx; dx <= rect.hx; dx++)
            for (dy = rect.ly; dy <= rect.hy; dy++)
                if (selection_getpoint(dx, dy, ov)) {
                    if (!c) {
                        *x = dx;
                        *y = dy;
                        if (removeit)
                            selection_setpoint(dx, dy, ov, 0);
                        return 1;
                    }
                    c--;
                }
    }
    *x = *y = -1;
    return 0;
}

void
selection_do_grow(struct selectionvar *ov, int dir)
{
    coordxy x, y;
    struct selectionvar *tmp;
    NhRect rect = cg.zeroNhRect;

    if (!ov)
        return;

    tmp = selection_new();

    if (dir == W_RANDOM)
        dir = random_wdir();

    selection_getbounds(ov, &rect);

    for (x = max(0, rect.lx-1); x <= min(COLNO-1, rect.hx+1); x++)
        for (y = max(0, rect.ly-1); y <= min(ROWNO-1, rect.hy+1); y++) {
            /* note:  dir is a mask of multiple directions, but the only
               way to specify diagonals is by including the two adjacent
               orthogonal directions, which effectively specifies three-
               way growth [WEST|NORTH => WEST plus WEST|NORTH plus NORTH] */
            if (((dir & W_WEST) && selection_getpoint(x + 1, y, ov))
                || (((dir & (W_WEST | W_NORTH)) == (W_WEST | W_NORTH))
                    && selection_getpoint(x + 1, y + 1, ov))
                || ((dir & W_NORTH) && selection_getpoint(x, y + 1, ov))
                || (((dir & (W_NORTH | W_EAST)) == (W_NORTH | W_EAST))
                    && selection_getpoint(x - 1, y + 1, ov))
                || ((dir & W_EAST) && selection_getpoint(x - 1, y, ov))
                || (((dir & (W_EAST | W_SOUTH)) == (W_EAST | W_SOUTH))
                    && selection_getpoint(x - 1, y - 1, ov))
                || ((dir & W_SOUTH) && selection_getpoint(x, y - 1, ov))
                ||  (((dir & (W_SOUTH | W_WEST)) == (W_SOUTH | W_WEST))
                     && selection_getpoint(x + 1, y - 1, ov))) {
                selection_setpoint(x, y, tmp, 1);
            }
        }

    selection_getbounds(tmp, &rect);

    for (x = rect.lx; x <= rect.hx; x++)
        for (y = rect.ly; y <= rect.hy; y++)
            if (selection_getpoint(x, y, tmp))
                selection_setpoint(x, y, ov, 1);

    selection_free(tmp, TRUE);
}

staticfn int (*selection_flood_check_func)(coordxy, coordxy);

void
set_selection_floodfillchk(int (*f)(coordxy, coordxy))
{
    selection_flood_check_func = f;
}

/* check whethere <x,y> is already in xs[],ys[] */
staticfn boolean
sel_flood_havepoint(
    coordxy x, coordxy y,
    coordxy xs[], coordxy ys[],
    int n)
{
    coordxy xx = x, yy = y;

    while (n > 0) {
        --n;
        if (xs[n] == xx && ys[n] == yy)
            return TRUE;
    }
    return FALSE;
}

void
selection_floodfill(
    struct selectionvar *ov,
    coordxy x, coordxy y,
    boolean diagonals)
{
    struct selectionvar *tmp = selection_new();
#define SEL_FLOOD_STACK (COLNO * ROWNO)
#define SEL_FLOOD(nx, ny) \
    do {                                      \
        if (idx < SEL_FLOOD_STACK) {          \
            dx[idx] = (nx);                   \
            dy[idx] = (ny);                   \
            idx++;                            \
        } else                                \
            panic(floodfill_stack_overrun);   \
    } while (0)
#define SEL_FLOOD_CHKDIR(mx, my, sel) \
    do {                                                        \
        if (isok((mx), (my))                                    \
            && (*selection_flood_check_func)((mx), (my))        \
            && !selection_getpoint((mx), (my), (sel))           \
            && !sel_flood_havepoint((mx), (my), dx, dy, idx))   \
            SEL_FLOOD((mx), (my));                              \
    } while (0)
    static const char floodfill_stack_overrun[] = "floodfill stack overrun";
    int idx = 0;
    coordxy dx[SEL_FLOOD_STACK];
    coordxy dy[SEL_FLOOD_STACK];

    if (selection_flood_check_func == (int (*)(coordxy, coordxy)) 0) {
        selection_free(tmp, TRUE);
        return;
    }
    SEL_FLOOD(x, y);
    do {
        idx--;
        x = dx[idx];
        y = dy[idx];
        if (isok(x, y)) {
            selection_setpoint(x, y, ov, 1);
            selection_setpoint(x, y, tmp, 1);
        }
        SEL_FLOOD_CHKDIR((x + 1), y, tmp);
        SEL_FLOOD_CHKDIR((x - 1), y, tmp);
        SEL_FLOOD_CHKDIR(x, (y + 1), tmp);
        SEL_FLOOD_CHKDIR(x, (y - 1), tmp);
        if (diagonals) {
            SEL_FLOOD_CHKDIR((x + 1), (y + 1), tmp);
            SEL_FLOOD_CHKDIR((x - 1), (y - 1), tmp);
            SEL_FLOOD_CHKDIR((x - 1), (y + 1), tmp);
            SEL_FLOOD_CHKDIR((x + 1), (y - 1), tmp);
        }
    } while (idx > 0);
#undef SEL_FLOOD
#undef SEL_FLOOD_STACK
#undef SEL_FLOOD_CHKDIR
    selection_free(tmp, TRUE);
}

/* McIlroy's Ellipse Algorithm */
void
selection_do_ellipse(
    struct selectionvar *ov,
    int xc, int yc,
    int a, int b,
    int filled)
{ /* e(x,y) = b^2*x^2 + a^2*y^2 - a^2*b^2 */
    int x = 0, y = b;
    long a2 = (long) a * a, b2 = (long) b * b;
    long crit1 = -(a2 / 4 + a % 2 + b2);
    long crit2 = -(b2 / 4 + b % 2 + a2);
    long crit3 = -(b2 / 4 + b % 2);
    long t = -a2 * y; /* e(x+1/2,y-1/2) - (a^2+b^2)/4 */
    long dxt = 2 * b2 * x, dyt = -2 * a2 * y;
    long d2xt = 2 * b2, d2yt = 2 * a2;
    long width = 1;
    long i;

    if (!ov)
        return;

    filled = !filled;

    if (!filled) {
        while (y >= 0 && x <= a) {
            selection_setpoint(xc + x, yc + y, ov, 1);
            if (x != 0 || y != 0)
                selection_setpoint(xc - x, yc - y, ov, 1);
            if (x != 0 && y != 0) {
                selection_setpoint(xc + x, yc - y, ov, 1);
                selection_setpoint(xc - x, yc + y, ov, 1);
            }
            if (t + b2 * x <= crit1       /* e(x+1,y-1/2) <= 0 */
                || t + a2 * y <= crit3) { /* e(x+1/2,y) <= 0 */
                x++;
                dxt += d2xt;
                t += dxt;
            } else if (t - a2 * y > crit2) { /* e(x+1/2,y-1) > 0 */
                y--;
                dyt += d2yt;
                t += dyt;
            } else {
                x++;
                dxt += d2xt;
                t += dxt;
                y--;
                dyt += d2yt;
                t += dyt;
            }
        }
    } else {
        while (y >= 0 && x <= a) {
            if (t + b2 * x <= crit1       /* e(x+1,y-1/2) <= 0 */
                || t + a2 * y <= crit3) { /* e(x+1/2,y) <= 0 */
                x++;
                dxt += d2xt;
                t += dxt;
                width += 2;
            } else if (t - a2 * y > crit2) { /* e(x+1/2,y-1) > 0 */
                for (i = 0; i < width; i++)
                    selection_setpoint(xc - x + i, yc - y, ov, 1);
                if (y != 0)
                    for (i = 0; i < width; i++)
                        selection_setpoint(xc - x + i, yc + y, ov, 1);
                y--;
                dyt += d2yt;
                t += dyt;
            } else {
                for (i = 0; i < width; i++)
                    selection_setpoint(xc - x + i, yc - y, ov, 1);
                if (y != 0)
                    for (i = 0; i < width; i++)
                        selection_setpoint(xc - x + i, yc + y, ov, 1);
                x++;
                dxt += d2xt;
                t += dxt;
                y--;
                dyt += d2yt;
                t += dyt;
                width += 2;
            }
        }
    }
}

/* square of distance from line segment (x1,y1, x2,y2) to point (x3,y3) */
staticfn long
line_dist_coord(long x1, long y1, long x2, long y2, long x3, long y3)
{
    long px = x2 - x1;
    long py = y2 - y1;
    long s = px * px + py * py;
    long x, y, dx, dy, distsq = 0;
    float lu = 0;

    if (x1 == x2 && y1 == y2)
        return dist2(x1, y1, x3, y3);

    lu = ((x3 - x1) * px + (y3 - y1) * py) / (float) s;
    if (lu > 1)
        lu = 1;
    else if (lu < 0)
        lu = 0;

    x = x1 + lu * px;
    y = y1 + lu * py;
    dx = x - x3;
    dy = y - y3;
    distsq = dx * dx + dy * dy;

    return distsq;
}

/* guts of l_selection_gradient */
void
selection_do_gradient(
    struct selectionvar *ov,
    long x, long y,
    long x2,long y2,
    long gtyp,
    long mind, long maxd)
{
    long dx, dy, dofs;

    if (mind > maxd) {
        long tmp = mind;
        mind = maxd;
        maxd = tmp;
    }

    dofs = maxd * maxd - mind * mind;
    if (dofs < 1)
        dofs = 1;

    switch (gtyp) {
    default:
        impossible("Unrecognized gradient type! Defaulting to radial...");
        FALLTHROUGH;
        /* FALLTHRU */
    case SEL_GRADIENT_RADIAL: {
        for (dx = 0; dx < COLNO; dx++)
            for (dy = 0; dy < ROWNO; dy++) {
                long d0 = line_dist_coord(x, y, x2, y2, dx, dy);

                if (d0 <= mind * mind
                    || (d0 <= maxd * maxd && d0 - mind * mind < rn2(dofs)))
                    selection_setpoint(dx, dy, ov, 1);
            }
        break;
    }
    case SEL_GRADIENT_SQUARE: {
        for (dx = 0; dx < COLNO; dx++)
            for (dy = 0; dy < ROWNO; dy++) {
                long d1 = line_dist_coord(x, y, x2, y2, x, dy);
                long d2 = line_dist_coord(x, y, x2, y2, dx, y);
                long d3 = line_dist_coord(x, y, x2, y2, x2, dy);
                long d4 = line_dist_coord(x, y, x2, y2, dx, y2);
                long d5 = line_dist_coord(x, y, x2, y2, dx, dy);
                long d0 = min(d5, min(max(d1, d2), max(d3, d4)));

                if (d0 <= mind * mind
                    || (d0 <= maxd * maxd && d0 - mind * mind < rn2(dofs)))
                    selection_setpoint(dx, dy, ov, 1);
            }
        break;
    } /*case*/
    } /*switch*/
}

/* bresenham line algo */
void
selection_do_line(
    coordxy x1, coordxy y1,
    coordxy x2, coordxy y2,
    struct selectionvar *ov)
{
    int d0, dx, dy, ai, bi, xi, yi;

    if (x1 < x2) {
        xi = 1;
        dx = x2 - x1;
    } else {
        xi = -1;
        dx = x1 - x2;
    }
    if (y1 < y2) {
        yi = 1;
        dy = y2 - y1;
    } else {
        yi = -1;
        dy = y1 - y2;
    }

    selection_setpoint(x1, y1, ov, 1);

    if (!dx && !dy) {
        /* single point - already all done */
        ;
    } else if (dx > dy) {
        ai = (dy - dx) * 2;
        bi = dy * 2;
        d0 = bi - dx;
        do {
            if (d0 >= 0) {
                y1 += yi;
                d0 += ai;
            } else
                d0 += bi;
            x1 += xi;
            selection_setpoint(x1, y1, ov, 1);
        } while (x1 != x2);
    } else {
        ai = (dx - dy) * 2;
        bi = dx * 2;
        d0 = bi - dy;
        do {
            if (d0 >= 0) {
                x1 += xi;
                d0 += ai;
            } else
                d0 += bi;
            y1 += yi;
            selection_setpoint(x1, y1, ov, 1);
        } while (y1 != y2);
    }
}

void
selection_do_randline(
    coordxy x1, coordxy y1,
    coordxy x2, coordxy y2,
    schar rough,
    schar rec,
    struct selectionvar *ov)
{
    int mx, my;
    int dx, dy;

    if (rec < 1 || (x2 == x1 && y2 == y1))
        return;

    if (rough > max(abs(x2 - x1), abs(y2 - y1)))
        rough = max(abs(x2 - x1), abs(y2 - y1));

    if (rough < 2) {
        mx = ((x1 + x2) / 2);
        my = ((y1 + y2) / 2);
    } else {
        do {
            dx = rn2(rough) - (rough / 2);
            dy = rn2(rough) - (rough / 2);
            mx = ((x1 + x2) / 2) + dx;
            my = ((y1 + y2) / 2) + dy;
        } while ((mx > COLNO - 1 || mx < 0 || my < 0 || my > ROWNO - 1));
    }

    if (!selection_getpoint(mx, my, ov)) {
        selection_setpoint(mx, my, ov, 1);
    }

    rough = (rough * 2) / 3;

    rec--;

    selection_do_randline(x1, y1, mx, my, rough, rec, ov);
    selection_do_randline(mx, my, x2, y2, rough, rec, ov);

    selection_setpoint(x2, y2, ov, 1);
}

void
selection_iterate(
    struct selectionvar *ov,
    select_iter_func func,
    genericptr_t arg)
{
    coordxy x, y;
    NhRect rect = cg.zeroNhRect;

    if (!ov)
        return;

    selection_getbounds(ov, &rect);

    for (x = rect.lx; x <= rect.hx; x++)
        for (y = rect.ly; y <= rect.hy; y++)
            if (isok(x,y) && selection_getpoint(x, y, ov))
                (*func)(x, y, arg);
}

/* selection is not rectangular, or has holes in it */
boolean
selection_is_irregular(struct selectionvar *sel)
{
    coordxy x, y;
    NhRect rect = cg.zeroNhRect;

    selection_getbounds(sel, &rect);

    for (x = rect.lx; x <= rect.hx; x++)
        for (y = rect.ly; y <= rect.hy; y++)
            if (isok(x,y) && !selection_getpoint(x, y, sel))
                return TRUE;

    return FALSE;
}

/* return a description of the selection size */
char *
selection_size_description(struct selectionvar *sel, char *buf)
{
    NhRect rect = cg.zeroNhRect;
    coordxy dx, dy;

    selection_getbounds(sel, &rect);
    dx = rect.hx - rect.lx + 1;
    dy = rect.hy - rect.ly + 1;
    Sprintf(buf, "%s %i by %i",
            selection_is_irregular(sel) ? "irregularly shaped"
            : (dx == dy) ? "square"
              : "rectangular",
            dx, dy);
    return buf;
}

struct selectionvar *
selection_from_mkroom(struct mkroom *croom)
{
    struct selectionvar *sel = selection_new();
    coordxy x, y;
    unsigned rmno;

    if (!croom && gc.coder && gc.coder->croom)
        croom = gc.coder->croom;
    if (!croom)
        return sel;

    rmno = (unsigned)((croom - svr.rooms) + ROOMOFFSET);
    for (y = croom->ly; y <= croom->hy; y++)
        for (x = croom->lx; x <= croom->hx; x++)
            if (isok(x, y) && !levl[x][y].edge
                && levl[x][y].roomno == rmno)
                selection_setpoint(x, y, sel, 1);
    return sel;
}

void
selection_force_newsyms(struct selectionvar *sel)
{
    coordxy x, y;

    for (x = 1; x < sel->wid; x++)
        for (y = 0; y < sel->hei; y++)
            if (selection_getpoint(x, y, sel))
                newsym_force(x, y);
}

/*selvar.c*/
