/*
 * C program for Conway's ``game of life''.
 *
 * Input is from command line and a file:
 * 
 * Command-line arguments are as follows:
 *   either the name of an input file or the keyword "random" and the size
 *     of the board and the seed for random-number generation
 *   number of steps
 *   how often to print results (P means print results every P steps)
 *
 * If an input file is specified, it contains a representation of the initial 
 *  board configuration:  N (size of board) and N*N values (each 0 or 1).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "timer.h"

/*
 * Data structure for board:
 *
 * "size" is the size of the (square) board.
 * "cells" and "new_cells" are pointers to 1D arrays to hold two board
 *   configurations (the current one and the one being computed for the next 
 *   time step).  Each array is of size (size+2)*(size+2), to allow for a 
 *   one-cell-wide border of always-dead cells.  (This simplifies the coding 
 *   a bit.)  The 2D board configuration is stored in this 1D array in 
 *   row-major order.
 */ 
typedef struct {
    int size;
    int* cells;
    int* new_cells;
} board_t;

/* macro to compute 1D index into actual array given 2D coordinates.
 * see read_board for typical usage.
 */
#define cell_index(row, col, ncols)  ((row)*((ncols)+2) + (col))

int read_board(FILE* infile, board_t *board);
int random_board(int size, int seed, board_t *board);
void update_board(board_t *board);
void print_board(FILE* outfile, board_t *board);
void clear_border(int rows, int cols, int *cells);

int main(int argc, char* argv[]) {
    int i;
    int steps = 0, print_interval = 0;
    board_t board;
    FILE* infile;
    double start_time, end_time;
    char* args_message = 
        "[ infile | 'random' boardsize seed ] num_steps print_interval";

    start_time = get_time();

    if (argc < 4) {
        fprintf(stderr, "usage:  %s %s\n", argv[0], args_message);
        return EXIT_FAILURE;
    }
    if (strcmp(argv[1], "random") != 0) {
        infile = fopen(argv[1], "r");
        if (infile == NULL) {
            fprintf(stderr, "unable to open input file %s\n", argv[1]);
            return EXIT_FAILURE;
        }
        steps = atoi(argv[2]);
        print_interval = atoi(argv[3]);
        if ((steps <= 0) || (print_interval <= 0)) {
            fprintf(stderr, "usage:  %s %s\n", argv[0], args_message);
            return EXIT_FAILURE;
        }
        if (read_board(infile, &board) != 0) 
            return EXIT_FAILURE;
        fclose(infile);
    }
    else {
        int size = 0;
        int seed = 0;
        if (argc < 6) {
            fprintf(stderr, "usage:  %s %s\n", argv[0], args_message);
            return EXIT_FAILURE;
        }
        size = atoi(argv[2]);
        seed = atoi(argv[3]);
        steps = atoi(argv[4]);
        print_interval = atoi(argv[5]);
        if ((steps <= 0) || (print_interval <= 0) ||
                (size <= 0) || (seed <= 0)) {
            fprintf(stderr, "usage:  %s %s\n", argv[0], args_message);
            return EXIT_FAILURE;
        }
        if (random_board(size, seed, &board) != 0)
            return EXIT_FAILURE;
    }

    fprintf(stdout, "Initial board\n\n");
    print_board(stdout, &board);
    fprintf(stdout, "\n\n");

    for (i = 0; i < steps; ++i) {
        update_board(&board);
        if (((i+1) % print_interval) == 0) {
            fprintf(stdout, "Board after step %d\n\n", i+1);
            print_board(stdout, &board);
            fprintf(stdout, "\n\n");
        }
    }

    end_time = get_time();

    /* writes this to standard error so it can be easily separated from
     * rest of output (which could be long)
     */
    fprintf(stderr, "\n\nTotal time %g\n", end_time - start_time);
    fprintf(stderr, "(Board size %d, %d steps, print interval %d)\n\n",
            board.size, steps, print_interval);

    return EXIT_SUCCESS;
}

/*
 * sets unused "edge" cells to 0 
 */
void clear_border(int rows, int cols, int *cells) {
    int r, c;
    for (c = 0; c < cols+2; ++c) {
        cells[cell_index(0, c, cols)] = 0;
        cells[cell_index(rows+1, c, cols)] = 0;
    }
    for (r = 0; r < rows+2; ++r) {
        cells[cell_index(r, 0, cols)] = 0;
        cells[cell_index(r, cols+1, cols)] = 0;
    }
}

/*
 * reads initial configuration from infile.
 * returns 0 if all is well, otherwise prints error message and returns
 *   a non-zero value.
 */
int read_board(FILE* infile, board_t *board) {
    int i, j, temp;
    if (fscanf(infile, "%d", &(board->size)) != 1) {
        fprintf(stderr, "unable to read size of board\n");
        return 1;
    }
    board->cells = malloc(((board->size)+2)*((board->size)+2)*sizeof(int));
    board->new_cells = malloc(((board->size)+2)*((board->size)+2)*sizeof(int));
    if ((board->cells == NULL) || (board->new_cells == NULL)) {
        fprintf(stderr, "unable to allocate space for board of size %d\n",
                (board->size));
        return 2;
    }
    for (i = 1; i <= board->size; ++i) {
        for (j = 1; j <= board->size; ++j) {
            if (fscanf(infile, "%d", &temp) != 1) {
                fprintf(stderr, "unable to read values for board\n");
                return 1;
            }
            if ((temp == 0) || (temp == 1))
                board->cells[cell_index(i, j, board->size)] = temp;
            else {
                fprintf(stderr, "unable to read values for board\n");
                return 1;
            }
        }
    }
    clear_border(board->size, board->size, board->cells);
    clear_border(board->size, board->size, board->new_cells);
    return 0;
}

/*
 * generates random board configuration for given size and seed.
 * returns 0 if all is well, otherwise prints error message and returns
 *   a non-zero value.
 */
int random_board(int size, int seed, board_t *board) {
    int i, j;
    board->size = size;
    srandom(seed);
    board->cells = malloc(((board->size)+2)*((board->size)+2)*sizeof(int));
    board->new_cells = malloc(((board->size)+2)*((board->size)+2)*sizeof(int));
    if ((board->cells == NULL) || (board->new_cells == NULL)) {
        fprintf(stderr, "unable to allocate space for board of size %d\n",
                (board->size));
        return 2;
    }
    for (i = 1; i <= board->size; ++i) {
        for (j = 1; j <= board->size; ++j) {
            board->cells[cell_index(i, j, board->size)] = 
                (random() < (RAND_MAX/2)) ? 0 : 1;
        }
    }
    clear_border(board->size, board->size, board->cells);
    clear_border(board->size, board->size, board->new_cells);
    return 0;
}

/*
 * updates board configuration (uses values in cells to compute values in 
 *   new_cells, then swaps pointers to "copy" from new_cells to cells).
 */
void update_board(board_t *board) {
    /* YOUR CODE GOES HERE */
}

/*
 * prints current board configuration.
 */
void print_board(FILE* outfile, board_t *board) {
    int i, j;
    for (i = 1; i <= board->size; ++i) {
        for (j = 1; j <= board->size; ++j) {
            if (board->cells[cell_index(i, j, board->size)] == 0)
                fprintf(outfile, ". ");
            else
                fprintf(outfile, "1 ");
        }
        fprintf(outfile, "\n");
    }
}