#include "../Framework/Framework.h"

#include "Chess.h"
#include "../Framework/OpenGL/glExtensions.h"
#include "../Framework/Math/Vertex.h"

#ifndef min
#define min(x, y) ((x < y)? x : y)
#endif

#ifndef max
#define max(x, y) ((x > y)? x : y)
#endif

//float Chess::radius[6] = { 10.54f, 14.26f, 12.67f, 12.93f, 14.73f, 15.15f };
float Chess::height[6] = { 33.99f, 53.05f, 57.58f, 41.99f, 69.10f, 79.24f };

bool operator == (const Move &m0, const Move &m1){
	return (m0.fromX == m1.fromX) && (m0.fromY == m1.fromY) && (m0.toX == m1.toX) && (m0.toY == m1.toY) && (m0.piece == m1.piece);
}

COLOR otherColor(COLOR color){
	return (color == BLACK)? WHITE : BLACK;
}

Chess::Chess(){
	lastTime = 0;
	reset();
}

void Chess::reset(){
	memset(board, 0, sizeof(board));

	for (unsigned int x = 0; x < 8; x++){
		board[1][x].piece = PAWN;
		board[1][x].color = BLACK;
		board[6][x].piece = PAWN;
		board[6][x].color = WHITE;
		board[0][x].color = BLACK;
		board[7][x].color = WHITE;
	}
	board[0][0].piece = board[0][7].piece = board[7][0].piece = board[7][7].piece = ROOK;
	board[0][1].piece = board[0][6].piece = board[7][1].piece = board[7][6].piece = KNIGHT;
	board[0][2].piece = board[0][5].piece = board[7][2].piece = board[7][5].piece = BISHOP;
	board[0][3].piece = board[7][3].piece = QUEEN;
	board[0][4].piece = board[7][4].piece = KING;

	currColor = WHITE;
	currAction = BEGIN;
	timeDelta = 4;

	history.clear();
}

void Chess::makeMove(Move &move){
	if (board[move.fromY][move.fromX].piece == PAWN){
		if (move.toY == 0 || move.toY == 7) board[move.fromY][move.fromX].piece = QUEEN;
	}
	board[move.toY][move.toX] = board[move.fromY][move.fromX];
	board[move.fromY][move.fromX].piece = NONE;
}

void Chess::findAllMoves(Set <Move> &moves, COLOR color){
	static int dirX[8] = { 1,  1, -1, -1, 1, -1,  0,  0 };
	static int dirY[8] = { 1, -1,  1, -1, 0,  0,  1, -1 };
	unsigned int x, y;

	for (y = 0; y < 8; y++){
		for (x = 0; x < 8; x++){
			if (board[y][x].piece != NONE && board[y][x].color == color){
				switch (board[y][x].piece){
				case PAWN:
					findPawnMoves(moves, x, y, color);
					break;
				case KNIGHT:
					findKnightMoves(moves, x, y, color);
					break;
				case BISHOP:
					findOtherMoves(moves, x, y, dirX, dirY, 4, 8, color);
					break;
				case ROOK:
					findOtherMoves(moves, x, y, dirX + 4, dirY + 4, 4, 8, color);
					break;
				case QUEEN:
					findOtherMoves(moves, x, y, dirX, dirY, 8, 8, color);
					break;
				case KING:
					findOtherMoves(moves, x, y, dirX, dirY, 8, 1, color);
					break;
				case NONE:
					break;
				};
			}
		}
	}
}

void Chess::findPawnMoves(Set <Move> &moves, unsigned int x, unsigned int y, COLOR color){
	unsigned int startY = (color == BLACK)? 1 :  6;
	unsigned int dirY   = (color == BLACK)? 1 : -1;

	if (board[y + dirY][x].piece == NONE){
		moves.add(Move(x, y, x, y + dirY, PAWN));

		if (y == startY && board[y + 2 * dirY][x].piece == NONE) moves.add(Move(x, y, x, y + 2 * dirY, PAWN));
	}

	if (x > 0 && board[y + dirY][x - 1].piece != NONE && board[y + dirY][x - 1].color != color) moves.add(Move(x, y, x - 1, y + dirY, PAWN));
	if (x < 7 && board[y + dirY][x + 1].piece != NONE && board[y + dirY][x + 1].color != color) moves.add(Move(x, y, x + 1, y + dirY, PAWN));
}

void Chess::findKnightMoves(Set <Move> &moves, unsigned int x, unsigned int y, COLOR color){
	static int offsetX[8] = { 1,  1, -1, -1, 2,  2, -2, -2 };
	static int offsetY[8] = { 2, -2,  2, -2, 1, -1,  1, -1 };

	for (unsigned int i = 0; i < 8; i++){
		unsigned int nx = x + offsetX[i];
		unsigned int ny = y + offsetY[i];

		if (nx < 8 && ny < 8 && (board[ny][nx].piece == NONE || board[ny][nx].color != color)) moves.add(Move(x, y, nx, ny, KNIGHT));
	}
}

void Chess::findOtherMoves(Set <Move> &moves, unsigned int x, unsigned int y, int *dirX, int *dirY, unsigned int nDir, unsigned int maxLength, COLOR color){
	PIECE piece = board[y][x].piece;

	for (unsigned int i = 0; i < nDir; i++){
		unsigned int sx = x;
		unsigned int sy = y;
		unsigned int n = maxLength;

		do {
			sx += dirX[i];
			sy += dirY[i];

			if (sx > 7 || sy > 7) break;
			if (board[sy][sx].piece == NONE){
				moves.add(Move(x, y, sx, sy, piece));
			} else {
				if (board[sy][sx].color != color) moves.add(Move(x, y, sx, sy, piece));
				break;
			}
		} while (--n);
	}
}

int getGainValue(PIECE piece){
	static int values[7] = { 0, 2, 7, 6, 10, 16, 100 };
	return values[piece];
}

static int achieveThreat[7][7] = {
	0, 0, 0,  0, 0, 0, 0,
	0, 1, 2,  1, 2, 1, 1,  // Pawn
	0, 5, 1,  5, 5, 5, 5,  // Knight
	0, 3, 5,  1, 5, 2, 3,  // Bishop
	0, 8, 9,  9, 1, 4, 3,  // Rook
	0, 5, 15, 4, 4, 1, 0,  // Queen
	0, 7, 15, 9, 9, 7, 0,  // King
	// Pawn, Knight, Bishop, Rook, Queen, King
};

static int fleeThreat[7][7] = {
	0, 0,  0,  0,  0,  0,  0,
	0, 1,  2,  1,  1,  1,  2,  // Pawn
	0, 5,  1,  7,  7,  7,  7,  // Knight
	0, 5,  7,  1,  7,  1,  1,  // Bishop
	0, 8,  9,  9,  1,  1,  8,  // Rook
	0, 15, 15, 9,  9,  1,  0,  // Queen
	0, 50, 50, 50, 50, 50, 50, // King
	// Pawn, Knight, Bishop, Rook, Queen, King
};

int Chess::evaluateThreat(COLOR color, int threatValues[7][7]){
	Set <Move> moves;
	findAllMoves(moves, color);
	int threat = 0;

	for (unsigned int i = 0; i < moves.getSize(); i++){
		threat += threatValues[board[moves[i].toY][moves[i].toX].piece][board[moves[i].fromY][moves[i].fromX].piece];
	}
	return threat;
}

void Chess::evaluateMoves(Set <Move> &moves){
	unsigned int i, j;

	for (i = 0; i < moves.getSize(); i++){
		Square fromSqr = board[moves[i].fromY][moves[i].fromX];
		Square toSqr   = board[moves[i].toY  ][moves[i].toX  ];

		moves[i].gain = getGainValue(toSqr.piece);
		if (fromSqr.piece == PAWN){
			if (moves[i].toY == 0 || moves[i].toY == 7) moves[i].gain += getGainValue(QUEEN);
		}

		moves[i].threatBefore = evaluateThreat(otherColor(currColor), fleeThreat);
		moves[i].counterThreatBefore = evaluateThreat(currColor, achieveThreat);

		makeMove(moves[i]);

		moves[i].threatAfter = evaluateThreat(otherColor(currColor), fleeThreat);
		moves[i].counterThreatAfter = evaluateThreat(currColor, achieveThreat);

		board[moves[i].fromY][moves[i].fromX] = fromSqr;
		board[moves[i].toY  ][moves[i].toX  ] = toSqr;

		moves[i].nHistory = 0;
		for (j = 0; j < history.getSize(); j++){
			if (moves[i] == history[j]) moves[i].nHistory++;
		}
	}
}

unsigned int Chess::getRemainingPieces() const {
	unsigned int n = 0;
	for (unsigned int y = 0; y < 8; y++){
		for (unsigned int x = 0; x < 8; x++){
			if (board[y][x].piece != NONE) n++;
		}
	}
	return n;
}


bool Chess::load(){
	static char *fileNames[] = {
		BASE_PATH"Models/Chess/Pawn.hmdl", BASE_PATH"Models/Chess/Knight.hmdl", BASE_PATH"Models/Chess/Bishop.hmdl",
		BASE_PATH"Models/Chess/Rook.hmdl", BASE_PATH"Models/Chess/Queen.hmdl",  BASE_PATH"Models/Chess/King.hmdl"
	};

	for (unsigned int i = 0; i < 6; i++){
		if (!pieces[i].loadFromFile(fileNames[i])) return false;
	}

	return true;
}

//#define ALERT(x) ::MessageBox(NULL, x, "Error", MB_OK | MB_ICONERROR);

bool Chess::initialize(Renderer *renderer){
	for (unsigned int i = 0; i < 6; i++){
		((OpenGLBatch *) pieces[i].getBatch(0))->uploadToVertexBuffer();
	}

	noise = renderer->addTexture3D(BASE_PATH"Textures/Turb32.dds");

	lightingVS = renderer->addVertexShader(BASE_PATH"Chess/Pieces.vsh");
	if (glGetError() != GL_NO_ERROR){
		char *str = (char *) glGetString(GL_PROGRAM_ERROR_STRING_ARB);
		ALERT(str, "Error");
		return false;
	}

	lightingFS = renderer->addFragmentShader(GL_FRAGMENT_PROGRAM_ARB, BASE_PATH"Chess/Pieces.fsh");
	if (glGetError() != GL_NO_ERROR){
		char *str = (char *) glGetString(GL_PROGRAM_ERROR_STRING_ARB);
		ALERT(str, "Error");
		return false;
	}

	depthVS = renderer->addVertexShader(BASE_PATH"Chess/Depth.vsh");
	if (glGetError() != GL_NO_ERROR){
		char *str = (char *) glGetString(GL_PROGRAM_ERROR_STRING_ARB);
		ALERT(str, "Error");
		return false;
	}

	depthFS = renderer->addFragmentShader(GL_FRAGMENT_PROGRAM_ARB, BASE_PATH"Chess/Depth.fsh");
	if (glGetError() != GL_NO_ERROR){
		char *str = (char *) glGetString(GL_PROGRAM_ERROR_STRING_ARB);
		ALERT(str, "Error");
		return false;
	}

	projShadowVS = renderer->addVertexShader(BASE_PATH"Chess/ProjectedShadow.vsh");
	if (glGetError() != GL_NO_ERROR){
		char *str = (char *) glGetString(GL_PROGRAM_ERROR_STRING_ARB);
		ALERT(str, "Error");
		return false;
	}

	projShadowFS = renderer->addFragmentShader(GL_FRAGMENT_PROGRAM_ARB, BASE_PATH"Chess/ProjectedShadow.fsh");
	if (glGetError() != GL_NO_ERROR){
		char *str = (char *) glGetString(GL_PROGRAM_ERROR_STRING_ARB);
		ALERT(str, "Error");
		return false;
	}

	return true;
}

int score(const Move &move){
	return move.gain + (move.threatBefore - move.threatAfter) + (move.counterThreatAfter - move.counterThreatBefore) - move.nHistory - move.piece;
}

int comp(const Move &m0, const Move &m1){
	return score(m1) - score(m0);
}

void Chess::update(const float currTime){
	time = 2 * (currTime - lastTime);

	if (time > timeDelta){
		if (currAction == END){
			reset();
		} else if (currAction == BEGIN){
			currAction = WAIT;
			timeDelta = 2;
		} else {
			if (currAction == MOVE){
				if (board[currMove.toY][currMove.toX].piece == KING){
					currAction = END;
					timeDelta = 4;
				}
				makeMove(currMove);
				while (history.getSize() > 20) history.orderedRemove(0);
				history.add(currMove);
			}

			if (currAction != END){
				Set <Move> moves;
				findAllMoves(moves, currColor);

				if (getRemainingPieces() <= 2){
					currAction = END;
					timeDelta = 4;
				} else {
					if (moves.getSize() > 0){
						evaluateMoves(moves);
						moves.sort(comp);

						float rnd = 1 - powf(float(rand()) / float(RAND_MAX), 8);
						unsigned int n = 0;
						for (n = 1; n < moves.getSize(); n++){
							if (score(moves[n]) < rnd * score(moves[0])) break;
						}

						currMove = moves[rand() % n];
						currAction = MOVE;
						timeDelta = sqrtf(sqrf(float(currMove.fromX) - float(currMove.toX)) + sqrf(float(currMove.fromY) - float(currMove.toY)));
					} else {
						currAction = WAIT;
					}
					currColor = otherColor(currColor);
				}
			}
		}
		time = 0;
		lastTime = currTime;
	}
}

void Chess::draw(Renderer *renderer, DrawMode drawMode, TextureID shadowMap){
	unsigned int x, y;

	float m = min(timeDelta, 1.6f);
	float s = max(time - timeDelta + m, 0) / m;
	float t = time / timeDelta;
	float lrp = t * t * (3 - 2 * t);
	float oxp = lrp * (currMove.toX - 3.5f) * 48.0f + (1 - lrp) * (currMove.fromX - 3.5f) * 48.0f;
	float ozp = lrp * (currMove.toY - 3.5f) * 48.0f + (1 - lrp) * (currMove.fromY - 3.5f) * 48.0f;

	if (drawMode == NORMAL){
		renderer->setVertexShader(lightingVS);
		renderer->setFragmentShader(lightingFS);
		renderer->setTextures(noise, shadowMap);
	} else if (drawMode == DEPTH){
		renderer->setVertexShader(depthVS);
		renderer->setFragmentShader(depthFS);
	} else {
		renderer->setVertexShader(projShadowVS);
		renderer->setFragmentShader(projShadowVS);
	}
	renderer->apply();

	for (y = 0; y < 8; y++){
		for (x = 0; x < 8; x++){
			if (board[y][x].piece != NONE){
				float xp = (x - 3.5f) * 48.0f;
				float yp = 0;
				float zp = (y - 3.5f) * 48.0f;

				if (currAction == MOVE && x == currMove.fromX && y == currMove.fromY){
					if (board[currMove.toY][currMove.toX].piece != NONE){
						float lrpY = 4 * s * (1 - s);
						yp = height[board[currMove.toY][currMove.toX].piece - 1] * lrpY * lrpY;
					} else if (board[y][x].piece == KNIGHT){
						float lrpY = 4 * t * (1 - t);
						yp = 40 * lrpY * lrpY;
					}
					xp = oxp;
					zp = ozp;
				} else if (currAction == MOVE && x == currMove.toX && y == currMove.toY){
					if (s > 0.5f){
						float lrpY = 4 * s * (1 - s);
						yp = height[board[currMove.toY][currMove.toX].piece - 1] * (lrpY * lrpY - 1);
						//ys = 1 + height[board[currMove.toY][currMove.toX].piece - 1] * (lrpY * lrpY - 1) / height[board[y][x].piece - 1];
					}
				} else if (currAction == BEGIN){
					yp = -1.03f * (1 - lrp) * height[board[y][x].piece - 1];
					//ys = lrp;
				} else if (currAction == END){
					yp = -1.03f * lrp * height[board[y][x].piece - 1];
					//ys = 1 - lrp;
				}

				if (board[y][x].color == BLACK){
					if (drawMode == NORMAL){
						glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, 0.1f, 0.1f, 0.0f, 0);
						glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, 0.4f, 0.2f, 0.1f, 0);
					}
					glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 0,  0, 0,  1, xp);
					glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 1, -1, 0,  0, zp);
				} else {
					if (drawMode == NORMAL){
						glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, 0.6f, 0.4f, 0.0f, 0);
						glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, 1.0f, 0.7f, 0.1f, 0);
					}
					glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 0,  0, 0, -1, xp);
					glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 1,  1, 0,  0, zp);
				}
				glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 2, 1, yp, 0, 0);

				pieces[board[y][x].piece - 1].draw();
			}
		}
	}
}
