{-# LANGUAGE ConstraintKinds       #-}
{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE ExplicitNamespaces    #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE KindSignatures        #-}
{-# LANGUAGE LiberalTypeSynonyms   #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds             #-}
{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TypeOperators         #-}
{-# LANGUAGE UndecidableInstances  #-}
-- | Syntactic sugar for 'MonadFSM' types, adding appropriate row
-- constraints and hiding complexity of the internal implementation.
module Motor.FSM.Sugar
  ( Action(..)
  , ActionMapping(..)
  , FromActions
  , NoActions
  , Actions
  , OnlyActions
  , Get
  , type (!-->)
  , type (!+)
  , type (!-)
  ) where

import           Data.Kind
import           Data.Row.Records
import           GHC.TypeLits     (Symbol)

-- | An 'Action' describes a resource action.
data Action
  = Add Type
  -- ^ Adds a new resource of the given 'Type'.
  | Remain Type
  -- ^ The existing resource of the given 'Type' remains the same.
  | To Type
       Type
  -- ^ Transitions an existing resource from the first 'Type' to a
  -- resource of the second 'Type'.
  | Delete Type
  -- ^ Deletes an existing resource of the given 'Type'.

-- | Mapping from 'Symbol' to some action 'a'.
data ActionMapping = (:=) Symbol Action

infixr 5 :=

-- | Translates a list of 'Action's to a 'Row'.
type family FromActions (as :: [ActionMapping]) (rs :: Row *) (c :: Constraint) :: (Row *, Constraint) where
  FromActions '[] rs c = '( rs, c)
  FromActions ((n ':= 'Add a) ': ts) r c =
    FromActions ts (Extend n a r) ( c
                                  , (Extend n a r .! n) ~ a
                                  )
  FromActions ((n ':= 'Delete a) ': ts) r c =
    FromActions ts (r .- n) ( c
                            , (r .! n) ~ a
                            )
  FromActions ((n ':= 'To a b) ': ts) r c =
    FromActions ts ((n .== b) .// r) ( c
                                     , (r .! n) ~ a
                                     , (((n .== b) .// r) .! n) ~ b
                                     )
  FromActions ((n ':= 'Remain a) ': ts) r c =
    FromActions ts r (c, (r .! n) ~ a)

type NoConstraint = (() :: Constraint)

-- | Alias for 'MonadFSM' that includes no actions.
type NoActions m (r :: Row *) a = m r r a

-- | Alias for 'MonadFSM' that uses 'FromActions' to construct rows.
type Actions m as (i :: Row *) a
   = forall o c. (FromActions as i NoConstraint ~ '( o, c), c) =>
                   m i o a

-- | Alias for 'MonadFSM' that uses 'FromActions' to construct rows,
-- starting from an 'Empty' row, i.e. allowing no /other/ resources.
type OnlyActions m as a = Actions m as Empty a

-- | Gets an existing resource in state 's'.
type Get m (r :: Row *) n = m r r (r .! n)

-- | Infix version of 'To'.
type (!-->) i o = 'To i o

infixl 6 !-->

-- | Add a named resource. Alias of 'Add'.
type (!+) (n :: Symbol) s = n ':= 'Add s

infix 6 !+

-- | Delete a named resource. Alias of 'Delete'.
type (!-) (n :: Symbol) s = n ':= 'Delete s

infix 6 !-