/**
 **************************************************
 * @file        Inkplate5_Game_Of_Life.ino
 * @brief       Conway’s Game of Life animation (community example) for Soldered Inkplate 5.
 *
 * @details     Community-created example that runs Conway’s Game of Life on the
 *              Inkplate 5 e-paper display. A random initial grid is generated,
 *              then the cellular automaton evolves over time. The animation is
 *              drawn using rectangle fills per cell and updated using partial
 *              updates for speed, with an occasional full refresh to reduce
 *              ghosting and maintain image quality.
 *
 * Requirements:
 * - Board:      Soldered Inkplate 5
 * - Hardware:   Inkplate 5, USB cable
 * - Extra:      None
 *
 * Configuration:
 * - Boards Manager -> Inkplate Boards -> Soldered Inkplate5
 * - Serial settings: 115200 baud (optional)
 *
 * Don't have Inkplate Boards in Arduino Boards Manager?
 * See https://docs.soldered.com/inkplate/10/quick-start-guide/
 *
 * How to use:
 * 1) Upload the sketch to Inkplate5.
 * 2) The program generates a random initial state and starts the simulation.
 * 3) Watch the Game of Life evolve on the e-paper display.
 *
 * Expected output:
 * - A continuously updating Conway’s Game of Life animation.
 * - Periodic full refresh after a number of partial updates.
 * - Automatic re-randomization when the simulation becomes too static.
 *
 * Notes:
 * - This example uses 1-bit (black & white) mode.
 * - Partial updates are used for faster animation.
 * - A full refresh is performed periodically (FULLREFRESH) to reduce ghosting.
 * - Original community author: claud9999 (https://github.com/claud9999)
 *
 * Docs:         https://docs.soldered.com/inkplate
 * Support:      https://forum.soldered.com/
 *
 * @author      claud9999 (community), adapted for Soldered examples
 * @date        2021-02-11
 * @license     GNU GPL V3
 **************************************************/

// Next 3 lines are a precaution, you can ignore those, and the example would also work without them
#ifndef ARDUINO_INKPLATE5
#error "Wrong board selection for this example, please select Soldered Inkplate5 in the boards menu."
#endif

#include "Inkplate.h"

Inkplate display(INKPLATE_1BIT);

#define FULLREFRESH 40

#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[(E_INK_WIDTH / MIN_CELLSZ) * (E_INK_HEIGHT / MIN_CELLSZ)],
    grid_b[(E_INK_WIDTH / MIN_CELLSZ) * (E_INK_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 = E_INK_WIDTH / cell_size;
    rows = E_INK_HEIGHT / cell_size;

    cell_maxage = (cell_size / 2) - 1;
    // Compute the "extra" space not covered by this grid, offset by half
    offset_x = (E_INK_WIDTH - cols * cell_size) / 2;
    offset_y = (E_INK_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);
    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();
}
