#include <cmath>
#include <ctime>
#include <GL/glut.h>
#include <list>
#include <string>
#include <strstream>
#include <vector>

#include "globale.h"
#include "Temps.h"
#include "drawespace.h"
#include "CVector3.h"
#include "CCamera.h"
#include "CCube.h"
#include "CBloc.h"
#include "CJeu.h"
#include "Gameinput.h"
#include "CWinStream.h"
#include "Gameover.h"


// Le flag pour savoir si un cube est vide est que sont x = -1 ou plutôt < 0



// Constructeur

CJeu::CJeu()
{
	Niveau = 0;
	NombreDePlans = 0;

	// Fonctions qui serviront à créer des blocs
	Createur[0]  = &CBloc::Create01;
	Createur[1]  = &CBloc::Create02;
	Createur[2]  = &CBloc::Create03;
	Createur[3]  = &CBloc::Create04;
	Createur[4]  = &CBloc::Create05;

	Createur[5]  = &CBloc::Create11;
	Createur[6]  = &CBloc::Create12;
	Createur[7]  = &CBloc::Create13;
	Createur[8]  = &CBloc::Create14;
	Createur[9]  = &CBloc::Create15;

	Createur[10] = &CBloc::Create20;
	Createur[11] = &CBloc::Create21;




	// Fonctions qui serviront à colorer les blocs
	Colorateur[0] = &CBloc::Couleur01;
	Colorateur[1] = &CBloc::Couleur02;
	Colorateur[2] = &CBloc::Couleur03;
	Colorateur[3] = &CBloc::Couleur04;
	Colorateur[4] = &CBloc::Couleur05;
	Colorateur[5] = &CBloc::Couleur06;
	Colorateur[6] = &CBloc::Couleur07;
	Colorateur[7] = &CBloc::Couleur08;
}




// Fonction qui dit si un bloc a le droit d'être où il est

bool CJeu::PeutEtreLa(CBloc& LeBloc)
{
	float i, j, k;

	for (int a = 0; a < LeBloc.LesCubes.size(); a++)
	{
		i = LeBloc.x + LeBloc.offX + LeBloc.LesCubes[a].x;
		j = LeBloc.y + LeBloc.offY + LeBloc.LesCubes[a].y;
		k = LeBloc.z + LeBloc.offZ + LeBloc.LesCubes[a].z - 0.5f;

		if ( (i >= MAX_X) || (i < 0) || (j >= MAX_Y) || (j < 0) || (k < 0) )
			return (false);

		if (k < MAX_Z)
			if (!(CubeMatrice[int(i)][int(j)][int(k)].x < 0))
				return (false);
	}

	return (true);
}




// Fonction qui crée un nouveau bloc

void CJeu::CreeBloc(void)
{
	Bloc = BlocPreview;

	BlocPreview.LesCubes.clear();

	(BlocPreview.*Colorateur[rand() % NBCOULDIFFERENTE])();
	(BlocPreview.*Createur[rand() % NBBLOCDIFFERENT])();
	//BlocPreview.CreatePlan();

	Bloc.Temps0 = GetTemps();

	if (PeutEtreLa(Bloc))
		CreeBlocGuide();
	else
		GameOver();
}




// Fonction qui place le bloc guide

void CJeu::CreeBlocGuide(void)
{
	BlocGuide = Bloc;
	
	for (int i = static_cast<int> (Bloc.z); i >= 0; i--)
	{
		BlocGuide.z = i;

		if (!PeutEtreLa(BlocGuide))
		{
			BlocGuide.z += 1;
			break;
		}
	}
}




// Fonction qui ajoute un bloc à la liste de blocs à dessiner et à la matrice 3D

void CJeu::AjouterBloc(CBloc& LeBloc)
{
	for (int i = 0; i < LeBloc.LesCubes.size(); i++)
	{
		LeBloc.LesCubes[i].x += LeBloc.x + LeBloc.offX;
		LeBloc.LesCubes[i].y += LeBloc.y + LeBloc.offY;
		LeBloc.LesCubes[i].z += LeBloc.z + LeBloc.offZ;
		
		LeBloc.LesCubes[i].r1 *= ASSOMBRISSEMENT;
		LeBloc.LesCubes[i].g1 *= ASSOMBRISSEMENT;
		LeBloc.LesCubes[i].b1 *= ASSOMBRISSEMENT;
		LeBloc.LesCubes[i].r2 *= ASSOMBRISSEMENT;
		LeBloc.LesCubes[i].g2 *= ASSOMBRISSEMENT;
		LeBloc.LesCubes[i].b2 *= ASSOMBRISSEMENT;

		CubeADessiner.push_back(LeBloc.LesCubes[i]);

		CubeMatrice[short(LeBloc.LesCubes[i].x)]
				   [short(LeBloc.LesCubes[i].y)]
				   [short(LeBloc.LesCubes[i].z)] = LeBloc.LesCubes[i];
	}


	// On vérifie si l'ajout de ce bloc remplit un plan

	GetPlans();

	if (Indices.size() != 0)
	{
		EnlevePlans();
		SetDropFunc();
	}
	else
		CreeBloc();
}




// Fonction qui détermine si des plans sont pleins

void CJeu::GetPlans(void)
{
	int i, j, k;
	bool EstPlein;

	for (k = 0; k < MAX_Z; k++)
	{
		EstPlein = true;

		for (i = 0; (i < MAX_X) && (EstPlein); i++)
			for (j = 0; (j < MAX_Y) && (EstPlein); j++)
				EstPlein = EstPlein && (CubeMatrice[i][j][k].x > 0);

		if (EstPlein)
			Indices.push_back(k);
	}
}




// Fonction qui enlève les blocs des plans à enlever

void CJeu::EnlevePlans(void)
{
	int Compteur = 0, i, j, k;

	for (k = Indices[0]; k < MAX_Z; k++)
	{
		if ((Indices.size() > Compteur) && (Indices[Compteur] == k))
		{
			// On retire
			for (i = 0; i < MAX_X; i++)
				for (j = 0; j < MAX_Y; j++)
				{
					CubeADessiner.remove(CubeMatrice[i][j][k]);
					CubeMatrice[i][j][k].x = -1.0f;
				}

			++Compteur;
		}
	}
}



// Fonction qui dit combien de plans à enlever il y a en-dessous d'un niveau indiqué

inline int GetNombrePlansEnDessous(std::vector< int > & Indices, float Hauteur)
{
	int i;
	for (i = 0; i < Indices.size(); i++)
		if (Indices[i] > Hauteur)
			break;
	return i;
}

// Fontion qui replace les cubes

void CJeu::DescendPlans(void)
{
	CubeADessiner.sort();

	int NbPlans;
	std::list< CCube >::iterator Iter = CubeADessiner.begin();

	for (int i = 0; i < CubeADessiner.size(); i++, Iter++)
	{
		NbPlans = GetNombrePlansEnDessous(Indices, Iter->z);

		if (NbPlans != 0)
		{
 			int x = int(Iter->x);
			int y = int(Iter->y);
			int z = int(Iter->z);

			CubeMatrice[x][y][z].z -= NbPlans;
			CubeMatrice[x][y][z - NbPlans] = CubeMatrice[x][y][z];
			CubeMatrice[x][y][z].x = -1.0f;
			Iter->z -= NbPlans;
		}
	}
}




// Initialisation des graphiques pour le jeu

void CJeu::Init(void)
{
	wout.LaPolice = 6;

	// Initialisation et création de la fenêtre
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB);
	glutInitWindowPosition(0,0);
	glutInitWindowSize(glutGet(GLUT_SCREEN_WIDTH), glutGet(GLUT_SCREEN_HEIGHT));
	glutCreateWindow("Tetris 3D");
	glutFullScreen();

	srand(time(0));

	glutSetCursor(GLUT_CURSOR_NONE);
	

	glClearColor(0.0, 0.0, 0.0, 1.0);

	glEnable(GL_DEPTH_TEST);

	glShadeModel(GL_SMOOTH);
	glLineWidth(1.5);

	glEnable(GL_BLEND);
	glDepthMask(GL_TRUE);

	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}





// Fonction Idle pendant le jeu

void MouseIdle(void)
{
	if (gCamera.EstAvance || gCamera.EstRecule)		
		JeuBougeSouris(CENTRE_X, CENTRE_Y);
	else
		(*gCamera.DisplayFunc)();
}




// Réglage des fonctions

void CJeu::SetGameFunc(void)
{
	gCamera.DisplayFunc = GameDisplay;

	glutIdleFunc(MouseIdle);
	glutDisplayFunc(GameDisplay);
	glutKeyboardFunc(JeuClavier);
	glutKeyboardUpFunc(JeuClavierUp);
	glutMouseFunc(JeuCliqueSouris);
	glutMotionFunc(JeuBougeSouris);
	glutPassiveMotionFunc(JeuBougeSouris);
}




// Réglage des fonctions lorsqu'un plan est enlevé

void CJeu::SetDropFunc(void)
{
	gCamera.DisplayFunc = DropDisplay;

	glutDisplayFunc(DropDisplay);
	glutKeyboardFunc(0);
}




// Dessin de la partie

void CJeu::GameDraw(void)
{
	gCamera.TempsLastFrame = GetTemps();

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	gCamera.CamLookAt();

	Bloc.Draw();
	BlocGuide.DrawGuide();

	std::list< CCube >::const_iterator Iter = CubeADessiner.begin();
	for (int i = 0; i < CubeADessiner.size(); i++)
	{
		Iter->Draw();
		Iter++;
	}

	DrawEspace();

	glFinish();
	glutSwapBuffers();
}




// Dessin de la partie quand un bloc est en train de s'enlever
// 3 étapes:	Au début, dessin du bloc qui flash
//				Ensuite, dessin des blocs qui tombent progressivement
//				Finalement, remettage des fonctions à leur état initial

void DropDisplay(void)
{
	gCamera.TempsLastFrame = GetTemps();
	static long Temps0 = GetTemps();
	static bool Done = false;

	if (Temps0 < 0)
		Temps0 = GetTemps();

	int i;
	std::list< CCube >::const_iterator Iter = gJeu.CubeADessiner.begin();
	float Progress;

	if (!Done)
	{
		if (GetTemps() - Temps0 > TEMPSDROPFLASH)
		{
			Done = true;
			Temps0 = GetTemps();
			return;
		}

		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		gCamera.CamLookAt();


		// Dessin des plans qui flashent

		Progress = (1+sin( 6.28f*(GetTemps() - Temps0) / float(TEMPSDROPFLASH >> 2))) / 2.0f;

		for (i = 0; i < gJeu.Indices.size(); i++)
		{
			glColor3f(1.0f, Progress*Progress, Progress);
			
			glBegin(GL_QUADS);
				glVertex3f(0, 0, gJeu.Indices[i]);
				glVertex3f(0, 0, gJeu.Indices[i]+1);
				glVertex3f(MAX_X, 0, gJeu.Indices[i]+1);
				glVertex3f(MAX_X, 0, gJeu.Indices[i]);
			glEnd();

			glBegin(GL_QUADS);
				glVertex3f(0, MAX_Y, gJeu.Indices[i]);
				glVertex3f(0, MAX_Y, gJeu.Indices[i]+1);
				glVertex3f(MAX_X, MAX_Y, gJeu.Indices[i]+1);
				glVertex3f(MAX_X, MAX_Y, gJeu.Indices[i]);
			glEnd();

			glBegin(GL_QUADS);
				glVertex3f(0, 0, gJeu.Indices[i]);
				glVertex3f(0, 0, gJeu.Indices[i]+1);
				glVertex3f(0, MAX_Y, gJeu.Indices[i]+1);
				glVertex3f(0, MAX_Y, gJeu.Indices[i]);
			glEnd();

			glBegin(GL_QUADS);
				glVertex3f(MAX_X, 0, gJeu.Indices[i]);
				glVertex3f(MAX_X, 0, gJeu.Indices[i]+1);
				glVertex3f(MAX_X, MAX_Y, gJeu.Indices[i]+1);
				glVertex3f(MAX_X, MAX_Y, gJeu.Indices[i]);
			glEnd();

			glBegin(GL_QUADS);
				glVertex3f(0, 0, gJeu.Indices[i]);
				glVertex3f(MAX_X, 0, gJeu.Indices[i]);
				glVertex3f(MAX_X, MAX_Y, gJeu.Indices[i]);
				glVertex3f(0, MAX_Y, gJeu.Indices[i]);
			glEnd();

			glBegin(GL_QUADS);
				glVertex3f(0, 0, gJeu.Indices[i]+1);
				glVertex3f(MAX_X, 0, gJeu.Indices[i]+1);
				glVertex3f(MAX_X, MAX_Y, gJeu.Indices[i]+1);
				glVertex3f(0, MAX_Y, gJeu.Indices[i]+1);
			glEnd();
		}


		for (i = 0; i < gJeu.CubeADessiner.size(); i++)
		{
			Iter->Draw();
			Iter++;
		}
	}
	else // Si Done est vrai
	{
		Progress = (float) (GetTemps() - Temps0) / (float) (TEMPSDROP);
		if (Progress > 1)
		{
			Done = false;
			gJeu.DescendPlans();
			gJeu.NombreDePlans += gJeu.Indices.size();
			gJeu.Niveau = gJeu.NombreDePlans / PLANPARNIVEAU;
			gJeu.Indices.clear();
			Temps0 = -1;
			gJeu.SetGameFunc();
			gJeu.CreeBloc();
			return;
		}


		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		gCamera.CamLookAt();

		
		gJeu.CubeADessiner.sort();

		glPushMatrix();

		int Compteur = 0;

		for (i = 0; i < gJeu.CubeADessiner.size(); i++)
		{
			while ((Compteur < gJeu.Indices.size()) && (gJeu.Indices[Compteur] < Iter->z))
			{
				++Compteur;
				glTranslatef(0,0, -Progress);
			}
			
			Iter->Draw();
			Iter++;
		}

		glPopMatrix();
	}


	DrawEspace();

	glFinish();
	glutSwapBuffers();
}
