module TurnLoop.Types
  ( Many
  , Step(..)
  , Result(..)
  , Starter(..)
  , Registry(..)
  , Lobby(..)
  , SessionEntry(..)
  , Session(..)
  , SessionRecord(..)
  , LabeledSession(..)
  , Sessions(..)
  , Results(..)
  , Thread(..)
  , Components(..)
  ) where

import Control.Concurrent

type Many rep a = (rep -> a)

data Step state terminal = Step
  { sState :: state
  , sTerminal  :: Maybe terminal
  } deriving (Show, Eq)

data Result sessionId rep userId state extra = Result
  { rStarter :: Starter sessionId rep userId
  , rState :: state
  , rExtra :: extra
  }

data Starter sessionId rep userId = Starter
  { sSessionId :: sessionId
  , sUserIds :: Many rep userId
  }

data Registry userId user m = Registry
  { rInsertUser :: user -> m userId
  , rGetUserById :: userId -> m (Maybe user)
  }

data Lobby sessionId rep userId m = Lobby
  { lTransferUser :: userId -> m (Maybe (Starter sessionId rep userId))
  , lDequeueUser :: sessionId -> m (Maybe userId)
  , lAnnounceSession :: Starter sessionId rep userId -> m ()
  }

data SessionEntry rep input state terminal m = SessionEntry
  { seThread :: Thread m
  , seGames :: Many rep (Session input state terminal)
  }

data Session input state terminal = Session
  { sInput :: Chan input
  , sStep :: Chan (Step state terminal)
  }

data SessionRecord userId rep input state terminal m = SessionRecord
  { srThread :: Thread m
  , srLabeled :: Many rep (LabeledSession userId input state terminal)
  }

data LabeledSession userId input state terminal = LabeledSession
  { lsUserId :: userId
  , lsSession :: Session input state terminal
  }

data Sessions sessionId userId rep input state terminal m = Sessions
  { sInsertSession :: (sessionId, SessionRecord userId rep input state terminal m) -> m ()
  , sFindSession :: sessionId -> m (Maybe (SessionRecord userId rep input state terminal m))
  , sRemoveSession :: sessionId -> m ()
  }

data Results sessionId rep userId state extra m = Results
  { rSaveResult :: Result sessionId rep userId state extra -> m ()
  , rFindResult :: sessionId -> m (Maybe (Result sessionId rep userId state extra))
  }

data Thread m = Thread
  { tThreadId :: ThreadId
  , tKill :: m ()
  }

data Components sessionId rep userId user input state extra terminal m = Components
  { cLobby :: Lobby sessionId rep userId m
  , cSessions :: Sessions sessionId userId rep input state terminal m
  , cRegistry :: Registry userId user m
  , cResults :: Results sessionId rep userId state extra m
  }