-- | Types for building (co-)recursive system programs.
--
-- The tools in this module allow to define the call-site and the main entry
-- point of a (co-)recursive program.
--
-- However, this module does not define 'callback' usages so that implementers
-- are free to chose the mechanism they want to:
-- - start the recursive program
-- - retrieve a return-value (if the recursive program ever returns)
module System.Process.Corecursive.Base
    ( App (..)
    , app
    , runApp
    , Self (..)
    ) where

-- | A datatype representing an instance of the (co-)recursive program.
--
-- A reason why the records in this type are separate from 'App' is that a
-- calling program may need to execute a fair amount of progress
-- before knowing the right 'executable' (e.g., in the case of a remote
-- invocation). Also, sometimes one may want to adapt 'Self'.
data Self t msg arg inst = Self {
    executable :: t inst
  -- ^ An  action to retrieve an instance of the (co-)recursive program
  -- the 'inst' type is leaved as an argument to allow representing cases where
  -- the execution is actually remote.
  , unparse    :: arg -> t msg
  -- ^ An action to unparse arguments. Typically from 'unparseArgs'.
  }
instance Functor t => Functor (Self t msg arg) where
    fmap f (Self e u) = Self (fmap f e) u

-- | A datatype wrapping everything needed to make a (co-)recursive main
-- function.
--
-- Application authors may find this type useful because it has a Functor
-- instance, allowing to adapt the result of a computation.
--
-- See 'app'.
data App t msg arg inst ret = App {
    parseArgs          :: msg -> t arg
  -- ^ Function parsing argument from a serialized message.
  , unparseArgs        :: arg -> t msg
  -- ^ Function un-parsing an argument into a serialized message.
  , corecursiveProgram :: Self t msg arg inst -> arg -> t ret
  -- ^ Actual program to run from a specification.
  }

instance Functor t => Functor (App t msg arg inst) where
    fmap f (App p u cp) =
        App p u $ \self arg -> fmap f (cp self arg)

-- | Constructor for an App.
app
  :: (msg -> m arg)
  -- ^ Function parsing argument from a serialized message.
  -> (arg -> m msg)
  -- ^ Function un-parsing an argument into a serialized message.
  -> (Self m msg arg inst -> arg -> m ret)
  -- ^ Actual program to run from a specification.
  -> App m msg arg inst ret
app = App

-- | Typical function to run an 'App' with simplifying assumptions:
--
-- - locate the Self instance once for the rest of the program
-- - reads the arguments once
-- - starts the actual program
runApp
  :: Monad m
  => m inst
  -- ^ An action to locate the self instance of an applictaion.
  -- This action is run exactly once and the result is stored in 'Self'.
  -> m msg
  -- ^ An action to retrieve the message to start the application with.
  -- This action is run exactly once, the result is then parsed and discarded.
  -> App m msg arg inst ret
  -- ^ The 'App' to run
  -> m ret
runApp getInst getMsg (App parse unparse go) = do
    inst <- getInst
    let self = Self (pure inst) unparse
    arg <- parse =<< getMsg
    go self arg