{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ViewPatterns #-}

-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Sum types that represent menu options,
-- modal dialogs, and buttons.
module Swarm.TUI.Model.Menu where

import Brick.Widgets.Dialog (Dialog)
import Brick.Widgets.List qualified as BL
import Control.Lens hiding (from, (<.>))
import Data.List.NonEmpty (NonEmpty (..))
import Data.List.NonEmpty qualified as NE
import Data.Map qualified as M
import Data.Text (Text)
import Data.Vector qualified as V
import Swarm.Game.Achievement.Definitions
import Swarm.Game.Entity as E
import Swarm.Game.ScenarioInfo (
  ScenarioCollection,
  ScenarioInfo (..),
  ScenarioInfoPair,
  ScenarioItem (..),
  scMap,
  scenarioCollectionToList,
 )
import Swarm.Game.State
import Swarm.TUI.Model.Name
import Swarm.Util
import System.FilePath (dropTrailingPathSeparator, splitPath, takeFileName)
import Witch (into)

------------------------------------------------------------
-- Menus and dialogs
------------------------------------------------------------

data ScenarioOutcome = WinModal | LoseModal
  deriving (Int -> ScenarioOutcome -> ShowS
[ScenarioOutcome] -> ShowS
ScenarioOutcome -> FilePath
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
showList :: [ScenarioOutcome] -> ShowS
$cshowList :: [ScenarioOutcome] -> ShowS
show :: ScenarioOutcome -> FilePath
$cshow :: ScenarioOutcome -> FilePath
showsPrec :: Int -> ScenarioOutcome -> ShowS
$cshowsPrec :: Int -> ScenarioOutcome -> ShowS
Show)

data ModalType
  = HelpModal
  | RecipesModal
  | CommandsModal
  | MessagesModal
  | EntityPaletteModal
  | TerrainPaletteModal
  | RobotsModal
  | ScenarioEndModal ScenarioOutcome
  | QuitModal
  | KeepPlayingModal
  | DescriptionModal Entity
  | GoalModal
  deriving (Int -> ModalType -> ShowS
[ModalType] -> ShowS
ModalType -> FilePath
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
showList :: [ModalType] -> ShowS
$cshowList :: [ModalType] -> ShowS
show :: ModalType -> FilePath
$cshow :: ModalType -> FilePath
showsPrec :: Int -> ModalType -> ShowS
$cshowsPrec :: Int -> ModalType -> ShowS
Show)

data ButtonAction
  = Cancel
  | KeepPlaying
  | StartOver Seed ScenarioInfoPair
  | QuitAction
  | Next ScenarioInfoPair

data Modal = Modal
  { Modal -> ModalType
_modalType :: ModalType
  , Modal -> Dialog ButtonAction Name
_modalDialog :: Dialog ButtonAction Name
  }

makeLenses ''Modal

data MainMenuEntry
  = NewGame
  | Tutorial
  | Achievements
  | Messages
  | About
  | Quit
  deriving (MainMenuEntry -> MainMenuEntry -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: MainMenuEntry -> MainMenuEntry -> Bool
$c/= :: MainMenuEntry -> MainMenuEntry -> Bool
== :: MainMenuEntry -> MainMenuEntry -> Bool
$c== :: MainMenuEntry -> MainMenuEntry -> Bool
Eq, Eq MainMenuEntry
MainMenuEntry -> MainMenuEntry -> Bool
MainMenuEntry -> MainMenuEntry -> Ordering
MainMenuEntry -> MainMenuEntry -> MainMenuEntry
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: MainMenuEntry -> MainMenuEntry -> MainMenuEntry
$cmin :: MainMenuEntry -> MainMenuEntry -> MainMenuEntry
max :: MainMenuEntry -> MainMenuEntry -> MainMenuEntry
$cmax :: MainMenuEntry -> MainMenuEntry -> MainMenuEntry
>= :: MainMenuEntry -> MainMenuEntry -> Bool
$c>= :: MainMenuEntry -> MainMenuEntry -> Bool
> :: MainMenuEntry -> MainMenuEntry -> Bool
$c> :: MainMenuEntry -> MainMenuEntry -> Bool
<= :: MainMenuEntry -> MainMenuEntry -> Bool
$c<= :: MainMenuEntry -> MainMenuEntry -> Bool
< :: MainMenuEntry -> MainMenuEntry -> Bool
$c< :: MainMenuEntry -> MainMenuEntry -> Bool
compare :: MainMenuEntry -> MainMenuEntry -> Ordering
$ccompare :: MainMenuEntry -> MainMenuEntry -> Ordering
Ord, Int -> MainMenuEntry -> ShowS
[MainMenuEntry] -> ShowS
MainMenuEntry -> FilePath
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
showList :: [MainMenuEntry] -> ShowS
$cshowList :: [MainMenuEntry] -> ShowS
show :: MainMenuEntry -> FilePath
$cshow :: MainMenuEntry -> FilePath
showsPrec :: Int -> MainMenuEntry -> ShowS
$cshowsPrec :: Int -> MainMenuEntry -> ShowS
Show, ReadPrec [MainMenuEntry]
ReadPrec MainMenuEntry
Int -> ReadS MainMenuEntry
ReadS [MainMenuEntry]
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [MainMenuEntry]
$creadListPrec :: ReadPrec [MainMenuEntry]
readPrec :: ReadPrec MainMenuEntry
$creadPrec :: ReadPrec MainMenuEntry
readList :: ReadS [MainMenuEntry]
$creadList :: ReadS [MainMenuEntry]
readsPrec :: Int -> ReadS MainMenuEntry
$creadsPrec :: Int -> ReadS MainMenuEntry
Read, MainMenuEntry
forall a. a -> a -> Bounded a
maxBound :: MainMenuEntry
$cmaxBound :: MainMenuEntry
minBound :: MainMenuEntry
$cminBound :: MainMenuEntry
Bounded, Int -> MainMenuEntry
MainMenuEntry -> Int
MainMenuEntry -> [MainMenuEntry]
MainMenuEntry -> MainMenuEntry
MainMenuEntry -> MainMenuEntry -> [MainMenuEntry]
MainMenuEntry -> MainMenuEntry -> MainMenuEntry -> [MainMenuEntry]
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
enumFromThenTo :: MainMenuEntry -> MainMenuEntry -> MainMenuEntry -> [MainMenuEntry]
$cenumFromThenTo :: MainMenuEntry -> MainMenuEntry -> MainMenuEntry -> [MainMenuEntry]
enumFromTo :: MainMenuEntry -> MainMenuEntry -> [MainMenuEntry]
$cenumFromTo :: MainMenuEntry -> MainMenuEntry -> [MainMenuEntry]
enumFromThen :: MainMenuEntry -> MainMenuEntry -> [MainMenuEntry]
$cenumFromThen :: MainMenuEntry -> MainMenuEntry -> [MainMenuEntry]
enumFrom :: MainMenuEntry -> [MainMenuEntry]
$cenumFrom :: MainMenuEntry -> [MainMenuEntry]
fromEnum :: MainMenuEntry -> Int
$cfromEnum :: MainMenuEntry -> Int
toEnum :: Int -> MainMenuEntry
$ctoEnum :: Int -> MainMenuEntry
pred :: MainMenuEntry -> MainMenuEntry
$cpred :: MainMenuEntry -> MainMenuEntry
succ :: MainMenuEntry -> MainMenuEntry
$csucc :: MainMenuEntry -> MainMenuEntry
Enum)

data Menu
  = -- | We started playing directly from command line, no menu to show
    NoMenu
  | MainMenu (BL.List Name MainMenuEntry)
  | -- | Stack of scenario item lists. INVARIANT: the currently selected
    -- menu item is ALWAYS the same as the scenario currently being played.
    -- See https://github.com/swarm-game/swarm/issues/1064 and
    -- https://github.com/swarm-game/swarm/pull/1065.
    NewGameMenu (NonEmpty (BL.List Name ScenarioItem))
  | AchievementsMenu (BL.List Name CategorizedAchievement)
  | MessagesMenu
  | AboutMenu

mainMenu :: MainMenuEntry -> BL.List Name MainMenuEntry
mainMenu :: MainMenuEntry -> List Name MainMenuEntry
mainMenu MainMenuEntry
e = forall (t :: * -> *) n e.
Foldable t =>
n -> t e -> Int -> GenericList n t e
BL.list Name
MenuList (forall a. [a] -> Vector a
V.fromList forall e. (Enum e, Bounded e) => [e]
listEnums) Int
1 forall a b. a -> (a -> b) -> b
& forall e (t :: * -> *) n.
(Eq e, Foldable t, Splittable t) =>
e -> GenericList n t e -> GenericList n t e
BL.listMoveToElement MainMenuEntry
e

makePrisms ''Menu

-- | Create a brick 'BL.List' of scenario items from a 'ScenarioCollection'.
mkScenarioList :: Bool -> ScenarioCollection -> BL.List Name ScenarioItem
mkScenarioList :: Bool -> ScenarioCollection -> List Name ScenarioItem
mkScenarioList Bool
cheat = forall a b c. (a -> b -> c) -> b -> a -> c
flip (forall (t :: * -> *) n e.
Foldable t =>
n -> t e -> Int -> GenericList n t e
BL.list Name
ScenarioList) Int
1 forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> Vector a
V.fromList forall b c a. (b -> c) -> (a -> b) -> a -> c
. [ScenarioItem] -> [ScenarioItem]
filterTest forall b c a. (b -> c) -> (a -> b) -> a -> c
. ScenarioCollection -> [ScenarioItem]
scenarioCollectionToList
 where
  filterTest :: [ScenarioItem] -> [ScenarioItem]
filterTest = if Bool
cheat then forall a. a -> a
id else forall a. (a -> Bool) -> [a] -> [a]
filter (\case SICollection Text
n ScenarioCollection
_ -> Text
n forall a. Eq a => a -> a -> Bool
/= Text
"Testing"; ScenarioItem
_ -> Bool
True)

-- | Given a 'ScenarioCollection' and a 'FilePath' which is the canonical
--   path to some folder or scenario, construct a 'NewGameMenu' stack
--   focused on the given item, if possible.
mkNewGameMenu :: Bool -> ScenarioCollection -> FilePath -> Maybe Menu
mkNewGameMenu :: Bool -> ScenarioCollection -> FilePath -> Maybe Menu
mkNewGameMenu Bool
cheat ScenarioCollection
sc FilePath
path = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap NonEmpty (List Name ScenarioItem) -> Menu
NewGameMenu forall a b. (a -> b) -> a -> b
$ forall a. [a] -> Maybe (NonEmpty a)
NE.nonEmpty forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Maybe ScenarioCollection
-> [FilePath]
-> [List Name ScenarioItem]
-> Maybe [List Name ScenarioItem]
go (forall a. a -> Maybe a
Just ScenarioCollection
sc) (FilePath -> [FilePath]
splitPath FilePath
path) []
 where
  go ::
    Maybe ScenarioCollection ->
    [FilePath] ->
    [BL.List Name ScenarioItem] ->
    Maybe [BL.List Name ScenarioItem]
  go :: Maybe ScenarioCollection
-> [FilePath]
-> [List Name ScenarioItem]
-> Maybe [List Name ScenarioItem]
go Maybe ScenarioCollection
_ [] [List Name ScenarioItem]
stk = forall a. a -> Maybe a
Just [List Name ScenarioItem]
stk
  go Maybe ScenarioCollection
Nothing [FilePath]
_ [List Name ScenarioItem]
_ = forall a. Maybe a
Nothing
  go (Just ScenarioCollection
curSC) (FilePath
thing : [FilePath]
rest) [List Name ScenarioItem]
stk = Maybe ScenarioCollection
-> [FilePath]
-> [List Name ScenarioItem]
-> Maybe [List Name ScenarioItem]
go Maybe ScenarioCollection
nextSC [FilePath]
rest (List Name ScenarioItem
lst forall a. a -> [a] -> [a]
: [List Name ScenarioItem]
stk)
   where
    hasName :: ScenarioItem -> Bool
    hasName :: ScenarioItem -> Bool
hasName (SISingle (Scenario
_, ScenarioInfo FilePath
pth ScenarioStatus
_)) = ShowS
takeFileName FilePath
pth forall a. Eq a => a -> a -> Bool
== FilePath
thing
    hasName (SICollection Text
nm ScenarioCollection
_) = Text
nm forall a. Eq a => a -> a -> Bool
== forall target source. From source target => source -> target
into @Text (ShowS
dropTrailingPathSeparator FilePath
thing)

    lst :: List Name ScenarioItem
lst = forall (t :: * -> *) e n.
(Foldable t, Splittable t) =>
(e -> Bool) -> GenericList n t e -> GenericList n t e
BL.listFindBy ScenarioItem -> Bool
hasName (Bool -> ScenarioCollection -> List Name ScenarioItem
mkScenarioList Bool
cheat ScenarioCollection
curSC)

    nextSC :: Maybe ScenarioCollection
nextSC = case forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup (ShowS
dropTrailingPathSeparator FilePath
thing) (ScenarioCollection -> Map FilePath ScenarioItem
scMap ScenarioCollection
curSC) of
      Just (SICollection Text
_ ScenarioCollection
c) -> forall a. a -> Maybe a
Just ScenarioCollection
c
      Maybe ScenarioItem
_ -> forall a. Maybe a
Nothing

------------------------------------------------------------
-- Inventory list entries
------------------------------------------------------------

-- | An entry in the inventory list displayed in the info panel.  We
--   can either have an entity with a count in the robot's inventory,
--   an entity equipped on the robot, or a labelled separator.  The
--   purpose of the separators is to show a clear distinction between
--   the robot's /inventory/ and its /equipped devices/.
data InventoryListEntry
  = Separator Text
  | InventoryEntry Count Entity
  | EquippedEntry Entity
  deriving (InventoryListEntry -> InventoryListEntry -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: InventoryListEntry -> InventoryListEntry -> Bool
$c/= :: InventoryListEntry -> InventoryListEntry -> Bool
== :: InventoryListEntry -> InventoryListEntry -> Bool
$c== :: InventoryListEntry -> InventoryListEntry -> Bool
Eq)

makePrisms ''InventoryListEntry