{-# 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-}