{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeInType #-}
module Language.Symantic.Typing.Constant where

import qualified Data.Kind as Kind
import qualified Data.Map.Strict as Map
import qualified Data.MonoTraversable as MT
import qualified Data.Sequences as Seqs
import Data.NonNull (NonNull)
import Data.Proxy
import Data.Text (Text)
import Data.Type.Equality
import qualified System.IO as IO

import Language.Symantic.Helper.Data.Type.List
import Language.Symantic.Helper.Data.Type.Peano
import Language.Symantic.Typing.Kind

-- * Type 'TyConst'
-- | A /type constant/,
-- indexed at the Haskell type and term level amongst a list of them.
-- When used, @c@ is actually wrapped within a 'Proxy'
-- to be able to handle constants of different 'Kind's.
data TyConst (cs::[Kind.Type]) (c::k) where
	TyConstZ :: SKind k -> TyConst (Proxy c ': cs) (c::k)
	TyConstS :: TyConst cs c -> TyConst (not_c ': cs) c
infixr 5 `TyConstS`

eq_Kind_TyConst :: TyConst cs (x::kx) -> TyConst cs (y::ky) -> Maybe (kx:~:ky)
eq_Kind_TyConst x y = eq_skind (kind_of_TyConst x) (kind_of_TyConst y)

instance TestEquality (TyConst cs) where
	testEquality TyConstZ{} TyConstZ{}     = Just Refl
	testEquality (TyConstS x) (TyConstS y) = testEquality x y
	testEquality _ _                   = Nothing

kind_of_TyConst :: TyConst cs (h::k) -> SKind k
kind_of_TyConst (TyConstZ ki) = ki
kind_of_TyConst (TyConstS c)  = kind_of_TyConst c

-- * Type 'Inj_TyConst'
-- | Convenient type synonym wrapping 'Inj_TyConstP'
-- applied on the correct 'Index'.
type Inj_TyConst cs c = Inj_TyConstP (Index cs (Proxy c)) cs c

-- | Inject a given /type constant/ @c@ into a list of them,
-- by returning a proof that 'Proxy'@ c@ is in @cs@.
inj_TyConst :: forall cs c. Inj_TyConst cs c => TyConst cs c
inj_TyConst = inj_TyConstP (Proxy @(Index cs (Proxy c)))

-- ** Class 'Inj_TyConstP'
class Inj_TyConstP p cs c where
	inj_TyConstP :: Proxy p -> TyConst cs c
{- NOTE: using this commented instance fails very badly due to GHC-8.0.1's bug #12933
         which makes it fail to calculate the right kind,
         it seems to wrongly reuse the kind of the first use (sic)…
instance IKind k => Inj_TyConstP Zero (Proxy c ': cs) (c::k) where
	inj_TyConstP _ = TyConstZ kind
-}
instance
 ( IKindP (Ty_of_Type k)
 , Type_of_Ty (Ty_of_Type k) ~ k
 ) => Inj_TyConstP Zero (Proxy c ': cs) (c::k) where
	inj_TyConstP _ = TyConstZ (kindP (Proxy :: Proxy (Ty_of_Type k)))
instance Inj_TyConstP p cs c => Inj_TyConstP (Succ p) (not_c ': cs) c where
	inj_TyConstP _p = TyConstS (inj_TyConstP (Proxy @p))

-- * Class 'Proj_TyConst'
-- | Convenient type synonym wrapping 'Proj_TyConstP'
-- applied on the correct 'Index'.
type Proj_TyConst cs c = Proj_TyConstP (Index cs (Proxy c)) cs c

-- | Project a 'TyConst' onto a Haskell type level /type constant/ @c@,
-- returning a proof that the 'TyConst' indexes @c@ iif. it's the case.
proj_TyConst :: forall cs k (c::k) (u::k).
 Proj_TyConst cs c => TyConst cs u -> Proxy c -> Maybe (c :~: u)
proj_TyConst = proj_TyConstP (Proxy @(Index cs (Proxy c)))

(=?) :: forall cs c u. Proj_TyConst cs c => TyConst cs u -> Proxy c -> Maybe (c :~: u)
(=?) = proj_TyConstP (Proxy @(Index cs (Proxy c)))

-- ** Type 'Proj_TyConstP'
class Proj_TyConstP p cs c where
	proj_TyConstP :: Proxy p -> TyConst cs u -> Proxy c -> Maybe (c :~: u)
instance Proj_TyConstP Zero (Proxy c ': cs) c where
	proj_TyConstP _p TyConstZ{} _c = Just Refl
	proj_TyConstP _p TyConstS{} _c = Nothing
instance Proj_TyConstP p cs c => Proj_TyConstP (Succ p) (not_c ': cs) c where
	proj_TyConstP _p TyConstZ{} _c  = Nothing
	proj_TyConstP _p (TyConstS u) c = proj_TyConstP (Proxy @p) u c

-- ** Type 'Proj_TyConsts'
type Proj_TyConsts cs cs_to_proj
 = Concat_Constraints (Proj_TyConstsR cs cs_to_proj)

-- *** Type family 'Proj_TyConstsR'
type family Proj_TyConstsR cs cs_to_proj where
 Proj_TyConstsR cs '[] = '[]
 Proj_TyConstsR cs (Proxy x ': xs) = Proj_TyConst cs x ': Proj_TyConstsR cs xs

-- * Class 'Show_TyConst'
class Show_TyConst cs where
	show_TyConst :: TyConst cs c -> String

-- deriving instance Show (TyConst cs c)
instance Show_TyConst cs => Show (TyConst cs c) where
	show = show_TyConst
instance Show_TyConst '[] where
	show_TyConst = error "Show_TyConst unreachable pattern"

-- TODO: move each of these to a dedicated module.
instance Show_TyConst cs => Show_TyConst (Proxy Bounded ': cs) where
	show_TyConst TyConstZ{} = "Bounded"
	show_TyConst (TyConstS c) = show_TyConst c
instance Show_TyConst cs => Show_TyConst (Proxy Enum ': cs) where
	show_TyConst TyConstZ{} = "Enum"
	show_TyConst (TyConstS c) = show_TyConst c
instance Show_TyConst cs => Show_TyConst (Proxy Fractional ': cs) where
	show_TyConst TyConstZ{} = "Fractional"
	show_TyConst (TyConstS c) = show_TyConst c
instance Show_TyConst cs => Show_TyConst (Proxy Real ': cs) where
	show_TyConst TyConstZ{} = "Real"
	show_TyConst (TyConstS c) = show_TyConst c

-- * Type 'Constants'
-- | Usual 'TyConst's.
type Constants = Terms ++ Constraints

-- ** Type 'Terms'
-- | Usual 'TyConst's of /terms constructors/.
type Terms =
 [ Proxy ()
 , Proxy (,)
 , Proxy (->)
 , Proxy []
 , Proxy Bool
 , Proxy Char
 , Proxy Either
 , Proxy Int
 , Proxy Integer
 , Proxy Integral
 , Proxy IO
 , Proxy IO.Handle
 , Proxy IO.IOMode
 , Proxy Map.Map
 , Proxy Maybe
 , Proxy NonNull
 , Proxy Text
 ]

-- ** Type 'Constraints'
-- | Usual 'TyConst's of /type constraint constructors/.
type Constraints =
 [ Proxy Applicative
 , Proxy Bounded
 , Proxy Enum
 , Proxy Eq
 , Proxy Foldable
 , Proxy Functor
 , Proxy Monad
 , Proxy Monoid
 , Proxy MT.MonoFoldable
 , Proxy MT.MonoFunctor
 , Proxy Num
 , Proxy Ord
 , Proxy Real
 , Proxy Seqs.IsSequence
 , Proxy Seqs.SemiSequence
 , Proxy Show
 , Proxy Traversable
 ]