{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
-- https://downloads.haskell.org/ghc/latest/docs/users_guide/using-warnings.html#ghc-flag--Wredundant-constraints
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
-- https://downloads.haskell.org/ghc/latest/docs/users_guide/using-warnings.html#ghc-flag--Wunticked-promoted-constructors
{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}
-- https://downloads.haskell.org/ghc/latest/docs/users_guide/using-warnings.html#ghc-flag--Wunused-type-patterns
{-# OPTIONS_GHC -Wno-unused-type-patterns #-}

-- | A `Topology` is a list of allowed transition for a state machine.
-- We are using it to enforce that only allowed transitions could be performed.
module Crem.Topology
  ( Topology (..)
  , TopologySym0
  , STopology (..)
  , AllowTransition (..)
  , AllowedTransition (..)
  , trivialTopology
  , sTrivialTopology
  , TrivialTopology
  , allowAllTopology
  , sAllowAllTopology
  , AllowAllTopology
  )
where

import "singletons-base" Data.Singletons.Base.TH
import "singletons-base" Prelude.Singletons

-- * Topology

-- | A `Topology` is a description of the topology of a state machine
-- It contains the collection of allowed transitions.
-- Since we are using this information at the type level, and then we want to
-- bring it down to the value level, we wrap it in `singletons`
$( singletons
    [d|
      newtype Topology vertex = Topology
        {edges :: [(vertex, [vertex])]}
      |]
 )

-- ** AllowedTransition

-- | An value of type of @AllowedTransition topology initial final@ is a proof
-- that the @topology@ allows transitions from the @initial@ to the @final@
-- state
data AllowTransition (topology :: Topology vertex) (initial :: vertex) (final :: vertex) where
  -- | We always allow an edge from a vertex to itself
  AllowIdentityEdge :: AllowTransition topology a a
  -- | If @a@ is the start and @b@ is the end of the first edge,
  -- then @map@ contains an edge from @a@ to @b@
  AllowFirstEdge :: AllowTransition ('Topology ('(a, b ': l1) ': l2)) a b
  -- | If we know that we have an edge from @a@ to @b@ in a topology,
  -- then we also have an edge from @a@ to @b@ if we add another edge out of @a@
  AllowAddingEdge
    :: AllowTransition ('Topology ('(a, l1) ': l2)) a b
    -> AllowTransition ('Topology ('(a, x ': l1) ': l2)) a b
  -- | If we know that we have an edge from @a@ to @b@ in @map@,
  -- then we also have an edge from @a@ to @b@ if we add another vertex
  AllowAddingVertex
    :: AllowTransition ('Topology map) a b
    -> AllowTransition ('Topology (x ': map)) a b

-- | The `AllowedTransition` type class enables to automatically perform proof search
-- for a `AllowTransition` term.
-- It has an instance for every constructor of `AllowTransition`
class AllowedTransition (topology :: Topology vertex) (initial :: vertex) (final :: vertex) where
  allowsTransition :: AllowTransition topology initial final

instance {-# INCOHERENT #-} AllowedTransition ('Topology ('(a, b ': l1) ': l2)) a b where
  allowsTransition :: AllowTransition ('Topology ('(a, b : l1) : l2)) a b
allowsTransition = AllowTransition ('Topology ('(a, b : l1) : l2)) a b
forall vertex (a :: vertex) (b :: vertex) (l1 :: [vertex])
       (l2 :: [(vertex, [vertex])]).
AllowTransition ('Topology ('(a, b : l1) : l2)) a b
AllowFirstEdge

instance {-# INCOHERENT #-} AllowedTransition ('Topology ('(a, l1) ': l2)) a b => AllowedTransition ('Topology ('(a, x ': l1) ': l2)) a b where
  allowsTransition :: AllowTransition ('Topology ('(a, x : l1) : l2)) a b
allowsTransition =
    AllowTransition ('Topology ('(a, l1) : l2)) a b
-> AllowTransition ('Topology ('(a, x : l1) : l2)) a b
forall {vertex} (a :: vertex) (k1 :: [vertex])
       (m :: [(vertex, [vertex])]) (b :: vertex) (k1 :: vertex).
AllowTransition ('Topology ('(a, k1) : m)) a b
-> AllowTransition ('Topology ('(a, k1 : k1) : m)) a b
AllowAddingEdge (AllowTransition ('Topology ('(a, l1) : l2)) a b
forall vertex (topology :: Topology vertex) (initial :: vertex)
       (final :: vertex).
AllowedTransition topology initial final =>
AllowTransition topology initial final
allowsTransition :: AllowTransition ('Topology ('(a, l1) ': l2)) a b)

instance {-# INCOHERENT #-} AllowedTransition ('Topology map) a b => AllowedTransition ('Topology (x ': map)) a b where
  allowsTransition :: AllowTransition ('Topology (x : map)) a b
allowsTransition =
    AllowTransition ('Topology map) a b
-> AllowTransition ('Topology (x : map)) a b
forall {vertex} (k1 :: [(vertex, [vertex])]) (a :: vertex)
       (b :: vertex) (m :: (vertex, [vertex])).
AllowTransition ('Topology k1) a b
-> AllowTransition ('Topology (m : k1)) a b
AllowAddingVertex (AllowTransition ('Topology map) a b
forall vertex (topology :: Topology vertex) (initial :: vertex)
       (final :: vertex).
AllowedTransition topology initial final =>
AllowTransition topology initial final
allowsTransition :: AllowTransition ('Topology map) a b)

instance {-# INCOHERENT #-} AllowedTransition topology a a where
  allowsTransition :: AllowTransition topology a a
allowsTransition = AllowTransition topology a a
forall vertex (topology :: Topology vertex) (a :: vertex).
AllowTransition topology a a
AllowIdentityEdge

-- ** Trivial topology

-- | The trivial topology only allows identity transitions.
-- Given a type @a@ for vertices, only trivial transitions, i.e. staying
-- at the same vertex, are allowed
$( singletons
    [d|
      trivialTopology :: Topology a
      trivialTopology = Topology []
      |]
 )

-- ** Allow all topology

-- | Given a type @a@ for vertices, every transition from one vertex to
-- any other is allowed
$( singletons
    [d|
      allowAllTopology :: (Bounded a, Enum a) => Topology a
      allowAllTopology = Topology [(a, [minBound .. maxBound]) | a <- [minBound .. maxBound]]
      |]
 )