/**
 * Conways Game of life
 * Author: Daniel Otte
 * License: GPLv3
 *
 *
 */

#include <stdint.h>
#ifdef AVR
	#include <util/delay.h>
	#include <avr/sfr_defs.h> /* for debugging */
#endif
#include "../config.h"
#include "../random/prng.h"
#include "../pixel.h"
#include "../util.h"

/******************************************************************************/

#undef DEBUG

#define XSIZE UNUM_COLS
#define YSIZE UNUM_ROWS

// optimizing for 8 bit archs while retaining compatibility with dimensions >127
#if UNUM_COLS < 128 && UNUM_ROWS < 128
	typedef uint8_t coord_t;
#else
	typedef unsigned int coord_t;
#endif

/*
 *  last line is for debug information
 */
#ifdef DEBUG
	#undef YSIZE
	#define YSIZE (UNUM_ROWS-1)
	#define DEBUG_ROW (UNUM_ROWS-1)
	#define DEBUG_BIT(pos, val) \
		setpixel((pixel){(pos) % XSIZE,DEBUG_ROW + (pos) / XSIZE}, (val) ? 3 : 0)
	#define DEBUG_BYTE(s,v) \
		DEBUG_BIT((s)*8+0, (v)&(1<<0)); \
		DEBUG_BIT((s)*8+1, (v)&(1<<1)); \
		DEBUG_BIT((s)*8+2, (v)&(1<<2)); \
		DEBUG_BIT((s)*8+3, (v)&(1<<3)); \
		DEBUG_BIT((s)*8+4, (v)&(1<<4)); \
		DEBUG_BIT((s)*8+5, (v)&(1<<5)); \
		DEBUG_BIT((s)*8+6, (v)&(1<<6)); \
		DEBUG_BIT((s)*8+7, (v)&(1<<7))
#else
	#define DEBUG_BIT(s,v)
	#define DEBUG_BYTE(s,v)
#endif

//#define GLIDER_TEST

#define BITSTUFFED
#define LOOP_DETECT_BUFFER_SIZE 8U

#ifndef GOL_DELAY
#define GOL_DELAY 100 /* milliseconds */
#endif

#ifndef GOL_CYCLES
#define GOL_CYCLES (2*60*3)
#endif

/******************************************************************************/
/******************************************************************************/

enum cell_e {
	dead = 0, alive = 1
};
#ifdef NDEBUG
	typedef uint8_t cell_t;
#else
	typedef enum cell_e cell_t;
#endif

#ifndef BITSTUFFED

#define FIELD_XSIZE XSIZE
#define FIELD_YSIZE YSIZE

typedef cell_t field_t[FIELD_YSIZE][FIELD_XSIZE];

/******************************************************************************/

void setcell(field_t pf, coord_t x, coord_t y, cell_t value) {
	pf[(y+FIELD_YSIZE) % FIELD_YSIZE][(x+FIELD_XSIZE) % FIELD_XSIZE] = value;
}

/******************************************************************************/

cell_t getcell(field_t pf, coord_t x, coord_t y) {
	return pf[(y+FIELD_YSIZE) % FIELD_YSIZE][(x+FIELD_XSIZE) % FIELD_XSIZE];
}

#else /* BITSTUFFED */

#define FIELD_XSIZE LINEBYTES
#define FIELD_YSIZE YSIZE

typedef uint8_t field_t[FIELD_YSIZE][FIELD_XSIZE];

/******************************************************************************/

static void setcell(field_t pf, coord_t x, coord_t y, cell_t value) {
	if (value != dead) {
		pf[y][x / 8] |= shl_table[x & 7];
	} else {
		pf[y][x / 8] &= ~shl_table[x & 7];
	}
}

/******************************************************************************/

static cell_t getcell(field_t pf, coord_t x, coord_t y) {
	return ((pf[y][x / 8]) & (shl_table[x & 7])) ? alive : dead;
}
#endif

/******************************************************************************/

uint8_t countsurroundingalive(field_t pf, coord_t x, coord_t y) {
#define P 1u
#define NX (XSIZE - 1u) /* emulated horizontal -1 */
#define NY (YSIZE - 1u) /* emulated vertical -1 */

#if XSIZE == YSIZE
	static coord_t const offset[] = {NX, NX, 0, P, P, P, 0, NX, NX, NX};
#else
	static coord_t const xoffset[] = {0, P, P, P, 0, NX, NX, NX};
	static coord_t const yoffset[] = {NY, NY, 0, P, P, P, 0, NY};
#endif

	uint8_t i, ret = 0;
	for (i = 8; i--;) {
		// getcell(...) returns either 0 or 1
#if XSIZE == YSIZE
		ret += getcell(pf, (coord_t)(x + offset[i+2u]) % XSIZE,
				(coord_t)(y + offset[i]) % YSIZE);
#else
		ret += getcell(pf, (coord_t)(x + xoffset[i]) % XSIZE,
				(coord_t)(y + yoffset[i]) % YSIZE);
#endif
	}

	return ret;
}

/******************************************************************************/

void nextiteration(field_t dest, field_t src) {
	coord_t x, y;
	uint8_t tc;
	for (y = YSIZE; y--;) {
		for (x = XSIZE; x--;) {
			cell_t cell;
			tc = countsurroundingalive(src, x, y);
			switch (tc) {
			case 2:
				/* keep */
				continue;
				break;
			case 3:
				/* alive */
				cell = alive;
				break;
			default:
				/* dead */
				cell = dead;
				break;
			}
			setcell(dest, x, y, cell);
		}
	}
}

/******************************************************************************/

#ifdef BITSTUFFED
void pfprint(field_t pf) {
	for (uint8_t i = NUMPLANE; i--;) {
		memcpy(pixmap[i], pf, sizeof(field_t));
	}
}
#else
void pfprint(field_t pf) {
	coord_t x,y;
	for(y=YSIZE; y--;) {
		for(x=XSIZE; x--;) {
			setpixel((pixel){x,y},getcell(pf,x,y)*3);
		}
	}
}
#endif

/******************************************************************************/

static void pfcopy(field_t dest, field_t src) {
	memcpy(dest, src, sizeof(field_t));
}

/******************************************************************************/

static int pfcmp(field_t dest, field_t src) {
	return memcmp(dest, src, sizeof(field_t));
}

#ifndef BITSTUFFED

/******************************************************************************/

uint8_t pfempty(field_t src) {
	coord_t x,y;
	for(y=YSIZE; y--;) {
		for(x=XSIZE; x--;) {
			if (getcell(src, x, y)==alive)
			return 0;
		}
	}
	return 1;
}
#else

/******************************************************************************/

uint8_t pfempty(field_t src) {
	coord_t x, y;
	for (y = 0; y < FIELD_YSIZE; ++y) {
		for (x = 0; x < FIELD_XSIZE; ++x) {
			if (src[y][x] != 0)
				return 0;
		}
	}
	return 1;
}
#endif

/******************************************************************************/

static void insertglider(field_t pf) {
#ifndef BITSTUFFED
	/*
	 *  #
	 * #
	 * ###
	 */
	                         setcell(pf, 1, 0, alive);
	setcell(pf, 2, 1, alive);
	setcell(pf, 0, 2, alive);setcell(pf, 1, 2, alive);setcell(pf, 2, 2, alive);
#else
	pf[0][0] |= 0x02; //  #
	pf[1][0] |= 0x04; // #
	pf[2][0] |= 0x07; // ###
#endif
}

/******************************************************************************/

static void pfinit(field_t pf)
{
	coord_t x, y;
#ifndef BITSTUFFED
	for (y = YSIZE; y--;) {
		for (x = XSIZE; x--;) {
			setcell(pf, x, y, (random8() & 1) ? alive : dead);
		}
	}
#else
	for (y = 0; y < FIELD_YSIZE; ++y) {
		for (x = 0; x < FIELD_XSIZE; ++x) {
			pf[y][x] = random8();
		}
	}
#endif
}

/******************************************************************************/

void gameoflife() {
	DEBUG_BYTE(0,0); // set debug bytes to zero
	DEBUG_BYTE(1,0);
	field_t pf1, pf2;
	field_t ldbuf[LOOP_DETECT_BUFFER_SIZE] = {{{0}}}; // loop detect buffer
	uint8_t ldbuf_idx = 0;
	uint16_t cycle;

#ifdef GLIDER_TEST
	/* initialize with glider */
	coord_t x,y;
	for(y = YSIZE; y--;) {
		for(x = XSIZE; x--;) {
			setcell(pf1, x, y, dead);
		}
	}
	insertglider(pf1);
#else
	/* initialize the field with random */
	pfinit(pf1);
#endif

	/* the main part */
	pfprint(pf1);
	for (cycle = 1; cycle < GOL_CYCLES; ++cycle) {
		DEBUG_BYTE(0, (uint8_t)(GOL_CYCLES-cycle) & 0xff); DEBUG_BYTE(1, SREG);
		wait(GOL_DELAY);
		pfcopy(pf2, pf1);
		nextiteration(pf1, pf2);
		pfprint(pf1);
		/* loop detection */
		if (!pfcmp(pf1, pf2)) {
			insertglider(pf1);
//			cycle = 1;
		}
		if (pfempty(pf1)) {
			/* kill game */
			return;
		}
		/* */
		uint8_t i;
		for (i = 0; i < LOOP_DETECT_BUFFER_SIZE; ++i) {
			if (!pfcmp(pf1, ldbuf[i])) {
				insertglider(pf1);
//				cycle = 1;
			}
		}
		pfcopy(ldbuf[ldbuf_idx], pf1);
		ldbuf_idx = (ldbuf_idx + 1u) % LOOP_DETECT_BUFFER_SIZE;
	}
}
