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

#include "life.h"
#include "loadsave.h"
#include "lookup.h"
#include "util.h"

/*** Private Constants ***/

static uint8 bitcount[0x100] = {
    0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
};

/*** Public Globals ***/

/* Note: We rely on implicit-initialization-to-0 for many globals and statics */

uint32      tick;
boolean     parity;      /* on an odd (1) or even (0) tick? */
uint32      population;
uint32      num_cages;

/* pattern_description is an array of length desc_num_lines, where each array entry is a single
 * line of text. These variables should be treated as read-only: call set_description to change the
 * description. */
char**      pattern_description;
int32       desc_num_lines;

/*** Private Globals ***/

/* doubly-linked lists */
cage_type*  active_cages;
cage_type*  sleeping_cages;
cage_type*  predictable_cages_onscreen;
cage_type*  predictable_cages_offscreen;

static cage_type*  dead_cages;               /* singly-linked list */
static cage_type*  cage_hash[XY_HASH_SIZE];  /* for finding new neighbors */
static uint32      sleeping_population, predictable_population[2];

/* The frontend tells us about the viewport dimensions, so that we know which cells should
 * be drawn onscreen. */
static int32       viewport_xstart = -1, viewport_ystart = -1;
static int32       viewport_xend   = -1, viewport_yend   = -1;

/*** Private Function Prototypes ***/

static void        add_description_line(const char* line);
static cage_type*  birth_cage(int32 x, int32 y);
static void        clear_description(void);
static cage_type*  find_cage(int32 x, int32 y);
void               find_live_cell_in_block(uint16 block, int32 xoff, int32 yoff,
                                           int32* x, int32* y);
static cage_type*  find_rattle_birth(int32 x, int32 y);
static void        kill_cage(cage_type* cage);
static void        move_cage(cage_type* cage, cage_type** old_list, cage_type** new_list);
static void        predict_cage(cage_type* cage);
static void        rattle_cage(cage_type* cage, boolean cage_changed);
static void        rattle_neighbors(cage_type* cage);
static void        tranquilize_cage(cage_type* cage);
static void        update_onscreen_flag(cage_type* cage);

/* Defined in the GUI code */
void draw_life_block(uint16 block, int32 xstart, int32 ystart, boolean full_update);

/*** Public Functions ***/

/* Completely clear the universe, emptying all cage lists, emptying the hash, resetting ticks to 0
 * and pattern_description to NULL.
 */
void clear_world(void)
{
    /* Clear all cage lists */
    clear_cage_list(&active_cages);
    clear_cage_list(&sleeping_cages);
    clear_cage_list(&predictable_cages_onscreen);
    clear_cage_list(&predictable_cages_offscreen);
    clear_cage_list(&dead_cages);
    num_cages = 0;

    /* Zero-out the hash */
    memset(cage_hash, 0, XY_HASH_SIZE * sizeof(cage_type*));

    /* Set ticks and population to 0 */
    tick = parity = population = sleeping_population = 0;
    predictable_population[0] = predictable_population[1] = 0;

    /* Clear the description */
    clear_description();
}

/* Deallocate the given linked list of cages and set to NULL.
 */
void clear_cage_list(cage_type** start)
{
    cage_type*  p;
    cage_type*  next;

    for (p=*start; p; p=next) {
        next = p->next;
        free(p);
    }
    *start = NULL;
}

/* Set, unset, or toggle the cell at the given XY coordinates.
 */
void draw_cell(int32 x, int32 y, draw_method_type draw_method)
{
    cage_type*  cage;
    int32       cagex, cagey;
    int32       xoffset, yoffset;
    uint16*     block;
    uint16      bitmask;

    /* Check if coordinates are out of bounds */
    if (x < 0 || x >= WORLD_SIZE || y < 0 || y >= WORLD_SIZE)
        return;

    /* Find the cage containing this cell, rattle it, birth it if necessary */
    cagex = x / CAGE_SIZE;
    cagey = y / CAGE_SIZE;
    cage = find_cage(cagex, cagey);
    if (cage)
        rattle_cage(cage, TRUE);
    else {
        if (draw_method == DRAW_UNSET)
            return;
        else
            cage = birth_cage(cagex, cagey);
    }
    rattle_neighbors(cage);

    /* Extract the uint16 representing the appropriate 4x4 block; get offsets relative
     * to the block.
     */
    xoffset = x % CAGE_SIZE;
    yoffset = y % CAGE_SIZE;
    if (yoffset < 4) {
        if (xoffset < 4)
            block = &(cage->bnw[parity]);
        else
            block = &(cage->bne[parity]);
    } else {
        if (xoffset < 4)
            block = &(cage->bsw[parity]);
        else
            block = &(cage->bse[parity]);
    }
    yoffset %= 4;
    xoffset %= 4;

    /* Set or unset the appropriate bit */
    bitmask = 1 << (yoffset*4 + xoffset);
    if (draw_method == DRAW_SET) {
        if (!(*block & bitmask)) {
            *block |= bitmask;
            population++;
        }
    }
    else {
        if (*block & bitmask) {
            *block &= ~bitmask;
            population--;
        }
    }
}

/* Find the coordinates of a live cell and place them in x and y. Prefer cells in active cages (not
 * stable or oscillating), then prefer oscillating cages, then grudgingly pick a stable one. This
 * function will also favor recently-born regions, since it reads from the heads of the cage lists
 * where births are appended. Return TRUE if a live cell was found, FALSE if the grid is empty.
 */
boolean find_active_cell(int32* x, int32* y)
{
    cage_type*  c;
    cage_type*  pick = NULL;
    int32       startx, starty;
    boolean     cur_pref = 0;   /* 0 = none found, 1 = stable, 2 = oscillating, 3 = active */
    boolean     pref;

    if (!population)
        return FALSE;

    for (c = active_cages; c; c=c->next) {
        if (IS_EMPTY(c))
            continue;
        pref = (TURNS_STABLE(c)      ? 1 :
                TURNS_OSCILLATING(c) ? 2 :
                                       3);
        if (pref > cur_pref) {
            pick = c;
            cur_pref = pref;
            if (pref == 3)
                break;
        }
    }

    if (cur_pref < 2) {
        if (predictable_cages_offscreen)
            pick = predictable_cages_offscreen;
        else if (predictable_cages_onscreen)
            pick = predictable_cages_onscreen;
        else if (sleeping_cages)
            pick = sleeping_cages;
        else
            return FALSE;   /* really we should never get here */
    }

    startx = pick->x * CAGE_SIZE;
    starty = pick->y * CAGE_SIZE;
    if (pick->bnw[parity])
        find_live_cell_in_block(pick->bnw[parity], startx, starty, x, y);
    else if (pick->bne[parity])
        find_live_cell_in_block(pick->bne[parity], startx+BLOCK_SIZE, starty, x, y);
    else if (pick->bsw[parity])
        find_live_cell_in_block(pick->bsw[parity], startx, starty+BLOCK_SIZE, x, y);
    else
        find_live_cell_in_block(pick->bse[parity], startx+BLOCK_SIZE, starty+BLOCK_SIZE, x, y);

    return TRUE;
}

/* Load a pattern from the given file, auto-detecting its format (and placing the format in
 * detected_format if non-NULL). Return a load_result_type value indicating whether the load was
 * successful, and if not what error occurred.
 */
load_result_type load_pattern(const char* path, file_format_type* detected_format)
{
    load_result_type  result = LOAD_SUCCESS;
    file_format_type  format = FORMAT_GLF;
    boolean  rle_after_end = FALSE;
    FILE*    f;
    char*    line;
    boolean  valid_xlife;
    int32    x, y;

    /* Open the file for reading */
    f = fopen(path, "r");
    if (!f)
        return LOAD_SYS_ERROR;

    /* Detect the file format, then set the appropriate loader function. Many formats will
     * announce themselves on the first line; alas, not all of them. */
    line = dgets(f);
    if (!line)
        result = LOAD_UNRECOGNIZED_FORMAT;
    else if (STR_STARTS_WITH(line, "#GLF")) {
        if (STR_EQUAL(line+5, GLF_VERSION))
            format = FORMAT_GLF;
        else
            result = LOAD_BAD_GLF_VERSION;
    } else if (STR_STARTS_WITH(line, "#Life")) {
        if (STR_EQUAL(line+6, "1.05"))
            format = FORMAT_LIF_105;
        else if (STR_EQUAL(line+6, "1.06"))
            format = FORMAT_LIF_106;
        else
            result = LOAD_BAD_LIF_VERSION;
    } else {
        /* It's either RLE, XLife, or an invalid file. Check for an RLE header. If none is found,
         * and the file looks vaguely like it could be XLife, choose XLife. */
        valid_xlife = (line[0] == '#' || sscanf(line, "%d %d", &x, &y) == 2);
        while (line[0] == '#') {
            free(line);
            line = dgets(f);
            if (!line)
                break;
        }
        rewind(f);
        if (line && process_rle_header(line, NULL, NULL, NULL, NULL))
            format = FORMAT_RLE;
        else if (valid_xlife) {
            format = FORMAT_XLIFE;
            /* Error out if this is a "structured" XLife file */
            while ((line = dgets(f)) != NULL) {
                if (STR_STARTS_WITH(line, "#B") || STR_STARTS_WITH(line, "#I")) {
                    result = LOAD_STRUCTURED_XLIFE;
                    break;
                }
            }
            rewind(f);
        } else
            result = LOAD_UNRECOGNIZED_FORMAT;
    }
    free(line);

    /* If no valid file format was detected, return an error */
    if (result != LOAD_SUCCESS) {
        fclose(f);
        return result;
    }

    /* We now know the file is valid so clear the grid */
    clear_world();

    /* Load pattern description from #C lines, #D lines, and for the RLE format, lines after the
     * trailing '!' marker. */
    while ((line = dgets(f)) != NULL) {
        if (rle_after_end)
            add_description_line(line);
        else if (format == FORMAT_RLE && line[0] != '#' && strchr(line, '!'))
            rle_after_end = TRUE;
        else if (STR_STARTS_WITH(line, "#D") || STR_STARTS_WITH(line, "#C"))
            add_description_line(line+2);
        free(line);
    }

    /* Call the loader function to do the rest of the work. */
    rewind(f);
    (loaders[format])(f);
    fclose(f);

    if (detected_format)
        *detected_format = format;
    return LOAD_SUCCESS;
}

/* Loops through all cages in the Life world, returning NULL at the end. This function should only
 * be used by one caller at a time, and should be used until NULL. It is safe to move cages between
 * linked lists while this loop is active.
 */
cage_type* loop_cages(void)
{
    static cage_type*  pos = NULL;
    static int32       hash_pos = -1;

    if (pos)
        pos = pos->hash_next;

    if (!pos) {
        hash_pos++;
        while (hash_pos < XY_HASH_SIZE && !cage_hash[hash_pos])
            hash_pos++;
        if (hash_pos == XY_HASH_SIZE) {
            hash_pos = -1;
            return NULL;
        }
        pos = cage_hash[hash_pos];
    }

    return pos;
}

/* Loops through all onscreen (contained in the viewport specified by set_viewport) cages in the
 * Life world, returning NULL at the end. This function should only be used by one caller at a
 * time, and should be used until NULL. It is assumed that cages will not be moved between linked
 * lists while this loop is active.
 */
cage_type* loop_cages_onscreen(void)
{
    static cage_type*  pos = NULL;
    static boolean     processed_sleepers, processed_preds;

    if (pos)
        pos = pos->next;
    else {
        pos = active_cages;
        processed_sleepers = processed_preds = FALSE;
    }
    if (!pos && !processed_sleepers) {
        processed_sleepers = TRUE;
        pos = sleeping_cages;
    }
    if (!pos && !processed_preds) {
        processed_preds = TRUE;
        pos = predictable_cages_onscreen;
    }

    while (pos && !IS_ONSCREEN(pos)) {
        pos = pos->next;
        if (!pos && !processed_sleepers) {
            processed_sleepers = TRUE;
            pos = sleeping_cages;
        }
        if (!pos && !processed_preds) {
            processed_preds = TRUE;
            pos = predictable_cages_onscreen;
        }
    }

    return pos;
}

/* Apply the given bitmasks to the four corners of the cage to reduce its contents, zeroing any
 * cells that are not 1 in the mask.
 */
void mask_cage(cage_type* cage, uint16 nw_mask, uint16 ne_mask, uint16 sw_mask, uint16 se_mask)
{
    uint16  nw, ne, sw, se;
    uint32  old_pop, new_pop;

    nw = cage->bnw[parity];
    ne = cage->bne[parity];
    sw = cage->bsw[parity];
    se = cage->bse[parity];
    old_pop = POPULATION_COUNT(nw, ne, sw, se);
    nw &= nw_mask;
    ne &= ne_mask;
    sw &= sw_mask;
    se &= se_mask;
    new_pop = POPULATION_COUNT(nw, ne, sw, se);

    if (old_pop != new_pop) {
        rattle_cage(cage, TRUE);
        rattle_neighbors(cage);
        cage->bnw[parity] = nw;
        cage->bne[parity] = ne;
        cage->bsw[parity] = sw;
        cage->bse[parity] = se;
        population -= old_pop - new_pop;
    }
}

/* Calculate the next Life generation. If update_display is true, update the display pixmap via
 * calls to the external function draw_life_block.
 */
void next_tick(boolean update_display)
{
    cage_type*  cage;
    cage_type*  next = NULL;
    boolean     oscillating_to_stable;
    int32       turns_stable, turns_oscillating;
    int32       t1, t2;
    int32       x, y;
    uint16      nw, ne, sw, se;
    uint16      old_nw, old_ne, old_sw, old_se;
    uint16      new_nw, new_ne, new_sw, new_se;
    uint16      neigh_nnw, neigh_nne, neigh_ssw, neigh_sse, neigh_wnw, neigh_wsw,
                neigh_ene, neigh_ese, neigh_nw, neigh_ne, neigh_sw, neigh_se;

    t1 = parity;
    t2 = !parity;
    population = sleeping_population + predictable_population[t2];

    for (cage=active_cages; cage; cage=next) {
        /* Store next-pointer in case we end up moving this cage to another linked list */
        next = cage->next;

        /* Extract the four 4x4 blocks in this cage, past and present, as well as any neighboring
         * 4x4 blocks that we'll need to know about.
         */
        nw = cage->bnw[t1];
        ne = cage->bne[t1];
        sw = cage->bsw[t1];
        se = cage->bse[t1];
        old_nw = cage->bnw[t2];
        old_ne = cage->bne[t2];
        old_sw = cage->bsw[t2];
        old_se = cage->bse[t2];
        if (cage->north) {
            neigh_nnw = cage->north->bsw[t1];
            neigh_nne = cage->north->bse[t1];
        } else
            neigh_nnw = neigh_nne = 0;
        if (cage->south) {
            neigh_ssw = cage->south->bnw[t1];
            neigh_sse = cage->south->bne[t1];
        } else
            neigh_ssw = neigh_sse = 0;
        if (cage->west) {
            neigh_wnw = cage->west->bne[t1];
            neigh_wsw = cage->west->bse[t1];
        } else
            neigh_wnw = neigh_wsw = 0;
        if (cage->east) {
            neigh_ene = cage->east->bnw[t1];
            neigh_ese = cage->east->bsw[t1];
        } else
            neigh_ene = neigh_ese = 0;
        neigh_nw = ((cage->nw) ? cage->nw->bse[t1] : 0);
        neigh_ne = ((cage->ne) ? cage->ne->bsw[t1] : 0);
        neigh_sw = ((cage->sw) ? cage->sw->bne[t1] : 0);
        neigh_se = ((cage->se) ? cage->se->bnw[t1] : 0);

        /* Calculate the new 4x4 blocks for this cage */
        new_nw =
            ul_lookup[(nw & 0x0777) | (neigh_nnw & 0x7000) | (neigh_wnw & 0x0888) | (neigh_nw & 0x8000)]   |
            ur_lookup[(nw & 0x0EEE) | (neigh_nnw & 0xE000) | (ne & 0x0111)        | (neigh_nne & 0x1000)]  |
            ll_lookup[(nw & 0x7770) | (sw & 0x0007)        | (neigh_wnw & 0x8880) | (neigh_wsw & 0x0008)]  |
            lr_lookup[(nw & 0xEEE0) | (sw & 0x000E)        | (ne & 0x1110)        | (se & 0x0001)];
        new_ne =
            ul_lookup[(ne & 0x0777) | (neigh_nne & 0x7000) | (nw & 0x0888)        | (neigh_nnw & 0x8000)]  |
            ur_lookup[(ne & 0x0EEE) | (neigh_nne & 0xE000) | (neigh_ene & 0x0111) | (neigh_ne & 0x1000)]   |
            ll_lookup[(ne & 0x7770) | (se & 0x0007)        | (nw & 0x8880)        | (sw & 0x0008)]         |
            lr_lookup[(ne & 0xEEE0) | (se & 0x000E)        | (neigh_ene & 0x1110) | (neigh_ese & 0x0001)];
        new_sw =
            ul_lookup[(sw & 0x0777) | (nw & 0x7000)        | (neigh_wsw & 0x0888) | (neigh_wnw & 0x8000)]  |
            ur_lookup[(sw & 0x0EEE) | (nw & 0xE000)        | (se & 0x0111)        | (ne & 0x1000)]         |
            ll_lookup[(sw & 0x7770) | (neigh_ssw & 0x0007) | (neigh_wsw & 0x8880) | (neigh_sw & 0x0008)]   |
            lr_lookup[(sw & 0xEEE0) | (neigh_ssw & 0x000E) | (se & 0x1110)        | (neigh_sse & 0x0001)];
        new_se =
            ul_lookup[(se & 0x0777) | (ne & 0x7000)        | (sw & 0x0888)        | (nw & 0x8000)]         |
            ur_lookup[(se & 0x0EEE) | (ne & 0xE000)        | (neigh_ese & 0x0111) | (neigh_ene & 0x1000)]  |
            ll_lookup[(se & 0x7770) | (neigh_sse & 0x0007) | (sw & 0x8880)        | (neigh_ssw & 0x0008)]  |
            lr_lookup[(se & 0xEEE0) | (neigh_sse & 0x000E) | (neigh_ese & 0x1110) | (neigh_se & 0x0001)];

        /* Update population count */
        population += POPULATION_COUNT(new_nw, new_ne, new_sw, new_se);

        /* Check if the cage is stable or oscillating, and update TURNS_STABLE and
         * TURNS_OSCILLATING accordingly. If it's been stable awhile, move it to sleeping_cages or
         * dead_cages (if empty). If it's been oscillating awhile, move it to predictable_cages.
         */
        turns_oscillating = TURNS_OSCILLATING(cage);
        oscillating_to_stable = FALSE;
        if (new_nw == nw && new_ne == ne && new_sw == sw && new_se == se) {
            turns_stable = TURNS_STABLE(cage);
            if (turns_stable == MAX_TURNS_STABLE) {
                if (!new_nw && !new_ne && !new_sw && !new_se)
                    kill_cage(cage);
                else
                    tranquilize_cage(cage);
                continue;   /* stable for quite awhile now: no need to redraw, apply new blocks, */
                            /* or rattle neighbors */
            } else {
                INC_TURNS_STABLE(cage);
                if (turns_stable)   /* stable for two ticks now: no need to redraw, apply new */
                    continue;       /* blocks, or rattle neighbors */
                turns_stable++;
                if (turns_oscillating) {
                    RESET_TURNS_OSCILLATING(cage);
                    turns_oscillating = 0;
                    oscillating_to_stable = TRUE;
                }
            }
        } else {
            RESET_TURNS_STABLE(cage);
            turns_stable = 0;
            if (new_nw == old_nw && new_ne == old_ne && new_sw == old_sw && new_se == old_se) {
                if (turns_oscillating == MAX_TURNS_OSCILLATING) {
                    predict_cage(cage);
                    continue;   /* no need to redraw (see below), apply new blocks, or rattle */
                } else {
                    INC_TURNS_OSCILLATING(cage);
                    turns_oscillating++;
                }
            }
            else {
                RESET_TURNS_OSCILLATING(cage);
                turns_oscillating = 0;
            }
        }

        /* Update the Life pixmap if necessary */
        if (!turns_stable && IS_ONSCREEN(cage) && update_display) {
            x = cage->x * CAGE_SIZE;
            y = cage->y * CAGE_SIZE;
            if (new_nw != nw)
                draw_life_block(new_nw, x, y, FALSE);
            if (new_ne != ne)
                draw_life_block(new_ne, x + BLOCK_SIZE, y, FALSE);
            if (new_sw != sw)
                draw_life_block(new_sw, x, y + BLOCK_SIZE, FALSE);
            if (new_se != se)
                draw_life_block(new_se, x + BLOCK_SIZE, y + BLOCK_SIZE, FALSE);
        }

        if (turns_oscillating)
            continue;   /* no need to apply blocks or rattle neighbors */

        /* Apply the new blocks */
        cage->bnw[t2] = new_nw;
        cage->bne[t2] = new_ne;
        cage->bsw[t2] = new_sw;
        cage->bse[t2] = new_se;

        /* No need to rattle neighbors if we've been stable for one tick, unless we were
         * previously oscillating */
        if (turns_stable && !oscillating_to_stable)
            continue;

        /* Rattle/find/create neighbors as needed */
        x = cage->x;
        y = cage->y;
        if ((new_nw & NORTH_EDGE_MASK) != (nw & NORTH_EDGE_MASK) ||
            (new_ne & NORTH_EDGE_MASK) != (ne & NORTH_EDGE_MASK) ||
            (new_nw & NORTH_EDGE_MASK) != (old_nw & NORTH_EDGE_MASK) ||
            (new_ne & NORTH_EDGE_MASK) != (old_ne & NORTH_EDGE_MASK)) {
            if (cage->north)
                rattle_cage(cage->north, FALSE);
            else {
                cage->north = find_rattle_birth(x, y-1);
                if (cage->north)
                    cage->north->south = cage;
            }
        }
        if ((new_sw & SOUTH_EDGE_MASK) != (sw & SOUTH_EDGE_MASK) ||
            (new_se & SOUTH_EDGE_MASK) != (se & SOUTH_EDGE_MASK) ||
            (new_sw & SOUTH_EDGE_MASK) != (old_sw & SOUTH_EDGE_MASK) ||
            (new_se & SOUTH_EDGE_MASK) != (old_se & SOUTH_EDGE_MASK)) {
            if (cage->south)
                rattle_cage(cage->south, FALSE);
            else {
                cage->south = find_rattle_birth(x, y+1);
                if (cage->south)
                    cage->south->north = cage;
            }
        }
        if ((new_nw & WEST_EDGE_MASK) != (nw & WEST_EDGE_MASK) ||
            (new_sw & WEST_EDGE_MASK) != (sw & WEST_EDGE_MASK) ||
            (new_nw & WEST_EDGE_MASK) != (old_nw & WEST_EDGE_MASK) ||
            (new_sw & WEST_EDGE_MASK) != (old_sw & WEST_EDGE_MASK)) {
            if (cage->west)
                rattle_cage(cage->west, FALSE);
            else {
                cage->west = find_rattle_birth(x-1, y);
                if (cage->west)
                    cage->west->east = cage;
            }
        }
        if ((new_ne & EAST_EDGE_MASK) != (ne & EAST_EDGE_MASK) ||
            (new_se & EAST_EDGE_MASK) != (se & EAST_EDGE_MASK) ||
            (new_ne & EAST_EDGE_MASK) != (old_ne & EAST_EDGE_MASK) ||
            (new_se & EAST_EDGE_MASK) != (old_se & EAST_EDGE_MASK)) {
            if (cage->east)
                rattle_cage(cage->east, FALSE);
            else {
                cage->east = find_rattle_birth(x+1, y);
                if (cage->east)
                    cage->east->west = cage;
            }
        }
        if ((new_nw & NW_CORNER_MASK) != (nw & NW_CORNER_MASK) ||
            (new_nw & NW_CORNER_MASK) != (old_nw & NW_CORNER_MASK)) {
            if (cage->nw)
                rattle_cage(cage->nw, FALSE);
            else {
                cage->nw = find_rattle_birth(x-1, y-1);
                if (cage->nw)
                    cage->nw->se = cage;
            }
        }
        if ((new_ne & NE_CORNER_MASK) != (ne & NE_CORNER_MASK) ||
            (new_ne & NE_CORNER_MASK) != (old_ne & NE_CORNER_MASK)) {
            if (cage->ne)
                rattle_cage(cage->ne, FALSE);
            else {
                cage->ne = find_rattle_birth(x+1, y-1);
                if (cage->ne)
                    cage->ne->sw = cage;
            }
        }
        if ((new_sw & SW_CORNER_MASK) != (sw & SW_CORNER_MASK) ||
            (new_sw & SW_CORNER_MASK) != (old_sw & SW_CORNER_MASK)) {
            if (cage->sw)
                rattle_cage(cage->sw, FALSE);
            else {
                cage->sw = find_rattle_birth(x-1, y+1);
                if (cage->sw)
                    cage->sw->ne = cage;
            }
        }
        if ((new_se & SE_CORNER_MASK) != (se & SE_CORNER_MASK) ||
            (new_se & SE_CORNER_MASK) != (old_se & SE_CORNER_MASK)) {
            if (cage->se)
                rattle_cage(cage->se, FALSE);
            else {
                cage->se = find_rattle_birth(x+1, y+1);
                if (cage->se)
                    cage->se->nw = cage;
            }
        }
    }

    /* Update Life pixmap for on-screen predictable cages */
    if (update_display) {
        for (cage=predictable_cages_onscreen; cage; cage=cage->next) {
            x = cage->x * CAGE_SIZE;
            y = cage->y * CAGE_SIZE;
            draw_life_block(cage->bnw[t2], x, y, FALSE);
            draw_life_block(cage->bne[t2], x + BLOCK_SIZE, y, FALSE);
            draw_life_block(cage->bsw[t2], x, y + BLOCK_SIZE, FALSE);
            draw_life_block(cage->bse[t2], x + BLOCK_SIZE, y + BLOCK_SIZE, FALSE);
        }
    }

    tick++;
    parity = !parity;
}

/* Save the pattern to the given file in the given format. Return a save_result_type value
 * indicating whether the save was successful, and if not what error occurred.
 */
save_result_type save_pattern(const char* path, file_format_type format)
{
    FILE*  f;
    int32  i;

    /* Return SAVE_DESC_INVALID if we're using one of the LIF formats or the RLE format and the
     * description is out of bounds. */
    if (format == FORMAT_LIF_105 || format == FORMAT_LIF_106) {
        if (desc_num_lines > LIF_DESC_MAX_LINES)
            return SAVE_DESC_INVALID;
        for (i=0; i < desc_num_lines; i++) {
            if (strlen(pattern_description[i]) > LIF_DESC_MAX_COLS)
                return SAVE_DESC_INVALID;
        }
    } else if (format == FORMAT_RLE) {
        for (i=0; i < desc_num_lines; i++) {
            if (strlen(pattern_description[i]) > RLE_DESC_MAX_COLS)
                return SAVE_DESC_INVALID;
        }
    }

    /* Open the file, then call the appropriate saver function to do the work */
    f = fopen(path, "w");
    if (!f)
        return SAVE_SYS_ERROR;
    (savers[format])(f);
    if (fclose(f) != 0)
        return SAVE_SYS_ERROR;
    return SAVE_SUCCESS;
}

/* Set the pattern description from desc, a multiline string.
 */
void set_description(const char* description)
{
    char*  desc;
    char*  newline_pos;
    char*  p;
    int32  line_num;
    int32  i;

    clear_description();
    desc = safe_strdup(description);

    /* Strip trailing newlines */
    for (i = strlen(desc) - 1;
         i >= 0 && desc[i] == '\n';
         i--) {}
    if (i < 0) {
        free(desc);
        return;
    }
    desc[i+1] = '\0';

    /* Count newlines to determine number of lines, then allocate the array */
    for (i=0, desc_num_lines=1; desc[i]; i++) {
        if (desc[i] == '\n')
            desc_num_lines++;
    }
    pattern_description = safe_malloc(desc_num_lines * sizeof(char*));

    /* Copy the text into pattern_description */
    for (p = desc, line_num = 0;
         p;
         p = (newline_pos ? newline_pos+1 : NULL), line_num++) {
        newline_pos = strchr(p, '\n');
        if (newline_pos)
            *newline_pos = '\0';
        pattern_description[line_num] = safe_strdup(p);
    }

    free(desc);
}

/* Set the current Life viewport (in cell coordinates), which is used to determine when to call
 * draw_life_block() in next_tick().
 */
void set_viewport(int32 xstart, int32 ystart, int32 xend, int32 yend)
{
    cage_type*  cage;
    cage_type*  next;
    cage_type*  org_pred_cages_offscreen;

    viewport_xstart = xstart;
    viewport_ystart = ystart;
    viewport_xend = xend;
    viewport_yend = yend;

    for (cage=active_cages; cage; cage=cage->next)
        update_onscreen_flag(cage);
    for (cage=sleeping_cages; cage; cage=cage->next)
        update_onscreen_flag(cage);
    org_pred_cages_offscreen = predictable_cages_offscreen;
    for (cage=predictable_cages_onscreen; cage; cage=next) {
        next = cage->next;
        update_onscreen_flag(cage);
        if (!IS_ONSCREEN(cage))
            move_cage(cage, &predictable_cages_onscreen, &predictable_cages_offscreen);
    }
    for (cage=org_pred_cages_offscreen; cage; cage=next) {
        next = cage->next;
        update_onscreen_flag(cage);
        if (IS_ONSCREEN(cage))
            move_cage(cage, &predictable_cages_offscreen, &predictable_cages_onscreen);
    }
}

/*** Private Functions ***/

/* Append the given line of text to the pattern description.
 */
static void add_description_line(const char* line)
{
    char* str;

    if (desc_num_lines == 0 && is_blank(line))
        return;
    str = safe_strdup(line);
    trim_whitespace(str);
    pattern_description = safe_realloc(pattern_description,
                                       (desc_num_lines+1) * sizeof(char*));
    pattern_description[desc_num_lines++] = str;
}

/* Allocate the cage at the given x, y position, find its neighbors, and insert it into
 * the active cages list and cage hash. Return a pointer to the new cage. If x and/or
 * y is out of bounds, return NULL.
 */
static cage_type* birth_cage(int32 x, int32 y)
{
    cage_type*  cage;
    uint16      hash;

    /* Return NULL if x and/or y is out of bounds */
    if (x < 0 || x >= WORLD_CAGES || y < 0 || y >= WORLD_CAGES)
        return NULL;

    /* Allocate, set X and Y */
    if (dead_cages) {
        cage = dead_cages;
        dead_cages = dead_cages->next;
        memset(cage, 0, sizeof(cage_type));
    } else
        cage = safe_calloc(1, sizeof(cage_type));
    cage->x = x;
    cage->y = y;

    /* Set whether it's onscreen or offscreen */
    update_onscreen_flag(cage);

    /* Find allocated neighbors */
    cage->north = find_cage(x, y-1);
    if (cage->north)
        cage->north->south = cage;
    cage->south = find_cage(x, y+1);
    if (cage->south)
        cage->south->north = cage;
    cage->west = find_cage(x-1, y);
    if (cage->west)
        cage->west->east = cage;
    cage->east = find_cage(x+1, y);
    if (cage->east)
        cage->east->west = cage;
    cage->nw = find_cage(x-1, y-1);
    if (cage->nw)
        cage->nw->se = cage;
    cage->ne = find_cage(x+1, y-1);
    if (cage->ne)
        cage->ne->sw = cage;
    cage->sw = find_cage(x-1, y+1);
    if (cage->sw)
        cage->sw->ne = cage;
    cage->se = find_cage(x+1, y+1);
    if (cage->se)
        cage->se->nw = cage;

    /* Insert into active cages list */
    cage->prev = NULL;
    cage->next = active_cages;
    if (active_cages)
        active_cages->prev = cage;
    active_cages = cage;

    /* Insert into cage hash */
    hash = XY_HASH(x,y);
    cage->hash_prev = NULL;
    cage->hash_next = cage_hash[hash];
    if (cage_hash[hash])
        cage_hash[hash]->hash_prev = cage;
    cage_hash[hash] = cage;

    num_cages++;
    return cage;
}

/* Clear the pattern description and free all memory associated with it.
 */
static void clear_description(void)
{
    int32  i;

    for (i=0; i < desc_num_lines; i++)
        free(pattern_description[i]);
    free(pattern_description);
    pattern_description = NULL;
    desc_num_lines = 0;
}

/* Search the cage hash for the cage with the given XY coordinates. Return a pointer to
 * the cage, or NULL if it isn't allocated.
 */
static cage_type* find_cage(int32 x, int32 y)
{
    uint16      hash;
    cage_type*  cage;

    if (x < 0 || x >= WORLD_CAGES || y < 0 || y >= WORLD_CAGES)
        return NULL;
    hash = XY_HASH(x,y);
    for (cage=cage_hash[hash];
         cage && !(cage->x == x && cage->y == y);
         cage = cage->hash_next) {}
    return cage;
}

/* Find a live cell in the given block (which has the given offsets) and return its cordinates
 * in x and y.
 */
void find_live_cell_in_block(uint16 block, int32 xoff, int32 yoff, int32* x, int32* y)
{
    int32  b;

    for (b=0; b < 16; b++) {
        if (block & (1 << b)) {
            *x = b%4 + xoff;
            *y = b/4 + yoff;
            return;
        }
    }
}

/* Search the cage hash for the cage with the given XY coordinates. If found, rattle it
 * and return a pointer to it, otherwise birth and return a pointer to a new cage.
 */
static cage_type* find_rattle_birth(int32 x, int32 y)
{
    cage_type* c;

    c = find_cage(x, y);
    if (c)
        rattle_cage(c, FALSE);
    else
        c = birth_cage(x, y);
    return c;
}

/* Move a cage into the dead_cages pool, deregister it from the hash, and set neighboring pointers
 * to it to NULL.
 */
static void kill_cage(cage_type* cage)
{
    /* Set neighboring pointers to self to NULL */
    if (cage->north)
        cage->north->south = NULL;
    if (cage->south)
        cage->south->north = NULL;
    if (cage->west)
        cage->west->east = NULL;
    if (cage->east)
        cage->east->west = NULL;
    if (cage->nw)
        cage->nw->se = NULL;
    if (cage->ne)
        cage->ne->sw = NULL;
    if (cage->sw)
        cage->sw->ne = NULL;
    if (cage->se)
        cage->se->nw = NULL;

    /* Move into dead_cages list */
    if (cage->prev)
        cage->prev->next = cage->next;
    else
        active_cages = cage->next;
    if (cage->next)
        cage->next->prev = cage->prev;
    cage->next = dead_cages;
    dead_cages = cage;

    /* Deregister from the cage hash */
    if (cage->hash_prev)
        cage->hash_prev->hash_next = cage->hash_next;
    else
        cage_hash[XY_HASH(cage->x, cage->y)] = cage->hash_next;
    if (cage->hash_next)
        cage->hash_next->hash_prev = cage->hash_prev;

    num_cages--;
}

/* Move the given cage from its place in old_list to the front of new_list (both doubly-linked
 * lists).
 */
static void move_cage(cage_type* cage, cage_type** old_list, cage_type** new_list)
{
    if (cage->prev)
        cage->prev->next = cage->next;
    else
        *old_list = cage->next;
    if (cage->next)
        cage->next->prev = cage->prev;
    cage->prev = NULL;
    cage->next = *new_list;
    if (*new_list)
        (*new_list)->prev = cage;
    *new_list = cage;
}

/* Flag a cage as predictable and move it to the appropriate list, adding its live cell counts for
 * odd and even ticks to predictable_population[2].
 */
static void predict_cage(cage_type* cage)
{
    SET_PREDICTABLE(cage);
    move_cage(cage, &active_cages,
              (IS_ONSCREEN(cage) ? &predictable_cages_onscreen : &predictable_cages_offscreen));
    predictable_population[0] += POPULATION_COUNT(cage->bnw[0], cage->bne[0], cage->bsw[0],
                                                  cage->bse[0]);
    predictable_population[1] += POPULATION_COUNT(cage->bnw[1], cage->bne[1], cage->bsw[1],
                                                  cage->bse[1]);
}

/* If cage_changed is true, set TURNS_STABLE and TURNS_OSCILLATING to 0. Otherwise, just make
 * sure they aren't too close to maxed out. If a cage is sleeping or predictable, also remove said
 * flag and move the cage to the front of active_cages, subtracting its live cell count from
 * sleeping_population or predictable_population[2].
 */
static void rattle_cage(cage_type* cage, boolean cage_changed)
{
    if (cage_changed) {
        RESET_TURNS_STABLE(cage);
        RESET_TURNS_OSCILLATING(cage);
    } else {
        while (TURNS_STABLE(cage) > MAX_TURNS_STABLE-2)
            DEC_TURNS_STABLE(cage);
        while (TURNS_OSCILLATING(cage) > MAX_TURNS_OSCILLATING-2)
            DEC_TURNS_OSCILLATING(cage);
    }

    if (IS_ASLEEP(cage)) {
        SET_AWAKE(cage);
        move_cage(cage, &sleeping_cages, &active_cages);
        sleeping_population -= POPULATION_COUNT(cage->bnw[0], cage->bne[0], cage->bsw[0],
                                                cage->bse[0]);
    } else if (IS_PREDICTABLE(cage)) {
        SET_UNPREDICTABLE(cage);
        move_cage(cage,
                  (IS_ONSCREEN(cage) ? &predictable_cages_onscreen : &predictable_cages_offscreen),
                  &active_cages);
        predictable_population[0] -= POPULATION_COUNT(cage->bnw[0], cage->bne[0], cage->bsw[0],
                                                      cage->bse[0]);
        predictable_population[1] -= POPULATION_COUNT(cage->bnw[1], cage->bne[1], cage->bsw[1],
                                                      cage->bse[1]);
    }
}

/* Find and rattle all neighbors of the given cage. Give birth to any neighbors that don't exist
 * yet.
 */
static void rattle_neighbors(cage_type* cage)
{
    if (cage->north)
        rattle_cage(cage->north, FALSE);
    else {
        cage->north = find_rattle_birth(cage->x, cage->y-1);
        if (cage->north)
            cage->north->south = cage;
    }
    if (cage->south)
        rattle_cage(cage->south, FALSE);
    else {
        cage->south = find_rattle_birth(cage->x, cage->y+1);
        if (cage->south)
            cage->south->north = cage;
    }
    if (cage->west)
        rattle_cage(cage->west, FALSE);
    else {
        cage->west = find_rattle_birth(cage->x-1, cage->y);
        if (cage->west)
            cage->west->east = cage;
    }
    if (cage->east)
        rattle_cage(cage->east, FALSE);
    else {
        cage->east = find_rattle_birth(cage->x+1, cage->y);
        if (cage->east)
            cage->east->west = cage;
    }
    if (cage->nw)
        rattle_cage(cage->nw, FALSE);
    else {
        cage->nw = find_rattle_birth(cage->x-1, cage->y-1);
        if (cage->nw)
            cage->nw->se = cage;
    }
    if (cage->ne)
        rattle_cage(cage->ne, FALSE);
    else {
        cage->ne = find_rattle_birth(cage->x+1, cage->y-1);
        if (cage->ne)
            cage->ne->sw = cage;
    }
    if (cage->sw)
        rattle_cage(cage->sw, FALSE);
    else {
        cage->sw = find_rattle_birth(cage->x-1, cage->y+1);
        if (cage->sw)
            cage->sw->ne = cage;
    }
    if (cage->se)
        rattle_cage(cage->se, FALSE);
    else {
        cage->se = find_rattle_birth(cage->x+1, cage->y+1);
        if (cage->se)
            cage->se->nw = cage;
    }
}

/* Flag a cage as asleep and move it to sleeping_cages, adding its live cell count to
 * sleeping_population.
 */
static void tranquilize_cage(cage_type* cage)
{
    SET_ASLEEP(cage);
    move_cage(cage, &active_cages, &sleeping_cages);
    sleeping_population += POPULATION_COUNT(cage->bnw[0], cage->bne[0], cage->bsw[0],
                                            cage->bse[0]);
}

/* Set or clear the "onscreen" flag for the given cage, depending on viewport settings.
 */
static void update_onscreen_flag(cage_type* cage)
{
    int32  x, y;

    x = cage->x * CAGE_SIZE;
    y = cage->y * CAGE_SIZE;
    if (x + CAGE_SIZE > viewport_xstart && x <= viewport_xend &&
        y + CAGE_SIZE > viewport_ystart && y <= viewport_yend) {
        SET_ONSCREEN(cage);
    }
    else
        SET_OFFSCREEN(cage);
}
