{-# language ConstraintKinds #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE LiberalTypeSynonyms #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Language.Symantic.Typing.Type
 ( module Language.Symantic.Typing.Type
 , Proxy(..)
 , (:~:)(..)
 , (:~~:)(..)
 , K.Constraint
 ) where

import Control.Applicative (Alternative(..))
import Data.Maybe (isJust)
import Data.Proxy
import Data.Semigroup ((<>))
import Data.Set (Set)
import Data.Type.Equality
import Data.Typeable
import qualified Data.Kind as K
import qualified Data.Set as Set

import Language.Symantic.Grammar
import Language.Symantic.Typing.List
import Language.Symantic.Typing.Kind
import Language.Symantic.Typing.Variable

-- * Type 'Type'
data Type (src::K.Type) (vs::[K.Type]) (t::kt) where
	TyConst :: src -> Len vs
	               -> Const src c
	               -> Type src vs c
	TyApp   :: src -> Type src vs f
	               -> Type src vs a
	               -> Type src vs (f a)
	               -- NOTE: @f a@ decomposition made possible
	               -- at the cost of requiring @TypeInType@.
	TyVar   :: src -> NameVar
	               -> Var src vs v
	               -> Type src vs v
	TyFam   :: src -> Len vs
	               -> Const src fam
	               -> Types src vs hs
	               -> Type src vs (Fam fam hs)
	{-
	TyPi    :: src -> Type src (arg ': vs) res
	               -> Type (Pi arg res) src vs
	TyQuant :: src -> NameHint
	               -> Kind src kv
	               -> (forall (v::kv). Type src vs v -> TypeK src vs k)
	               -> Type src vs (h::k)
	TyEq :: src -> Type x src vs
	            -> Type y src vs
	            -> Type (x #~ y) src vs
	-}

instance Source src => Eq (Type src vs t) where
	x == y = isJust $ eqType x y
instance Inj_Source (TypeVT src) src => TestEquality (Type src vs) where
	testEquality = eqType

type instance SourceOf (Type src vs t) = src
instance Source src => Sourced (Type src vs t) where
	sourceOf (TyConst src _l _c)     = src
	sourceOf (TyApp   src _f _a)     = src
	sourceOf (TyVar   src _n _v)     = src
	sourceOf (TyFam   src _l _f _as) = src
	
	setSource (TyConst _src l c)    src = TyConst src l c
	setSource (TyApp   _src f a)    src = TyApp   src f a
	setSource (TyVar   _src n v)    src = TyVar   src n v
	setSource (TyFam   _src l f as) src = TyFam   src l f as

instance Inj_Source (TypeVT src) src => KindOf (Type src vs) where
	kindOf t = kindOfType t `source` TypeVT t

instance ConstsOf (Type src vs t) where
	constsOf (TyConst _src _len c)    = Set.singleton $ ConstC c
	constsOf TyVar{}                  = Set.empty
	constsOf (TyApp _src f a)         = constsOf f `Set.union` constsOf a
	constsOf (TyFam _src _len fam as) = constsOf fam `Set.union` constsOf as

type instance VarsOf (Type src vs t) = vs
instance LenVars (Type src vs t) where
	lenVars (TyConst _src len _c) = len
	lenVars (TyApp _src f _a)     = lenVars f
	lenVars (TyVar   _src _n v)   = lenVars v
	lenVars (TyFam _src l _f _as) = l
instance UsedVarsOf (Type src vs t) where
	usedVarsOf vs TyConst{} k = k vs
	usedVarsOf vs (TyFam _src _len _fam as) k = usedVarsOf vs as k
	usedVarsOf vs (TyApp _src f a) k =
		usedVarsOf vs f $ \vs' ->
		usedVarsOf vs' a k
	usedVarsOf vs (TyVar _src _n v) k =
		case insertUsedVars v vs of
		 Nothing  -> k vs
		 Just vs' -> k vs'
instance VarOccursIn (Type src vs t) where
	varOccursIn v (TyVar _src _n v') | Just{} <- v `eqVarKi` v' = False
	varOccursIn _v TyVar{}                  = False
	varOccursIn _v TyConst{}                = False
	varOccursIn v (TyApp _src f a)          = varOccursIn v f || varOccursIn v a
	varOccursIn v (TyFam _src _len _fam as) = varOccursIn v as
instance AllocVars (Type src) where
	allocVarsL len (TyConst src l c)  = TyConst src (addLen len l) c
	allocVarsL len (TyApp src f a)    = TyApp src (allocVarsL len f) (allocVarsL len a)
	allocVarsL len (TyVar src n v)    = TyVar src n $ allocVarsL len v
	allocVarsL len (TyFam src l f as) = TyFam src (addLen len l) f $ allocVarsL len `mapTys` as
	
	allocVarsR len (TyConst src l c)  = TyConst src (addLen l len) c
	allocVarsR len (TyApp src f a)    = TyApp src (allocVarsR len f) (allocVarsR len a)
	allocVarsR len (TyVar src n v)    = TyVar src n $ allocVarsR len v
	allocVarsR len (TyFam src l f as) = TyFam src (addLen l len) f $ allocVarsR len `mapTys` as

-- | Like 'TyConst', but using 'noSource' and 'inj_Const'.
-- 
-- FIXME: remove @kc@ when GHC's #12933 is fixed.
tyConst ::
 forall kc (c::kc) src vs.
 Source src =>
 Inj_Len vs =>
 Constable c =>
 Inj_KindP (Ty_of_Type kc) =>
 Type_of_Ty (Ty_of_Type kc) ~ kc =>
 Type src vs c
tyConst = TyConst noSource inj_Len inj_Const
 -- NOTE: The forall brings @c@ first in the type variables,
 -- which is convenient to use @TypeApplications@.

-- | Like 'TyConst', but using 'noSource' and 'inj_Const'.
-- 
-- FIXME: remove @kc@ when GHC's #12933 is fixed.
tyConstLen ::
 forall kc (c::kc) src vs.
 Source src =>
 Constable c =>
 Inj_KindP (Ty_of_Type kc) =>
 Type_of_Ty (Ty_of_Type kc) ~ kc =>
 Len vs ->
 Type src vs c
tyConstLen len = TyConst noSource len inj_Const

-- | Like 'TyApp', but using 'noSource'.
tyApp ::
 Source src =>
 Type src vs f ->
 Type src vs a ->
 Type src vs (f a)
tyApp = TyApp noSource
infixl 9 `tyApp`

-- | Like 'TyVar', but using 'noSource'.
tyVar ::
 Source src =>
 NameVar ->
 Var src vs v ->
 Type src vs v
tyVar = TyVar noSource

-- | Like 'TyFam', but using 'noSource'.
-- 
-- FIXME: remove @kc@ when GHC's #12933 is fixed.
tyFam ::
 forall kc (c::kc) as vs src.
 Source src =>
 Inj_Len vs =>
 Constable c =>
 Inj_KindP (Ty_of_Type kc) =>
 Type_of_Ty (Ty_of_Type kc) ~ kc =>
 Types src vs as ->
 Type src vs (Fam c as)
tyFam = TyFam noSource inj_Len (inj_Const @c)

-- | Qualify a 'Type'.
tyQual ::
 Source src =>
 Type src vs q ->
 Type src vs t ->
 (Qual q ->
  Type src vs t ->
  Type src vs (q #> t)) ->
 Type src vs (q #> t)
tyQual q t f =
	case proveConstraint q of
	 Just Dict -> f Dict t
	 Nothing -> q #> t
infix 1 `tyQual`

-- ** Type 'TypeK'
-- | Existential 'Type' of a known 'Kind'.
data TypeK src vs kt = forall (t::kt). TypeK (Type src vs t)
type instance SourceOf (TypeK src vs ki) = src
type instance VarsOf (TypeK src vs ki) = vs
instance Source src => Eq (TypeK src vs ki) where
	TypeK x == TypeK y = isJust $ eqType x y

-- ** Type 'TypeT'
-- | Existential for 'Type'.
data TypeT src vs = forall t. TypeT (Type src vs t)
type instance SourceOf (TypeT src vs) = src
type instance VarsOf (TypeT src vs) = vs
instance Source src => Eq (TypeT src vs) where
	TypeT x == TypeT y = isJust $ eqTypeKi x y

-- ** Type 'TypeVT'
-- | Existential polymorphic 'Type'.
data TypeVT src = forall vs t. TypeVT (Type src vs t)
type instance SourceOf (TypeVT src) = src
instance Source src => Eq (TypeVT src) where
	TypeVT x == TypeVT y =
		let (x', y') = appendVars x y in
		isJust $ eqTypeKi x' y'

-- * Type 'Const'
-- | /Type constant/.
data Const src (c::kc) where
	Const :: Constable c
	      => Kind src kc
	      -> Const src (c::kc)

type instance SourceOf (Const src c) = src
instance ConstsOf (Const src c) where
	constsOf = Set.singleton . ConstC
instance Inj_Source (ConstC src) src => KindOf (Const src) where
	kindOf c@(Const kc) = kc `source` ConstC c

kindOfConst :: Const src (t::kt) -> Kind src kt
kindOfConst (Const kc) = kc

-- ** Class 'Constable'
class
 ( Typeable c
 , FixityOf c
 , ClassInstancesFor c
 , TypeInstancesFor c
 ) => Constable c
instance
 ( Typeable c
 , FixityOf c
 , ClassInstancesFor c
 , TypeInstancesFor c
 ) => Constable c

-- ** Class 'ConstsOf'
-- | Return the 'Const's used by something.
class ConstsOf a where
	constsOf :: a -> Set (ConstC (SourceOf a))

-- ** Class 'FixityOf'
-- | Return the 'Fixity' of something.
class FixityOf (c::kc) where
	fixityOf :: proxy c -> Maybe Fixity
	fixityOf _c = Nothing
instance FixityOf (c::K.Type)
instance FixityOf (c::K.Constraint)

-- Show
instance Show (Const src c) where
	showsPrec p c@Const{} = showsPrec p $ typeRep c

-- ** Type 'ConstC'
data ConstC src
 = forall c. ConstC (Const src c)
instance Eq (ConstC src) where
 ConstC x == ConstC y = isJust $ x `eqConstKi` y
instance Ord (ConstC src) where
 ConstC x `compare` ConstC y = x `ordConst` y

-- * Type '(->)'
-- | The function 'Type' @(->)@,
-- with an infix notation more readable.
(~>) ::
 Source src =>
 Constable (->) =>
 Type src vs a ->
 Type src vs b ->
 Type src vs (a -> b)
(~>) a b = (tyConstLen @(K (->)) @(->) (lenVars a) `tyApp` a) `tyApp` b
infixr 0 ~>
instance FixityOf (->) where
	fixityOf _c = Just $ Fixity2 $ infixR 0
{- NOTE: defined in Lib.Function
instance ClassInstancesFor (->)
instance TypeInstancesFor  (->)
-}

-- ** Type family 'FunArg'
type family FunArg (fun::K.Type) :: K.Type where
	FunArg (q #> f) = FunArg f
	FunArg (a -> b) = a

-- ** Type family 'FunRes'
type family FunRes (fun::K.Type) :: K.Type where
	FunRes (q #> f) = FunRes f
	FunRes (a -> b) = b

-- | Return, when the given 'Type' is a function,
-- the argument of that function.
unTyFun ::
 forall t src tys.
 Inj_Source (TypeVT src) src =>
 Constable (->) =>
 Type src tys t ->
 Maybe ( Type src tys (FunArg t)
       , Type src tys (FunRes t) )
unTyFun ty_ini = go ty_ini
	where
	go ::
	 Type src tys x ->
	 Maybe ( Type src tys (FunArg x)
	       , Type src tys (FunRes x) )
	go (TyApp _ (TyApp _ (TyConst _ _ f) a) b)
	 | Just HRefl <- proj_ConstKi @(K (->)) @(->) f
	 = Just ((a `source` TypeVT ty_ini), (b `source` TypeVT ty_ini))
	go (TyApp _ (TyApp _ (TyConst _ _ f) _a) b)
	 | Just HRefl <- proj_ConstKi @(K (#>)) @(#>) f
	 = go b
	go TyApp{}   = Nothing
	go TyConst{} = Nothing
	go TyVar{}   = Nothing
	go TyFam{}   = Nothing

-- ** Type @(#>)@
-- | /Type constant/ to qualify a 'Type'.
newtype (#>) (q::K.Constraint) (a::K.Type) = Qual (q => a)
instance FixityOf (#>) where
	fixityOf _c = Just $ Fixity2 $ infixR 0
instance ClassInstancesFor (#>)
instance TypeInstancesFor  (#>)

(#>) ::
 Source src =>
 Type src vs a ->
 Type src vs b ->
 Type src vs (a #> b)
(#>) a b = (tyConstLen @(K (#>)) @(#>) (lenVars a) `tyApp` a) `tyApp` b
infixr 0 #>
 -- NOTE: should actually be (-1)
 -- to compose well with @infixr 0 (->)@
 -- but this is not allowed by GHC.

-- ** Class @(#)@
-- | 'K.Constraint' union.
class    (x, y) => x # y
instance (x, y) => x # y
instance FixityOf (#) where
	fixityOf _c = Just $ Fixity2 $ infixB SideL 0
instance ClassInstancesFor (#) where
	proveConstraintFor _c (TyApp _ (TyApp _ c q0) q1)
	 | Just HRefl <- proj_ConstKiTy @(K (#)) @(#) c
	 , Just Dict <- proveConstraint q0
	 , Just Dict <- proveConstraint q1
	 = Just Dict
	proveConstraintFor _c _q = Nothing
instance TypeInstancesFor (#)

(#) ::
 Source src =>
 Type src vs qx ->
 Type src vs qy ->
 Type src vs (qx # qy)
(#) a b = (tyConstLen @(K (#)) @(#) (lenVars a) `tyApp` a) `tyApp` b
infixr 2 #

noConstraint ::
 Source src =>
 Inj_Len vs =>
 Type src vs (()::K.Constraint)
noConstraint = tyConstLen @K.Constraint @(()::K.Constraint) inj_Len

noConstraintLen ::
 Source src =>
 Len vs ->
 Type src vs (()::K.Constraint)
noConstraintLen = tyConstLen @K.Constraint @(()::K.Constraint)

instance ClassInstancesFor (()::K.Constraint) where
	proveConstraintFor _c q
	 | Just HRefl <- proj_ConstKiTy @K.Constraint @(()::K.Constraint) q
	 = Just Dict
	proveConstraintFor _c _q = Nothing
instance TypeInstancesFor (()::K.Constraint)

-- ** Class @(#~)@
-- | /Type equality/ 'K.Constraint'.
class    (x ~ y) => x #~ y
instance (x ~ y) => x #~ y
instance FixityOf (#~) where
	fixityOf _ = Just $ Fixity2 $ infixB SideL 0
instance
 ( Inj_Kind k
 , Typeable k
 ) => ClassInstancesFor ((#~)::k -> k -> K.Constraint) where
	proveConstraintFor _c (TyApp _ (TyApp _ c a) b)
	 | Just HRefl <- proj_ConstKiTy @(k -> k -> K.Constraint) @(#~) c
	 , Just Refl  <- eqType a b
	 = Just Dict
	proveConstraintFor _c _q = Nothing
instance TypeInstancesFor  (#~)

(#~) ::
 forall k a b src vs.
 Source src =>
 Typeable k =>
 Inj_Kind k =>
 Type src vs (a :: k) ->
 Type src vs (b :: k) ->
 Type src vs (a #~ b)
(#~) a b = (tyConstLen @(K (#~)) @(#~) (lenVars a) `tyApp` a) `tyApp` b
infixr 2 #~

{-
const_EqTy ::
 forall k src.
 Source src =>
 Typeable k =>
 Inj_Kind k =>
 Const src ((#~)::k -> k -> K.Constraint)
const_EqTy = inj_Const @(#~)
-}

-- | /Type equality/.
eqType ::
 Source src =>
 Type src tys (x::k) ->
 Type src tys (y::k) ->
 Maybe (x:~:y)
eqType x y | Just HRefl <- eqTypeKi x y = Just Refl
eqType _x _y = Nothing

-- | /Heterogeneous type equality/:
-- return two proofs when two 'Type's are equals:
-- one for the type and one for the kind.
eqTypeKi ::
 Source src =>
 Type src tys (x::kx) ->
 Type src tys (y::ky) ->
 Maybe (x:~~:y)
eqTypeKi (TyConst _sx _lx x) (TyConst _sy _ly y) = eqConstKi x y
eqTypeKi (TyVar   _sx _nx x) (TyVar   _sy _ny y) = eqVarKi   x y
eqTypeKi (TyApp _sx fx ax) (TyApp _sy fy ay)
 | Just HRefl <- eqTypeKi fx fy
 , Just HRefl <- eqTypeKi ax ay
 = Just HRefl
eqTypeKi (TyFam _ _lx fx ax) (TyFam _ _ly fy ay)
 | Just HRefl <- eqConstKi fx fy
 , Just HRefl <- eqTypes ax ay
 = Just HRefl
-- NOTE: 'TyFam' is expanded as much as possible.
eqTypeKi x@TyFam{} y = eqTypeKi (expandFam x) y
eqTypeKi x y@TyFam{} = eqTypeKi x (expandFam y)
eqTypeKi _x _y = Nothing

eqTypes ::
 Source src =>
 Types src tys x ->
 Types src tys y ->
 Maybe (x:~~:y)
eqTypes TypesZ TypesZ = Just HRefl
eqTypes (x `TypesS` sx) (y `TypesS` sy)
 | Just HRefl <- eqTypeKi x y
 , Just HRefl <- eqTypes sx sy
 = Just HRefl
eqTypes _x _y = Nothing

-- *** Comparison
-- | Compare two 'Type's.
ordType ::
 Source src =>
 Type src tys (x::kx) ->
 Type src tys (y::ky) ->
 Ordering
-- NOTE: 'TyFam' is expanded as much as possible.
ordType (TyFam _ _lx fx ax) (TyFam _ _ly fy ay) = ordConst fx fy <> ordTypes ax ay
ordType x@TyFam{} y = ordType (expandFam x) y
ordType x y@TyFam{} = ordType x (expandFam y)
ordType (TyConst _sx _lx x) (TyConst _sy _ly y) = ordConst x y
ordType TyConst{} _y = LT
ordType _x TyConst{} = GT
ordType (TyVar _sx _kx x) (TyVar _sy _ky y) = ordVarKi x y
ordType TyVar{} _y = LT
ordType _x TyVar{} = GT
ordType (TyApp _sx fx xx) (TyApp _sy fy xy) = ordType fx fy <> ordType xx xy
-- ordType TyApp{} _y = LT
-- ordType _x TyApp{} = GT

ordTypes ::
 Source src =>
 Types src tys x ->
 Types src tys y ->
 Ordering
ordTypes TypesZ TypesZ   = EQ
ordTypes TypesZ TypesS{} = LT
ordTypes TypesS{} TypesZ = GT
ordTypes (x `TypesS` sx) (y `TypesS` sy) = ordType x y <> ordTypes sx sy

-- * Type 'Qual'
-- | Captures the proof of a 'K.Constraint'
-- (and its dictionaries when it contains type classes):
-- pattern matching on the 'Dict' constructor
-- brings the 'K.Constraint' into scope.
data Qual q where
	Dict :: q => Qual q

-- ** Class 'ClassInstancesFor'
class ClassInstancesFor c where
	proveConstraintFor
	 :: Source src
	 => proxy c
	 -> Type src vs q
	 -> Maybe (Qual q)
	proveConstraintFor _c _q = Nothing

proveConstraint :: Source src => Type src vs q -> Maybe (Qual q)
proveConstraint q = foldr proj Nothing $ constsOf q
	where proj (ConstC c@Const{}) = (<|> proveConstraintFor c q)

-- ** Injection
inj_Const ::
 forall c src.
 Source src =>
 Constable c =>
 Inj_KindP (Ty_of_Type (K c)) =>
 Type_of_Ty (Ty_of_Type (K c)) ~ K c =>
 Const src c
inj_Const = Const $ inj_KindP @(Ty_of_Type (K c)) noSource

inj_ConstKi ::
 forall kc c src.
 Source src =>
 Constable c =>
 Kind src kc ->
 Const src (c::kc)
inj_ConstKi = Const

-- ** Projection
{- XXX: not working because of GHC's #12933
proj_ConstKi ::
 forall c u src.
 Constable c =>
 (K c ~ Type_of_Ty (Ty_of_Type (K c))) =>
 Inj_Kind (K c) =>
 Const src u ->
 Maybe (c :~~: u)
proj_ConstKi (Const ku) = do
	Refl <- eqKind ku $ inj_Kind @(K c) @()
	Refl :: u:~:c <- eqT
	Just HRefl
-}

-- | Like 'proj_ConstKi', but with kind equality already known.
proj_Const ::
 forall c u src.
 Typeable c =>
 Const src u ->
 Maybe (c :~: u)
proj_Const Const{} = eqT

-- | Like 'proj_Const', but on a 'Type'.
proj_ConstTy ::
 forall c u src vs.
 Typeable c =>
 Type src vs u ->
 Maybe (c :~: u)
proj_ConstTy (TyConst _src _len Const{}) = eqT
proj_ConstTy _ = Nothing

-- | XXX: 'proj_ConstKi' must be given the @kc@ kind explicitely,
-- when used multiple times in the same pattern match,
-- because of GHC's #12933.
proj_ConstKi ::
 forall kc (c::kc) u src.
 Typeable c =>
 (kc ~ Type_of_Ty (Ty_of_Type kc)) =>
 Inj_KindP (Ty_of_Type kc) =>
 Const src u ->
 Maybe (c :~~: u)
proj_ConstKi (Const ku) = do
	Refl <- eqKind ku $ inj_KindP @(Ty_of_Type kc) ()
	Refl :: u:~:c <- eqT
	Just HRefl

-- | Like 'proj_ConstKi', but on a 'Type'.
proj_ConstKiTy ::
 forall kc (c::kc) u src vs.
 Typeable c =>
 (kc ~ Type_of_Ty (Ty_of_Type kc)) =>
 Inj_KindP (Ty_of_Type kc) =>
 Type src vs u ->
 Maybe (c :~~: u)
proj_ConstKiTy (TyConst _src _len c) = proj_ConstKi c
proj_ConstKiTy _ = Nothing

eqConst ::
 Const src_x (x::k) ->
 Const src_y (y::k) ->
 Maybe (x:~:y)
eqConst (Const _kx) (Const _ky) = eqT
instance Eq (Const src c) where
	x == y = isJust $ eqConstKi x y
instance Ord (Const src c) where
	compare = ordConst

eqConstKi ::
 forall src_x src_y kx ky x y.
 Const src_x (x::kx) ->
 Const src_y (y::ky) ->
 Maybe (x:~~:y)
eqConstKi (Const kx) (Const ky)
 | Just Refl <- eqKind kx ky
 , Just (Refl::x:~:y) <- eqT
 = Just HRefl
eqConstKi _x _y = Nothing

ordConst ::
 forall src_x src_y kx ky x y.
 Const src_x (x::kx) ->
 Const src_y (y::ky) ->
 Ordering
ordConst x@Const{} y@Const{} =
	typeRep x `compare` typeRep y

normalizeQualsTy ::
 Source src =>
 Type src tys (t::kt) ->
 TypeK src tys kt
normalizeQualsTy = go
	where
	go ::
	 Source src =>
	 Type src tys (a::ka) ->
	 TypeK src tys ka
	-- (q0, q1)
	go (TyApp s1 (TyApp s0 f q0) q1)
	 | Just HRefl <- proj_ConstKiTy @(K (#)) @(#) f
	 , TypeK q0' <- go q0
	 , TypeK q1' <- go q1 =
		case (proveConstraint q0', proveConstraint q1') of
		 (Just{}, Just{})  -> TypeK $ tyConstLen @K.Constraint @(()::K.Constraint) $ lenVars q0
		 (Just{}, Nothing) -> TypeK q1'
		 (Nothing, Just{}) -> TypeK q0'
		 (Nothing, Nothing) ->
			case q0' `ordType` q1' of
			 LT -> TypeK $ TyApp s1 (TyApp s0 f q0') q1'
			 EQ -> TypeK $ q0'
			 GT -> TypeK $ TyApp s0 (TyApp s1 f q1') q0'
	-- q0 => (q1 => t)
	go (TyApp s1 (TyApp s0 f0@(TyConst sf0 lf0 _) q0) (TyApp s2 (TyApp s3 f1 q1) t))
	 | Just HRefl <- proj_ConstKiTy @(K (#>)) @(#>) f0
	 , Just HRefl <- proj_ConstKiTy @(K (#>)) @(#>) f1
	 , TypeK q0' <- go q0
	 , TypeK q1' <- go q1 =
		case (proveConstraint q0', proveConstraint q1') of
		 (Just{}, Just{})  -> TypeK t
		 (Just{}, Nothing) -> TypeK $ TyApp s2 (TyApp s3 f1 q1) t
		 (Nothing, Just{}) -> TypeK $ TyApp s1 (TyApp s0 f0 q0) t
		 (Nothing, Nothing) ->
			case q0' `ordType` q1' of
			 LT ->
				let q0q1 = TyApp s2 (TyApp s0 (TyConst sf0 lf0 $ inj_Const @(#)) q0') q1' in
				TypeK $ TyApp s2 (TyApp s3 f1 q0q1) t
			 EQ -> TypeK $ TyApp s2 (TyApp s3 f1 q1') t
			 GT ->
				let q1q0 = TyApp s0 (TyApp s2 (TyConst sf0 lf0 $ inj_Const @(#)) q1') q0' in
				TypeK $ TyApp s2 (TyApp s3 f1 q1q0) t
	-- q => t
	go (TyApp _sa (TyApp _sq (TyConst _sc _lc c) q) t)
	 | Just HRefl <- proj_ConstKi @(K (#>)) @(#>) c =
		case proveConstraint q of
		 Just{} -> go t
		 Nothing
		  | TypeK q' <- go q
		  , TypeK t' <- go t
		  -> TypeK $ q' #> t'
	-- q
	go q
	 | Just Refl <- KiConstraint () `eqKind` kindOfType q
	 = case proveConstraint q of
		 Just{}  -> TypeK $ tyConstLen @K.Constraint @(()::K.Constraint) $ lenVars q
		 Nothing -> TypeK q
	go (TyApp src f a)
	 | TypeK f' <- go f
	 , TypeK a' <- go a
	 = TypeK $ TyApp src f' a'
	go t@TyFam{}   = go (expandFam t)
	go t@TyConst{} = TypeK t
	go t@TyVar{}   = TypeK t

-- * Type family 'Fam'
-- | Apply the /type family/ selected by @fam@
-- to a list of types (within a 'Proxy').
-- 
-- | NOTE: |Fam|'s 'Kind' MUST be the same than @fam@'s.
type family Fam (fam::k) (hs::[K.Type]) :: k
-- type family FamKi fam :: K.Type

-- ** Class 'TypeInstancesFor'
class TypeInstancesFor (c::kc) where
	expandFamFor
	 :: Source src
	 => proxy c
	 -> Len vs
	 -> Const src fam
	 -> Types src vs ts
	 -> Maybe (Type src vs (Fam fam ts))
	expandFamFor _c _len _fam _as = Nothing

-- ** Class 'ExpandFam'
class ExpandFam a where
	expandFam :: Source (SourceOf a) => a -> a
instance ExpandFam (Type src vs t) where
	expandFam t@TyConst{} = t
	expandFam t@TyVar{} = t
	expandFam (TyApp src f a) = TyApp src (expandFam f) (expandFam a)
	expandFam t@(TyFam _src len fam as) =
		case foldr proj Nothing $ constsOf fam `Set.union` constsOf as of
		 Nothing -> t
		 Just t' -> expandFam t'
		where proj (ConstC c@Const{}) = (<|> expandFamFor c len fam as)
		-- XXX: this can loop…

-- | Like 'kindOf' but keep the old 'Source'.
kindOfType :: Type src vs (t::kt) -> Kind src kt
kindOfType (TyConst _src _l c) = kindOfConst c
kindOfType (TyApp _src f _a) =
	case kindOfType f of
	 KiFun _src_kf _kx kf -> kf
kindOfType (TyVar _src _n v)         = kindOfVar v
kindOfType (TyFam _src _len fam _as) = kindOfConst fam

-- | Remove unused 'Var's from ther context.
normalizeVarsTy ::
 Type src vs (t::kt) ->
 (forall vs'. Type src vs' (t::kt) -> ret) -> ret
normalizeVarsTy ty k =
	usedVarsOf UsedVarsZ ty $ \vs ->
	k $ goTy vs ty
	where
	goTy ::
	 UsedVars src vs vs' ->
	 Type src vs t ->
	 Type src vs' t
	goTy vs (TyConst src _len c) = TyConst src (lenVars vs) c
	goTy vs (TyApp src f a)      = TyApp src (goTy vs f) (goTy vs a)
	goTy vs (TyVar src n v) =
		case v `lookupUsedVars` vs of
		 Nothing -> error "[BUG] normalizeVarsTy: impossible lookup fail"
		 Just v' -> TyVar src n v'
	goTy vs (TyFam src _len fam as) = TyFam src (lenVars vs) fam $ goTys vs as
	
	goTys ::
	 UsedVars src vs vs' ->
	 Types src vs ts ->
	 Types src vs' ts
	goTys _vs TypesZ = TypesZ
	goTys vs (TypesS t ts) =
		goTy vs t `TypesS`
		goTys vs ts

-- * Type 'Types'
data Types src vs (ts::[K.Type]) where
	TypesZ :: Types src vs '[]
	TypesS :: Type src vs t
	       -> Types src vs ts
	       -> Types src vs (Proxy t ': ts)
infixr 5 `TypesS`

type instance SourceOf (Types src vs ts) = src
instance ConstsOf (Types src vs ts) where
	constsOf TypesZ = Set.empty
	constsOf (TypesS t ts) = constsOf t `Set.union` constsOf ts
type instance VarsOf (Types src vs ts) = vs
instance UsedVarsOf (Types src vs ts) where
	usedVarsOf vs TypesZ k = k vs
	usedVarsOf vs (TypesS t ts) k =
		usedVarsOf vs t $ \vs' ->
		usedVarsOf vs' ts k
instance VarOccursIn (Types src vs ts) where
	varOccursIn _v TypesZ = False
	varOccursIn v (TypesS t ts) = varOccursIn v t || varOccursIn v ts

mapTys ::
 (forall kt (t::kt). Type src vs t -> Type src vs' t) ->
 Types src vs  ts ->
 Types src vs' ts
mapTys _f TypesZ = TypesZ
mapTys f (TypesS t ts) = f t `TypesS` mapTys f ts

foldlTys ::
 (forall k (t::k). Type src vs t -> acc -> acc) ->
 Types src vs ts -> acc -> acc
foldlTys _f TypesZ acc = acc
foldlTys f (TypesS t ts) acc = foldlTys f ts (f t acc)

-- * Class 'TypeOf'
class TypeOf a where
	typeOf :: a t -> Type (SourceOf (a t)) (VarsOf (a t)) t