{-# LANGUAGE DeriveGeneric #-}
-- | The type of kinds of rooms, halls and passages.
module Game.LambdaHack.Content.PlaceKind
  ( PlaceKind(..), makeData
  , Cover(..), Fence(..)
  , PlaceEntry(..), deadEndId
#ifdef EXPOSE_INTERNAL
    -- * Internal operations
  , validateSingle, validateAll
#endif
  ) where

import Prelude ()

import Game.LambdaHack.Core.Prelude

import           Data.Binary
import           Data.Char (chr)
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

-- | Parameters for the generation of small areas within a dungeon level.
data PlaceKind = PlaceKind
  { psymbol       :: Char          -- ^ a symbol
  , 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
  , poverrideDark :: [(Char, GroupName TileKind)]  -- ^ dark legend override
  , poverrideLit  :: [(Char, GroupName TileKind)]  -- ^ lit legend override
  }
  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)

data PlaceEntry =
    PEntry (ContentId PlaceKind)
  | PAround (ContentId PlaceKind)
  | PEnd (ContentId PlaceKind)
  deriving (Show, Eq, Generic)

instance Binary PlaceEntry

deadEndId :: ContentId PlaceKind
{-# INLINE deadEndId #-}
deadEndId = toContentId 0

validateOverride :: [(Char, GroupName TileKind)] -> [Text]
validateOverride ov =
  let symbols = sort $ map fst ov
      duplicated = filter (uncurry (==)) $ zip symbols (chr 0 : symbols)
  in if null duplicated
     then []
     else [ "duplicated override symbols:"
            <+> T.pack (intersperse ' ' $ map fst duplicated) ]

-- | Catch invalid place kind definitions. In particular, verify that
-- the top-left corner map is rectangular and not empty.
validateSingle :: PlaceKind -> [Text]
validateSingle PlaceKind{..} =
  let dxcorner = case ptopLeft of
        [] -> 0
        l : _ -> T.length l
  in [ "top-left corner empty" | dxcorner == 0 ]
     ++ [ "top-left corner not rectangular"
        | any (/= dxcorner) (map T.length ptopLeft) ]
     ++ validateRarity prarity
     ++ validateOverride poverrideDark
     ++ validateOverride poverrideLit

-- | Validate all place kinds.
validateAll :: ContentData TileKind -> [PlaceKind] -> ContentData PlaceKind
            -> [Text]
validateAll cotile content _ =
  let overrides place = poverrideDark place ++ poverrideLit place
      missingOverride = filter (not . omemberGroup cotile)
                        $ concatMap (map snd . overrides) content
  in [ "override tile groups not in content:" <+> tshow missingOverride
     | not $ null missingOverride ]

makeData :: ContentData TileKind -> [PlaceKind] -> ContentData PlaceKind
makeData cotile =
  makeContentData "PlaceKind" pname pfreq validateSingle (validateAll cotile)