content: {-# LANGUAGE DeriveGeneric #-}
  -- | The type of place kinds. Every room in the game is an instantiated
  -- place kind.
  module Game.LambdaHack.Content.PlaceKind
    ( PlaceKind(..), makeData
    , Cover(..), Fence(..)
    , PlaceEntry(..), deadEndId, overridePlaceKind, override2PlaceKind
  #ifdef EXPOSE_INTERNAL
      -- * Internal operations
    , validateSingle, validateAll
  #endif
    ) where
  
  import Prelude ()
  
  import Game.LambdaHack.Core.Prelude
  
  import           Data.Binary
  import qualified Data.EnumMap.Strict as EM
  import qualified Data.Text as T
  import           GHC.Generics (Generic)
  
  import Game.LambdaHack.Content.TileKind (TileKind)
  import Game.LambdaHack.Definition.ContentData
  import Game.LambdaHack.Definition.Defs
  import Game.LambdaHack.Definition.DefsInternal
  
  -- | Parameters for the generation of small areas within a dungeon level.
  data PlaceKind = PlaceKind
    { pname       :: Text          -- ^ short description, singular or plural
    , pfreq       :: Freqs PlaceKind  -- ^ frequency within groups
    , prarity     :: Rarity        -- ^ rarity on given depths
    , pcover      :: Cover         -- ^ how to fill whole place using the corner
    , pfence      :: Fence         -- ^ whether to fence place with solid border
    , ptopLeft    :: [Text]        -- ^ plan of the top-left corner of the place
    , plegendDark :: EM.EnumMap Char (GroupName TileKind)  -- ^ dark legend
    , plegendLit  :: EM.EnumMap Char (GroupName TileKind)  -- ^ lit legend
    }
    deriving Show  -- No Eq and Ord to make extending logically sound
  
  -- | A method of filling the whole area (except for CVerbatim and CMirror,
  -- which are just placed in the middle of the area) by transforming
  -- a given corner.
  data Cover =
      CAlternate  -- ^ reflect every other corner, overlapping 1 row and column
    | CStretch    -- ^ fill symmetrically 4 corners and stretch their borders
    | CReflect    -- ^ tile separately and symmetrically quarters of the place
    | CVerbatim   -- ^ just build the given interior, without filling the area
    | CMirror     -- ^ build the given interior in one of 4 mirrored variants
    deriving (Show, Eq)
  
  -- | The choice of a fence type for the place.
  data Fence =
      FWall   -- ^ put a solid wall fence around the place
    | FFloor  -- ^ leave an empty space, like the room's floor
    | FGround -- ^ leave an empty space, like the cave's ground
    | FNone   -- ^ skip the fence and fill all with the place proper
    deriving (Show, Eq)
  
  -- | Places are rooms and other dungeon features, their names can be seen
  -- on a level map by aiming at a position that is an entry to the place
  -- (an individual entrance point, an approach area around the place
  -- or a phantom entry not on the map, but only used for statistics
  -- to witness the place exists). Entries are proxies for initial places
  -- created on the level (which may be otherwise eradicated by burrowing
  -- the walls, etc.) and so used for dungeon statistics.
  -- The statistics are presented in the @Dashboard/displace place lore@ menu.
  data PlaceEntry =
      PEntry (ContentId PlaceKind)
    | PAround (ContentId PlaceKind)
    | PExists (ContentId PlaceKind)
    deriving (Show, Eq, Generic)
  
  instance Binary PlaceEntry
  
  deadEndId :: ContentId PlaceKind
  {-# INLINE deadEndId #-}
  deadEndId = toContentId 0
  
  overridePlaceKind :: [(Char, GroupName TileKind)] -> PlaceKind -> PlaceKind
  overridePlaceKind l pk = pk
    { plegendDark = EM.fromList l `EM.union` plegendDark pk
    , plegendLit = EM.fromList l `EM.union` plegendLit pk }
  
  override2PlaceKind :: [(Char, GroupName TileKind)]
                     -> [(Char, GroupName TileKind)]
                     -> PlaceKind
                     -> PlaceKind
  override2PlaceKind lDark lLit pk = pk
    { plegendDark = EM.fromList lDark `EM.union` plegendDark pk
    , plegendLit = EM.fromList lLit `EM.union` plegendLit pk }
  
  -- | Catch invalid place kind definitions. In particular, verify that
  -- the top-left corner map is rectangular and not empty.
  validateSingle :: ContentData TileKind -> PlaceKind -> [Text]
  validateSingle cotile PlaceKind{..} =
    let dxcorner = case ptopLeft of
          [] -> 0
          l : _ -> T.length l
        inLegend :: Text -> EM.EnumMap Char (GroupName TileKind) -> Char -> [Text]
        inLegend _ _ 'X' = []  -- special placeholder symbol; TODO: unhardwire
        inLegend legendName m c = case EM.lookup c m of
          Nothing -> [tshow c <+> "tile code not found in" <+> legendName]
          Just grp -> [ tshow c <+> "tile code has group"
                        <+> displayGroupName grp
                        <+> "with null frequency in tile content"
                      | not $ oexistsGroup cotile grp ]
        inLegendAll legendName m = concatMap (inLegend legendName m)
                                             (concatMap T.unpack ptopLeft)
    in [ "top-left corner empty" | dxcorner == 0 ]
       ++ [ "top-left corner not rectangular"
          | any ((/= dxcorner) . T.length) ptopLeft ]
       ++ inLegendAll "plegendDark" plegendDark
       ++ inLegendAll "plegendLit" plegendLit
       ++ validateRarity prarity
  
  -- | Validate all place kinds.
  validateAll :: [PlaceKind] -> ContentData PlaceKind -> [Text]
  validateAll _ _ = []  -- so far, always valid
  
  makeData :: ContentData TileKind
           -> [PlaceKind] -> [GroupName PlaceKind] -> [GroupName PlaceKind]
           -> ContentData PlaceKind
  makeData cotile = makeContentData "PlaceKind" pname pfreq
                                    (validateSingle cotile) validateAll
  