/**
 * \file CoachKernel.cpp
 *
 * Kernel implementation of the CoachKernel class
 */

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

#define MAX_PLAYER_AGE 15

///////////////////////////////////////////////////////////////////////////////
// CoachKernel interface

Interface* CoachKernel::GetInterface(int task) const
{
	if (task == MEMORY_TASK) return (CoachMemoryInterface*)this;
	else if (task == SYNCHRONIZATION_TASK) return (CoachSynchronizationInterface*)this;
	else return NULL;
}

///////////////////////////////////////////////////////////////////////////////
// CoachKernel construction

CoachKernel::CoachKernel(
	const char* name /* = "CoachKernel"*/,
	const char* desc /* = "CoachKernel module"*/)
	: Module(name, desc, "v0.0.001")
{
	// initialisation of simulation parameters
	serverParams = NULL;
	playerParams = NULL;
	playerTypes = NULL;

	playMode = PM_BeforeKickOff;
	timeInPlayOn = 0;
	continousTimeInPlayOn = 0;
	canSendCLmessage = 0;
	sayCoachCnt = 0;
	scoreOur = 0;
	scoreTheir = 0;
	timeOfLastVisualInfo = 0;
	memset(&lastBallInfo, 0, sizeof(lastBallInfo));
	lastNearestPlayer = 0;
	memset(&lastKick, 0, sizeof(lastKick));
	FIFObottom = FIFOtop = 0;
	advice = 0;

	// all the players are of default type
	memset(&playerTypesByNumber, 0, sizeof(playerTypesByNumber));

	// no statistics by now
	memset(&statsBall, 0, sizeof(statsBall));
	memset(statsOurPlayer, 0, 12 * sizeof(statsOurPlayer[0]));
	memset(statsTheirPlayer, 0, 12 * sizeof(statsTheirPlayer[0]));

	// default logo directory
	teamGraphicDir[0] = '\0';

	for(int i = 0; i < 32; i++) for(int j = 0; j < 8; j++) teamGraphicOk[i][j] = false;
	teamGraphicOkCount = 0;
	teamGraphicTries = 0;

	Main::GetInstance()->RegisterModule(this, MEMORY_TASK);
	Main::GetInstance()->RegisterModule(this, SYNCHRONIZATION_TASK);
}

int CoachKernel::Init(int argc, char** argv, int)
{
	for(int i = 1; i < argc; i++)
	{
		if(strcmp(argv[i], "-h") == 0)
		{
			fprintf(stderr, "\n%s %s switches\n\n", (char*) GetName(), (char*) GetVer());
			fprintf(stderr, "\t-h: this help\n");
			fprintf(stderr, "\t-l <dir>: directory with team logo files\n\n");
			fprintf(stderr, "\t          <dir> HAS TO end with a slash/backslash, e.g.\n");
			fprintf(stderr, "\t          \"-l c:\\player\\logo\\\" or");
			fprintf(stderr, "\t          \"-l ~/player/logo\"");

			return INIT_ERR;
		}
		else if(strcmp(argv[i], "-l") == 0)
		{
			if(argv[++i] != NULL) strncpy(teamGraphicDir, argv[i], 512);
		}
	}

	return INIT_OK;
}

///////////////////////////////////////////////////////////////////////////////
// CoachKernel operations
bool CoachKernel::IsTimeStoped() const
{
	switch(GetPlayMode())
	{
	// modes when time is stopped
	case PM_BeforeKickOff:
	case PM_AfterGoal_Our:
	case PM_AfterGoal_Their:
		return true;
	default:
		// simulation time is running
		return false;
	}
}

int CoachKernel::GetFormation(bool our)
{
	static float defs, mids, atts;
	static float min, lse; // least square error
	static int i, mini;

	atts = mids = defs = 0;
	if(our)
	{
		for(i = 1; i <= 11; i++)
		{
			atts += statsOurPlayer[i].cyclesAttacker;
			defs += statsOurPlayer[i].cyclesDefender;
			mids += statsOurPlayer[i].cyclesMidfielder;
		}
	}
	else
	{
		for(i = 1; i <= 11; i++)
		{
			atts += statsTheirPlayer[i].cyclesAttacker;
			defs += statsTheirPlayer[i].cyclesDefender;
			mids += statsTheirPlayer[i].cyclesMidfielder;
		}
	}
	defs = defs / timeInPlayOn;
	mids = mids / timeInPlayOn;
	atts = atts / timeInPlayOn;

	min = 1e10; // big number
	mini = 0;
	for(i = 0; i < NUM_FORMATIONS; i++)
	{
		lse = (formation[i].defs - defs) * (formation[i].defs - defs) +
			(formation[i].mids - mids) * (formation[i].mids - mids) +
			(formation[i].atts - atts) * (formation[i].atts - atts);
		if(lse < min)
		{
			min = lse;
			mini = i;
		}
	}

	return mini;
}

int CoachKernel::CanSendMessage()
{
	if(canSendCLmessage == 0) return 1;
	else // we can use freeform message (???)
	{
		static int ffwp = serverParams->freeform_wait_period;
		// number 3 is just for sure
		if((sayCoachCnt < serverParams->say_coach_cnt_max) &&
		   (continousTimeInPlayOn / ffwp > 0) &&
		   (continousTimeInPlayOn % ffwp > 3) &&
		   (continousTimeInPlayOn % ffwp < serverParams->freeform_wait_period - 3))
		{
			return 2;
		}
	}

	return 0;
}

void CoachKernel::SayMessage(const char* msg)
{
	static int i;

	if(msg == NULL) { // send the first message in FIFObuffer
		if(FIFObottom != FIFOtop) // buffer is not empty
		{
			if((i = CanSendMessage()) > 0)
			{
				if(i == 2) // we can send freeform message
				{
					sayCoachCnt++;
					// \todo we have to alter message to '(freeform "<message>")'
				}
				commandGen->Say(FIFObuffer[FIFObottom++]);
				if(FIFObottom > FIFO_BUFFER_SIZE) FIFObottom = 0;
				// +3 just to be sure
				canSendCLmessage = serverParams->clang_win_size + 3;
			}
		}
	} 
	else // msg != NULL
	{
		if((i = CanSendMessage()) > 0) // send it immediately
		{
			if(i == 2) // we can send freeform message
			{
				sayCoachCnt++;
				// \todo we have to alter message to '(freeform "<message>")'
			}
			commandGen->Say(msg);
			// +3 just to be sure
			canSendCLmessage = serverParams->clang_win_size + 3;
		}
		else // place it into the buffer
		{
			strcpy(FIFObuffer[FIFOtop++], msg);
			if(FIFOtop > FIFO_BUFFER_SIZE) FIFOtop = 0;
			if(FIFOtop == FIFObottom) // buffer is full
			{
				FIFObottom++;
				if(FIFObottom > FIFO_BUFFER_SIZE) FIFObottom = 0;
			}
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
// CoachKernel destruction

CoachKernel::~CoachKernel()
{
	int i, j;
	PLAYER_STATISTICS wholeTeam;

	TXTN("   Cycles in play on mode: %d", timeInPlayOn);
	TXTN("  Cycles ball on our side: %d", statsBall.cyclesOnOurHalf);
	TXTN("Cycles ball on their side: %d", statsBall.cyclesOnTheirHalf);
	if(timeInPlayOn == 0) timeInPlayOn = 1;

	memset(&wholeTeam, 0, sizeof(wholeTeam));
	for(i = 1; i <= 11; i++)
	{
		if(statsOurPlayer[i].cyclesNearBall == 0) 
			statsOurPlayer[i].cyclesNearBall = 1;

		TXTN("Our player number %2d:", i);
		TXTN(" - shoots on goal: %d", statsOurPlayer[i].shootsOnGoal);
		wholeTeam.shootsOnGoal += statsOurPlayer[i].shootsOnGoal;
		if(statsOurPlayer[i].shootsOnGoal > 0){
			TXTN(" - average length of shoot: %.2lf", statsOurPlayer[i].sumLenShootsOnGoal / statsOurPlayer[i].shootsOnGoal);
			wholeTeam.sumLenShootsOnGoal += statsOurPlayer[i].sumLenShootsOnGoal;
		}

		TXTN(" - cycles with ball: %d", statsOurPlayer[i].cyclesWithBall);
		wholeTeam.cyclesWithBall += statsOurPlayer[i].cyclesWithBall;

		TXTN(" - average distance from ball (x): %f", 
			statsOurPlayer[i].sumDistFromBall.GetX() / timeInPlayOn); 
		TXTN(" - average distance from ball (y): %f", 
			statsOurPlayer[i].sumDistFromBall.GetY() / timeInPlayOn);
		wholeTeam.sumDistFromBall += statsOurPlayer[i].sumDistFromBall;

		TXTN(" - average speed near ball: %f", 
			statsOurPlayer[i].sumSpeedNearBall / statsOurPlayer[i].cyclesNearBall);
		wholeTeam.sumSpeedNearBall += statsOurPlayer[i].sumSpeedNearBall;
		wholeTeam.cyclesNearBall += statsOurPlayer[i].cyclesNearBall;

		for(j = 0; j < PASS_LEN_COUNT; j++)
		{
			TXTN(" - number of all passes of length to %.1fm: %d", passLens[j], statsOurPlayer[i].passesAll[j]);
			wholeTeam.passesAll[j] += statsOurPlayer[i].passesAll[j];
		}
		TXTN(" - number of all passes of length more than %.1fm: %d", passLens[j - 1], statsOurPlayer[i].passesAll[j]);
		wholeTeam.passesAll[j] += statsOurPlayer[i].passesAll[j];

		for(j = 0; j < PASS_LEN_COUNT; j++)
		{
			TXTN(" - number of successful passes of length to %.1fm: %d", passLens[j], statsOurPlayer[i].passesSuccess[j]);
			wholeTeam.passesSuccess[j] += statsOurPlayer[i].passesSuccess[j];
			if(statsOurPlayer[i].passesSuccess[j] == 0) 
				statsOurPlayer[i].passesSuccess[j] = 1;
		}
		TXTN(" - number of successful passes of length more than %.1fm: %d", passLens[j - 1], statsOurPlayer[i].passesSuccess[j]);
		wholeTeam.passesSuccess[j] += statsOurPlayer[i].passesSuccess[j];

//		TXTN(" - average length of successful passes: %f", 
//			statsOurPlayer[i].sumPassSucLen / statsOurPlayer[i].passesSuccess);
//		wholeTeam.sumPassSucLen += statsOurPlayer[i].sumPassSucLen;

		TXTN(" - number of received passes: %d", statsOurPlayer[i].passesReceived);
		wholeTeam.passesReceived += statsOurPlayer[i].passesReceived;

		TXTN(" - number of interceptions: %d", statsOurPlayer[i].interceptions);
		wholeTeam.interceptions += statsOurPlayer[i].interceptions;

		wholeTeam.cyclesAttacker += statsOurPlayer[i].cyclesAttacker;
		wholeTeam.cyclesDefender += statsOurPlayer[i].cyclesDefender;
		wholeTeam.cyclesMidfielder += statsOurPlayer[i].cyclesMidfielder;
	}
	TXTN("------------------------------------\nSummary for our team:");
	TXTN(" - shoots on goal: %d", wholeTeam.shootsOnGoal);
	if(wholeTeam.shootsOnGoal > 0){
		TXTN(" - average length of shoot: %.2lf", wholeTeam.sumLenShootsOnGoal / wholeTeam.shootsOnGoal);
	}
	TXTN(" - cycles with ball: %d", wholeTeam.cyclesWithBall);
	TXTN(" - average speed near ball: %f", 
		wholeTeam.sumSpeedNearBall / wholeTeam.cyclesNearBall);
	for(j = 0; j < PASS_LEN_COUNT; j++)
	{
		TXTN(" - number of all passes of length to %.1fm: %d", passLens[j], wholeTeam.passesAll[j]);
		TXTN(" - number of successful passes of length to %.1fm: %d", passLens[j], wholeTeam.passesSuccess[j]);
	}
	TXTN(" - number of all passes of length more than %.1fm: %d", passLens[j - 1], wholeTeam.passesAll[j]);
	TXTN(" - number of successful passes of length more than %.1fm: %d", passLens[j - 1], wholeTeam.passesSuccess[j]);

	TXTN(" - %% of successful passes for various lengths respectively:");
	for(j = 0; j <= PASS_LEN_COUNT; j++)
	{
		if(wholeTeam.passesAll[j] == 0) TXT("N/A ");
		else TXT("%.2f ", (float)wholeTeam.passesSuccess[j] / wholeTeam.passesAll[j] * 100.0f);
	}
	TXTN("");

	TXTN(" - estimated number of defenders, midfielders and attackers, respectively:");
	TXTN("%.2f %.2f %.2f", 
		(float)wholeTeam.cyclesDefender / timeInPlayOn, 
		(float)wholeTeam.cyclesMidfielder / timeInPlayOn, 
		(float)wholeTeam.cyclesAttacker / timeInPlayOn);

	/*
	if(wholeTeam.passesSuccess == 0) 
		wholeTeam.passesSuccess = 1;
	TXTN(" - average length of successful passes: %f", 
		wholeTeam.sumPassSucLen / wholeTeam.passesSuccess);
*/
	TXTN(" - number of interceptions: %d", wholeTeam.interceptions);

	TXTN("------------------------------------");
	memset(&wholeTeam, 0, sizeof(wholeTeam));
	for(i = 1; i <= 11; i++)
	{
		if(statsTheirPlayer[i].cyclesNearBall == 0) 
			statsTheirPlayer[i].cyclesNearBall = 1;

		TXTN("Their player number %2d:", i);
		TXTN(" - shoots on goal: %d", statsTheirPlayer[i].shootsOnGoal);
		wholeTeam.shootsOnGoal += statsTheirPlayer[i].shootsOnGoal;
		if(statsTheirPlayer[i].shootsOnGoal > 0){
			TXTN(" - average length of shoot: %.2lf", statsTheirPlayer[i].sumLenShootsOnGoal / statsTheirPlayer[i].shootsOnGoal);
			wholeTeam.sumLenShootsOnGoal += statsTheirPlayer[i].sumLenShootsOnGoal;
		}

		TXTN(" - cycles with ball: %d", statsTheirPlayer[i].cyclesWithBall);
		wholeTeam.cyclesWithBall += statsTheirPlayer[i].cyclesWithBall;

		TXTN(" - average distance from ball (x): %f", 
			statsTheirPlayer[i].sumDistFromBall.GetX() / timeInPlayOn); 
		TXTN(" - average distance from ball (y): %f", 
			statsTheirPlayer[i].sumDistFromBall.GetY() / timeInPlayOn);
		wholeTeam.sumDistFromBall += statsTheirPlayer[i].sumDistFromBall;

		TXTN(" - average speed near ball: %f", 
			statsTheirPlayer[i].sumSpeedNearBall / statsTheirPlayer[i].cyclesNearBall);
		wholeTeam.sumSpeedNearBall += statsTheirPlayer[i].sumSpeedNearBall;
		wholeTeam.cyclesNearBall += statsTheirPlayer[i].cyclesNearBall;

		for(j = 0; j < PASS_LEN_COUNT; j++)
		{
			TXTN(" - number of all passes of length to %.1fm: %d", passLens[j], statsTheirPlayer[i].passesAll[j]);
			wholeTeam.passesAll[j] += statsTheirPlayer[i].passesAll[j];
		}
		TXTN(" - number of all passes of length more than %.1fm: %d", passLens[j - 1], statsTheirPlayer[i].passesAll[j]);
		wholeTeam.passesAll[j] += statsTheirPlayer[i].passesAll[j];

		for(j = 0; j < PASS_LEN_COUNT; j++)
		{
			TXTN(" - number of successful passes of length to %.1fm: %d", passLens[j], statsTheirPlayer[i].passesSuccess[j]);
			wholeTeam.passesSuccess[j] += statsTheirPlayer[i].passesSuccess[j];
			if(statsTheirPlayer[i].passesSuccess[j] == 0) 
				statsTheirPlayer[i].passesSuccess[j] = 1;
		}
		TXTN(" - number of successful passes of length more than %.1fm: %d", passLens[j - 1], statsTheirPlayer[i].passesSuccess[j]);
		wholeTeam.passesSuccess[j] += statsTheirPlayer[i].passesSuccess[j];

//		TXTN(" - average length of successful passes: %f", 
//			statsTheirPlayer[i].sumPassSucLen / statsTheirPlayer[i].passesSuccess);
//		wholeTeam.sumPassSucLen += statsTheirPlayer[i].sumPassSucLen;

		TXTN(" - number of received passes: %d", statsTheirPlayer[i].passesReceived);
		wholeTeam.passesReceived += statsTheirPlayer[i].passesReceived;

		TXTN(" - number of interceptions: %d", statsTheirPlayer[i].interceptions);
		wholeTeam.interceptions += statsTheirPlayer[i].interceptions;

		wholeTeam.cyclesAttacker += statsTheirPlayer[i].cyclesAttacker;
		wholeTeam.cyclesDefender += statsTheirPlayer[i].cyclesDefender;
		wholeTeam.cyclesMidfielder += statsTheirPlayer[i].cyclesMidfielder;
	}
	TXTN("------------------------------------\nSummary for their team:");
	TXTN(" - shoots on goal: %d", wholeTeam.shootsOnGoal);
	if(wholeTeam.shootsOnGoal > 0){
		TXTN(" - average length of shoot: %.2lf", wholeTeam.sumLenShootsOnGoal / wholeTeam.shootsOnGoal);
	}
	TXTN(" - cycles with ball: %d", wholeTeam.cyclesWithBall);
	TXTN(" - average speed near ball: %f", 
		wholeTeam.sumSpeedNearBall / wholeTeam.cyclesNearBall);
	for(j = 0; j < PASS_LEN_COUNT; j++)
	{
		TXTN(" - number of all passes of length to %.1fm: %d", passLens[j], wholeTeam.passesAll[j]);
		TXTN(" - number of successful passes of length to %.1fm: %d", passLens[j], wholeTeam.passesSuccess[j]);
	}
	TXTN(" - number of all passes of length more than %.1fm: %d", passLens[j - 1], wholeTeam.passesAll[j]);
	TXTN(" - number of successful passes of length more than %.1fm: %d", passLens[j - 1], wholeTeam.passesSuccess[j]);

	TXTN(" - %% of successful passes for various lengths respectively:");
	for(j = 0; j <= PASS_LEN_COUNT; j++)
	{
		if(wholeTeam.passesAll[j] == 0) TXT("N/A ");
		else TXT("%.2f ", (float)wholeTeam.passesSuccess[j] / wholeTeam.passesAll[j] * 100.0f);
	}
	TXTN("");

	TXTN(" - estimated number of defenders, midfielders and attackers, respectively:");
	TXTN("%.2f %.2f %.2f", 
		(float)wholeTeam.cyclesDefender / timeInPlayOn, 
		(float)wholeTeam.cyclesMidfielder / timeInPlayOn, 
		(float)wholeTeam.cyclesAttacker / timeInPlayOn);

	/*
	if(wholeTeam.passesSuccess == 0) 
		wholeTeam.passesSuccess = 1;
	TXTN(" - average length of successful passes: %f", 
		wholeTeam.sumPassSucLen / wholeTeam.passesSuccess);
*/
	TXTN(" - number of interceptions: %d", wholeTeam.interceptions);

	// simulation parameters
	if(serverParams != NULL) delete serverParams;
	if(playerParams != NULL) delete playerParams;
	if(playerTypes != NULL) delete[] playerTypes;
}

static int XpmReadFileToData(const char* name, char*** data)
{
	FILE* fin;
	char c, pc, r[1000];
	int cnt, lines;
	bool commentInline, commentLong, quote;
	
	fin = fopen(name, "rt");
	if(fin == NULL) return -1;
	*data = NULL;
	commentInline = false;
	commentLong = false;
	quote = false;
	lines = 0;
	c = '\0';

	while(1)
	{
		pc = c;
		if(fscanf(fin, "%c", &c) != 1) break;
		
		if(quote)
		{
			if(c == '"' && pc != '\\')
			{
				quote = false;
				r[cnt] = '\0';
				lines++;

				*data = (char**) realloc(*data, lines * sizeof(char*));
				if(*data == NULL){fclose(fin);return -2;}

				(*data)[lines - 1] = (char*) malloc((cnt + 1) * sizeof(char));
				if((*data)[lines - 1] == NULL){fclose(fin);return -2;}

				strcpy((*data)[lines - 1], r);
			}
			else
			{
				if(c == '"' && pc == '\\') r[cnt - 1] = '"';
				else r[cnt++] = c;
			}
		}
		else if(commentLong)
		{
			if(c == '/' && pc == '*') commentLong = false;
		}
		else if(commentInline)
		{
			if(c == '\n') commentInline = false;
		}
		else
		{
			if(c == '"') {quote = true; cnt = 0;}
			if(c == '/' && pc == '/') commentInline = true;
			if(c == '*' && pc == '/') commentLong = true;
		}
	}

	fclose(fin);

	return lines;
}

static void XpmFree(char*** data, int lines)
{
	static int i;
	for(i = 0; i < lines; i++)
	{
		free((*data)[i]);
		(*data)[i] = NULL;
	}
	free(*data);
	*data = NULL;
}

bool CoachKernel::SendTeamGraphic(const char* path)
{
	int i, j, k;
	char name[512];
	char msg[2048];
	char** data;

	// If we have already sent all graphics, do nothing
	if(teamGraphicOkCount >= 256) return true;
	teamGraphicTries++;

	for(i = 0; i < 32; i++)
	{
		for(j = 0; j < 8; j++)
		{
			if(!teamGraphicOk[i][j])
			{
			    sprintf(name, "%slogo-%02i-%02i.xpm", path, i, j);
    
			    int rval = XpmReadFileToData(name, &data);
			    if(rval <= 0) return false;
    
			    int r, c, p, a;
			    if(sscanf(data[0], "%i %i %i %i", &r, &c, &p, &a) != 4) return false;
			    int lines = r + p + a;
    
			    msg[0] = '\0';
			    for(k = 0; k < lines; k++)
			    {
				    if(k != 0) strcat(msg, " ");
				    strcat(msg, "\"");
				    strcat(msg, data[k]);
				    strcat(msg, "\"");
			    }
    
			    XpmFree(&data, rval);
    
				if(msg[0] != '\0') (void) commandGen->TeamGraphic(i, j, msg);
				else return false;
			}
		}
	}

	return true;
}

/// Handler for 'ok team_graphic' messages
void CoachKernel::OnOkTeamGraphic(int x, int y)
{
	teamGraphicOk[x][y] = true;
	teamGraphicOkCount++;
}
