#include <assert.h>
#include <stdio.h>
#include <string.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include <algorithm>
#include "glut.h"

//For cpp bots
#ifdef BOTWITHGLUT
void BotThink();
bool botshoot = true;
bool botrun = true;
#else
bool botshoot = false;
bool botrun = false;
#endif

//COPIED FROM GLOBAL.H
//game field is a square with side = XXX
static const double FIELD_SIZE = 1.0;

const double MAX_FPS = 1000.0;
const double MAX_DELTATIME = 1.0;
#include "point.h"
#include "../ReadMe/world.h"
World *world = 0;

const char *APP_NAME = "Arcade";
inline int getTime() { return glutGet(GLUT_ELAPSED_TIME); }

int width, height;

bool inmainloop = false;
double lastframe;

double2 WorldToGL(double2 w) { return (w / FIELD_SIZE)*2.0 - double2(1.0, 1.0); };
double2 ScreenToWorld(int2 s) {
	double2 pos(double(s.x)/width, 1.0 - double(s.y)/height);
	return pos * FIELD_SIZE;
};

bool pressedkey[4];
bool pressedmouse;
int mouseX, mouseY;
bool uKeyDown(unsigned char key) {
	if (key == 'w') { pressedkey[0] = true; return true; }
	if (key == 's') { pressedkey[1] = true; return true; }
	if (key == 'd') { pressedkey[2] = true; return true; }
	if (key == 'a') { pressedkey[3] = true; return true; }
	return false;
}
bool uKeyUp(unsigned char key) {
	if (key == 'w') { pressedkey[0] = false; return true; }
	if (key == 's') { pressedkey[1] = false; return true; }
	if (key == 'd') { pressedkey[2] = false; return true; }
	if (key == 'a') { pressedkey[3] = false; return true; }
	return false;
}
double2 getKeysDir() {
	double2 res;
	if (pressedkey[0]) res += double2(0, 1);
	if (pressedkey[1]) res += double2(0, -1);
	if (pressedkey[2]) res += double2(1, 0);
	if (pressedkey[3]) res += double2(-1, 0);
	if (!res > 0.5) res /= !res;
	return res;
}
double2 getMouseDir() {
	return ScreenToWorld(int2(mouseX, mouseY)) - double2(world->player.PositionX, world->player.PositionY);
}


void Init() {
}
void Exit() {
	delete world;
	glutDestroyWindow(glutGetWindow());
#ifdef _VS_MEM_LEAKS
	_CrtDumpMemoryLeaks();
#endif
	exit(0);
};

template<class T> void DrawEntity(const T &ent) {
	if (ent.ClassName < 0) return;

	static const int N = 10;
	double rad = ent.Radius + 0.001;
	double2 position(ent.PositionX, ent.PositionY);

	glPointSize(2);
	glBegin(GL_POINTS);
		double2 pos = WorldToGL(position);
		glVertex2dv(&pos.x);
	glEnd();

	glBegin(GL_POLYGON);
		for (int j = 0; j<=N; j++) {
			double ang = 4.0*acos(0.0) * j/N;
			double2 v = WorldToGL(position + double2(cos(ang), sin(ang)) * rad);
			glVertex2dv(&v.x);
		}
	glEnd();
}

template<class T> void DrawHealthBar(const T &ent) {
	if (ent.ClassName < 0) return;

	double rad = ent.Radius;
	double wdt = (ent.ClassName==0 ? 0.2 : 0.05);
	double wfill = wdt * ent.HitPoints / ent.MaxHitPoints;
	double hgt = 0.02;
	double pad = hgt;
	double2 pos = WorldToGL(double2(ent.PositionX, ent.PositionY)) + double2(-wdt*0.5, rad + pad);

	glColor3d(0.5, 1.0, 0.5);
	glBegin(GL_POLYGON);
		glVertex2d(pos.x, pos.y);
		glVertex2d(pos.x+wfill, pos.y);
		glVertex2d(pos.x+wfill, pos.y+hgt);
		glVertex2d(pos.x, pos.y+hgt);
	glEnd();
	glColor3d(0.5, 0.2, 0.2);
	glBegin(GL_POLYGON);
		glVertex2d(pos.x+wfill, pos.y);
		glVertex2d(pos.x+wdt, pos.y);
		glVertex2d(pos.x+wdt, pos.y+hgt);
		glVertex2d(pos.x+wfill, pos.y+hgt);
	glEnd();
}

int BACKCOLOR = 0;
static void display(void) {
	if (BACKCOLOR) glClearColor(0.0, 0.0, 0.0, 0.0);
	else glClearColor(0.1, 0.1, 0.1, 0.0);
	glClear(GL_COLOR_BUFFER_BIT);
	
	glColor3d(1.0, 1.0, 1.0);
	DrawEntity(world->player);
	glColor3d(0.8, 0.2, 0.2);
	for (int i = 0; i<World::MAXMONSTERS; i++) DrawEntity(world->monsters[i]);
	glColor3d(0.3, 0.5, 1.0);
	for (int i = 0; i<World::MAXMISSILES; i++) DrawEntity(world->missiles[i]);

	DrawHealthBar(world->player);
	for (int i = 0; i<World::MAXMONSTERS; i++) DrawHealthBar(world->monsters[i]);

	glutSwapBuffers();
}

static void reshape(int width, int height) {
	glViewport(0, 0, width, height);
	::width = width;
	::height = height;
	glutPostRedisplay();
}

static void keyboardDown(unsigned char c, int x, int y) {
	if (c == 27) Exit();
	uKeyDown(c);
}

static void keyboardUp(unsigned char c, int x, int y) {
#ifdef BOTWITHGLUT
	if (c == '1') botrun = !botrun;
	if (c == '2') botshoot = !botshoot;
#endif
	uKeyUp(c);
}

static void motion(int x, int y) {
	mouseX = x;
	mouseY = y;
}

static void mouse(int button, int state, int x, int y) {
	if (button == GLUT_LEFT_BUTTON) pressedmouse = (state == GLUT_DOWN);
}

static void idle(void) {
	if (!inmainloop) {
		Init();
		inmainloop = true;
		lastframe = getTime();
		return;
	}
	double newframe = getTime();
	double deltatime = (newframe - lastframe) * 0.001;
	if (deltatime < 1.0 / MAX_FPS) return;

	//update fps
	static double lastfpscalc = getTime();
	static int thissecondcount = 0;
	static double fps = MAX_FPS;
	if (newframe - lastfpscalc > 1000.0) {
		fps = thissecondcount / (newframe - lastfpscalc) * 1000.0;
		thissecondcount = 0;
		lastfpscalc = newframe;
	}
	thissecondcount++;

	//count living entities
	int lpls = 1;
	int lmons = 0;
	int lmiss = 0;
	for (int i = 0; i<World::MAXMONSTERS; i++) lmons += (world->monsters[i].ClassName >= 0);
	for (int i = 0; i<World::MAXMISSILES; i++) lmiss += (world->missiles[i].ClassName >= 0);
	char buff[256];
	sprintf(buff, "FPS = %0.2lf     Entities = %d (%d:%d:%d)", fps, lpls+lmons+lmiss, lpls, lmons, lmiss);

	glutSetWindowTitle(buff);
	lastframe = newframe;

	if (world->player.HitPoints >= -1e-5) {
		double2 vrun = getKeysDir();
		double2 vshoot = (pressedmouse ? getMouseDir() : double2());
#ifdef BOTWITHGLUT
		BotThink();
#endif
		if (!botrun) world->run(vrun.x, vrun.y);
		if (!botshoot) world->shoot(vshoot.x, vshoot.y);
		//send move and wait for world refresh
		world->makeMove();
	}

	glutPostRedisplay();
}

int main(int argc, char **argv) {
	if (argc < 3) {
		fprintf(stderr, "Parameters: [IP] [Port]\n");
		return 666;
	}
	world = new World(argv[1], atoi(argv[2]));

	glutInit(&argc, argv);
	glutInitWindowPosition(0, 0);
	glutInitWindowSize(700, 700);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
	glutCreateWindow(APP_NAME);

	glutIdleFunc(idle);
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboardDown);
	glutKeyboardUpFunc(keyboardUp);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);
	glutPassiveMotionFunc(motion);

	glDisable(GL_DEPTH_TEST);

	glutMainLoop();
	return 0;
}
