{-# LANGUAGE OverloadedStrings #-}
-- | Factions taking part in the game: e.g., two human players controlling
-- the hero faction battling the monster and the animal factions.
module Game.LambdaHack.Common.Faction
  ( FactionId, FactionDict, Faction(..), Diplomacy(..), Outcome(..), Status(..)
  , isHumanFact, usesAIFact, isSpawnFact, isSummonFact
  , isAtWar, isAllied
  ) where

import Data.Binary
import qualified Data.EnumMap.Strict as EM
import Data.Maybe
import Data.Text (Text)

import Game.LambdaHack.Common.Actor
import qualified Game.LambdaHack.Common.Color as Color
import qualified Game.LambdaHack.Common.Kind as Kind
import Game.LambdaHack.Common.Misc
import Game.LambdaHack.Content.FactionKind
import Game.LambdaHack.Content.StrategyKind

-- | All factions in the game, indexed by faction identifier.
type FactionDict = EM.EnumMap FactionId Faction

data Faction = Faction
  { gkind     :: !(Kind.Id FactionKind)   -- ^ the kind of the faction
  , gname     :: !Text                    -- ^ individual name
  , gconfig   :: !Text                    -- ^ raw name specified in config
  , gAiLeader :: !(Maybe (Kind.Id StrategyKind))
                                          -- ^ AI for the leaders;
                                          -- Nothing means human-controlled
  , gAiMember :: !(Maybe (Kind.Id StrategyKind))
                                          -- ^ AI to use for other actors;
                                          -- Nothing means human-controlled
  , gdipl     :: !Dipl                    -- ^ diplomatic mode
  , gquit     :: !(Maybe Status)          -- ^ cause of game end/exit
  , gleader   :: !(Maybe ActorId)
  , gcolor    :: !Color.Color             -- ^ color of actors or their frames
  , ginitial  :: !Int                     -- ^ number of initial actors
  , gentry    :: !LevelId                 -- ^ level where initial actors start
  }
  deriving (Show, Eq)

-- | Diplomacy states. Higher overwrite lower in case of assymetric content.
data Diplomacy =
    Unknown
  | Neutral
  | Alliance
  | War
  deriving (Show, Eq, Ord)

type Dipl = EM.EnumMap FactionId Diplomacy

-- | Outcome of a game.
data Outcome =
    Killed    -- ^ the faction was eliminated
  | Defeated  -- ^ the faction lost the game in another way
  | Camping   -- ^ game is supended
  | Conquer   -- ^ the player won by eliminating all rivals
  | Escape    -- ^ the player escaped the dungeon alive
  | Restart   -- ^ game is restarted
  deriving (Show, Eq, Ord)

-- | Current game status.
data Status = Status
  { stOutcome :: Outcome  -- ^ current game outcome
  , stDepth   :: Int      -- ^ depth of the final encounter
  , stInfo    :: Text     -- ^ extra information
  }
  deriving (Show, Eq, Ord)

-- | Tell whether the faction is controlled (at least partially) by a human.
isHumanFact :: Faction -> Bool
isHumanFact fact = isNothing (gAiLeader fact) || isNothing (gAiMember fact)

-- | Tell whether the faction uses AI to control any of its actors.
usesAIFact :: Faction -> Bool
usesAIFact fact = isJust (gAiLeader fact) || isJust (gAiMember fact)

-- | Tell whether the faction can spawn actors.
isSpawnFact :: Kind.COps -> Faction -> Bool
isSpawnFact Kind.COps{cofact=Kind.Ops{okind}} fact =
  let kind = okind (gkind fact)
  in maybe False (> 0) $ lookup "spawn" $ ffreq kind

-- | Tell whether actors of the faction can be summoned by items, etc..
isSummonFact :: Kind.COps -> Faction -> Bool
isSummonFact Kind.COps{cofact=Kind.Ops{okind}} fact =
  let kind = okind (gkind fact)
  in maybe False (> 0) $ lookup "summon" $ ffreq kind

-- | Check if factions are at war. Assumes symmetry.
isAtWar :: Faction -> FactionId -> Bool
isAtWar fact fid = War == EM.findWithDefault Unknown fid (gdipl fact)

-- | Check if factions are allied. Assumes symmetry.
isAllied :: Faction -> FactionId -> Bool
isAllied fact fid = Alliance == EM.findWithDefault Unknown fid (gdipl fact)

instance Binary Faction where
  put Faction{..} = do
    put gkind
    put gname
    put gconfig
    put gAiLeader
    put gAiMember
    put gdipl
    put gquit
    put gleader
    put gcolor
    put ginitial
    put gentry
  get = do
    gkind <- get
    gname <- get
    gconfig <- get
    gAiLeader <- get
    gAiMember <- get
    gdipl <- get
    gquit <- get
    gleader <- get
    gcolor <- get
    ginitial <- get
    gentry <- get
    return Faction{..}

instance Binary Diplomacy where
  put Unknown  = putWord8 0
  put Neutral  = putWord8 1
  put Alliance = putWord8 2
  put War      = putWord8 3
  get = do
    tag <- getWord8
    case tag of
      0 -> return Unknown
      1 -> return Neutral
      2 -> return Alliance
      3 -> return War
      _ -> fail "no parse (Diplomacy)"

instance Binary Outcome where
  put Killed = putWord8 0
  put Defeated = putWord8 1
  put Camping = putWord8 2
  put Conquer = putWord8 3
  put Escape = putWord8 4
  put Restart = putWord8 5
  get = do
    tag <- getWord8
    case tag of
      0 -> return Killed
      1 -> return Defeated
      2 -> return Camping
      3 -> return Conquer
      4 -> return Escape
      5 -> return Restart
      _ -> fail "no parse (Outcome)"

instance Binary Status where
  put Status{..} = do
    put stOutcome
    put stDepth
    put stInfo
  get = do
    stOutcome <- get
    stDepth <- get
    stInfo <- get
    return Status{..}