#include <Arduino.h>
#include "display.h"
#include "gameoflife.h"

// https://github.com/Stavrosfil/game-of-life-esp32

int g[SCREEN_HEIGHT][SCREEN_WIDTH];
int arrayCopy[SCREEN_HEIGHT][SCREEN_WIDTH];

bool runGame = true;
bool gameOver = false;

int currentTick;
int finalTicks;
int cellsAliveBefore;
int noEvolutionTicks;

const uint scoreScreenTimeout = 5000;
unsigned long currentMillis;

void setupGameOfLife()
{
  gameOver = false;
  currentTick = 0;
  finalTicks = 0;
  cellsAliveBefore = 0;
  noEvolutionTicks = 0;
  randomSeed(analogRead(34));
  createRandomMatrix(g);
  for (int i = 0; i < 10; i++)
  {
    addGlider(random(SCREEN_HEIGHT), random(SCREEN_WIDTH), g);
  }
}

void createRandomMatrix(int (&a)[SCREEN_HEIGHT][SCREEN_WIDTH])
{
  for (int i = 0; i < SCREEN_HEIGHT; i++)
  {
    for (int j = 0; j < SCREEN_WIDTH; j++)
    {
      a[i][j] = random(100) < 25 ? 1 : 0;
    }
  }
}

void gameOfLife(int (&a)[SCREEN_HEIGHT][SCREEN_WIDTH])
{
  currentTick++;
  int cellsAliveNow = 0;

  for (int i = 0; i < SCREEN_HEIGHT; i++)
  {
    for (int j = 0; j < SCREEN_WIDTH; j++)
    {
      arrayCopy[i][j] = a[i][j];
    }
  }

  for (int i = 0; i < SCREEN_HEIGHT; i++)
  {
    for (int j = 0; j < SCREEN_WIDTH; j++)
    {
      int total = (a[i][(j - 1) % SCREEN_WIDTH] + a[i][(j + 1) % SCREEN_WIDTH] + a[(i - 1) % SCREEN_HEIGHT][j] +
                   a[(i + 1) % SCREEN_HEIGHT][j] + a[(i - 1) % SCREEN_HEIGHT][(j - 1) % SCREEN_WIDTH] +
                   a[(i - 1) % SCREEN_HEIGHT][(j + 1) % SCREEN_WIDTH] +
                   a[(i + 1) % SCREEN_HEIGHT][(j - 1) % SCREEN_WIDTH] +
                   a[(i + 1) % SCREEN_HEIGHT][(j + 1) % SCREEN_WIDTH]);

      if (a[i][j] == 1)
      {
        if (total < 2 || total > 3)
        {
          arrayCopy[i][j] = 0;
          cellsAliveNow--;
        }
      }
      else if (total == 3)
      {
        arrayCopy[i][j] = 1;
        cellsAliveNow++;
      }
    }
  }

  for (int i = 0; i < SCREEN_HEIGHT; i++)
  {
    for (int j = 0; j < SCREEN_WIDTH; j++)
    {
      a[i][j] = arrayCopy[i][j];
    }
  }

  if (currentTick % 2)
  {
    cellsAliveBefore = cellsAliveNow;
  }

  if (cellsAliveNow >= cellsAliveBefore - 3 &&
      cellsAliveNow <= cellsAliveBefore + 3)
  {
    noEvolutionTicks++;
  }
  else
  {
    noEvolutionTicks = 0;
  }

  if (noEvolutionTicks > noEvolutionTicksLimit)
  {
    if (!gameOver)
    {
      finalTicks = currentTick - noEvolutionTicksLimit;
      currentMillis = millis();
      showEndScreen(finalTicks);
      gameOver = true;
    } else {
      int brightness = 255 * (1 - ((millis() - currentMillis) / scoreScreenTimeout));
      fadeOutGame(brightness);
    }
  }
}

void addGlider(int i1, int j1, int (&a)[SCREEN_HEIGHT][SCREEN_WIDTH])
{
  // 010
  // 001
  // 111
  int glider[3][3] = {{0, 0, 1}, {1, 0, 1}, {0, 1, 1}};
  for (int i = 0; i < 3; i++)
  {
    for (int j = 0; j < 3; j++)
    {
      a[i1 + i][j1 + j] = glider[i][j];
    }
  }
}

void gameLoop()
{
  if (runGame)
  {
    gameOfLife(g);
    if (gameOver)
    {
      if (millis() - currentMillis > scoreScreenTimeout)
      {
        resetGame();
      }
    }
  }
}

void resetGame()
{
  setupGameOfLife();
  clearDisplay();
}