--------------------------------------------------------------------------------
-- |
--
-- Module      :  Data.Memoizer.Session
-- Description :  Sessions for command memoizers
-- Copyright   :  (c) Alice Rixte 2024
-- License     :  BSD 3
-- Maintainer  :  alice.rixte@u-bordeaux.fr
-- Stability   :  unstable
-- Portability :  portable
--
-- Memoize several sessions and switch between them.
--
-- This enables the use of the @\\ghcisection{My Session}@ and
-- @\\ghcicontinue{My Session}@ command in the @ghci@ LuaTex package.
--
-- = Usage
--
--
-- Let us store the result of some commands (we alternate between @memo@ and
-- @memo'@ to avoid recursive definitions)
--
-- >>> import Prelude hiding (lookup)
--
-- >>> memo = initSession "main" :: SessionMemoizer String String String
-- >>> memo' = storeResult "x=1" "" memo
-- >>> memo = storeResult "y=2" "" memo'
-- >>> memo' = storeResult "x+y" "3" memo
--
-- Let use create a new session:
--
-- >>> memo = newSession "My Session" memo'
-- >>> memo' = storeResult "a=1" "" memo
--
-- Now if we create a new session called "main" again, we can use the memoized
-- values:
--
-- >>> memo = newSession "main" memo'
-- >>> lookup "x=1" memo
-- Just ""
--
--
-- But we still have some commands to add to "My Session":
--
-- >>> memo' = continueSession "My Session" memo
-- >>> memo = storeResult "a" "1" memo'
--
-- Now let's restart "My Session":
--
-- >>> memo' = newSession "My Session" memo
-- >>> lookup "a=1" memo
-- Just ""
-- >>> memo = nextCmd memo'
-- >>> lookup "a" memo
-- Just "1"
--
--------------------------------------------------------------------------------

module Data.Memoizer.Sessions
  ( SessionMemoizer (..)
  , initSession
  , newSession
  , continueSession
  , storeResult
  , deleteResult
  , lookup
  , nextCmd
  ) where

import Prelude hiding (lookup)
import qualified Data.Memoizer.Commands as Cmd
import qualified Data.Map as Map

-- | A container of  memoizers for sequences of commands.
--
-- * @k@ is the key representing the name of a session
-- * @a@ is the type of commands
-- * @b@ is the result of a command
--
data SessionMemoizer k a b = SessionMemoizer
  { forall k a b. SessionMemoizer k a b -> Map k (CmdMemoizer a b)
sessionMap :: Map.Map k (Cmd.CmdMemoizer a b)
  , forall k a b. SessionMemoizer k a b -> k
currentSession :: k
  }
  deriving (Int -> SessionMemoizer k a b -> ShowS
[SessionMemoizer k a b] -> ShowS
SessionMemoizer k a b -> String
(Int -> SessionMemoizer k a b -> ShowS)
-> (SessionMemoizer k a b -> String)
-> ([SessionMemoizer k a b] -> ShowS)
-> Show (SessionMemoizer k a b)
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
forall k a b.
(Show a, Show b, Show k) =>
Int -> SessionMemoizer k a b -> ShowS
forall k a b.
(Show a, Show b, Show k) =>
[SessionMemoizer k a b] -> ShowS
forall k a b.
(Show a, Show b, Show k) =>
SessionMemoizer k a b -> String
$cshowsPrec :: forall k a b.
(Show a, Show b, Show k) =>
Int -> SessionMemoizer k a b -> ShowS
showsPrec :: Int -> SessionMemoizer k a b -> ShowS
$cshow :: forall k a b.
(Show a, Show b, Show k) =>
SessionMemoizer k a b -> String
show :: SessionMemoizer k a b -> String
$cshowList :: forall k a b.
(Show a, Show b, Show k) =>
[SessionMemoizer k a b] -> ShowS
showList :: [SessionMemoizer k a b] -> ShowS
Show,SessionMemoizer k a b -> SessionMemoizer k a b -> Bool
(SessionMemoizer k a b -> SessionMemoizer k a b -> Bool)
-> (SessionMemoizer k a b -> SessionMemoizer k a b -> Bool)
-> Eq (SessionMemoizer k a b)
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall k a b.
(Eq a, Eq b, Eq k) =>
SessionMemoizer k a b -> SessionMemoizer k a b -> Bool
$c== :: forall k a b.
(Eq a, Eq b, Eq k) =>
SessionMemoizer k a b -> SessionMemoizer k a b -> Bool
== :: SessionMemoizer k a b -> SessionMemoizer k a b -> Bool
$c/= :: forall k a b.
(Eq a, Eq b, Eq k) =>
SessionMemoizer k a b -> SessionMemoizer k a b -> Bool
/= :: SessionMemoizer k a b -> SessionMemoizer k a b -> Bool
Eq)

undefinedSession :: String
undefinedSession :: String
undefinedSession = String
"UndefinedSession : The current Session does not exist.\
  \ This should never happen. Please report this as a bug."

lookupCmd :: Ord k => SessionMemoizer k a b -> Cmd.CmdMemoizer a b
lookupCmd :: forall k a b. Ord k => SessionMemoizer k a b -> CmdMemoizer a b
lookupCmd (SessionMemoizer Map k (CmdMemoizer a b)
ms k
k) = case k -> Map k (CmdMemoizer a b) -> Maybe (CmdMemoizer a b)
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup k
k Map k (CmdMemoizer a b)
ms of
  Maybe (CmdMemoizer a b)
Nothing -> String -> CmdMemoizer a b
forall a. HasCallStack => String -> a
error (String -> CmdMemoizer a b) -> String -> CmdMemoizer a b
forall a b. (a -> b) -> a -> b
$ String
"lookup : " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
undefinedSession
  Just CmdMemoizer a b
m -> CmdMemoizer a b
m

mapCmd :: Ord k =>
  (Cmd.CmdMemoizer a b -> Cmd.CmdMemoizer a b)
  -> SessionMemoizer k a b -> SessionMemoizer k a b
mapCmd :: forall k a b.
Ord k =>
(CmdMemoizer a b -> CmdMemoizer a b)
-> SessionMemoizer k a b -> SessionMemoizer k a b
mapCmd CmdMemoizer a b -> CmdMemoizer a b
f sm :: SessionMemoizer k a b
sm@(SessionMemoizer Map k (CmdMemoizer a b)
ms k
k) =
  SessionMemoizer k a b
sm {sessionMap = Map.insert k (f (lookupCmd sm)) ms }

-------------------------------------------------------------------------------

-- | Create a new session memoizer using a default session.
--
initSession :: Ord k => k -> SessionMemoizer k a b
initSession :: forall k a b. Ord k => k -> SessionMemoizer k a b
initSession k
k = Map k (CmdMemoizer a b) -> k -> SessionMemoizer k a b
forall k a b. Map k (CmdMemoizer a b) -> k -> SessionMemoizer k a b
SessionMemoizer (k
-> CmdMemoizer a b
-> Map k (CmdMemoizer a b)
-> Map k (CmdMemoizer a b)
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert k
k CmdMemoizer a b
forall a b. CmdMemoizer a b
Cmd.empty Map k (CmdMemoizer a b)
forall k a. Map k a
Map.empty) k
k

-- | Add a new session to memoize. If that session already existes, it is
-- @'Data.Memoizer.Commands.restart'@ed.
--
newSession :: Ord k => k -> SessionMemoizer k a b -> SessionMemoizer k a b
newSession :: forall k a b.
Ord k =>
k -> SessionMemoizer k a b -> SessionMemoizer k a b
newSession k
k (SessionMemoizer Map k (CmdMemoizer a b)
ms k
_) =
  case k -> Map k (CmdMemoizer a b) -> Maybe (CmdMemoizer a b)
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup k
k Map k (CmdMemoizer a b)
ms of
    Maybe (CmdMemoizer a b)
Nothing -> Map k (CmdMemoizer a b) -> k -> SessionMemoizer k a b
forall k a b. Map k (CmdMemoizer a b) -> k -> SessionMemoizer k a b
SessionMemoizer (k
-> CmdMemoizer a b
-> Map k (CmdMemoizer a b)
-> Map k (CmdMemoizer a b)
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert k
k CmdMemoizer a b
forall a b. CmdMemoizer a b
Cmd.empty Map k (CmdMemoizer a b)
ms) k
k
    Just CmdMemoizer a b
m -> Map k (CmdMemoizer a b) -> k -> SessionMemoizer k a b
forall k a b. Map k (CmdMemoizer a b) -> k -> SessionMemoizer k a b
SessionMemoizer (k
-> CmdMemoizer a b
-> Map k (CmdMemoizer a b)
-> Map k (CmdMemoizer a b)
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert k
k (CmdMemoizer a b -> CmdMemoizer a b
forall a b. CmdMemoizer a b -> CmdMemoizer a b
Cmd.restart CmdMemoizer a b
m) Map k (CmdMemoizer a b)
ms) k
k

-- | Continue an existing session.
--
continueSession :: Ord k => k -> SessionMemoizer k a b -> SessionMemoizer k a b
continueSession :: forall k a b.
Ord k =>
k -> SessionMemoizer k a b -> SessionMemoizer k a b
continueSession k
k SessionMemoizer k a b
m = SessionMemoizer k a b
m {currentSession = k}

-- | Lookup the memoized result of the current session.
--
lookup :: (Eq a, Ord k) => a -> SessionMemoizer k a b -> Maybe b
lookup :: forall a k b.
(Eq a, Ord k) =>
a -> SessionMemoizer k a b -> Maybe b
lookup a
a  = a -> CmdMemoizer a b -> Maybe b
forall a b. Eq a => a -> CmdMemoizer a b -> Maybe b
Cmd.lookup a
a (CmdMemoizer a b -> Maybe b)
-> (SessionMemoizer k a b -> CmdMemoizer a b)
-> SessionMemoizer k a b
-> Maybe b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SessionMemoizer k a b -> CmdMemoizer a b
forall k a b. Ord k => SessionMemoizer k a b -> CmdMemoizer a b
lookupCmd

-- | Move the current session to the next command.
--
nextCmd :: Ord k => SessionMemoizer k a b -> SessionMemoizer k a b
nextCmd :: forall k a b.
Ord k =>
SessionMemoizer k a b -> SessionMemoizer k a b
nextCmd = (CmdMemoizer a b -> CmdMemoizer a b)
-> SessionMemoizer k a b -> SessionMemoizer k a b
forall k a b.
Ord k =>
(CmdMemoizer a b -> CmdMemoizer a b)
-> SessionMemoizer k a b -> SessionMemoizer k a b
mapCmd CmdMemoizer a b -> CmdMemoizer a b
forall a b. CmdMemoizer a b -> CmdMemoizer a b
Cmd.nextCmd

-- | Store a result in the current session.
--
-- This will prevent access to any memoized result of the current session until
-- @'newSession'@ is used.
--
storeResult :: Ord k => a -> b -> SessionMemoizer k a b -> SessionMemoizer k a b
storeResult :: forall k a b.
Ord k =>
a -> b -> SessionMemoizer k a b -> SessionMemoizer k a b
storeResult a
a b
b = (CmdMemoizer a b -> CmdMemoizer a b)
-> SessionMemoizer k a b -> SessionMemoizer k a b
forall k a b.
Ord k =>
(CmdMemoizer a b -> CmdMemoizer a b)
-> SessionMemoizer k a b -> SessionMemoizer k a b
mapCmd (a -> b -> CmdMemoizer a b -> CmdMemoizer a b
forall a b. a -> b -> CmdMemoizer a b -> CmdMemoizer a b
Cmd.storeResult a
a b
b)

-- | Delete the current result in the current session.
--
-- This will prevent access to any memoized result  of the current session until
-- @'newSession'@ is used.
--
deleteResult :: Ord k
  =>  SessionMemoizer k a b -> SessionMemoizer k a b
deleteResult :: forall k a b.
Ord k =>
SessionMemoizer k a b -> SessionMemoizer k a b
deleteResult = (CmdMemoizer a b -> CmdMemoizer a b)
-> SessionMemoizer k a b -> SessionMemoizer k a b
forall k a b.
Ord k =>
(CmdMemoizer a b -> CmdMemoizer a b)
-> SessionMemoizer k a b -> SessionMemoizer k a b
mapCmd CmdMemoizer a b -> CmdMemoizer a b
forall a b. CmdMemoizer a b -> CmdMemoizer a b
Cmd.deleteResult