/**
 **************************************************
 * @file        Inkplate6PLUS_Game_Of_Life.ino
 * @brief       Conway’s Game of Life animation on Inkplate 6PLUS using fast
 *              partial e-paper updates.
 *
 * @details     This community example runs Conway’s Game of Life on the Inkplate
 *              6PLUS e-paper display. The simulation is rendered as a grid of
 *              square cells whose size is randomized on reset to vary the look
 *              and performance. Cells “age” visually: newly born cells are drawn
 *              solid black, while older living cells get a white interior that
 *              expands with age, creating a distinctive fade/outline effect.
 *
 *              The sketch uses double-buffered grids (current/next) and toroidal
 *              wraparound at the edges (neighbors beyond an edge wrap to the
 *              opposite side). The display runs in 1-bit (BW) mode and uses
 *              partialUpdate() for most frames. A full refresh (display()) is
 *              forced periodically to reduce ghosting.
 *
 * Requirements:
 * - Board:      Soldered Inkplate 6PLUS
 * - Hardware:   Inkplate 6PLUS, USB cable
 * - Extra:      none
 *
 * Configuration:
 * - Boards Manager -> Inkplate Boards -> Soldered Inkplate6PLUS
 * - Serial settings: 115200 baud (optional; not required for operation)
 *
 * Don't have Inkplate Boards in Arduino Boards Manager?
 * See https://docs.soldered.com/inkplate/6PLUS/quick-start-guide/
 *
 * How to use:
 * 1) Select the board "Soldered Inkplate 6PLUS" and upload the sketch.
 * 2) The Game of Life starts automatically after boot.
 * 3) The simulation occasionally re-randomizes the grid (and cell size) when
 *    activity drops below a threshold to keep the animation interesting.
 *
 * Expected output:
 * - Display: Continuous Game of Life animation with cell aging visuals.
 * - Refresh behavior: Mostly partial updates, with a full refresh every
 *   FULLREFRESH frames.
 *
 * Notes:
 * - Display mode is 1-bit (BW). Partial updates are used heavily for speed.
 * - E-paper partial updates accumulate ghosting; periodic full refresh
 *   (display()) is required. This example performs a full refresh every
 *   FULLREFRESH frames (default: 40).
 * - This example does not use the touchscreen and does not use deep sleep.
 *
 * Docs:         https://docs.soldered.com/inkplate
 * Support:      https://forum.soldered.com/
 *
 * @author      claud9999 (community example), adapted for Soldered Inkplate 6PLUS
 * @date        2020-09-24
 * @license     GNU GPL V3
 **************************************************/

// Next 3 lines are a precaution, you can ignore those, and the example would also work without them
// Next 3 lines are a precaution, you can ignore those, and the example would also work without them
#if !defined(ARDUINO_INKPLATE6PLUS) && !defined(ARDUINO_INKPLATE6PLUSV2)
#error "Wrong board selection for this example, please select e-radionica Inkplate 6Plus or Soldered Inkplate 6Plus in the boards menu."
#endif

#include <Inkplate.h>

Inkplate display(INKPLATE_1BIT);

#define FULLREFRESH 40

#define SCREEN_WIDTH  E_INK_WIDTH
#define SCREEN_HEIGHT E_INK_HEIGHT

#define MIN_CELLSZ 8
#define MAX_CELLSZ 40

// Initialize dimensions to the minimum size cell, maximum size arrays
int cell_size = 0, cols = 0, rows = 0, cell_maxage = 0, offset_x = 0, offset_y = 0;

char grid_a[(SCREEN_WIDTH / MIN_CELLSZ) * (SCREEN_HEIGHT / MIN_CELLSZ)],
    grid_b[(SCREEN_WIDTH / MIN_CELLSZ) * (SCREEN_HEIGHT / MIN_CELLSZ)];

char *grid_curr = grid_a, *grid_next = grid_b, *grid_tmp = NULL, cell_curr, cell_next;

int dx = 0, dy = 0, nx = 0, ny = 0, neighbors = 0, cell_delta = 0, frame_count = 0;

void randomize()
{
    cell_size = random(MIN_CELLSZ, MAX_CELLSZ + 1);

    // Compute the (rounded-down) number of rows and columns
    cols = SCREEN_WIDTH / cell_size;
    rows = SCREEN_HEIGHT / cell_size;

    cell_maxage = (cell_size / 2) - 1;
    // Compute the "extra" space not covered by this grid, offset by half
    offset_x = (SCREEN_WIDTH - cols * cell_size) / 2;
    offset_y = (SCREEN_HEIGHT - rows * cell_size) / 2;

    display.display();

    display.clearDisplay();

    // Compute a random density...
    int density = random(5, 15);

    // And for that density, populate the initial grid
    for (int j = 0; j < cols; j++)
    {
        for (int i = 0; i < rows; i++)
            grid_curr[j + i * cols] = random(density) == 0;
    }

    frame_count = 0;
}

void setup()
{
    Serial.begin(115200);
    // Uncomment this line if you have a USB Power Only Inkplate6PLUS
    // Must be called before display.begin()!
    //display.setInkplatePowerMode(INKPLATE_USB_PWR_ONLY);
    display.begin();
    randomize();
}

void loop()
{
    cell_delta = 0;
    for (int j = 0; j < cols; j++)
    {
        for (int i = 0; i < rows; i++)
        {
            // Count neighboring cells
            neighbors = 0;
            for (dx = -1; dx < 2; dx++)
            {
                for (dy = -1; dy < 2; dy++)
                {
                    if (dx == 0 && dy == 0)
                        continue; // Skip "me"

                    nx = j + dx;
                    if (nx < 0)
                        nx = cols - 1;
                    else if (nx >= cols)
                        nx = 0;
                    ny = i + dy;
                    if (ny < 0)
                        ny = rows - 1;
                    else if (ny >= rows)
                        ny = 0;

                    if (grid_curr[nx + ny * cols])
                        neighbors++;
                }
            }

            cell_curr = grid_curr[j + i * cols];
            cell_next = 0;
            switch (neighbors)
            {
            case 2: // Alive with 2 neighbors remains alive
                if (!cell_curr)
                    break;
                // Else cell is alive drop through
            case 3: // 3 neighbors == alive
                cell_next = cell_curr + 1;
                if (cell_next > cell_maxage)
                    cell_next = cell_maxage;
            }

            if ((cell_next != 0 && cell_curr == 0) || (cell_next == 0 && cell_curr != 0))
                cell_delta++;

            if (cell_next)
            {
                // If this is a new cell, paint it black
                if (cell_next == 1)
                    display.fillRect(j * cell_size + offset_x, i * cell_size + offset_y, cell_size, cell_size, 1);
                // Otherwise paint the inside white depending on how old it is
                else
                    display.fillRect(j * cell_size + cell_size / 2 - cell_next + offset_x,
                                     i * cell_size + cell_size / 2 - cell_next + offset_y, cell_next * 2, cell_next * 2,
                                     0);
            }
            else if (cell_curr)
            {
                // Otherwise it's died, paint the whole cell white
                display.fillRect(j * cell_size + offset_x, i * cell_size + offset_y, cell_size, cell_size, 0);
            }

            grid_next[j + i * cols] = cell_next;
        }
    }
    // Swap which grid is current
    grid_tmp = grid_next;
    grid_next = grid_curr;
    grid_curr = grid_tmp;

    // The longer this goes, the more cells this has,
    // The more change is required or we reset
    if (cell_delta * cell_size < frame_count)
        randomize();
    else
        frame_count++;

    // Update whole screen after FULLREFRESH partials
    if (frame_count % FULLREFRESH == 0)
        display.display();
    else
        display.partialUpdate();
}
