{-# LANGUAGE DeriveFunctor, DeriveGeneric, OverloadedStrings #-}
-- | Effects of content on other content. No operation in this module
-- involves the 'State' or 'Action' type.
module Game.LambdaHack.Common.Effect
  ( Effect(..), effectTrav, effectToSuffix, effectToBenefit
  ) where

import qualified Control.Monad.State as St
import Data.Binary
import qualified Data.Hashable as Hashable
import Data.Text (Text)
import GHC.Generics (Generic)

import Game.LambdaHack.Common.Msg
import Game.LambdaHack.Common.Random
import Game.LambdaHack.Utils.Assert

-- TODO: document each constructor
-- Effects of items, tiles, etc. The type argument represents power.
-- either as a random formula dependent on level, or as a final rolled value.
data Effect a =
    NoEffect
  | Heal Int
  | Hurt !RollDice a
  | Mindprobe Int       -- the @Int@ is a hack to send the result to clients
  | Dominate
  | CallFriend Int
  | Summon Int
  | CreateItem Int
  | ApplyPerfume
  | Regeneration a
  | Searching a
  | Ascend Int
  | Descend Int
  | Escape
  deriving (Show, Read, Eq, Ord, Generic, Functor)

instance Hashable.Hashable a => Hashable.Hashable (Effect a)

instance Binary a => Binary (Effect a)

-- TODO: Traversable?
-- | Transform an effect using a stateful function.
effectTrav :: Effect a -> (a -> St.State s b) -> St.State s (Effect b)
effectTrav NoEffect _ = return NoEffect
effectTrav (Heal p) _ = return $ Heal p
effectTrav (Hurt dice a) f = do
  b <- f a
  return $ Hurt dice b
effectTrav (Mindprobe x) _ = return $ Mindprobe x
effectTrav Dominate _ = return Dominate
effectTrav (CallFriend p) _ = return $ CallFriend p
effectTrav (Summon p) _ = return $ Summon p
effectTrav (CreateItem p) _ = return $ CreateItem p
effectTrav ApplyPerfume _ = return ApplyPerfume
effectTrav (Regeneration a) f = do
  b <- f a
  return $ Regeneration b
effectTrav (Searching a) f = do
  b <- f a
  return $ Searching b
effectTrav (Ascend p) _ = return $ Ascend p
effectTrav (Descend p) _ = return $ Descend p
effectTrav Escape _ = return Escape

-- | Suffix to append to a basic content name if the content causes the effect.
effectToSuff :: Effect a -> (a -> Text) -> Text
effectToSuff effect f =
  case St.evalState (effectTrav effect $ return . f) () of
    NoEffect -> ""
    Heal p | p > 0 -> "of healing" <> affixBonus p
    Heal 0 -> "of bloodletting"
    Heal p -> "of wounding" <> affixBonus p
    Hurt dice t -> "(" <> showT dice <> ")" <> t
    Mindprobe{} -> "of soul searching"
    Dominate -> "of domination"
    CallFriend p -> "of aid calling" <> affixPower p
    Summon p -> "of summoning" <> affixPower p
    CreateItem p -> "of item creation" <> affixPower p
    ApplyPerfume -> "of rose water"
    Regeneration t -> "of regeneration" <> t
    Searching t -> "of searching" <> t
    Ascend p -> "of ascending" <> affixPower p
    Descend p -> "of descending" <> affixPower p
    Escape -> "of escaping"

effectToSuffix :: Effect Int -> Text
effectToSuffix effect = effectToSuff effect affixBonus

affixPower :: Int -> Text
affixPower p = case compare p 1 of
  EQ -> ""
  LT -> assert `failure` p
  GT -> " (+" <> showT p <> ")"

affixBonus :: Int -> Text
affixBonus p = case compare p 0 of
  EQ -> ""
  LT -> " (" <> showT p <> ")"
  GT -> " (+" <> showT p <> ")"

-- | How much AI benefits from applying the effect. Multipllied by item p.
-- Negative means harm to the enemy when thrown at him. Effects with zero
-- benefit won't ever be used, neither actively nor passively.
effectToBenefit :: Effect Int -> Int
effectToBenefit NoEffect = 0
effectToBenefit (Heal p) = p * 10       -- TODO: depends on (maxhp - hp)
effectToBenefit (Hurt _ p) = -(p * 10)  -- TODO: dice ignored for now
effectToBenefit Mindprobe{} = 0         -- AI can't benefit yet
effectToBenefit Dominate = 1            -- hard to use; TODO: limit by IQ
effectToBenefit (CallFriend p) = p * 100
effectToBenefit Summon{} = 5            -- may or may not spawn a friendly
effectToBenefit (CreateItem p) = p * 20
effectToBenefit ApplyPerfume = 0
effectToBenefit Regeneration{} = 0      -- bigger benefit from carrying around
effectToBenefit Searching{} = 0         -- AI doesn't search yet
effectToBenefit Ascend{} = 5            -- AI can't choose levels smartly yet
effectToBenefit Descend{} = 20          -- but it prefers to hide deep down
effectToBenefit Escape = 100            -- hero AI wants to win ASAP, monster
                                        -- AI sits on the exit to block it