/**
 * \file CoachSynchro.cpp
 *
 * Synchro implementation of the CoachKernel class
 */

#include "Coach/CoachAnalyser.h"

#include "Coach/CoachKernel.h"
#include <math.h>
#include <stdio.h>
#include <string.h>

#ifdef WIN32
#include <windows.h>
#endif

void CoachKernel::OnChangePlayerType(unsigned number, int type)
{
	if(playerParams && playerTypes) {	// we support heterogeneous types
		if(number >=1 && number <= 11 && type >=0 && type < playerParams->player_types) {
			playerTypesByNumber[number] = type;
		}
	}
}

void CoachKernel::OnInit(bool side)
{
	commandGen = (CommandGen*) Main::GetInstance()->GetInterface(COMMANDS_TASK);
	commandGen->Eye("on");
//	commandGen->TeamNames(); // quite useless
	ourSide = side;

	TXTN("Connected, %s side, COACH", side ? "left" : "right");

#ifdef WIN32
	SetConsoleTitle("Coach");
#endif

//	Starting();
}

void CoachKernel::OnChangeMode(int time, PlayMode mode)
{
	// change of sides
	if(mode == PM_BeforeKickOff)
	{
		ourSide = ourSide;
		TXTN("---- Our Side is %s ----", ourSide ? "left" : "right");
	}
	if((mode == PM_AfterGoal_Our && ourSide) || 
		(mode == PM_AfterGoal_Their && !ourSide))
	{
		scoreOur++;
		TXTN("Score %d : %d (%d)", scoreOur, scoreTheir, scoreTheir - scoreOur);
	}
	if((mode == PM_AfterGoal_Their && ourSide) || 
		(mode == PM_AfterGoal_Our && !ourSide))
	{
		scoreTheir++;
		TXTN("Score %d : %d (%d)", scoreOur, scoreTheir, scoreTheir - scoreOur);
	}

	if(mode == PM_TimeOver) sayCoachCnt = 0;

	memset(&lastBallInfo, 0, sizeof(lastBallInfo));
	lastKick.who = 0;
	if(continousTimeInPlayOn > 0)
		TXTN(" PlayOn was continously %d cycles", continousTimeInPlayOn);
	continousTimeInPlayOn = 0;
	canSendCLmessage = 0;
	playMode = mode;
}

void CoachKernel::OnHear(int time, bool our, int uniformNumber, const char* message)
{
	/// \todo can parse messages from our players in order to find out, what they intend to do
	if(our == true){
	}
}

void CoachKernel::OnScore(int time, unsigned int our, unsigned int their)
{
	scoreOur = our;
	scoreTheir = their;
}

void CoachKernel::OnSeeGlobal(const COACH_VISUAL_INFO* vi)
{
	static writen = false;
	static int i;
	static Vector ballDistXY;  // distance from ball separately for x and y
	static float ballDist;     // distance from ball
	static float nearestDist;  // to find out player nearest to ball
	static int type;           // heterogeneous type of player being processed
	static int numCurr;        // number of player being processed
	static Point ballExpPos;   // ball's expected position (when not kicked)
	static float rmax;         // maximum variation in ball's expected position
	static float passLength;   // pass length
	static int lenIndex;       // index according to pass length
	static float intersectLeftGoalY;  // y coordinate where the ball will <name>
	static float intersectRightGoalY; // y coordinate where the ball will <name>
	
	// Send logo to server if not already sent
	if(teamGraphicOkCount < 256 && teamGraphicTries < TEAM_GRAPHIC_MAX_TRIES)
	{
		DBGN("Sending logo (ok cnt %i)", teamGraphicOkCount);
		if(!SendTeamGraphic(teamGraphicDir))
		{
			WARNN("Error while sending logo (directory: \"%s\")", teamGraphicDir);
			teamGraphicTries = TEAM_GRAPHIC_MAX_TRIES;
		}
	}

	simTime = vi->time;
/*	if((simTime >= 599) && (!writen))
	{
		analyser.printToFile("c:\\test1.txt");
		writen = true;
	}*/

	// BEGIN ZMENA 01.05.2005
	static int iChangeTypes = 1; 
	int endTime = 4;
	int nTries = 3;

	if(GetPlayMode() == PM_BeforeKickOff)
	{
		if (playerParams != NULL && playerParams->player_types > 0 && nHeteroCounter == playerParams->player_types)
		{
			if (vi->oursCount == 11)
			{
				if (((iChangeTypes % endTime) == 0) && (iChangeTypes <= endTime*nTries))
				{
					MakePlayerSelection();
					if (iChangeTypes == endTime*nTries)
						iChangeTypes = -1;
				}
				if (iChangeTypes > 0)
				{
					iChangeTypes++;
				}
			}
		}
	}
	// END ZMENA 01.05.2005
	
	if(simTime == timeOfLastVisualInfo) return;
	timeOfLastVisualInfo = simTime;

	if(GetPlayMode() != PM_PlayOn) {
		canSendCLmessage = 0;
	}
	// if FIFO buffer is not empty, try to send message
	if(FIFObottom != FIFOtop) SayMessage(NULL);

	if(GetPlayMode() != PM_PlayOn)
	{
		// find out nearest player (who kicks off)
		lastNearestPlayer = 0;
		nearestDist = 200; // big number
		
		for(i = 0; i < vi->oursCount; i++)
		{
			numCurr = vi->ours[i].uniformNumber;
			ballDist = (vi->ours[i].pos - vi->ball.pos).GetLength();
			
			if(ballDist < nearestDist)
			{
				lastNearestPlayer = numCurr;
				nearestDist = ballDist;
			}
		}
		
		for(i = 0; i < vi->theirCount; i++)
		{
			numCurr = vi->their[i].uniformNumber;
			ballDist = (vi->their[i].pos - vi->ball.pos).GetLength();
			
			if(ballDist < nearestDist)
			{
				lastNearestPlayer = numCurr + 11;
				nearestDist = ballDist;
			}
		}

		return;
	}

	if(GetPlayMode() == PM_PlayOn) 
	{
		timeInPlayOn++;
		continousTimeInPlayOn++;
		if(canSendCLmessage > 0) canSendCLmessage--;

// ********************** ANALYZA *********************
		analyser.update(vi);

// ********************** ANALYZA *********************
	}

	char msg[8196];
// kazdych n cyklov vyhodnotime analyzu
	if ((simTime % 300 ==0) && (simTime > 3))
	{
		unsigned int attack, defence;
		char formationassembly[20] = {0};
//		char counts[4];
		
		int numatk, numdef, nummid, offset;
		strcpy(msg, "(define (definerule ChangeFormation direc ((true) Formation");

		//urcia sa pocty hracov v subformaciach
		attack = analyser.countAttackFitness(&formations);
		numatk = formations.GetPlayersCountInUnit(attack);
//		attack = 16; numatk = 4;
		defence = analyser.countDefenceFitness(&formations);
		offset = defence%100;
		defence -= offset;
		defence /= 100;
		numdef = 4;//formations.GetPlayersCountInUnit(defence);

		nummid = 10-numatk-numdef;
		switch(nummid)
		{
			// TODO prerobit mid podla toho, ktore subformacie budu linie o n hracoch(2-5)
		case 1:
			sprintf(formationassembly,"%d%d%d%c%c%c%c)))", numdef, nummid, numatk,
				defence + 'A', 'A', attack + 'A', offset + 'A');
			strcat(msg, formationassembly);
			TXT("Formation change: %s", msg);
			SayMessage(msg);
			break;
		case 2:
			sprintf(formationassembly,"%d%d%d%c%c%c%c)))", numdef, nummid, numatk,
				defence + 'A', 'B', attack + 'A', offset + 'A');
			strcat(msg, formationassembly);
			TXT("Formation change: %s", msg);
			SayMessage(msg);
			break;
		case 3:
			sprintf(formationassembly,"%d%d%d%c%c%c%c)))", numdef, nummid, numatk,
				defence + 'A', 'H', attack + 'A', offset + 'A');
			strcat(msg, formationassembly);
			TXT("Formation change: %s", msg);
			SayMessage(msg);
			break;
		case 4:
			sprintf(formationassembly,"%d%d%d%c%c%c%c)))", numdef, nummid, numatk,
				defence + 'A', 'O', attack + 'A', offset + 'A');
			strcat(msg, formationassembly);
			TXT("Formation change: %s", msg);
			SayMessage(msg);
			break;
/*		case 5:
			sprintf(formationassembly,"%d%d%d%c%c%c%c)))", numdef, nummid, numatk,
				defence + 'A', 'U', attack + 'A', offset + 'A');
			strcat(msg, formationassembly);
			SayMessage(msg);
			break;
		case 6:
			sprintf(formationassembly,"%d%d%d%c%c%c%c)))", numdef, nummid, numatk,
				defence + 'A', 'Z', attack + 'A', offset + 'A');
			strcat(msg, formationassembly);
			SayMessage(msg);
			break;*/
		default:
			TXT("Invalid Formation\n\n");
			break;
		}
		analyser.reset();
	}

/*  Vypneme menenie formacie na zaklade inkasovanych golov
	if(scoreTheir - scoreOur > 5)
	{
		if(advice != 3)
		{
			advice = 3;
			strcpy(msg, "(define (definerule ChangeFormation direc ((true) Formation622AAA)))");
			SayMessage(msg);
		}
	}
	else if(scoreTheir - scoreOur > 3)
	{
		if(advice != 2)
		{
			advice = 2;
			strcpy(msg, "(define (definerule ChangeFormation direc ((true) Formation523AAB)))");
			SayMessage(msg);
		}
	}
	else if(scoreTheir - scoreOur > 1)
	{
		if(advice != 1)
		{
			advice = 1;
			strcpy(msg, "(define (definerule ChangeFormation direc ((true) Formation433AAB)))");
			SayMessage(msg);
		}
	}
*/

	if(timeInPlayOn == 900 && scoreTheir - scoreOur <= 1) // send change formation
	{
		// ball was in middlefield more than 60% of playtime
		if(statsBall.cyclesInZone[2] / (float)timeInPlayOn > .6f)
		{
			strcpy(msg, "(define (definerule ChangeFormation direc ((true) Formation343BAB)))");
			SayMessage(msg);
		}
	}
/*
	if(timeInPlayOn % 900 == 899) // send change formation
	{
		strcpy(msg, "(define (definerule ChangeFormation direc ((true) Formation");
		switch(GetFormation(false))
		{
			case FORMATION622:
				strcat(msg, "334AAA");
			break;
			case FORMATION532:
				strcat(msg, "334AAA");
			break;
			case FORMATION433:
				strcat(msg, "433AAB");
			break;
			case FORMATION523:
				strcat(msg, "343AAB");
			break;
			case FORMATION343:
				strcat(msg, "343AAB");
			break;
			case FORMATION334:
				strcat(msg, "532ABB");
			break;
		}
		strcat(msg, ")))");
//		SayMessage(msg);
	}
*/

	// kick detection
	ballExpPos = lastBallInfo.pos + lastBallInfo.speed;
	rmax = serverParams->ball_rand * lastBallInfo.speed.GetLength();

	// find out, if the expected position variation exceeded rmax -> it's a kick
	if((fabs(vi->ball.speed.GetX()) > 0.001 || fabs(vi->ball.speed.GetY()) > 0.001) &&
	   (fabs(ballExpPos.GetX() - vi->ball.pos.GetX()) > rmax ||
	    fabs(ballExpPos.GetY() - vi->ball.pos.GetY()) > rmax)) 
	{
		TXT("KICK in %d by %d", simTime - 1, lastNearestPlayer);
		if(lastKick.who > 0)
		{
			passLength = (lastKick.from - lastBallInfo.pos).GetLength();
			lenIndex = GetLengthIndex(passLength);

			if(lastNearestPlayer < 12 && lastKick.who < 12 ||
			   lastNearestPlayer > 11 && lastKick.who > 11)
			{
				if(lastNearestPlayer == lastKick.who) TXTN(" dribble"); 
				else 
				{
					TXTN(" successful pass %fm",
						(lastKick.from - lastBallInfo.pos).GetLength());

					if(lastNearestPlayer < 12)
					{
						statsOurPlayer[lastNearestPlayer].passesReceived++;
						statsOurPlayer[lastKick.who].passesAll[lenIndex]++;
						statsOurPlayer[lastKick.who].passesSuccess[lenIndex]++;
						statsOurPlayer[lastKick.who].sumPassSucLen += passLength;
					}
					else 
					{
						statsTheirPlayer[lastNearestPlayer - 11].passesReceived++;
						statsTheirPlayer[lastKick.who - 11].passesAll[lenIndex]++;
						statsTheirPlayer[lastKick.who - 11].passesSuccess[lenIndex]++;
						statsTheirPlayer[lastKick.who - 11].sumPassSucLen += passLength;
					}
				}
			}
			else
			{
				TXTN(" intercepted pass");
				if(lastNearestPlayer < 12)
				{
					statsOurPlayer[lastNearestPlayer].interceptions++;
					statsTheirPlayer[lastKick.who - 11].passesAll[lenIndex]++;
				}
				else
				{
					statsTheirPlayer[lastNearestPlayer - 11].interceptions++;
					statsOurPlayer[lastKick.who].passesAll[lenIndex]++;
				}
			}
		}
		else TXTN("");

		// Find out if it is a kick on goal
		static float t;
		static float len, spdl;
		t = (flagPos[GOAL_L].GetX() - vi->ball.pos.GetX()) / vi->ball.speed.GetX();
		intersectLeftGoalY = vi->ball.pos.GetY() + vi->ball.speed.GetY() * t;
		if(t > 0 && intersectLeftGoalY > -10 && intersectLeftGoalY < 10){
			len = (vi->ball.pos - Point(flagPos[GOAL_L].GetX(), intersectLeftGoalY)).GetLength();
			spdl = vi->ball.speed.GetLength() * serverParams->ball_decay;
			while(len > 0 && spdl > SPEED_ON_GOAL_LINE_MIN) {
				len -= spdl;
				spdl *= serverParams->ball_decay;
			}
			if(len <= 0){
				len = (vi->ball.pos - Point(flagPos[GOAL_L].GetX(), intersectLeftGoalY)).GetLength();
				// If it's our player and our side is right
				if(lastNearestPlayer < 12 && ourSide == false){
					statsOurPlayer[lastNearestPlayer].shootsOnGoal++;
					statsOurPlayer[lastNearestPlayer].sumLenShootsOnGoal += len;
					TXTN(" - on left goal from %.2fm (exp. speed on goal line = %.2f)", len, spdl);
				}
				// If it's their player and our side is left
				if(lastNearestPlayer > 11 && ourSide == true){
					statsTheirPlayer[lastNearestPlayer - 11].shootsOnGoal++;
					statsTheirPlayer[lastNearestPlayer - 11].sumLenShootsOnGoal += len;
					TXTN(" - on left goal from %.2fm (exp. speed on goal line = %.2f)", len, spdl);
				}
			}
		}

		t = (flagPos[GOAL_R].GetX() - vi->ball.pos.GetX()) / vi->ball.speed.GetX();
		intersectRightGoalY = vi->ball.pos.GetY() + vi->ball.speed.GetY() * t;
		if(t > 0 && intersectRightGoalY > -10 && intersectRightGoalY < 10){
			len = (vi->ball.pos - Point(flagPos[GOAL_R].GetX(), intersectRightGoalY)).GetLength();
			spdl = vi->ball.speed.GetLength();
			while(len > 0 && spdl > SPEED_ON_GOAL_LINE_MIN) {
				len -= spdl;
				spdl *= serverParams->ball_decay;
			}
			if(len <= 0){
				len = (vi->ball.pos - Point(flagPos[GOAL_R].GetX(), intersectRightGoalY)).GetLength();
				// If it's our player and our side is left
				if(lastNearestPlayer < 12 && ourSide == true){
					statsOurPlayer[lastNearestPlayer].shootsOnGoal++;
					statsOurPlayer[lastNearestPlayer].sumLenShootsOnGoal += len;
					TXTN(" - on right goal from %.2fm (exp. speed on goal line = %.2f)", len, spdl);
				}
				// If it's their player and our side is right
				if(lastNearestPlayer > 11 && ourSide == false){
					statsTheirPlayer[lastNearestPlayer - 11].shootsOnGoal++;
					statsTheirPlayer[lastNearestPlayer - 11].sumLenShootsOnGoal += len;
					TXTN(" - on right goal from %.2fm (exp. speed on goal line = %.2f)", len, spdl);
				}
			}
		}

		lastKick.from = lastBallInfo.pos;
		lastKick.when = simTime - 1;
		lastKick.who = lastNearestPlayer;
	}
	lastBallInfo = vi->ball;

	// trace statistics for ball
	if(vi->ball.pos.GetX() < 0) // ball is on the left side
	{
		if(ourSide == true) statsBall.cyclesOnOurHalf++;
		else statsBall.cyclesOnTheirHalf++;
	} 
  else // ball is on the right side
	{
		if(ourSide == false) statsBall.cyclesOnOurHalf++;
		else statsBall.cyclesOnTheirHalf++;
	}
  for(i = 0; i < NUM_ZONES; i++)
  {
    if(zones[i].from >= vi->ball.pos.GetX() && vi->ball.pos.GetX() <= zones[i].to)
     statsBall.cyclesInZone[i]++;
  }
	
	lastNearestPlayer = 0;
	nearestDist = 200; // big number

	// trace statistics for our players
	for(i = 0; i < vi->oursCount; i++)
	{
		numCurr = vi->ours[i].uniformNumber;
		type = GetTypeByNumber(numCurr);
		ballDistXY.SetX((float) fabs(vi->ours[i].pos.GetX() - vi->ball.pos.GetX()));
		ballDistXY.SetY((float) fabs(vi->ours[i].pos.GetY() - vi->ball.pos.GetY()));
		ballDist = (vi->ours[i].pos - vi->ball.pos).GetLength();

		if(ballDist < BALL_NEAR_DIST)
		{
			statsOurPlayer[numCurr].cyclesNearBall++;
			statsOurPlayer[numCurr].sumSpeedNearBall += 
				vi->ours[i].speed.GetLength();
		}

		// determine his role (D, M or A) according to distance from ball
		// and which side it is on
		if(ballDist < DIST_X_MID_FIELDER)
		{
			statsOurPlayer[numCurr].cyclesMidfielder++;
		}
		else  
		{
			// player is between ball and right goal
			if(vi->ball.pos.GetX() < vi->ours[i].pos.GetX())
			{ 
				// we are on the left side
				if(ourSide == true) statsOurPlayer[numCurr].cyclesAttacker++;
				else statsOurPlayer[numCurr].cyclesDefender++;
			}
			else // player is between ball and left goal
			{
				// we are on the left side
				if(ourSide == true) statsOurPlayer[numCurr].cyclesDefender++;
				else statsOurPlayer[numCurr].cyclesAttacker++;
			}
		}

		statsOurPlayer[numCurr].sumDistFromBall += ballDistXY;

		// ball is kickable
		if(serverParams->ball_size + 
			playerTypes[type].player_size + 
			playerTypes[type].kickable_margin > 
			ballDist)
		{
			statsOurPlayer[numCurr].cyclesWithBall++;
		}

		// find out nearest player to the ball
		if(ballDist < nearestDist)
		{
			lastNearestPlayer = numCurr;
			nearestDist = ballDist;
		}
	}

	// trace statistics for their players
	for(i = 0; i < vi->theirCount; i++)
	{
		numCurr = vi->their[i].uniformNumber;
		type = GetTypeByNumber(numCurr);
		ballDistXY.SetX((float) fabs(vi->their[i].pos.GetX() - vi->ball.pos.GetX()));
		ballDistXY.SetY((float) fabs(vi->their[i].pos.GetY() - vi->ball.pos.GetY()));
		ballDist = (vi->their[i].pos - vi->ball.pos).GetLength();

		if(ballDist < BALL_NEAR_DIST)
		{
			statsTheirPlayer[numCurr].cyclesNearBall++;
			statsTheirPlayer[numCurr].sumSpeedNearBall += 
				vi->their[i].speed.GetLength();
		}

		// determine his role (D, M or A) according to distance from ball
		// and which side it is on
		if(ballDist < DIST_X_MID_FIELDER)
		{
			statsTheirPlayer[numCurr].cyclesMidfielder++;
		}
		else  
		{
			// player is between ball and right goal
			if(vi->ball.pos.GetX() < vi->their[i].pos.GetX())
			{ 
				// we are on the right side
				if(ourSide != true) statsTheirPlayer[numCurr].cyclesAttacker++;
				else statsTheirPlayer[numCurr].cyclesDefender++;
			}
			else // player is between ball and left goal
			{
				// we are on the right side
				if(ourSide != true) statsTheirPlayer[numCurr].cyclesDefender++;
				else statsTheirPlayer[numCurr].cyclesAttacker++;
			}
		}

		statsTheirPlayer[numCurr].sumDistFromBall += ballDistXY;

		// ball is kickable
		if(serverParams->ball_size + 
			playerTypes[type].player_size + 
			playerTypes[type].kickable_margin > 
			ballDist)
		{
			statsTheirPlayer[numCurr].cyclesWithBall++;
		}

		// find out nearest player to the ball
		if(ballDist < nearestDist)
		{
			lastNearestPlayer = numCurr + 11;
			nearestDist = ballDist;
		}
	}
}
