#include <curses.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/ioctl.h>

#define EMPTY 0x32
#define ALIVE 0x42
#define BIRTH 0x62
#define DEATH 0x64

static char **local_screen;
static int cols, lines;
static unsigned int usec = 100000;
static WINDOW *this;
void alarm_handler(int);
void resize_array(int);

int main(int argc, char *argv[]) {
  int i, j;
  int ch;
  char inputline[256];
  FILE *stream;
  signal(SIGWINCH, resize_array);
  this = initscr();
  noecho();
  cols = COLS;
  lines = LINES;
  if((stream = fopen("life.txt", "r")) == NULL) {
    perror("fopen()");
    exit(1);
  }
  /* Get ready for calloc() and malloc() */
  local_screen = calloc(cols, 4);
  for(i = 0; i < cols; i++)
    if((local_screen[i] = malloc(lines)) == NULL)
      perror("malloc()");

  for(j = 0; j < lines; j++) {
    fgets(inputline, cols, stream);
    for(i = 0; i < cols; i++) {
      if(inputline[i] == '*') {
        local_screen[i][j] = ALIVE;
        mvaddch(j, i, '*');
      }
      else local_screen[i][j] = EMPTY;
    }
  }
  fclose(stream);
  refresh();
  signal(SIGALRM, alarm_handler);
  /* Event loop */
  while(1) {
    ch = getch();
    switch(ch) {
    case 0x72:
      /* We need to start it running */
      signal(SIGALRM, alarm_handler);
      ualarm(usec, usec);
      break;
    case 0x73:
      /* Need to stop execution */
      signal(SIGALRM, SIG_IGN);
      break;
    case 0x2b:
      /* Time to speed things up */
      if(usec >= 10000) {
        usec -= 5000;
      }
      ualarm(usec, usec);
      break;
    case 0x2d:
      /* Slow down */
      usec += 5000;
      ualarm(usec, usec);
      break;
    case 0x71:
      /* Time to quit */
      clear();
      endwin();
      exit(0);
      break;
    }
  }
  clear();
  endwin();
  return 0;
}

void alarm_handler(int sig_num) {
  int i, j;
  int touching = 0;
  for(j = 0; j < lines; j++) {
    for(i = 0; i < cols; i++) {
      if(((i - 1) > 0) && ((j - 1) > 0) && ((i + 1) < cols) &&
         ((j + 1) < lines)) {
        /* We're in the middle.  Check all around */
        if(local_screen[i][j] == EMPTY) {
          touching = 0;
          if((local_screen[i - 1][j - 1] == ALIVE) ||
             (local_screen[i - 1][j - 1] == DEATH)) touching++;
          if((local_screen[i - 1][j] == ALIVE) ||
             (local_screen[i - 1][j] == DEATH)) touching++;
          if((local_screen[i - 1][j + 1] == ALIVE) ||
             (local_screen[i - 1][j + 1] == DEATH)) touching++;
          if((local_screen[i][j - 1] == ALIVE) ||
             (local_screen[i][j - 1] == DEATH)) touching++;
          if((local_screen[i][j + 1] == ALIVE) ||
             (local_screen[i][j + 1] == DEATH)) touching++;
          if((local_screen[i + 1][j - 1] == ALIVE) ||
             (local_screen[i + 1][j - 1] == DEATH)) touching++;
          if((local_screen[i + 1][j] == ALIVE) ||
             (local_screen[i + 1][j] == DEATH)) touching++;
          if((local_screen[i + 1][j + 1] == ALIVE) ||
             (local_screen[i + 1][j + 1] == DEATH)) touching++;
          /* Do we get an addition to the family? */
          if(touching > 2) local_screen[i][j] = BIRTH;
        }
        else if(local_screen[i][j] == ALIVE) {
          touching = 0;
          if((local_screen[i - 1][j - 1] == ALIVE) ||
             (local_screen[i - 1][j - 1] == DEATH)) touching++;
          if((local_screen[i - 1][j] == ALIVE) ||
             (local_screen[i - 1][j] == DEATH)) touching++;
          if((local_screen[i - 1][j + 1] == ALIVE) ||
             (local_screen[i - 1][j + 1] == DEATH)) touching++;
          if((local_screen[i][j - 1] == ALIVE) ||
             (local_screen[i][j - 1] == DEATH)) touching++;
          if((local_screen[i][j + 1] == ALIVE) ||
             (local_screen[i][j + 1] == DEATH)) touching++;
          if((local_screen[i + 1][j - 1] == ALIVE) ||
             (local_screen[i + 1][j - 1] == DEATH)) touching++;
          if((local_screen[i + 1][j] == ALIVE) ||
             (local_screen[i + 1][j] == DEATH)) touching++;
          if((local_screen[i + 1][j + 1] == ALIVE) ||
             (local_screen[i + 1][j + 1] == DEATH)) touching++;
          /* Check if anyone is checking out */
          if((touching < 2) || (touching > 3)) local_screen[i][j] = DEATH;
        }
      }
      else if(((i - 1) < 0) && ((j - 1) < 0)) {
        /* Upper left corner */
        if(local_screen[i][j] == EMPTY) {
          touching = 0;
          if((local_screen[i][j + 1] == ALIVE) ||
             (local_screen[i][j + 1] == DEATH)) touching++;
          if((local_screen[i + 1][j] == ALIVE) ||
             (local_screen[i + 1][j] == DEATH)) touching++;
          if((local_screen[i + 1][j + 1] == ALIVE) ||
             (local_screen[i + 1][j + 1] == DEATH)) touching++;
          /* Do we get an addition to the family? */
          if(touching > 2) local_screen[i][j] = BIRTH;
        }
        else if(local_screen[i][j] == ALIVE) {
          touching = 0;
          if((local_screen[i][j + 1] == ALIVE) ||
             (local_screen[i][j + 1] == DEATH)) touching++;
          if((local_screen[i + 1][j] == ALIVE) ||
             (local_screen[i + 1][j] == DEATH)) touching++;
          if((local_screen[i + 1][j + 1] == ALIVE) ||
             (local_screen[i + 1][j + 1] == DEATH)) touching++;
          /* Check if anyone is checking out */
          if((touching < 2) || (touching > 3)) local_screen[i][j] = DEATH;
        }
      }
      else if(((i - 1) < 0) && ((j + 1) >= lines)) {
        /* Bottom left corner */
        if(local_screen[i][j] == EMPTY) {
          touching = 0;
          if((local_screen[i][j - 1] == ALIVE) ||
             (local_screen[i][j - 1] == DEATH)) touching++;
          if((local_screen[i + 1][j - 1] == ALIVE) ||
             (local_screen[i + 1][j - 1] == DEATH)) touching++;
          if((local_screen[i + 1][j] == ALIVE) ||
             (local_screen[i + 1][j] == DEATH)) touching++;
          /* Do we get an addition to the family? */
          if(touching > 2) local_screen[i][j] = BIRTH;
        }
        else if(local_screen[i][j] == ALIVE) {
          touching = 0;
          if((local_screen[i][j - 1] == ALIVE) ||
             (local_screen[i][j - 1] == DEATH)) touching++;
          if((local_screen[i + 1][j - 1] == ALIVE) ||
             (local_screen[i + 1][j - 1] == DEATH)) touching++;
          if((local_screen[i + 1][j] == ALIVE) ||
             (local_screen[i + 1][j] == DEATH)) touching++;
          /* Check if anyone is checking out */
          if((touching < 2) || (touching > 3)) local_screen[i][j] = DEATH;
        }
      }
      else if(((i + 1) >= cols) && ((j - 1) < 0)) {
        /* Upper right corner */
        if(local_screen[i][j] == EMPTY) {
          touching = 0;
          if((local_screen[i - 1][j] == ALIVE) ||
             (local_screen[i - 1][j] == DEATH)) touching++;
          if((local_screen[i - 1][j + 1] == ALIVE) ||
             (local_screen[i - 1][j + 1] == DEATH)) touching++;
          if((local_screen[i][j + 1] == ALIVE) ||
             (local_screen[i][j + 1] == DEATH)) touching++;
          /* Do we get an addition to the family? */
          if(touching > 2) local_screen[i][j] = BIRTH;
        }
        else if(local_screen[i][j] == ALIVE) {
          touching = 0;
          if((local_screen[i - 1][j] == ALIVE) ||
             (local_screen[i - 1][j] == DEATH)) touching++;
          if((local_screen[i - 1][j + 1] == ALIVE) ||
             (local_screen[i - 1][j + 1] == DEATH)) touching++;
          if((local_screen[i][j + 1] == ALIVE) ||
             (local_screen[i][j + 1] == DEATH)) touching++;
          /* Check if anyone is checking out */
          if((touching < 2) || (touching > 3)) local_screen[i][j] = DEATH;
        }
      }
      else if(((i + 1) >= cols) && ((j + 1) >= lines)) {
        /* Bottom right corner */
        if(local_screen[i][j] == EMPTY) {
          touching = 0;
          if((local_screen[i - 1][j - 1] == ALIVE) ||
             (local_screen[i - 1][j - 1] == DEATH)) touching++;
          if((local_screen[i - 1][j] == ALIVE) ||
             (local_screen[i - 1][j] == DEATH)) touching++;
          if((local_screen[i][j - 1] == ALIVE) ||
             (local_screen[i][j - 1] == DEATH)) touching++;
          /* Do we get an addition to the family? */
          if(touching > 2) local_screen[i][j] = BIRTH;
        }
        else if(local_screen[i][j] == ALIVE) {
          touching = 0;
          if((local_screen[i - 1][j - 1] == ALIVE) ||
             (local_screen[i - 1][j - 1] == DEATH)) touching++;
          if((local_screen[i - 1][j] == ALIVE) ||
             (local_screen[i - 1][j] == DEATH)) touching++;
          if((local_screen[i][j - 1] == ALIVE) ||
             (local_screen[i][j - 1] == DEATH)) touching++;
          /* Check if anyone is checking out */
          if((touching < 2) || (touching > 3)) local_screen[i][j] = DEATH;
        }
      }
      else if(((i - 1) < 0) && (!((j + 1) >= lines)) && (!((j - 1) < 0))) {
        /* Left edge, not a corner */
        if(local_screen[i][j] == EMPTY) {
          touching = 0;
          if((local_screen[i][j - 1] == ALIVE) ||
             (local_screen[i][j - 1] == DEATH)) touching++;
          if((local_screen[i][j + 1] == ALIVE) ||
             (local_screen[i][j + 1] == DEATH)) touching++;
          if((local_screen[i + 1][j - 1] == ALIVE) ||
             (local_screen[i + 1][j - 1] == DEATH)) touching++;
          if((local_screen[i + 1][j] == ALIVE) ||
             (local_screen[i + 1][j] == DEATH)) touching++;
          if((local_screen[i + 1][j + 1] == ALIVE) ||
             (local_screen[i + 1][j + 1] == DEATH)) touching++;
          /* Do we get an addition to the family? */
          if(touching > 2) local_screen[i][j] = BIRTH;
        }
        else if(local_screen[i][j] == ALIVE) {
          touching = 0;
          if((local_screen[i][j - 1] == ALIVE) ||
             (local_screen[i][j - 1] == DEATH)) touching++;
          if((local_screen[i][j + 1] == ALIVE) ||
             (local_screen[i][j + 1] == DEATH)) touching++;
          if((local_screen[i + 1][j - 1] == ALIVE) ||
             (local_screen[i + 1][j - 1] == DEATH)) touching++;
          if((local_screen[i + 1][j] == ALIVE) ||
             (local_screen[i + 1][j] == DEATH)) touching++;
          if((local_screen[i + 1][j + 1] == ALIVE) ||
             (local_screen[i + 1][j + 1] == DEATH)) touching++;
          /* Check if anyone is checking out */
          if((touching < 2) || (touching > 3)) local_screen[i][j] = DEATH;
        }
      }
      else if(((i + 1) >= cols) && (!((j + 1) >= lines)) && (!((j - 1) < 0))) {
        /* Right edge, not a corner */
        if(local_screen[i][j] == EMPTY) {
          touching = 0;
          if((local_screen[i - 1][j - 1] == ALIVE) ||
             (local_screen[i - 1][j - 1] == DEATH)) touching++;
          if((local_screen[i - 1][j] == ALIVE) ||
             (local_screen[i - 1][j] == DEATH)) touching++;
          if((local_screen[i - 1][j + 1] == ALIVE) ||
             (local_screen[i - 1][j + 1] == DEATH)) touching++;
          if((local_screen[i][j - 1] == ALIVE) ||
             (local_screen[i][j - 1] == DEATH)) touching++;
          if((local_screen[i][j + 1] == ALIVE) ||
             (local_screen[i][j + 1] == DEATH)) touching++;
          /* Do we get an addition to the family? */
          if(touching > 2) local_screen[i][j] = BIRTH;
        }
        else if(local_screen[i][j] == ALIVE) {
          touching = 0;
          if((local_screen[i - 1][j - 1] == ALIVE) ||
             (local_screen[i - 1][j - 1] == DEATH)) touching++;
          if((local_screen[i - 1][j] == ALIVE) ||
             (local_screen[i - 1][j] == DEATH)) touching++;
          if((local_screen[i - 1][j + 1] == ALIVE) ||
             (local_screen[i - 1][j + 1] == DEATH)) touching++;
          if((local_screen[i][j - 1] == ALIVE) ||
             (local_screen[i][j - 1] == DEATH)) touching++;
          if((local_screen[i][j + 1] == ALIVE) ||
             (local_screen[i][j + 1] == DEATH)) touching++;
          /* Check if anyone is checking out */
          if((touching < 2) || (touching > 3)) local_screen[i][j] = DEATH;
        }
      }
      else if(((j - 1) < 0) && (!((i + 1) >= cols)) && (!((i - 1) < 0))) {
        /* Top edge, not a corner */
        if(local_screen[i][j] == EMPTY) {
          touching = 0;
          if((local_screen[i - 1][j] == ALIVE) ||
             (local_screen[i - 1][j] == DEATH)) touching++;
          if((local_screen[i - 1][j + 1] == ALIVE) ||
             (local_screen[i - 1][j + 1] == DEATH)) touching++;
          if((local_screen[i][j + 1] == ALIVE) ||
             (local_screen[i][j + 1] == DEATH)) touching++;
          if((local_screen[i + 1][j] == ALIVE) ||
             (local_screen[i + 1][j] == DEATH)) touching++;
          if((local_screen[i + 1][j + 1] == ALIVE) ||
             (local_screen[i + 1][j + 1] == DEATH)) touching++;
          /* Do we get an addition to the family? */
          if(touching > 2) local_screen[i][j] = BIRTH;
        }
        else if(local_screen[i][j] == ALIVE) {
          touching = 0;
          if((local_screen[i - 1][j] == ALIVE) ||
             (local_screen[i - 1][j] == DEATH)) touching++;
          if((local_screen[i - 1][j + 1] == ALIVE) ||
             (local_screen[i - 1][j + 1] == DEATH)) touching++;
          if((local_screen[i][j + 1] == ALIVE) ||
             (local_screen[i][j + 1] == DEATH)) touching++;
          if((local_screen[i + 1][j] == ALIVE) ||
             (local_screen[i + 1][j] == DEATH)) touching++;
          if((local_screen[i + 1][j + 1] == ALIVE) ||
             (local_screen[i + 1][j + 1] == DEATH)) touching++;
          /* Check if anyone is checking out */
          if((touching < 2) || (touching > 3)) local_screen[i][j] = DEATH;
        }
      }
      else {
        /* Bottom edge, not a corner */
        if(local_screen[i][j] == EMPTY) {
          touching = 0;
          if((local_screen[i - 1][j - 1] == ALIVE) ||
             (local_screen[i - 1][j - 1] == DEATH)) touching++;
          if((local_screen[i - 1][j] == ALIVE) ||
             (local_screen[i - 1][j] == DEATH)) touching++;
          if((local_screen[i][j - 1] == ALIVE) ||
             (local_screen[i][j - 1] == DEATH)) touching++;
          if((local_screen[i + 1][j - 1] == ALIVE) ||
             (local_screen[i + 1][j - 1] == DEATH)) touching++;
          if((local_screen[i + 1][j] == ALIVE) ||
             (local_screen[i + 1][j] == DEATH)) touching++;
          /* Do we get an addition to the family? */
          if(touching > 2) local_screen[i][j] = BIRTH;
        }
        else if(local_screen[i][j] == ALIVE) {
          touching = 0;
          if((local_screen[i - 1][j - 1] == ALIVE) ||
             (local_screen[i - 1][j - 1] == DEATH)) touching++;
          if((local_screen[i - 1][j] == ALIVE) ||
             (local_screen[i - 1][j] == DEATH)) touching++;
          if((local_screen[i][j - 1] == ALIVE) ||
             (local_screen[i][j - 1] == DEATH)) touching++;
          if((local_screen[i + 1][j - 1] == ALIVE) ||
             (local_screen[i + 1][j - 1] == DEATH)) touching++;
          if((local_screen[i + 1][j] == ALIVE) ||
             (local_screen[i + 1][j] == DEATH)) touching++;
          /* Check if anyone is checking out */
          if((touching < 2) || (touching > 3)) local_screen[i][j] = DEATH;
        }
      }
    }
  }
  for(j = 0; j < lines; j++) {
    for(i = 0; i < cols; i++)
      if(local_screen[i][j] == BIRTH) {
        local_screen[i][j] = ALIVE;
        mvaddch(j, i, '*');
      }
      else if(local_screen[i][j] == DEATH) {
        local_screen[i][j] = EMPTY;
        mvaddch(j, i, ' ');
      }
  }
  refresh();
  signal(SIGALRM, alarm_handler);
}
  
void resize_array(int sig_num) {
  int i, j, a, b, oldcols, oldlines;
  char **temp;
#ifdef TIOCGWINSZ
  struct winsize ws;
#else
#ifdef TIOCGSIZE
  struct ttysize ts;
#endif
#endif
  oldcols = cols;
  oldlines = lines;
  /* Get the new dimensions of the screen */
#ifdef TIOCGWINSZ
  ioctl(1, TIOCGWINSZ, &ws);
  cols = ws.ws_col;
  lines = ws.ws_row;
#else
#ifdef TIOCGSIZE
  ioctl(1, TIOCGSIZE, &ts);
  cols = ts.ts_cols;
  lines = ts.ts_lines;
#endif
#endif
  clear();
  if((cols >= oldcols) || (lines >= oldlines)) {
    /* Need to malloc more space */
    /* Get ready for calloc() and malloc() */
    temp = calloc(oldcols, 4);
    for(i = 0; i < oldcols; i++)
      if((temp[i] = malloc(oldlines)) == NULL)
        perror("malloc()");
    /* Copy the old array */
    for(i = 0; i < oldcols; i++)
      for(j = 0; j < oldlines; j++)
        temp[i][j] = local_screen[i][j];
    /* Now free up our memory before malloc'ing more.  Just easier */
    for(i = 0; i < oldcols; i++)
      free(local_screen[i]);
    free(local_screen);
    local_screen = calloc(cols, 4);
    for(i = 0; i < cols; i++)
      local_screen[i] = malloc(lines);
    /* Zero the array out */
    for(i = 0; i < cols; i++)
      for(j = 0; j < lines; j++)
        local_screen[i][j] = EMPTY;
    /* Now copy back my data */
    for(i = 0; i < oldcols; i++)
      for(j = 0; j < oldlines; j++) {
        local_screen[i][j] = temp[i][j];
      }
    /* Free up temp, then refresh the screen */
    for(i = 0; i < oldcols; i++)
      free(temp[i]);
    free(temp);
    /* Time to refresh */
    for(i = 0; i < cols; i++)
      for(j = 0; j < lines; j++) {
        if(local_screen[i][j] == ALIVE) {
          mvaddch(j, i, '*');
        }
        else if(local_screen[i][j] == EMPTY) {
          mvaddch(j, i, ' ');
        }
      }
    refresh();
  }
}