{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fconstraint-solver-iterations=7 #-}
module Language.Symantic.Typing.Constraint where

import Control.Applicative ((<|>))
import Data.Proxy

import Language.Symantic.Typing.Type

-- * Type 'TyCon'
-- | Captures the proof of a 'Constraint'
-- (and its dictionary for a type class):
-- pattern matching on the 'TyCon' constructor
-- brings the 'Constraint' into scope.
data TyCon c where
	TyCon :: c => TyCon c

-- * Type family 'TyConsts_imported_by'
-- | Return the /type constant/s that a given /type constant/
-- wants to be part of the final list of /type constants/.
type family TyConsts_imported_by (c::k) :: [*]
type instance TyConsts_imported_by Bounded = '[]
type instance TyConsts_imported_by Enum    = '[]
type instance TyConsts_imported_by Real    = '[]

-- * Type 'Proj_TyCon'
-- | Convenient type synonym wrapping 'Proj_TyConR'
-- to initiate its recursion.
type Proj_TyCon cs = Proj_TyConR cs cs

-- | Project the 'Constraint' indexed by the given 'Type'
-- onto its proof, captured by 'TyCon' when it holds.
proj_TyCon
 :: forall cs q. Proj_TyCon cs
 => Type cs q
 -> Maybe (TyCon q)
proj_TyCon = proj_TyConR (Proxy @cs)

-- ** Class 'Proj_TyConR'
-- | Intermediate type class to construct an instance of 'Proj_TyCon'
-- from many instances of 'Proj_TyConC', one for each item of @cs@.
--
-- * @cs@: starting  list of /type constants/.
-- * @rs@: remaining list of /type constants/.
class Proj_TyConR cs rs where
	proj_TyConR :: Proxy rs -> Type cs q -> Maybe (TyCon q)
	proj_TyConR _rs _q = Nothing

-- | Test whether @c@ handles the work of 'Proj_TyCon' or not,
-- or recurse on @rs@, preserving the starting list of /type constants/.
instance
 ( Proj_TyConC cs c
 , Proj_TyConR cs rs
 ) => Proj_TyConR cs (c ': rs) where
	proj_TyConR _rs q =
		proj_TyConC (Proxy @c)  q <|>
		proj_TyConR (Proxy @rs) q
-- | End the recursion.
instance Proj_TyConR cs '[]

-- ** Class 'Proj_TyConC'
-- | Handle the work of 'Proj_TyCon' for a given /type constant/ @c@,
-- that is: maybe it handles the given 'Constraint',
-- and if so, maybe the 'Constraint' holds.
class Proj_TyConC cs c where
	proj_TyConC :: Proxy c -> Type cs q -> Maybe (TyCon q)
	proj_TyConC _c _q = Nothing

instance Proj_TyConC cs (Proxy Bounded)
instance Proj_TyConC cs (Proxy Enum)
instance Proj_TyConC cs (Proxy Real)