-- | Screen frames.
module Game.LambdaHack.Client.UI.Frame
  ( SingleFrame(..), Frames
  , blankSingleFrame, overlayFrame, overlayFrameWithLines
  ) where

import Prelude ()

import Game.LambdaHack.Common.Prelude

import Data.Word (Word32)

import Game.LambdaHack.Client.UI.Overlay
import Game.LambdaHack.Common.Color
import Game.LambdaHack.Common.Misc
import Game.LambdaHack.Common.Point
import qualified Game.LambdaHack.Common.PointArray as PointArray

-- | An overlay that fits on the screen (or is meant to be truncated on display)
-- and is padded to fill the whole screen
-- and is displayed as a single game screen frame.
--
-- Note that we don't provide a list of color-highlighed positions separately,
-- because overlays need to obscure not only map, but the highlights as well.
newtype SingleFrame = SingleFrame
  {singleFrame :: PointArray.GArray Word32 AttrCharW32}
  deriving (Eq, Show)

-- | Sequences of screen frames, including delays.
type Frames = [Maybe FrameForall]

blankSingleFrame :: SingleFrame
blankSingleFrame =
  let lxsize = fst normalLevelBound + 1
      lysize = snd normalLevelBound + 4
  in SingleFrame $ PointArray.replicateA lxsize lysize spaceAttrW32

-- | Truncate the overlay: for each line, if it's too long, it's truncated
-- and if there are too many lines, excess is dropped and warning is appended.
truncateLines :: Bool -> [AttrLine] -> [AttrLine]
truncateLines onBlank l =
  let lxsize = fst normalLevelBound + 1
      lysize = snd normalLevelBound + 1
      canvasLength = if onBlank then lysize + 3 else lysize + 1
      topLayer = if length l <= canvasLength
                 then l ++ [[] | length l < canvasLength && length l > 3]
                 else take (canvasLength - 1) l
                      ++ [stringToAL "--a portion of the text trimmed--"]
      f lenPrev lenNext layerLine =
        truncateAttrLine lxsize layerLine (max lenPrev lenNext)
      lens = map (min (lxsize - 1) . length) topLayer
  in zipWith3 f (0 : lens) (drop 1 lens ++ [0]) topLayer

-- | Add a space at the message end, for display overlayed over the level map.
-- Also trim (do not wrap!) too long lines.
truncateAttrLine :: X -> AttrLine -> X -> AttrLine
truncateAttrLine w xs lenMax =
  case compare w (length xs) of
    LT -> let discarded = drop w xs
          in if all (== spaceAttrW32) discarded
             then take w xs
             else take (w - 1) xs ++ [attrChar2ToW32 BrBlack '$']
    EQ -> xs
    GT -> let xsSpace = if null xs || last xs == spaceAttrW32
                        then xs
                        else xs ++ [spaceAttrW32]
              whiteN = max (40 - length xsSpace) (1 + lenMax - length xsSpace)
          in xsSpace ++ replicate whiteN spaceAttrW32

-- | Overlays either the game map only or the whole empty screen frame.
-- We assume the lines of the overlay are not too long nor too many.
overlayFrame :: Overlay -> FrameForall -> FrameForall
overlayFrame ov ff = FrameForall $ \v -> do
  unFrameForall ff v
  mapM_ (\(offset, l) -> unFrameForall (writeLine offset l) v) ov

overlayFrameWithLines :: Bool -> [AttrLine] -> FrameForall -> FrameForall
overlayFrameWithLines onBlank l msf =
  let lxsize = fst normalLevelBound + 1
      ov = map (\(y, al) -> (y * lxsize, al))
           $ zip [0..] $ truncateLines onBlank l
  in overlayFrame ov msf