content: -- | The type of game modes.
  module Game.LambdaHack.Content.ModeKind
    ( pattern CAMPAIGN_SCENARIO, pattern INSERT_COIN
    , ModeKind(..), makeData
    , Caves, Roster
    , mandatoryGroups
  #ifdef EXPOSE_INTERNAL
      -- * Internal operations
    , validateSingle, validateAll, validateSingleRoster
  #endif
    ) where
  
  import Prelude ()
  
  import Game.LambdaHack.Core.Prelude
  
  import qualified Data.Text as T
  
  import           Game.LambdaHack.Content.CaveKind (CaveKind)
  import           Game.LambdaHack.Content.FactionKind
    (FactionKind (..), Outcome (..))
  import           Game.LambdaHack.Content.ItemKind (ItemKind)
  import qualified Game.LambdaHack.Core.Dice as Dice
  import           Game.LambdaHack.Definition.ContentData
  import           Game.LambdaHack.Definition.Defs
  import           Game.LambdaHack.Definition.DefsInternal
  
  -- | Game mode specification.
  data ModeKind = ModeKind
    { mname     :: Text            -- ^ short description
    , mfreq     :: Freqs ModeKind  -- ^ frequency within groups
    , mtutorial :: Bool            -- ^ whether to show tutorial messages, etc.
    , mattract  :: Bool            -- ^ whether this is an attract mode
    , mroster   :: Roster          -- ^ factions taking part in the game
    , mcaves    :: Caves           -- ^ arena of the game
    , mendMsg   :: [(Outcome, Text)]
        -- ^ messages displayed at each particular game ends; if message empty,
        --   the screen is skipped
    , mrules    :: Text            -- ^ rules note
    , mdesc     :: Text            -- ^ description
    , mreason   :: Text            -- ^ why/when the mode should be played
    , mhint     :: Text            -- ^ hints in case player faces difficulties
    }
    deriving Show
  
  -- | Requested cave groups for particular level intervals.
  type Caves = [([Int], [GroupName CaveKind])]
  
  -- | The specification of factions and of levels, numbers and groups
  -- of their initial members.
  type Roster = [( GroupName FactionKind
                 , [(Int, Dice.Dice, GroupName ItemKind)] )]
  
  -- | Catch invalid game mode kind definitions.
  validateSingle :: ContentData FactionKind -> ModeKind -> [Text]
  validateSingle cofact ModeKind{..} =
    [ "mname longer than 22" | T.length mname > 22 ]
    ++ let f cave@(ns, l) =
             [ "not enough or too many levels for required cave groups:"
               <+> tshow cave
             | length ns /= length l ]
       in concatMap f mcaves
    ++ validateSingleRoster cofact mcaves mroster
  
  -- | Checks, in particular, that there is at least one faction with fneverEmpty
  -- or the game would get stuck as soon as the dungeon is devoid of actors.
  validateSingleRoster :: ContentData FactionKind -> Caves -> Roster -> [Text]
  validateSingleRoster cofact caves roster =
    let emptyGroups = filter (not . oexistsGroup cofact) $ map fst roster
    in [ "the following faction kind groups have no representative with non-zero frequency:"
         <+> T.intercalate ", " (map displayGroupName emptyGroups)
       | not $ null emptyGroups ]
    ++ let fkKeepsAlive acc _ _ fk = acc && fneverEmpty fk
             -- all of group elements have to keep level alive, hence conjunction
           fkGroupKeepsAlive (fkGroup, _) =
             ofoldlGroup' cofact fkGroup fkKeepsAlive True
       in [ "potentially no faction keeps the dungeon alive"
          | not $ any fkGroupKeepsAlive roster ]
    ++ let fkHasUIor acc _ _ fk = acc || fhasUI fk
             -- single group element having UI already incurs the risk
             -- of duplication, hence disjunction
           fkGroupHasUIor (fkGroup, _) =
             ofoldlGroup' cofact fkGroup fkHasUIor False
       in [ "potentially more than one UI client"
          | length (filter fkGroupHasUIor roster) > 1 ]
    ++ let fkHasUIand acc _ _ fk = acc && fhasUI fk
             -- single group element missing UI already incurs the risk
             -- of no UI in the whole game, hence disjunction
           fkGroupHasUIand (fkGroup, _) =
             ofoldlGroup' cofact fkGroup fkHasUIand True
       in [ "potentially less than one UI client"
          | not (any fkGroupHasUIand roster) ]
    ++ let fkTokens acc _ _ fk = fteam fk : acc
           fkGroupTokens (fkGroup, _) = ofoldlGroup' cofact fkGroup fkTokens []
           tokens = concatMap (nub . sort . fkGroupTokens) roster
           nubTokens = nub . sort $ tokens
       in [ "potentially duplicate team continuity token"
          | length tokens /= length nubTokens ]
    ++ let keys = concatMap fst caves  -- permitted to be empty, for tests
           minD = minimum keys
           maxD = maximum keys
           f (_, l) = concatMap g l
           g i3@(ln, _, _) =
             [ "initial actor levels not among caves:" <+> tshow i3
             | ln `notElem` keys ]
       in concatMap f roster
          ++ [ "player is confused by both positive and negative level numbers"
             | not (null keys) && signum minD /= signum maxD ]
          ++ [ "player is confused by level numer zero"
             | 0 `elem` keys ]
  
  -- | Validate game mode kinds together.
  validateAll :: [ModeKind] -> ContentData ModeKind -> [Text]
  validateAll _ _ = []  -- so far, always valid
  
  -- * Mandatory item groups
  
  mandatoryGroups :: [GroupName ModeKind]
  mandatoryGroups =
         [CAMPAIGN_SCENARIO, INSERT_COIN]
  
  pattern CAMPAIGN_SCENARIO, INSERT_COIN :: GroupName ModeKind
  
  pattern CAMPAIGN_SCENARIO = GroupName "campaign scenario"
  pattern INSERT_COIN = GroupName "insert coin"
  
  makeData :: ContentData FactionKind
           -> [ModeKind] -> [GroupName ModeKind] -> [GroupName ModeKind]
           -> ContentData ModeKind
  makeData cofact content groupNamesSingleton groupNames =
    makeContentData "ModeKind" mname mfreq (validateSingle cofact) validateAll
                    content
                    groupNamesSingleton
                    (mandatoryGroups ++ groupNames)
  