#include <unistd.h>
#include <iostream>
#include <stdlib.h>
#include <curses.h>
#include <string>
#include <pthread.h>
#include <math.h>
#include <vector>
#include "map.h"
#include "console.h"
#include "block.h"
#include "wall.h"
#include "player.h"
#include "beast.h"
#include "super.h"
#include "baby.h"
#include "main.h"
#include "egg.h"

// the map object to use for storing all our game info.
map* m;
player* playas[NUM_PLAYERS];

// forward declaration of functions used by threads
void *level_thread(void *arg);
void *super_beast_thread(void *arg);
void *beast_thread(void *arg);
void *egg_thread(void *arg);

int level = 1;

int main() {
	using namespace std;

	// various ncurses initialization calls.
	initscr(); cbreak(); noecho(); start_color();
	halfdelay(1);
	keypad(stdscr, true);
	// initialize colorpair values, brought to you by the ncurses
	// manpage
	for(int i = 0; i < COLOR_PAIRS; i++)
		init_pair(i, i%COLORS, i/COLORS);

	// screenwriting buffer console
	console *c = new console();
	m = new map(COLS-1, LINES-1);

	// so far this is all just testing stuff
	// create some objects and print them to a console
	block b;
	wall w;

	// the map object has it's own methods for writing itself to
	// a console, which is set by the setcon() function
	m->setcon(c);
	// different map functions
	m->scatter(&b, int(COLS*LINES*.5));
	m->makeborder(&w);

	for (int i = 0; i < NUM_PLAYERS; i++) {
		playas[i] = new player(i);
		m->scatter(playas[i]);
	}

	bool liveplayers;
	if (NUM_PLAYERS) liveplayers = true;

	level = 1;
	pthread_t levthread;
	pthread_create(&levthread, NULL, level_thread, NULL);
	while (liveplayers) {
		m->printmap();
		// refresh is an ncurses function, just redraws the screen
		refresh();

		liveplayers = false;
		char ch = getch();
		for (int i = 0; i < NUM_PLAYERS; i++) {
			if (playas[i]->isalive()) liveplayers = true;
			playas[i]->move(ch);
		}
	}
	level = 0;

	//m->printmap();
	//refresh();
	//char a;
	//cin >> a;
	// weird stuff happens when the program ends if you don't use endwin()
	refresh();
	endwin();
	delete m;
	delete c;

	int winner[] = {0, 0};
	for (int i = 0; i < NUM_PLAYERS; i++) {
		if (playas[i]->getscore() > winner[1]) {
			winner[0] = i;
			winner[1] = playas[i]->getscore();
		}
		cout << "Player " << i << ": " << playas[i]->getscore() << endl;
	}
	cout << "Player " << winner[0] << " winned!" << endl;
}

void *level_thread(void *arg) {

	for (level = 1; level <= 10; level++) {
		int beasts = 3 + level*2;
		int supers = (int)((level-1)*sqrt((double)level/2));
		int eggs = (int)(((double)level/3)*level/3);

		vector<pthread_t> threads;
		int i;
		for (i = 0; i < beasts; i++) {
			pthread_t newthread;
			pthread_create(&newthread, NULL, beast_thread, NULL);
			threads.push_back(newthread);
		}
		for (i = 0; i < supers; i++) {
			pthread_t newthread;
			pthread_create(&newthread, NULL, super_beast_thread, NULL);
			threads.push_back(newthread);
		}
		for (i = 0; i < eggs; i++) {
			pthread_t newthread;
			pthread_create(&newthread, NULL, egg_thread, NULL);
			threads.push_back(newthread);
		}

		for (i = 0; i < threads.size(); i++)
			pthread_join(threads[i], NULL);
	}

	level = 0;
}	
		
void *beast_thread(void *arg) {
	beast *b;
	if (arg) b = (beast*)arg;
	else {
		b = new beast();
		m->scatter(b);
	}

	while (b->isalive() && level)
	{
		usleep(1000000);
		b->move();
	}

	delete b;

	return NULL;
}

void *super_beast_thread(void *arg) {
	super *b;
	if (arg) b = (super*)arg;
	else {
		b = new super();
		m->scatter(b);
	}

	while (b->isalive() && !b->explode() && level)
	{
		usleep(1000000);
		b->move();
	}

	pthread_t threads[5];
	bool thread_created[5];
	if (b->explode()) {
		int bx = b->getx();
		int by = b->gety();
		m->setsprite(bx, by, new block());
		// spawn new beast threads
		int j = 0;
		for (int i = 0; i < 9; i++)
		{
			int x = bx + (i % 3) - 1;
			int y = by + (i / 3) - 1;

			if (i % 2) {
				string type = "null";
				if (m->spriteat(x, y))
					type = m->spriteat(x, y)->mytype();
				if (type == "block" || type == "null")
					m->setsprite(x, y, NULL);
			}
			else {
				string type = "null";
				if (m->spriteat(x, y))
					type = m->spriteat(x, y)->mytype();
				if (type == "block" || type == "null") {
					beast *spawn = new beast();
					m->setsprite(x, y, NULL);
					spawn->setpos(x, y);
					m->setsprite(x, y, spawn);

					if (j < 5) {
						thread_created[j] = true;
						pthread_create(&threads[j++], NULL, beast_thread, (void*)spawn);
					} else thread_created[j++] = false;
				}
			}
		}
	}

	// wait on sub threads
	for (int j = 0; j < 5; j++)
		if (thread_created[j])
			pthread_join(threads[j], NULL);

	delete b;
	return NULL;
}

void *egg_thread(void *arg) {
	egg *e;
	if (arg) e = (egg*)arg;
	else {
		e = new egg;
		m->scatter(e);
	}

	while (e->isalive() && !e->hatched() && level)
	{
		usleep(1000000);
		e->move();
	}

	pthread_t thread;
	bool spawned = false;
	if (e->isalive()) {
		// spawn new beast thread
		baby *spawn = new baby;
		spawn->setpos(e->getx(), e->gety());

		pthread_create(&thread, NULL, beast_thread, (void*)spawn);
		spawned = true;
	}

	delete e;

	if (spawned) pthread_join(thread, NULL);

	return NULL;
}
