{-# LANGUAGE UnicodeSyntax, Rank2Types, ExistentialQuantification #-}
-- | This module defines the part of the SoccerFun API that is concerned with the player data types.
module SoccerFun.Player where

import SoccerFun.RefereeAction
import SoccerFun.Prelude
import SoccerFun.Ball
import SoccerFun.Geometry
import SoccerFun.Types
import Control.Monad.State
import SoccerFun.Field
import Data.List (find)

data Player = m. Player 
	{playerID  PlayerID,         -- ^ must be unique
	 name  String,               -- ^ need not be unique
	 height  Length,             -- ^ should be in range [minHeight..maxHeight]
	 pos  Position,              -- ^ should be on the ball field
	 speed  Speed,               -- ^ absolute direction and velocity with which player is moving
	 nose  Angle,                -- ^ absolute direction in which player is looking
	 skills  MajorSkills,        -- ^ these improve performance of affected actions
	 effect  Maybe PlayerEffect, -- ^ The effect(s) of the previous action
	 stamina  Stamina,           -- ^ current stamina of a player: 1.0 is optimal, 0.0 is worst
	 health  Health,             -- ^ current health of a player: 1.0 is optimal, 0.0 is worst
	 brain  Brain (PlayerAI m) m -- ^ The precious asset: use and update the memory and make decisions
	}

instance Eq Player where f1 == f2 = playerID f1 == playerID f2
instance Show Player where show (Player {playerID = pid}) = show pid

type PlayerAI memory = BrainInput  State memory PlayerAction

data BrainInput = BrainInput
	{referee  [RefereeAction], -- ^ the referee actions
	 ball     BallState,       -- ^ the state of the ball
	 others	 [Player],        -- ^ all other ball players
	 me		 Player           -- ^ the player himself
	}

type PlayerWithAction = (PlayerAction, PlayerID)
type PlayerWithEffect = (Maybe PlayerEffect, PlayerID)

type MajorSkills = (Skill,Skill,Skill)
data Skill
	= Running        -- ^ Faster running without ball in possession
	| Dribbling      -- ^ Faster running with ball in possession
	| Rotating       -- ^ Wider range of rotation
	| Gaining        -- ^ Better ball gaining ability
	| Kicking        -- ^ More accurate and wider ball kicking
	| Heading        -- ^ More accurate and wider ball heading
	| Feinting       -- ^ Wider range of feint manouvre
	| Jumping        -- ^ Further jumping
	| Catching       -- ^ Better catching
	| Tackling       -- ^ More effective tackling
	| Schwalbing     -- ^ Better acting of tackles
	| PlayingTheater -- ^ Better acting of playing theater
	deriving (Eq,Show)

data FeintDirection = FeintLeft | FeintRight deriving (Eq, Show)

-- | actions a player can intend to perform
data PlayerAction
	= Move Speed Angle         -- ^ wish to rotate over given angle, and then move with given speed
	| Feint FeintDirection     -- ^ wish to make feint manouvre
	| KickBall Speed3D         -- ^ wish to kick ball with given speed
	| HeadBall Speed3D         -- ^ wish to head ball with given speed
	| GainBall                 -- ^ wish to gain possession of the ball from other player
	| CatchBall                -- ^ wish to catch the ball with his hands
	| Tackle PlayerID Velocity -- ^ wish to tackle identified player, higher velocity is higher chance of succes AND injury (and foul?)
	| Schwalbe                 -- ^ wish to fall as if he was tackled
	| PlayTheater              -- ^ wish to act as if he was hurt
	deriving (Eq,Show)

data PlayerEffect = Moved Speed Angle  -- ^ player has rotated with given angle, and then ran with given speed
	| Feinted FeintDirection            -- ^ player had feinted
	| KickedBall (Maybe Speed3D)        -- ^ player kicked ball (Just v) with velocity, or didn't (Nothing)
	| HeadedBall (Maybe Speed3D)        -- ^ player headed ball (Just v) with velocity, or didn't (Nothing)
	| GainedBall Success                -- ^ player attempt to gain ball from other player
	| CaughtBall Success                -- ^ player caught the ball with his hands
	| Tackled PlayerID Velocity Success -- ^ player attempt to tackle an opponent
	| Schwalbed                         -- ^ player had performed a schwalbe
	| PlayedTheater                     -- ^ player had started to act hurt
	| OnTheGround FramesToGo            -- ^ tackled by someone else; FramesToGo is the amount of frames that you will be on the ground

type Stamina = Float
type Health = Float

defaultPlayer  PlayerID  Player
defaultPlayer playerID = Player
	{playerID = playerID,
	 name = "default",
	 height = 1.6,
	 pos = zero,
	 speed = zero,
	 nose = zero,
	 skills = (Running, Kicking, Dribbling),
	 effect = Nothing,
	 stamina = maxStamina,
	 health = maxHealth,
	 brain = Brain
	 	{m = error "You need to provide defaultPlayer with a new brain.",
	 	 ai = const $ return $ Move zero zero}}

identifyPlayer  PlayerID  Player  Bool
identifyPlayer id fb = id == (playerID fb)

playerIdentity  Player  PlayerID
playerIdentity fb = (playerID fb)

-- | getBall returns the ball (containing its position and speed-information)
-- | that is either free or gained by a player.
-- | For this reason, the list of players must contain all players, otherwise
-- | this function may fail.
getBall  BallState  [Player]  Ball
getBall (Free ball) _ = ball
getBall (GainedBy playerID) allPlayers = case find (identifyPlayer playerID) allPlayers of
	Nothing  error "getBall: no player found with requested identifier."
	Just (Player {pos=pos,speed=speed})  mkBall pos speed

-- | Returns True if the ball is held by a Keeper in his own penaltyarea
-- | Returns False when the ball is held by a Keeper in open field
-- | Returns False when the ball is not held by a Keeper
-- | Keepers should be numbered with 1.
ballGainedByKeeper  BallState  [Player]  ClubName  Home  Field  Bool
ballGainedByKeeper (Free _) _ _ _ _ = False
ballGainedByKeeper (GainedBy playerID) allPlayers club home field
	= case filter (identifyPlayer playerID) allPlayers of
		[keeper]  playerNo playerID == 1 && inPenaltyArea field (if (clubName playerID==club) then home else (other home)) (pos keeper)
		wrongNumber  error "ballGainedByKeeper: wrong number of keepers found."

clonePlayer  Brain (PlayerAI m) m  Player  Player
clonePlayer brain (Player playerID name height pos speed nose skills effect stamina health _)
	= (Player playerID name height pos speed nose skills effect stamina health brain)

class SameClub a where sameClub  a  a  Bool -- ^ belong to same club

-- TODO: move this to SoccerFun.Geometry
class GetPosition a where getPosition  a  Position

inRadiusOfPlayer  Position  Player  Bool -- ^ True iff position touches/hits player
inRadiusOfPlayer p player = inRadiusOfPosition (zero {pxy=p}) xWidthPlayer yWidthPlayer (height player) (pos player)

skillsAsList  Player  [Skill] -- ^ Skills of the player as a list
skillsAsList fb = (\(a,b,c)[a,b,c]) (skills fb)

isFirstHalf  Half  Bool
isFirstHalf FirstHalf = True
isFirstHalf _ = False

isSecondHalf  Half  Bool
isSecondHalf SecondHalf = True
isSecondHalf _ = False

-- | chest size of player
xWidthPlayer = 0.7/2.0
-- | stomach size of player
yWidthPlayer = 0.4/2.0

getClubName  Player  ClubName
getClubName fb = nameOf (playerID fb)
isKeeper  Player  Bool
isKeeper fb = playerNo (playerID fb) == 1
isFielder  Player  Bool
isFielder fb = not (isKeeper fb)

-- | minimum length of a person. Advantages:  better gainball; better stamina at sprinting; better dribbling; less health damage when fall, better rotating.
minLength	= 1.6	 Float
-- | maximum length of a person. Advantages:	wider  gainball; better stamina at running;   higher headball;  improved catching; harder kicking.
maxLength	= 2.1	 Float
-- | minimum height of a person. Advantages: better gainball; better stamina at sprinting; better dribbling; less health damage when fall, better rotating.
minHeight = 1.6  Float
-- | maximum height of a person. Advantages: wider gainball; better stamina at running; higher headball; improved catching; harder kicking.
maxHeight = 2.1  Float
maxStamina = 1.0  Float
maxHealth = 1.0  Float

{-| Player attribute dependent abilities:
		use these functions to make your player correctly dependent of abilities.
-}
maxGainReach  Player  Metre
maxGainReach fb = (if (elem Gaining (skillsAsList fb)) then 0.5 else 0.3) * (height fb)

-- | vertical jumping
maxJumpReach  Player  Metre
maxJumpReach fb = (if (elem Jumping (skillsAsList fb)) then 0.6 else 0.4) * (height fb)

maxGainVelocityDifference  Player  Metre  Velocity
maxGainVelocityDifference fb dPlayerBall = (if (elem Gaining (skillsAsList fb)) then 15.0 else 10.0) - distanceDifficulty where
	distanceDifficulty = max zero (((0.8*(height fb))**4.0)*(dPlayerBall/(height fb)))

maxCatchVelocityDifference  Player  Metre  Velocity
maxCatchVelocityDifference fb dPlayerBall = (if (elem Gaining (skillsAsList fb)) then 20.0 else 17.0) - distanceDifficulty where
	distanceDifficulty = max zero (((0.8*(height fb))**4.0) * (dPlayerBall/(height fb)))

maxKickReach  Player  Metre
maxKickReach fb = (if (elem Kicking (skillsAsList fb)) then 0.6 else 0.4) * (height fb)

maxHeadReach  Player  Metre
maxHeadReach fb = (if (elem Heading (skillsAsList fb)) then 0.4 else 0.2) * (height fb)

-- | includes horizontal jumping
maxCatchReach  Player  Metre
maxCatchReach fb = (if (elem Catching (skillsAsList fb)) then 1.8 else 1.5) * (height fb)

maxTackleReach  Player  Metre
maxTackleReach fb = (if (elem Tackling (skillsAsList fb)) then 0.33 else 0.25) * (height fb)

maxVelocityBallKick  Player  Velocity
maxVelocityBallKick fb = (if (elem Kicking (skillsAsList fb)) then 27.0 else 25.0 + (height fb)/2.0) * (0.2*fatHealth+0.8) where
	fatHealth = getHealthStaminaFactor (health fb) (stamina fb)

maxVelocityBallHead  Player  Velocity  Velocity
maxVelocityBallHead fb ballSpeed = 0.7*ballSpeed + (if (elem Heading (skillsAsList fb)) then 7.0 else 5.0)*(0.1*fatHealth+0.9) where
	fatHealth = getHealthStaminaFactor (health fb) (stamina fb)

maxKickingDeviation  Player  Angle
maxKickingDeviation skills = pi/2.0-- if (elem Kicking skills) (pi/18.0) (pi/2.0)

maxHeadingDeviation  Player  Angle
maxHeadingDeviation skills = pi/4.0-- if (elem Heading skills) (pi/16.0) (pi/5.0)

-- | maximum angle with which player can rotate
maxRotateAngle  Player  Angle
maxRotateAngle fb = pi/18.0*((5.0/(velocity $ speed fb))*(height fb/2.0))

-- | maximum side step of player for feint manouvre
maxFeintStep  Player  Metre
maxFeintStep fb = if (elem Feinting (skillsAsList fb)) then 0.75 else 0.5

-- | combination of stamina and health
type HealthStaminaFactor = Float

getHealthStaminaFactor  Health  Stamina  HealthStaminaFactor
getHealthStaminaFactor health stamina
	| stamina <= health = stamina
	| otherwise = (stamina + health) / 2


teamHome  ATeam  Half  Home
teamHome team half
	| team == Team1 && half == FirstHalf || team == Team2 && half == SecondHalf
											= West
	| otherwise = East

opponentHome  ATeam  Half  Home
opponentHome team half
	| team == Team2 && half == FirstHalf || team == Team1 && half == SecondHalf
											= West
	| otherwise = East

isMove  PlayerAction  Bool
isMove (Move _ _) = True
isMove _ = False

isGainBall  PlayerAction  Bool
isGainBall GainBall = True
isGainBall _ = False

isCatchBall  PlayerAction  Bool
isCatchBall CatchBall = True
isCatchBall _ = False

isKickBall  PlayerAction  Bool
isKickBall (KickBall _) = True
isKickBall _ = False

isHeadBall  PlayerAction  Bool
isHeadBall (HeadBall _) = True
isHeadBall _ = False

isFeint  PlayerAction  Bool
isFeint (Feint _) = True
isFeint _ = False

isPlayerTackle  PlayerAction  Bool
isPlayerTackle (Tackle _ _) = True
isPlayerTackle _ = False

isSchwalbe  PlayerAction  Bool
isSchwalbe Schwalbe = True
isSchwalbe _ = False

isPlayTheater  PlayerAction  Bool
isPlayTheater PlayTheater = True
isPlayTheater _ = False


isSkillOfAction  Skill  PlayerAction  Bool
isSkillOfAction Running (Move _ _) = True
isSkillOfAction Rotating (Move _ _) = True
isSkillOfAction Gaining GainBall = True
isSkillOfAction Kicking (KickBall _) = True
isSkillOfAction Heading (HeadBall _) = True
isSkillOfAction Feinting (Feint _) = True
isSkillOfAction Tackling (Tackle _ _) = True
isSkillOfAction Schwalbing Schwalbe = True
isSkillOfAction Catching CatchBall = True
isSkillOfAction PlayingTheater PlayTheater = True
isSkillOfAction _ _ = False

isActionOnBall  PlayerAction  Bool
isActionOnBall GainBall = True
isActionOnBall CatchBall = True
isActionOnBall (KickBall _) = True
isActionOnBall (HeadBall _) = True
isActionOnBall _ = False



isMoved  PlayerEffect  Bool
isMoved (Moved _ _) = True
isMoved _ = False

isGainedBall  PlayerEffect  Bool
isGainedBall (GainedBall _) = True
isGainedBall _ = False

isKickedBall  PlayerEffect  Bool
isKickedBall (KickedBall _) = True
isKickedBall _ = False

isHeadedBall  PlayerEffect  Bool
isHeadedBall (HeadedBall _) = True
isHeadedBall _ = False

isFeinted  PlayerEffect  Bool
isFeinted (Feinted _) = True
isFeinted _ = False

isTackled  PlayerEffect  Bool
isTackled (Tackled _ _ _) = True
isTackled _ = False

isSchwalbed  PlayerEffect  Bool
isSchwalbed Schwalbed = True
isSchwalbed _ = False

isCaughtBall  PlayerEffect  Bool
isCaughtBall (CaughtBall _) = True
isCaughtBall _ = False

isPlayedTheater  PlayerEffect  Bool
isPlayedTheater PlayedTheater = True
isPlayedTheater _ = False

isOnTheGround  PlayerEffect  Bool
isOnTheGround (OnTheGround _) = True
isOnTheGround _ = False


failPlayerAction  PlayerAction  PlayerEffect
failPlayerAction (Move s a) = Moved s a
failPlayerAction GainBall = GainedBall Fail
failPlayerAction CatchBall = CaughtBall Fail
failPlayerAction (KickBall v) = KickedBall Nothing
failPlayerAction (HeadBall v) = HeadedBall Nothing
failPlayerAction (Feint d) = Feinted d
failPlayerAction (Tackle p v) = Tackled p v Fail
failPlayerAction Schwalbe = Schwalbed
failPlayerAction PlayTheater = PlayedTheater
--failPlayerAction _ = error "failPlayerAction: unknown action failed"

instance GetPosition Player where getPosition fb = (pos fb)
instance NameOf Player where nameOf fb = name fb
instance NameOf PlayerID where nameOf f = clubName f
instance SameClub PlayerID where sameClub id1 id2 = nameOf id1 == nameOf id2
instance SameClub Player where sameClub fb1 fb2 = sameClub (playerID fb1) (playerID fb2)


{- Player attribute dependent abilities:
-}

{-isReprimanded ∷ PlayerEffect → Bool
isReprimanded (Reprimanded _) = True
isReprimanded _ = False

isScoredGoal ∷ PlayerEffect → Bool
isScoredGoal (ScoredGoal _) = True
isScoredGoal _ = False-}