#include <iostream>
#include "WindowModule.h"
#include "Camera.h"
#include "Shader.h"
#include "ShaderProgram.h"
#include "PyramidMesh.h"
#include "BoxMesh.h"
#include "WorldObject.h"
#include "SnakeMesh.h"
#include <SDL_mixer.h>
#include <cstring>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cmath>

using namespace std;
using namespace window;
using namespace logic;
using namespace graphics;
using namespace glm;

enum ACTION {
	LOOKAROUND, ESCAPE, LOOKDOWN, LOOKUP, MOVEAROUND, EXIT, NONE, GOUP, GODOWN
};

const int N = 12;
const int M = 12;
const int DELTA = 4;
WindowModule * WINDOW;
Camera * CAMERA;
ShaderProgram * PROGRAM;
ShaderProgram * BACKGROUND;
BoxMesh * mesh;
WorldObject * background;
vector<WorldObject*> objects;
WorldObject * snake;
WorldObject * snake2;
WorldObject * snake3;
SnakeMesh * snakeMesh;
SnakeMesh * snakeMesh2;
SnakeMesh * snakeMesh3;
vector<float> scaleOffset;
char enabled[N*M];
int blink[N*M];
unsigned ticks = 0;
unsigned STEPS = 0;
bool startShowingWorld = false;
ACTION currentAction=LOOKAROUND;
ACTION nextAction;


void Init();
void Loop();
void Render();
void Update();
void ReadEvents();
void printOpenGLErrors();
void addObject(int i, int j);
void Escape();
void LookAround();
void LookDown();
void LookUp();
void GoUp();
void GoDown();
void follow();

bool noLookingUp = false;
bool noGoUp = false;
int goUpLimit = 80;
int goUpStep = 0;
int goDownLimit = 45;
int goDownStep = 0;

int main(int argc, char * argv[])
{
	Init();
	Loop();
	return 0;
}

void Init() {

	srand(time(NULL));
	WINDOW = new WindowModule();
	WINDOW->start();

	SDL_SetRelativeMouseMode(SDL_TRUE);

	float ratio = float(WINDOW->getWidth()) / float(WINDOW->getHeight());
	CAMERA = new Camera(0.7, ratio, glm::radians(60.0f), 0.5, 220, 
		glm::vec3(30.0f, 37.0f, 30.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec3(0.0f, 1.0f, 0.0f));

	PROGRAM = new ShaderProgram(new Shader("shader.vert", GL_VERTEX_SHADER), new Shader("shader.frag", GL_FRAGMENT_SHADER));
	PROGRAM->useProgram();

	BACKGROUND = new ShaderProgram(new Shader("background.vert", GL_VERTEX_SHADER), new Shader("background.frag", GL_FRAGMENT_SHADER));

	glEnable(GL_DEPTH_TEST);

	// opengl stuff
	glClearColor(0, 0, 0, 1);

	mesh = new BoxMesh();
	for (int i = 0; i < N; ++i) {
		for (int j = 0; j < M; ++j) {
			addObject(i, j);
		}
	}

	background = new WorldObject(mesh, vec3(0), vec3(0), vec3(65));
	snakeMesh = new SnakeMesh(8800);
	snakeMesh2 = new SnakeMesh(4000);
	snakeMesh3 = new SnakeMesh(1000);
	snake = new WorldObject(snakeMesh, vec3(-45, 0, 20), vec3(0), vec3(10));
	snake2 = new WorldObject(snakeMesh2, vec3(-75, 0, 40), vec3(0), vec3(20));
	snake3 = new WorldObject(snakeMesh3, vec3(-86, 0, 64), vec3(0), vec3(28));

	if (MIX_INIT_MP3 != Mix_Init(MIX_INIT_MP3)) {
		printf("Mix_Init: %s\n", Mix_GetError());
		exit(1);
	}
	Mix_OpenAudio(22050, AUDIO_S16SYS, 2, 640);
	Mix_Music *music = Mix_LoadMUS("music.mp3");
	
	Mix_PlayMusic(music, -1);
}

void Render() {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	

	BACKGROUND->useProgram();
	CAMERA->feedFrustrum(BACKGROUND);
	CAMERA->feedUVN(BACKGROUND);
	//background->render(BACKGROUND);
	snake->render(BACKGROUND);
	snake2->render(BACKGROUND);
	snake3->render(BACKGROUND);

	PROGRAM->useProgram();
	CAMERA->feedFrustrum(PROGRAM);
	CAMERA->feedUVN(PROGRAM);
	CAMERA->feedCameraData(PROGRAM);
	GLint blinkLoc = PROGRAM->getLocation("BLINK");
	if (STEPS > 3220) {
		for (size_t i = 0; i < objects.size(); ++i) {
			if (enabled[i]) {
				glUniform1i(blinkLoc, blink[i]);
				objects[i]->render(PROGRAM);
			}
		}
	}
	

	printOpenGLErrors();
}

void Loop() {
	Uint32 fps = 60;
	Uint32 millisPerFrame = 1000 / fps;
	Uint32 frameStart, frameEnd, timeSpent;
	while (true) {
		frameStart = SDL_GetTicks();
		ticks = frameStart;
		++STEPS;
		ReadEvents();
		Update();
		Render();

		SDL_GL_SwapWindow(WINDOW->getSDLWindow());

		frameEnd = SDL_GetTicks();
		timeSpent = (frameEnd - frameStart);
		if (timeSpent < millisPerFrame) {
			SDL_Delay(millisPerFrame - timeSpent);
		}
	}
}
int goForEffects = 0;
void Update() {
	static float off = 0.0f;
	static const float offStep = 6.0f;
	static const float SCALE = 2.0f;
	off += offStep;
	if (off > 360.0) off -= 360.0;
	memset(blink, 0, sizeof(blink));
	for (size_t i = 0; i < objects.size() / 3; ++i) {
		int pos = rand() % objects.size();
		blink[pos] = 1;
	}
	for (size_t i = 0; i < objects.size(); ++i) {
		objects[i]->m_scale.y += sin(glm::radians(scaleOffset[i] + off));
	}
	snakeMesh->grow();
	snakeMesh->grow();

	snakeMesh2->grow();
	snakeMesh2->grow();

	snakeMesh3->grow();
	//follow();
	
	if (STEPS > 2900 && goUpLimit != 120) {
		goUpStep = 0;
		goUpLimit = 120;
		currentAction = GOUP;
		
	}
	
	if (STEPS > 3400 && goUpStep >= goUpLimit && goForEffects!=1) {
		std::cout << "CALL" << std::endl;
		for (int i = 0; i < 50; ++i) CAMERA->move(glm::vec3(-1.0, 0, 0));
		CAMERA->move(glm::vec3(0.0, 0.0, -4));
		CAMERA->updateOrientation();
		goForEffects = 1;
		currentAction = NONE;

	}
	if (goForEffects) {
		//std::cout << "GO FOR EFFECTS" << std::endl;
		// start some effect
	}
	if (STEPS > 4150) {
		Escape();
	}
	if (currentAction == ESCAPE) {
		Escape();
	}
	else if (currentAction == EXIT) {
		exit(0);
	}
	else if (currentAction == LOOKAROUND) {
		LookAround();
	}
	else if (currentAction == LOOKDOWN) {
		LookDown();
	}
	else if (currentAction == LOOKUP) {
		LookUp();
	}
	else if (currentAction == GOUP) {
		GoUp();
	}
	else if (currentAction == GODOWN) {
		GoDown();
	}
}

void ReadEvents() {

	SDL_Event sdlEvent;
	while (SDL_PollEvent(&sdlEvent)) {
		if (sdlEvent.type == SDL_KEYDOWN) {
			if (sdlEvent.key.keysym.sym == SDLK_UP) {
				CAMERA->move(glm::vec3(0.0f, 0.0f, -1.0f));
			}
			else if (sdlEvent.key.keysym.sym == SDLK_DOWN) {
				CAMERA->move(glm::vec3(0.0f, 0.0f, 1.0f));
			}
			else if (sdlEvent.key.keysym.sym == SDLK_LEFT) {
				CAMERA->move(glm::vec3(-1.0f, 0.0f, 0.0f));
			}
			else if (sdlEvent.key.keysym.sym == SDLK_RIGHT) {
				CAMERA->move(glm::vec3(1.0f, 0.0f, 0.0f));
			}
			else if (sdlEvent.key.keysym.sym == SDLK_q) {
				exit(1);

			}
		}
		else if (sdlEvent.type == SDL_MOUSEMOTION) {
			CAMERA->orientate(sdlEvent.motion.xrel, sdlEvent.motion.yrel);
		}
	}

}

void printOpenGLErrors() {
	GLenum glErr;
	while ((glErr = glGetError()) != GL_NO_ERROR) {
		std::cerr << "OpenGL error: " << glErr << std::endl;
	}
}

void addObject(int i, int j) {
	objects.push_back(new WorldObject(mesh, vec3(i*DELTA, 0, j*DELTA), vec3(0.0), vec3(1.0)));
	scaleOffset.push_back(rand()%360);
	enabled[objects.size()-1] = 1;
}

void follow() {
	static unsigned snakeIdx = 0;
	static glm::vec3 prev;
	static int steps = 0;
	static int maxSteps = 10;
	if (snakeIdx > 250) {
		startShowingWorld = true;
		return;
	}
	if (snakeIdx == 0) {
		glm::vec3 sum = glm::vec3(0.0f);
		for (int i = 0; i < 4; ++i) {
			for (int j = 0; j < 3; ++j) sum[j] += snakeMesh->VERTICES[i][j];
		}
		sum /= 4.0f;
		sum *= 10;
		sum.x -= 45;
		sum.z += 20;
		
		prev = sum;
		CAMERA->m_position = sum;
		
	}
	if (snakeMesh->numVertices >160) {
		glm::vec3 sum = glm::vec3(0.0f);
		for (int i = 0; i < 4; ++i) {
			for (int j = 0; j < 3; ++j) sum[j] += snakeMesh->VERTICES[snakeIdx + i][j];
		}
		sum /= 4.0f;
		sum *= 10;
		sum.x -= 45;
		sum.z += 20;
		
		CAMERA->m_position += (sum - prev) / float(maxSteps);
		CAMERA->orientate(5 * sqrt(snakeIdx), 5 * sqrt(snakeIdx));
		++steps;
		if (steps == maxSteps) {
			prev = sum;
			snakeIdx += 1;
			steps = 0;
			CAMERA->m_position = sum;
		}
		
	}
	
}

int sound = 128;

void Escape() {
	static int steps = 0;
	CAMERA->orientate(0, 21);
	if (steps > 5) {
		CAMERA->m_speed = 0.3f;
		CAMERA->move(vec3(0, 0, 1));
	}
	if (CAMERA->m_position.y > 10*snakeMesh->yMax + 18 + CAMERA->m_farZ &&
		CAMERA->m_position.y > 20*snakeMesh2->yMax + 18 + CAMERA->m_farZ &&
		CAMERA->m_position.y > 27*snakeMesh3->yMax + 18 + CAMERA->m_farZ
		) currentAction = EXIT;
	Mix_VolumeMusic(sound);
	if (STEPS % 4 == 0) --sound;
	if (sound < 0) sound = 0;
	++steps;
}

void LookAround() {
	static double EPS = 0.0001;
	/*
	while (!(CAMERA->m_direction.y-EPS < 0.0 && CAMERA->m_direction.y + EPS > 0.0)) {
		if (CAMERA->m_direction.y < 0.0) {
			CAMERA->orientate(0, 0.5);
		}
		else {
			CAMERA->orientate(0, -0.5);
		}
	}*/
	
	static float xAngle = 0.0;
	static float yAngle = 0.0;
	CAMERA->orientate(10, 5*sin(yAngle));
	yAngle += 0.02;
	if (STEPS > 1000 && !noLookingUp) {
		currentAction = GODOWN;
		nextAction = LOOKUP;
		noLookingUp = true;
		return;
	}
	if (STEPS % 150 == 0 && !noGoUp) {
		currentAction = GOUP;
		noGoUp = true;
	}
}

void LookDown() {
	if (dot(glm::vec3(0.0f, -1.0f, 0.0f), CAMERA->m_direction) > 0.7) currentAction = LOOKAROUND;
	CAMERA->orientate(0.0, 3.0);
}

void LookUp() {
	if (dot(glm::vec3(0.0f, 1.0f, 0.0f), CAMERA->m_direction) > 0.15) currentAction = LOOKAROUND;
	CAMERA->orientate(0.0, -3.0);
}

void GoUp() {
	if (dot(glm::vec3(0.0f, -1.0f, 0.0f), CAMERA->m_direction) < 0.97) {
		CAMERA->orientate(0.0, 4.5);
		return;
	}
	if (goUpStep > goUpLimit) {
		currentAction = LOOKDOWN;
		return;
	}
	CAMERA->move(vec3(0.0f, 0.0f, 2.0f));
	++goUpStep;
}

void GoDown() {
	if (dot(glm::vec3(0.0f, -1.0f, 0.0f), CAMERA->m_direction) < 0.99) {
		CAMERA->orientate(0.0, 4.5);
		return;
	}
	if (goDownStep > goDownLimit) {
		currentAction = LOOKUP;
		return;
	}
	CAMERA->move(vec3(0.0f, 0.0f, -2.0f));
	++goDownStep;
}