#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <curses.h>

typedef struct {
  int x, y;
} coord_t;

typedef struct {
  coord_t motion;
  int length;
  char symbol;
  int growth;
  coord_t *coords;
} snake_t;

typedef struct {
  int x, y;
  int weight;
} food_t;

snake_t *sn1;
int maxx, maxy;
food_t *food;

#define pdebug(fmt...) //{ char out[256]; sprintf(out, fmt); fprintf(stderr, "%s\n", out); }

/*
 *
 * Snake
 *
 */

void
snake_show(snake_t *sn) {
  int item;

  pdebug("show x %d", sn->length);
  for (item = 0; item < sn->length; item++) {
    mvaddch(sn->coords[item].y, sn->coords[item].x, sn->symbol);
    pdebug("%d: [%d;%d] %c", item, sn->coords[item].y, sn->coords[item].x, sn->symbol);
  }
  
  /*refresh();*/
}

/* does nothing */
void
snake_hide(snake_t *sn) {
#if 0
  int item;

  for (item = 0; item < sn->length; item++) {
    mvaddch(sn->coords[item].y, sn->coords[item].x, /*' '*/ sn->symbol);
  }
  
  /*refresh();*/
#endif
}

void
snake_raise(snake_t *sn) {
  coord_t *old_coords, *new_coords;
  int len;
  
  pdebug("raise");
  
  sn->length++;

  /* XXX realloc() raises SIGSEGV */
  pdebug("realloc -> %d", sn->length);
  old_coords = sn->coords;
  len = sn->length;
  pdebug("realloc");
  new_coords = malloc(len * sizeof(snake_t));
  pdebug("assign");
  sn->coords = new_coords;
  pdebug("move");
  memmove(sn->coords + 1, old_coords, (sn->length - 1) * sizeof(snake_t));
  pdebug("realloc successful");
  //free(old_coords);
  
  sn->coords[0].x = sn->coords[1].x + sn->motion.x;
  sn->coords[0].y = sn->coords[1].y + sn->motion.y;
}

void
snake_move(snake_t *sn) {
  pdebug("move");
  mvaddch(sn->coords[sn->length - 1].y, sn->coords[sn->length - 1].x, ' ');
  
  pdebug("memmove -> %d", sn->length);
  memmove(sn->coords + 1, sn->coords, (sn->length - 1) * sizeof(snake_t));
  pdebug("memmove successful");
  
  sn->coords[0].x = sn->coords[1].x + sn->motion.x;
  sn->coords[0].y = sn->coords[1].y + sn->motion.y;
}

int
snake_member(snake_t *sn, int x, int y) {
  int item;

  pdebug("member");
  for (item = 1; item < sn->length; item ++) {
    if (sn->coords[item].x == x && sn->coords[item].y == y) {
      pdebug("found");
      return 1;
    }
  }
  pdebug("not found");

  return 0;
}

void
snake_gameover(snake_t *sn) {
  char str[256], sym, col, gostr[256];

  pdebug("gameover");
  
  sym = sn->symbol;
  
  sprintf(gostr, " Game Over ");
  str[0] = 0;
  
  for (col = 0; col < maxx; col ++) {
    str[col] = sym;
  }
  str[col] = 0;

  strncpy(str + (maxx - strlen(gostr)) / 2, gostr, strlen(gostr));
  
  mvaddstr(0, 0, str);

  getch();
}

/*
 *
 * Food
 *
 */

void
food_place(food_t *foo) {
  foo->x = (random() % maxx);
  foo->y = (random() % maxy);
  foo->weight = (random() % 7) + 1;
}

void
food_show(food_t *foo) {
  char symtab[]="`.:;%&#@";
  
  mvaddch(foo->y, foo->x, symtab[foo->weight]);
}

/*
 *
 * Main
 *
 */

int
main() {
  srandom(time(NULL));
  
  initscr();
  cbreak();
  noecho();
  nonl();
  intrflush(stdscr, FALSE);
  keypad(stdscr, TRUE);
  nodelay(stdscr, TRUE);
  leaveok(stdscr, TRUE);
  curs_set(0);
  getmaxyx(stdscr, maxy, maxx);
  
  sn1 = malloc(sizeof(snake_t));
  sn1->motion.x = 0;
  sn1->motion.y = 1;
  sn1->symbol = '*';
  sn1->length = 1;
  sn1->growth = 10;
  sn1->coords = malloc(sizeof(snake_t) * 1);
  sn1->coords[0].x = 10;
  sn1->coords[0].y = 10;
  snake_show(sn1);

  food = malloc(sizeof(food_t));
  food_place(food);
  food_show(food);
 
  while (1) {
    static int inkey, oldinkey, sleep, paused;

    pdebug("-- main loop");

    sleep = 0;
    inkey = getch();
    oldinkey = inkey;
    snake_hide(sn1);

    switch (inkey) {
      case 'Q':
      case 'q':		goto bye;

      case 'P':
      case 'p':		paused = 1;
			goto turn_end;
		
      case KEY_LEFT:	sn1->motion.x = -1; sn1->motion.y = 0;
			break;
		
      case KEY_RIGHT:	sn1->motion.x = 1; sn1->motion.y = 0;
			break;
		
      case KEY_UP:	sn1->motion.x = 0; sn1->motion.y = -1;
			break;
		
      case KEY_DOWN:	sn1->motion.x = 0; sn1->motion.y = 1;
			break;
			
      case ERR:		sleep = 1;
			
      default:		if (paused)
			  goto turn_end;
			break;
    }

    pdebug("checks");

    paused = 0;

    while (inkey != ERR && inkey == oldinkey) {
      pdebug("ink %d, old %d", inkey, oldinkey);
      oldinkey = inkey;
      inkey = getch();
    }
    if (oldinkey != inkey)
      ungetch(inkey);
    pdebug(" ink %d, old %d", inkey, oldinkey);
    
    if (sn1->growth) {
      sn1->growth--;
      snake_raise(sn1);
    } else {
      snake_move(sn1);
    }
    
    if (food->x == sn1->coords[0].x && food->y == sn1->coords[0].y) {
      sn1->growth += food->weight;
      food_place(food);
    }

    pdebug("override & bounds");

    if (sn1->coords[0].x < 0 || sn1->coords[0].y < 0 ||
	sn1->coords[0].x >= maxx || sn1->coords[0].y >= maxy ||
	snake_member(sn1, sn1->coords[0].x, sn1->coords[0].y)) {
      snake_gameover(sn1);
      goto bye;
    }

turn_end:
    pdebug("eoloop");
    
    move(0, 0);
    clrtoeol();
    snake_show(sn1);
    food_show(food);
    if (paused)
      mvaddstr(0, 0, "-- PAUSED --");
    refresh();
    
    if (sleep)
      usleep(100000);
    else
      usleep(50000);
  }

bye:
  pdebug("bye");
  snake_show(sn1);
  refresh();
  endwin();
#if 0
  printf("brm\n");
  free(food);
  printf("brm\n");
  free(sn1);
  printf("brm\n");
#endif
  return 0;
}
