#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// OLED display setup
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define GRID_WIDTH 8    // Smaller 8x8 grid for Game of Life
#define GRID_HEIGHT 8
#define CELL_SIZE 16    // Larger cell size to fit smaller grid

// Declaration for SSD1306 display
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Grids to store cell states
int grid[GRID_WIDTH][GRID_HEIGHT];
int nextGrid[GRID_WIDTH][GRID_HEIGHT];

// Part 1: Setup the OLED display and initialize the grid
void setup() {
  Serial.begin(9600);  // Start serial communication for debugging

  // Initialize OLED
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);  // Loop forever if OLED setup fails
  }

  // Clear display at the start
  display.clearDisplay();
  display.display();
  Serial.println(F("OLED Initialized"));

  // Initialize the grid with random states
  initializeGrid();

  // Draw the initial grid state
  drawGrid();
}

// Part 2: Initialize the grid with random cells
void initializeGrid() {
  Serial.println(F("Initializing grid, with 33% live cells"));
  for (int x = 0; x < GRID_WIDTH; x++) {
    for (int y = 0; y < GRID_HEIGHT; y++) {
      //33% cell alive (1); 67% chance cell is dead (0)
      grid[x][y] = (random(3) == 0) ? 1 : 0;  
      Serial.print(F("grid["));
      Serial.print(x);
      Serial.print(F("]["));
      Serial.print(y);
      Serial.print(F("] = "));
      Serial.println(grid[x][y]);  // Print cell state for debugging
    }
  }
  Serial.println(F("Grid initialization complete"));
}

// Part 3: Implement Game of Life rules and update the grid
void updateGrid() {
  for (int x = 0; x < GRID_WIDTH; x++) {
    for (int y = 0; y < GRID_HEIGHT; y++) {
      int neighbors = countNeighbors(x, y);

      // Apply the Game of Life rules
      if (grid[x][y] == 1) {
        if (neighbors < 2 || neighbors > 3) {
          nextGrid[x][y] = 0;  // Cell dies
        } else {
          nextGrid[x][y] = 1;  // Cell survives
        }
      } else {
        if (neighbors == 3) {
          nextGrid[x][y] = 1;  // Cell becomes alive
        } else {
          nextGrid[x][y] = 0;  // Cell remains dead
        }
      }
    }
  }

  // Copy nextGrid to grid
  for (int x = 0; x < GRID_WIDTH; x++) {
    for (int y = 0; y < GRID_HEIGHT; y++) {
      grid[x][y] = nextGrid[x][y];
    }
  }
}

// Part 3: Count live neighbors around a cell
int countNeighbors(int x, int y) {
  int sum = 0;
  for (int i = -1; i <= 1; i++) {
    for (int j = -1; j <= 1; j++) {
      int col = (x + i + GRID_WIDTH) % GRID_WIDTH;
      int row = (y + j + GRID_HEIGHT) % GRID_HEIGHT;
      sum += grid[col][row];
    }
  }
  sum -= grid[x][y];  // Remove the cell itself from the count
  return sum;
}

// Part 4: Draw the grid on the OLED display
void drawGrid() {
  display.clearDisplay();  // Clear the display before drawing
  
  for (int x = 0; x < GRID_WIDTH; x++) {
    for (int y = 0; y < GRID_HEIGHT; y++) {
      if (grid[x][y] == 1) {
        // Draw a filled square for live cells
        display.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE, SSD1306_WHITE);
      } else {
        // Optionally, draw empty squares for dead cells (for a grid effect)
        display.drawRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE, SSD1306_WHITE);
      }
    }
  }

  display.display();  // Show the updated grid on the screen
  Serial.println(F("Grid drawn"));
}

// Part 5: Main loop, update the grid and display the next generation
void loop() {
  drawGrid();    // Draw the current generation
  updateGrid();  // Apply the Game of Life rules to get the next generation
  delay(250);    // Add a delay for visual effect
}