#include "Player/PlayerSkills.h"
#include "Player/PlayerTactics.h"
#include "Common/Main.h"
#include "Common/Tasks.h"
#include "Common/Support/Debug.h"
#include <stdio.h>
#include <math.h>
#include <assert.h>
#include <string.h>
#include <limits.h>

#include "DataTypes.h"

#define AFTER_CATCH_CYCLES 5
#define AFTER_CATCH_DRIBBLE_CYCLES 2
#define POS_ERROR_DELTA 3


static inline bool DoLinesIntersect(Point A, Vector U, Point B, Vector V)
{
	// Do lines (A + t1 * U) and (B + t2 * V) intersect?
	float t1 = (U.GetX() * B.GetY() - U.GetX() * A.GetY() - U.GetY() * B.GetX() + U.GetY() * A.GetX()) / (U.GetY() * V.GetX() - U.GetX() * V.GetY());
	float t2 = (V.GetX() * A.GetY() - V.GetX() * B.GetY() - V.GetY() * A.GetX() + V.GetY() * B.GetX()) / (V.GetY() * U.GetX() - V.GetX() * U.GetY());

	return t1 >= 0.f && t1 <= 1.f && t2 >= 0.f && t2 <= 1.f;
}

static Angle CalcDir(const Vector& vector, Angle rotation)
{
	Vector tmp = vector - rotation;
	return tmp.GetAngle();
}







bool PlayerTactics::WatchIntercept(ASReceive* as, bool excludeMyself)
{
	TXTN("%i: -> WATCHINTERCEPT excludeMyself = %d, RunAfterBall = %d", world->state->GetSimTime(), excludeMyself, RunAfterBall);

	float karea = 0.8f * (world->params->server->player_size + world->params->server->ball_size + world->params->server->kickable_margin);

	Ball tmpBall(as->ball ? *as->ball : *world->ball);
	Ball tmpBall2(tmpBall);
	
	Player* player = world->me;

	bool ourKickIn = world->state->IsOurKickIn();

	
	// Supposed player speeds
	float myDashPower = as->dashPower * world->me->GetEffort(); //RecommDashPower(true) * world->me->GetEffort();
	float friendDashPower = world->variables->GetFriendDashAdjust() * (world->params->server->stamina_inc_max + world->params->server->maxpower) / 2;
	float enemyDashPower = world->variables->GetEnemyDashAdjust() * world->params->server->maxpower;

	// Number of known players
	int friendCnt = world->friends.GetSize();
	int enemyCnt = world->enemies.GetSize();

	int i;

	// Current distance run by all players.
	float myDist = 0, myrDistance = 0;
	
	float* friendDists = (float*) malloc(sizeof(float) * friendCnt);
	for(i = 0; i < friendCnt; i++) friendDists[i] = 0;
	
	float* enemyDists = (float*) malloc(sizeof(float) * enemyCnt);
	for(i = 0; i < enemyCnt; i++) enemyDists[i] = 0;
	
	// Current speeds of all players
	float mySpd = player->vel.GetLength();
	
	float* friendSpds = (float*) malloc(sizeof(float) * friendCnt);
	for(i = 0; i < friendCnt; i++) 
	{
		if((world->friends[i].GetAge() == 0) && (world->friends[i].vel.GetLength() != 0.f))
			friendSpds[i] = world->friends[i].vel.GetLength();
		else
			friendSpds[i] = 0.7f * world->params->server->player_speed_max;

	}

	float* enemySpds = (float*) malloc(sizeof(float) * enemyCnt);
	
	for(i = 0; i < enemyCnt; i++) 
	{
		if(((world->enemies[i].pos - player->pos).GetLength() < world->params->server->visible_distance) || (world->enemies[i].GetAge() != 0) ||
			(world->enemies[i].vel.GetLength() == 0.f))
			enemySpds[i] = world->params->server->player_speed_max;
		else
			enemySpds[i] = 0.7f * world->enemies[i].vel.GetLength();

	}


	bool mincatchInArea = false;
	int mincatchCycle = -1;
	Point mincatchPoint;

	bool womovecatch = false;
	int womovecatchCycle = -1;

	bool wotareacatch = false;
	int wotareacatchCycle = -1;
	Point wotareacatchPoint; 

	bool ballenterarea = false;
	int ballenterareaCycle = -1;
	Point ballenterareaPoint;

	bool ballexitarea = false;
	int ballexitareaCycle = -1;
	Point ballexitareaPoint;

	bool ballcatchenter = false, ballcatchexit = false;


	Point catch_point;
	
	int caught = -1;
	int cycle = 0;
	
	bool shallrun = false;
	
	int mecaught = -1;
	int numfriendscaught = 0;
	int numenemiescaught = 0;

	int firstfriend = -1;
	bool our = false;
	int cdistance = -1;

	float myrDist;

	int* friendGotBall = (int*) malloc(friendCnt * sizeof(int));
	int* enemyGotBall = (int*) malloc(enemyCnt  * sizeof(int));
	for(i = 0; i < friendCnt; i++) friendGotBall[i] = -1;
	for(i = 0; i < enemyCnt; i++) enemyGotBall[i] = -1;

	// Point* enemyPos = (Point*) malloc(enemyCnt * sizeof(Point));
	// for(i = 0; i < enemyCnt; i++) enemyPos[i] = world->enemies[i].pos;

	float mySpdTurn = 0, mySpdNoTurn = player->vel.GetLength();
	float myDistTurn = 0, myDistNoTurn = 0;


	bool ballwasoutside = false;

	for(cycle = 1;cycle<=30;cycle++)
	{
		tmpBall.Step();


		if(!excludeMyself)
		{
			
			CatchSimRunningPlayer(myDashPower, &mySpdNoTurn, &myDistNoTurn);
			
			if(cycle == 1)
			{
				//float temp;
				//CatchSimRunningPlayer(0, &mySpdTurn, &temp);
			}
			else
			{
				CatchSimRunningPlayer(myDashPower, &mySpdTurn, &myDistTurn);
			}
		

			Point noTurnPos = player->pos + PolarVector(myDistNoTurn, player->GetAbsDir());
			Point noTurnPosArea = player->pos + PolarVector((myDistNoTurn < CATCH_DIST) ? myDistNoTurn : CATCH_DIST, 
				player->GetAbsDir());



			float ballDiffNoTurn = (tmpBall.pos - noTurnPos).GetLength();
			if(DoLinesIntersect(player->pos, noTurnPos - world->me->pos, tmpBall.pos, tmpBall.vel)) 
				ballDiffNoTurn = -ballDiffNoTurn;

			float ballDiffNoTurnArea = (tmpBall.pos - noTurnPosArea).GetLength();
			if(DoLinesIntersect(player->pos, noTurnPosArea - world->me->pos, tmpBall.pos, tmpBall.vel)) 
				ballDiffNoTurnArea = -ballDiffNoTurnArea;


			float ballDiffWithTurn = (tmpBall.pos - player->pos).GetLength() - myDistTurn;
			float ballDiffWithTurnArea = (tmpBall.pos - player->pos).GetLength() - 
				((myDistTurn < CATCH_DIST) ? myDistTurn : CATCH_DIST);
		
			


			if(ballDiffNoTurn < karea)
			{
				myrDist = ballDiffNoTurn;
			}
			else
			{
				myrDist = ballDiffWithTurn;
			}

			if(myrDist <= karea)
			{
				if(mincatchCycle == -1)
				{
					mincatchCycle = cycle;
					mincatchPoint = tmpBall.pos;
					
					if((mincatchPoint - player->pos).GetLength() < CATCH_DIST) mincatchInArea = true;
				}

			}

			

			if(ballDiffNoTurnArea < karea)
			{
				if(!wotareacatch)
				{
					wotareacatch = true;
					wotareacatchPoint = tmpBall.pos;
					wotareacatchCycle = cycle;
				}
			}

			if((tmpBall.pos - player->pos).GetLength() > CATCH_DIST) ballwasoutside = true;

			if(((tmpBall.pos - player->pos).GetLength() < CATCH_DIST) && ballwasoutside)
			{
				if(!ballenterarea) 
				{
					ballenterarea = true;
					ballenterareaCycle = cycle;
					ballenterareaPoint = tmpBall.pos;
				}

				ballexitareaPoint = tmpBall.pos;
			}

			if(((tmpBall.pos - player->pos).GetLength() > CATCH_DIST) && ballenterarea && !ballexitarea)
			{
				ballexitarea = true;
				ballexitareaCycle = cycle - 1;

			}

			if(((tmpBall.pos - player->pos).GetLength() < karea) && !womovecatch)
			{
				womovecatchCycle = cycle;
				womovecatch = true;			
			}

			if(ballenterareaCycle == cycle)
			{
				if(ballDiffWithTurnArea < karea) ballcatchenter = true;
			}
		
			if(ballexitareaCycle == cycle)
			{
				if(ballDiffWithTurnArea < karea) ballcatchexit = true;
			}

		}

	}
		
	if((womovecatchCycle != -1) && (mincatchCycle > womovecatchCycle))
	{
		mincatchCycle = womovecatchCycle;
		mincatchPoint = player->pos;
	}

	cycle = 0;

	while(1)
	{
		if((friendCnt == 0) && ((enemyCnt == 0) || ourKickIn)) break;

		cycle++;
		tmpBall2.Step();		

		for(i = 0; i < friendCnt; i++)
		{
			CatchSimRunningPlayer(friendDashPower, &friendSpds[i], &friendDists[i]);

			float distance = (tmpBall2.pos - world->friends[i].pos).GetLength() - friendDists[i];
			//TXTN("SPOLUHRAC %d vzdialeny od lopty %f", i, distance);

			if(distance < karea)
			{
				if(caught == -1)
				{
					caught = cycle;
					catch_point = tmpBall2.pos;
					our = true;
				}
	
				if(firstfriend == -1) firstfriend = cycle;
				if(friendGotBall[i] == -1)
				{
					friendGotBall[i] = cycle;
					numfriendscaught++;
				}

				if(cdistance == -1 && !our) cdistance = cycle - caught;
			}
		}


		if(!ourKickIn)
		{
			for(i = 0; i < enemyCnt; i++)
			{
				CatchSimRunningPlayer(enemyDashPower, &enemySpds[i], &enemyDists[i]);

				float distance = (tmpBall2.pos - world->enemies[i].pos).GetLength() - enemyDists[i];
			
				if(caught == cycle)
				{
				//	enemyPos[i] = world->enemies[i].pos + (world->enemies[i].pos - tmpBall2.pos)*(distance/(tmpBall2.pos - world->enemies[i].pos).GetLength());
				}


				if(distance < karea)
				{
					if(caught == -1)
					{
						caught = cycle;
						catch_point = tmpBall2.pos;
						our = false;
					}

					if(cdistance == -1 && our) cdistance = cycle - caught;
					if(cdistance == 0) our = false;

					if(enemyGotBall[i] == -1)
					{
						enemyGotBall[i] = cycle;
						numenemiescaught++;
					}
					
				}
			}
		}

	
		// If the ball is out, ignore it, unless it's one of the OurKickIn modes.
		if(!ourKickIn &&
			((tmpBall2.pos.GetX() < world->params->flags[LT].GetX() - POS_ERROR_DELTA) ||
			(tmpBall2.pos.GetY() < world->params->flags[LT].GetY() - POS_ERROR_DELTA) ||
			(tmpBall2.pos.GetX() > world->params->flags[RB].GetX() + POS_ERROR_DELTA) ||
			(tmpBall2.pos.GetY() > world->params->flags[RB].GetY() + POS_ERROR_DELTA)))
		{
			break;

		}
		
	
		if((caught != -1) && (cycle >= caught + AFTER_CATCH_CYCLES)) break;
	
	}

	
	// spracovanie

	as->catchwithoutrun = false;

	// docasne : kvoli slabemu obzeraniu ma hrac slaby prehlad co je za nim takze ak lopta smeruje na neho tak po nej bezi
	if(womovecatch && wotareacatch && (wotareacatchCycle < womovecatchCycle))
	{
		mecaught = wotareacatchCycle;
		catch_point = wotareacatchPoint;
	}
	else if(womovecatch)
	{
		if((caught == -1) || (womovecatchCycle < caught))
		{
			mecaught = womovecatchCycle;
			catch_point = player->pos;
			as->catchwithoutrun = true;
		}
		else
		{
			mecaught = mincatchCycle;
			catch_point = mincatchPoint;
		}
	}
	else if(wotareacatch)
	{
		if((caught == -1) || (wotareacatchCycle < caught))
		{
			mecaught = wotareacatchCycle;
			catch_point = wotareacatchPoint;
		}
		else
		{
			mecaught = mincatchCycle;

			if((caught == -1) || (mincatchCycle < caught))
			{
				catch_point = mincatchPoint;
			}
		}

	}
	/*else if(ballexitarea && ((caught == -1) || (ballexitareaCycle < caught)))
	{
		mecaught = ballexitareaCycle;
		catch_point = ballexitareaPoint;
	}*/
	else if(ballenterarea && ((caught == -1) || (ballenterareaCycle < caught)))
	{
		mecaught = ballenterareaCycle;
		catch_point = ballenterareaPoint;
	}
	else
	{
		mecaught = mincatchCycle;

		if((mincatchCycle < caught) || (caught==-1))
		{
			catch_point = mincatchPoint;
		}
	}

	TXTN("WATCHINTERCEPT :");
	TXTN("\twomovewcatch : %d", womovecatch);
	TXTN("\twotareacatch : %d", wotareacatch);
	TXTN("\tmincatchcycle : %d", mincatchCycle);
	TXTN("\twomovecatchCycle : %d", womovecatchCycle);
	TXTN("\twotareacatchCycle : %d", wotareacatchCycle);
	TXTN("\tballexitareaCycle : %d", ballexitareaCycle);
	TXTN("\tballenterareaCycle : %d", ballenterareaCycle);
	TXTN("\tmecaught : %d", mecaught);
	TXTN("\tcaught : %d", caught);
	TXTN("\tfirstfriend : %d", firstfriend);

	if((mecaught == -1) && (caught == -1)) return false;

	if(mecaught != -1)
	{
		if((caught == -1) || (mecaught <= caught + 3))
		{
			shallrun = true;
		}
		else if((caught <= 4) && (mecaught < (caught + (ourKickIn ? 5 : 1))) && our)
		{
			if(RunAfterBall != 0) 
				shallrun = true;
			else
			{
				int ln = 20;
		
				if((mecaught <= (caught + 1)) || ourKickIn) ln = player->GetUniform();

				for(i = 0; i < friendCnt; i++)
				{
					if((friendGotBall[i]!=-1) && ((friendGotBall[i] <= caught + 1) || ourKickIn))
						if(world->friends[i].GetUniform() > ln) ln = world->friends[i].GetUniform();
				}

				if(ln == player->GetUniform()) shallrun = true; else shallrun = false;
			}
			
		}
		else if(!our)
		{
			if((firstfriend == -1) || (mecaught < firstfriend) || (RunAfterBall != 0))
			{
				shallrun = true;
			}
			else
			{
				float nearestfrienddistance = GetNearestFriendDistance(catch_point);
				float mydist = (catch_point - player->pos).GetLength();

				TXTN("\tmydist to catch point %f, nearfriend %f", mydist, nearestfrienddistance);
				if(mydist < nearestfrienddistance)
					shallrun = true;
				else
					shallrun = false;
			}
		}
			
			
		as->cyclesToIntercept = ((mecaught < caught) || (caught == -1)) ? mecaught : caught;
		as->cyclesdistance = (mecaught < caught) ?  ((our) ? (caught - mecaught + cdistance):cdistance) : 
			((cdistance < (mecaught - caught) ? cdistance : (mecaught - caught)));
		as->our = ((mecaught <= caught) || (caught == -1)) ? true : our;
		as->receivePoint = catch_point;
		as->shallrun = shallrun;

	}
	else
	{
		as->cyclesToIntercept = caught;
		as->cyclesdistance = cdistance;
		as->our = our;
		as->receivePoint = catch_point;
		as->shallrun = false;
	}

	TXTN("\tshallrun : %d", as->shallrun);
	TXTN("\tcyclesToItercept : %d", as->cyclesToIntercept);
	TXTN("\tcyclesdistance : %d", as->cyclesdistance);
	TXTN("\tour : %d", as->our);
	TXTN("\tnumfriendscaught : %d", numfriendscaught);
	TXTN("\tnumenemiescaught : %d", numenemiescaught);


	free(friendDists); free(enemyDists);
	free(friendSpds); free(enemySpds);
	free(friendGotBall);

	return true;
	
}




// vrati true ak existuje lepsie miesto na prijatie prihravky
/*
bool PlayerTactics::FindBetterPosition(ASReceive* as)
{
	Vector positions[8];

	positions[0].SetX(5.0f);
	positions[0].SetY(0.0f);

	positions[1].SetX(3.5f);
	positions[1].SetY(3.5f);
	
	positions[2].SetX(0.0f);
	positions[2].SetY(5.0f);
	
	positions[3].SetX(-3.5f);
	positions[3].SetY(3.5f);
	
	positions[4].SetX(-5.0f);
	positions[4].SetY(0.0f);

	positions[5].SetX(-3.5f);
	positions[5].SetY(-3.5f);
	
	positions[6].SetX(0.0f);
	positions[6].SetY(-5.0f);
	
	positions[7].SetX(3.5f);
	positions[7].SetY(-3.5f);

	Point fpDefaultPosition = GetHomePos();
	
	float bestpass = 0;
	int bestdir;

	for(int i = 0; i < 8; i++) 
	{
		Point fpDestination = world->me->pos + positions[i];
		
		if(IsOffsidePosition(fpDestination) || IsOutsideField(fpDestination)) continue;

		float fTargetDistance = (fpDestination - fpDefaultPosition).GetLength();
		float fMyDistance = (world->me->pos - fpDefaultPosition).GetLength();

		if(fTargetDistance < 20.0f || fTargetDistance < fMyDistance) 
		{
			if(GetNearestFriendDistance(fpDestination) > 5.0f) 
			{
				float pass = EstimatePassPosibility(as, fTargetDistance);
				if(pass > bestpass)
				{
					bestpass = pass;
					bestdir = i;
				}
			}
		}
	}
	
	if(bestpass > 0.7)
	{
		as.runTo = world->me->pos + position[bestdir];
		return true;
	}

	return false;
}




float PlayerTactics::EstimatePassPosibility(ASReceive* as, Point to)
{

}
*/




AS* PlayerTactics::TryInterceptBall(ASPass* faction)
{
	ASReceive* as = new ASReceive;

	as->ball = NULL;
	as->type = INTERCEPT;
	as->action = DONOTHING;
	as->payoff = -1;
	as->shallrun = false;

	if(DoIDribble() || world->state->IsOurKickIn())
		as->dashPower = /*100;*/RecommendDashToMaxSpeed();
	else
		as->dashPower = RecommDashPower(true);

	if((world->state->GetPlayMode() != PM_PlayOn) && ((world->ball->pos - world->me->pos).GetLength() > 8.0f))
	{
		if(ShouldKickIn())
		{
			as->our = true;
			as->shallrun = true;
			as->receivePoint = world->ball->pos;
			as->action = INTERCEPTRUN;
			as->payoff = 10;
			as->hurry = false;
			as->forlife = false;
		}

		return as;
	}
	else if(WatchIntercept(as, (DontRunAfterBall > 0) ? true : false))
	{

		if(as->our)
		{
			if(as->catchwithoutrun == true)
			{
				if(as->cyclesToIntercept == 1)
				{
					World* oldworld = world;
					World* newworld = new World(*world);
					world = newworld;

					TXTN("PREDICT : idem odsimulovat dalsi krok");

					ignoreNotKickable = true;
					world->Step();
					AS* as2 = MyAction();
					ignoreNotKickable = false;

					world = oldworld;
					delete newworld;
			
					if(as2 != NULL)
					{
						if((as2->action == TURN) || (as2->action == SPRACUJLOPTU))
						{
							ASDribble* asd = (ASDribble*)as2;

							if(asd->changedir && (asd->turnDir != -1))
							{
								TXTN("PREDICT : v dalsom cykle by som sa otocil tak to spravim teraz (akcia %d)", as2->action);
								
								as2->action = TURN;
								delete as;
								return as2;
							}

							

						}
						else if(as2->action == PASSTOPLAYER)
						{
							delete as2;
							return as;
						}

						delete as2;
					}
				}
				else
				{
					as->action = INTERCEPTTURN;
					as->payoff = 10;
				}

				return as;
			}
			else if(as->shallrun)
			{
				/*if(as->cyclesToIntercept == 2)
				{
					TurnNeckTo(DegAngle(0.f));
					newViewWidth = NORMAL_VIEW;
				}
				else*/
				{
					newViewWidth = NARROW_VIEW;
				}


				if(as->cyclesdistance <= 2)
				{
					as->forlife = true;
				}
				
				as->hurry = true;
				as->action = INTERCEPTRUN;
				as->payoff = 5;
			}
			else
			{
			/*	if(as.cyclesToIntercept <= 5 && FindBetterPosition(as))
				{
					as.hurry = true;
					as.forlife = false;
					as.action = RUNTOBETTERPOS;
					as.payoff = 5;
				}
				else */
				{
					as->action = DONOTHING;
					as->payoff = 1;
				}
			
			}		

		}
		else
		{
			if((as->shallrun) /*|| ((as->receivePoint - world->me->pos).GetLength() <= 10.0f) && (DistanceToOurGoal(as->receivePoint) < 25.f)*/)
			{
				as->action = INTERCEPTRUN;
				as->shallrun = true;
				as->forlife = true;
				as->hurry = true;
				as->payoff = 10;

				/*if((RunAfterBall == 0) && (as->cyclesdistance != 1))
				{
					Vector diff = as->receivePoint - world->me->pos;
					TXTN("Uhol medzi mnou a miestom chytenia je %f", diff.GetAngle().GetDeg());

					FormationRole role = m_cFormation.GetPlayerRole(world->me->GetUniform());

					if((role == PR_Defensman) && (abs(diff.GetAngle().GetDeg()) > 20.f) && as->receivePoint.GetX() > -45.f)
					{
					
						as->receivePoint.SetX(as->receivePoint.GetX() - 3.0f);	
						TXTN("Loptu nechyti nas hrac takze bezi pred miesto chytenia %f, %f", as->receivePoint.GetX(), as->receivePoint.GetY());
					}
				}*/


			}

			return as;
		}
	}

	return as;

}


void PlayerSkills::MoveToInterceptPoint(ASReceive* as)
{
	if(as->action == INTERCEPTRUN)
	{
		if(DoIDribble())
			RunToPoint(as->receivePoint, as->hurry, as->forlife, 0.8f, 15);
		else
			RunToPoint(as->receivePoint, as->hurry, as->forlife, 1.0f, 10);
	}

	if(as->action == INTERCEPTTURN)
	{	
		TurnTo(world->ball->pos);
	}
}

