#include "gol.h"

int main(int argc, char *argv[])
{
    grid *gol;
    if (argc > 1)
    {
        gol = load(argv[2]);
    }
    else
    {
        gol = newGrid(50, 50, 0.25, 1);
    }
    gameLoop(gol);
    return 0;
}

void gameLoop(grid *gol)
{
    while (true)
    {
        if (!gol->paused)
        {
            // clear le terminal
            printf("\033[2J\033[H");
            // check les voisins de chaque cellule
            checkNeighbor(gol);
            // update leurs state selon les regles du jeu
            updateState(gol);

            sleep(gol->update_rate);
            printGrid(gol);
        }
    }
    deleteGrid(gol);
}

grid *newGrid(int rows, int cols, float alive, float update_rate)
{
    srand(time(NULL));

    int nb_alive = (rows * cols) * alive;

    // aloc le jeux et la grille
    grid *gol = malloc(sizeof(grid));
    gol->game = malloc(rows * cols * sizeof(game));

    // init les variables
    gol->rows = rows;
    gol->cols = cols;
    gol->paused = false;
    gol->update_rate = update_rate;

    // init à false toutes les cellules
    for (int i = 0; i < rows * cols; i++)
    {
        gol->game[i].state = false;
        gol->game[i].neighbor_alive = 0;
    }

    // placer les cellules vivantes
    while (nb_alive != 0)
    {
        int x = rand() % rows;
        int y = rand() % cols;
        if (!gol->game[x * cols + y].state)
        {
            gol->game[x * cols + y].state = true;
            nb_alive--;
        }
    }
    return gol;
}

void deleteGrid(grid *gol)
{
    if (gol)
    {
        if (gol->game)
        {
            free(gol->game);
        }
        free(gol);
    }
}

void checkNeighbor(grid *gol)
{
    for (int i = 0; i < gol->rows; i++)
    {
        for (int j = 0; j < gol->cols; j++)
        {

            gol->game[i * gol->cols + j].neighbor_alive = 0; // reset compteur

            for (int k = -1; k <= 1; k++)
            {
                for (int l = -1; l <= 1; l++)
                {
                    int x = i + k;
                    int y = j + l;

                    // on ignore la cellule elle-même
                    if (x == i && y == j)
                        continue;

                    if (inGrid(x, y, gol))
                    {
                        if (gol->game[x * gol->cols + y].state)
                        {
                            gol->game[i * gol->cols + j].neighbor_alive++;
                        }
                    }
                }
            }
        }
    }
}

void updateState(grid *gol)
{
    for (int i = 0; i < gol->rows; i++)
    {
        for (int j = 0; j < gol->cols; j++)
        {
            if (gol->game[i * gol->cols + j].neighbor_alive == 3 || (gol->game[i * gol->cols + j].neighbor_alive == 2 && gol->game[i * gol->cols + j].state == true))
            {
                gol->game[i * gol->cols + j].state = true;
            }
            else
            {
                gol->game[i * gol->cols + j].state = false;
            }
        }
    }
}

bool inGrid(int x, int y, grid *gol)
{
    // vérifier que (x,y) est dans la grille
    return (x >= 0 && x < gol->rows && y >= 0 && y < gol->cols);
}

void changeState(int x, int y, bool state, grid *gol)
{
    if (inGrid(x, y, gol))
    {
        gol->game[x * gol->cols + y].state = state;
    }
}

grid *load(char *filename)
{
    FILE *f = fopen(filename, "rb");
    if (!f)
    {
        printf("Erreur: impossible d'ouvrir %s\n", filename);
        return NULL;
    }

    int nb_rows, nb_cols;
    float update_rate;
    fscanf(f, "%d %d %f\n", &nb_rows, &nb_cols, &update_rate);
    grid *gol = newGrid(nb_rows, nb_cols, 0, update_rate);
    setPause(true, gol);
    for (int row = 0; row < nb_rows; row++)
    {
        for (int col = 0; col < nb_cols; col++)
        {
            char s;
            fscanf(f, "%c ", &s);
            switch (s)
            {
            // load the state based on the char in the file
            case '1':
                gol->game[row * nb_cols + col].state = true;
                break;
            case '0':
                gol->game[row * nb_cols + col].state = false;
                break;
            default:
                fprintf(stderr, "load read an invalid state!\n");
                exit(EXIT_FAILURE);
            };
        }
        fscanf(f, "\n");
    }
    fclose(f);
    printf("\nFile %s loaded\n\n", filename);
    return gol;
}

void save(char *filename, grid *gol)
{
    if (!gol)
        return;

    FILE *f = fopen(filename, "wb");
    if (!f)
    {
        printf("Error, Can't create : %s\n", filename);
        return;
    }
    fprintf(f, "%d %d %f\n", getRows(gol), getCols(gol),
            getRate(gol));
    for (unsigned int i = 0; i < getRows(gol); i++)
    {
        for (unsigned int j = 0; j < getCols(gol); j++)
        {
            fprintf(f, "%d ", getState(i, j, gol) ? 1 : 0);
        }
        fprintf(f, "\n");
    }
    fclose(f);
    printf("File saved as %s!\n", filename);
};

void printGrid(grid *gol)
{
    printf("\n");
    for (int i = 0; i < gol->rows; i++)
    {
        for (int j = 0; j < gol->cols; j++)
        {
            printf("%c ", gol->game[i * gol->cols + j].state ? 'O' : '.');
        }
        printf("\n");
    }
}

bool getPause(grid *gol)
{
    return gol->paused;
}

void setPause(bool pause, grid *gol)
{
    gol->paused = pause;
}

float getRate(grid *gol)
{
    return gol->update_rate;
}

void setRate(float rate, grid *gol)
{
    gol->update_rate = rate;
}

int getRows(grid *gol)
{
    return gol->rows;
}

int getCols(grid *gol){
    return gol->cols;
}

bool getState(int x, int y, grid *gol)
{
    return gol->game[x * gol->cols + y].state;
}