#include <SDL.h>
#include <SDL2_gfxPrimitives.h>

#include <cstdlib>
#include <ctime>

/*********************/
/** GLOBAL SETTINGS **/
/*********************/

int WIDTH = 1280;
int HEIGHT = 720;
int pixelsize = 20; // make above params a multiple of this number
int inverse_frequency = 6; // the higher the number, the less initial alive blocks
							// sensible values: 2-10

/*****************/
/** PIXEL CLASS **/
/*****************/

class Pixel {
	int x, y;
	bool alive;
	bool newstate;
public:
	static unsigned size;
	Pixel() : x(0), y(0), alive(!(rand()%inverse_frequency)) {}
	void draw(SDL_Renderer* sdlRenderer);
	void setPos(unsigned x, unsigned y);
	void setState(bool live) { alive = live; newstate = live; }
	void prepare(unsigned n);
	void progress();
	bool isalive() { return alive; }
};

// static variable initialization
unsigned Pixel::size = pixelsize;

void Pixel::draw(SDL_Renderer *sdlRenderer) {
	boxRGBA(sdlRenderer, x*size+1, y*size+1, (x+1)*size-1, (y+1)*size-1, 0, alive?250:0, alive?70:0, 255);
}

// determines the state of a pixel in the next phase
void Pixel::prepare(unsigned n) {
	if(alive) {
		if(n < 2 || n > 3)
			newstate = false;
	}
	else { // if dead
		if(n == 3)
			newstate = true;
	}		
}

// changes the pixel's current state to the next one
void Pixel::progress() {
	alive = newstate;
}

void Pixel::setPos(unsigned x, unsigned y) {
	this->x = x;
	this->y = y;
}


/***********************/
/** PIXELMATRIX CLASS **/
/***********************/

class PixelMatrix {
	unsigned vpix;
	unsigned hpix;
	SDL_Renderer *renderer;
	Pixel** pixels;
	
	PixelMatrix(Pixel const&);
	PixelMatrix& operator=(PixelMatrix&);
public:
	PixelMatrix(SDL_Renderer* sdlRenderer);
	~PixelMatrix();
	int alive_neighbours(int i, int j);
	void progress();
	void draw();
	void create(int x, int y);
	void kill(int x, int y);
	void clear();
};

PixelMatrix::PixelMatrix(SDL_Renderer* sdlRenderer) 
: vpix(HEIGHT/Pixel::size)
, hpix(WIDTH/Pixel::size)
, renderer(sdlRenderer)
{
	pixels = new Pixel*[vpix];
	for(unsigned i = 0; i < vpix; i++)
		pixels[i] = new Pixel[hpix];
	
	for(unsigned i = 0; i < vpix; i++)
		for(unsigned j = 0; j < hpix; j++)
			pixels[i][j].setPos(j, i);
}

// frees the 2-dimensional array of pixels
PixelMatrix::~PixelMatrix() {
	for(unsigned i = 0; i < vpix; i++)
		delete[] pixels[i];
	delete[] pixels;
}

// counts the alive neighbours of a pixel
int PixelMatrix::alive_neighbours(int i, int j) {
	int count = 0;
	if(0 < i - 1) {
		if(0 < j - 1 && pixels[i-1][j-1].isalive())
			count++;
		if(pixels[i-1][j].isalive())
			count ++;
		if(j + 1 < (int)hpix && pixels[i-1][j+1].isalive())
			count++;				
	}
	
	if(0 < j - 1 && pixels[i][j-1].isalive())
		count++;			
	if(j + 1 < (int)hpix && pixels[i][j+1].isalive())
		count++;
	
	if(i + 1 < (int)vpix) {
		if(0 < j - 1 && pixels[i+1][j-1].isalive())
			count++;
		if(pixels[i+1][j].isalive())
			count ++;
		if(j + 1 < (int)hpix && pixels[i+1][j+1].isalive())
			count++;	
	}			
	
	return count;
}

void PixelMatrix::progress() {
	for(unsigned i = 0; i < vpix; i++)
		for(unsigned j = 0; j < hpix; j++)
			pixels[i][j].prepare(alive_neighbours(i, j));
	for(unsigned i = 0; i < vpix; i++)
		for(unsigned j = 0; j < hpix; j++)
			pixels[i][j].progress();
}

void PixelMatrix::draw() {
	for(unsigned i = 0; i < vpix; i++)
		for(unsigned j = 0; j < hpix; j++)
			pixels[i][j].draw(renderer);
	SDL_RenderPresent(renderer);
}

// used for left clicks
void PixelMatrix::create(int x, int y) {
	x /= Pixel::size;
	y /= Pixel::size;
	pixels[y][x].setState(true);
}

// used for right clicks
void PixelMatrix::kill(int x, int y) {
	x /= Pixel::size;
	y /= Pixel::size;
	pixels[y][x].setState(false);
}

// used for the backspace action
void PixelMatrix::clear() {
	for(unsigned i = 0; i < vpix; i++)
		for(unsigned j = 0; j < hpix; j++)
			pixels[i][j].setState(false);
}


/********************/
/** MAIN FUNCTIONS **/
/********************/

Uint32 timer(Uint32 ms, void *param) {
    SDL_Event ev;
    ev.type = SDL_USEREVENT;
    SDL_PushEvent(&ev);
    return ms;
}

int main(int argc, char *argv[]) {
	srand(time(0));
	SDL_Event ev;
	SDL_Window *sdlWindow;
	SDL_Renderer *sdlRenderer;
	SDL_TimerID id;
	//id = SDL_AddTimer(100, timer, NULL); // used to be started here, caused startup freezes (?)
	try {
		sdlWindow = SDL_CreateWindow("Game of Life",
								 SDL_WINDOWPOS_UNDEFINED,	// Alternatively, use SDL_WINDOWPOS_CENTERED here
								 SDL_WINDOWPOS_UNDEFINED,
								 WIDTH, HEIGHT,					// Set both to 0 if you use fullscreen
								 0);						// Change 0 to SDL_WINDOW_FULLSCREEN_DESKTOP for fullscreen
		if(sdlWindow == NULL) throw 1;
		
		sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, 0);
		if(sdlRenderer == NULL) throw 1;
	} catch (int error) {
		SDL_Quit();
		return error;
	}
	
	// sets renderer colour to black
	SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, 255);
	// paints the screen with the set colour
	SDL_RenderClear(sdlRenderer);
	// submits changes to the screen
	SDL_RenderPresent(sdlRenderer);
	
	// grid
	for(int i = 0; i <= WIDTH; i+=Pixel::size)
		lineRGBA(sdlRenderer, i, 0, i, HEIGHT, 200, 255, 255, 35);	
	for(int i = 0; i <= HEIGHT; i+=Pixel::size)
		lineRGBA(sdlRenderer, 0, i, WIDTH, i, 200, 255, 255, 35);
	
	PixelMatrix m(sdlRenderer);
	m.draw();
	
	bool pause = false;
	bool quit = false;
	id = SDL_AddTimer(100, timer, NULL);
	while(SDL_WaitEvent(&ev) && !quit) {
		switch(ev.type) {
			case SDL_KEYDOWN:
				switch(ev.key.keysym.sym) {
					case SDLK_SPACE:	// SPACE: pauses game
						pause = !pause;
					break;
					case SDLK_BACKSPACE:// BACKSPACE: kills all blocks
						m.clear();
						m.draw();
					break;
					case SDLK_ESCAPE:	// ESCAPE: quits game
						quit = true;
					break;
				}
			break;
			case SDL_MOUSEBUTTONDOWN:
				//pause = true;
				switch(ev.button.button) {
					case SDL_BUTTON_LEFT:	// LEFT MOUSE: creates new blocks
						m.create(ev.button.x, ev.button.y);
						while(SDL_WaitEvent(&ev) && ev.type != SDL_MOUSEBUTTONUP) {
							if(ev.type == SDL_MOUSEMOTION) {
								m.create(ev.button.x, ev.button.y);
								m.draw();
							}
						}
					break;
					case SDL_BUTTON_RIGHT:	// RIGHT MOUSE: kills alive blocks
						m.kill(ev.button.x, ev.button.y);
						while(SDL_WaitEvent(&ev) && ev.type != SDL_MOUSEBUTTONUP) {
							if(ev.type == SDL_MOUSEMOTION) {
								m.kill(ev.button.x, ev.button.y);
								m.draw();
							}
						}
					break;
				}
				m.draw();
			break;
			case SDL_QUIT:
				quit = true;
			break;
			case SDL_USEREVENT:	// normal progession of the game handled here
				if(!pause) {
					m.progress();
					m.draw();
				}
			break;
		}
	}
	
	SDL_RemoveTimer(id);
	SDL_Quit();
	
	return 0;
}