{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Row.Dictionaries
--
-- This module exports various dictionaries that help the type-checker when
-- dealing with row-types.
--
-- For the various axioms, type variables are consistently in the following order:
--
--  * Any types that do not belong later.
--
--  * Labels
--
--  * Row-types
--
--      * If applicable, the type in the row-type at the given label goes after
--      each row-type
--
--  * Constraints
-----------------------------------------------------------------------------


module Data.Row.Dictionaries
  ( -- * Axioms
    uniqueMap, uniqueAp, uniqueApSingle, uniqueZip
  , extendHas, mapHas, apHas, apSingleHas
  , mapExtendSwap, apExtendSwap, apSingleExtendSwap, zipExtendSwap
  , FreeForall
  , FreeBiForall
  , freeForall
  , mapForall
  , apSingleForall
  -- ** Helper Types
  , IsA(..)
  , As(..)
  , ActsOn(..)
  , As'(..)
  -- * Re-exports
  , Dict(..), (:-)(..), HasDict(..), (\\), withDict
  , Unconstrained, Unconstrained1, Unconstrained2

  )
where

import Data.Constraint
import Data.Functor.Const
import Data.Proxy
import qualified Unsafe.Coerce as UNSAFE
import GHC.TypeLits

import Data.Row.Internal



-- | This data type is used to for its ability to existentially bind a type
-- variable.  Particularly, it says that for the type 'a', there exists a 't'
-- such that @a ~ f t@ and @c t@ holds.
data As c f a where
  As :: forall c f a t. (a ~ f t, c t) => As c f a

-- | A class to capture the idea of 'As' so that it can be partially applied in
-- a context.
class IsA c f a where
  as :: As c f a

instance c a => IsA c f (f a) where
  as = As

-- | Like 'As', but here we know the underlying value is some 'f' applied to the
-- given type 'a'.
data As' c t a where
  As' :: forall c f a t. (a ~ f t, c f) => As' c t a

-- | A class to capture the idea of 'As'' so that it can be partially applied in
-- a context.
class ActsOn c t a where
  actsOn :: As' c t a

instance c f => ActsOn c t (f t) where
  actsOn = As'

-- | An internal type used by the 'metamorph' in 'mapForall'.
newtype MapForall c f (r :: Row k) = MapForall { unMapForall :: Dict (Forall (Map f r) (IsA c f)) }

-- | An internal type used by the 'metamorph' in 'apSingleForall'.
newtype ApSingleForall c a (fs :: Row (k -> k')) = ApSingleForall
  { unApSingleForall :: Dict (Forall (ApSingle fs a) (ActsOn c a)) }

-- | This allows us to derive a @Forall (Map f r) ..@ from a @Forall r ..@.
mapForall :: forall f ρ c. Forall ρ c :- Forall (Map f ρ) (IsA c f)
mapForall = Sub $ unMapForall $ metamorph @_ @ρ @c @Const @Proxy @(MapForall c f) @Proxy Proxy empty uncons cons $ Proxy
  where empty _ = MapForall Dict
        uncons _ _ = Const Proxy
        cons :: forall  τ ρ. (KnownSymbol , c τ, FrontExtends  τ ρ, AllUniqueLabels (Extend  τ ρ))
             => Label  -> Const (MapForall c f ρ) (Proxy τ)
             -> MapForall c f (Extend  τ ρ)
        cons _ (Const (MapForall Dict)) = case frontExtendsDict @ @τ @ρ of
          FrontExtendsDict Dict -> MapForall Dict
            \\ mapExtendSwap @f @ @τ @ρ
            \\ uniqueMap @f @(Extend  τ ρ)

-- | This allows us to derive a @Forall (ApSingle f r) ..@ from a @Forall f ..@.
apSingleForall :: forall a fs c. Forall fs c :- Forall (ApSingle fs a) (ActsOn c a)
apSingleForall = Sub $ unApSingleForall $ metamorph @_ @fs @c @Const @Proxy @(ApSingleForall c a) @Proxy Proxy empty uncons cons $ Proxy
  where empty _ = ApSingleForall Dict
        uncons _ _ = Const Proxy
        cons :: forall  τ ρ. (KnownSymbol , c τ, FrontExtends  τ ρ, AllUniqueLabels (Extend  τ ρ))
             => Label  -> Const (ApSingleForall c a ρ) (Proxy τ)
             -> ApSingleForall c a (Extend  τ ρ)
        cons _ (Const (ApSingleForall Dict)) = case frontExtendsDict @ @τ @ρ of
          FrontExtendsDict Dict -> ApSingleForall Dict
            \\ apSingleExtendSwap @a @ @τ @ρ
            \\ uniqueApSingle @a @(Extend  τ ρ)

-- | Allow any 'Forall' over a row-type, be usable for 'Unconstrained1'.
freeForall :: forall r c. Forall r c :- Forall r Unconstrained1
freeForall = Sub $ UNSAFE.unsafeCoerce @(Dict (Forall r c)) Dict

-- | `FreeForall` can be used when a `Forall` constraint is necessary but there
-- is no particular constraint we care about.
type FreeForall r = Forall r Unconstrained1

-- | `FreeForall` can be used when a `BiForall` constraint is necessary but
-- there is no particular constraint we care about.
type FreeBiForall r1 r2 = BiForall r1 r2 Unconstrained2

-- | If we know that 'r' has been extended with @l .== t@, then we know that this
-- extension at the label 'l' must be 't'.
extendHas :: forall l t r. Dict (Extend l t r .! l  t)
extendHas = UNSAFE.unsafeCoerce $ Dict @Unconstrained

-- | This allows us to derive @Map f r .! l ≈ f t@ from @r .! l ≈ t@
mapHas :: forall f l t r. (r .! l  t) :- (Map f r .! l  f t, Map f r .- l  Map f (r .- l))
mapHas = Sub $ UNSAFE.unsafeCoerce $ Dict @(r .! l  t)

-- | This allows us to derive @Ap ϕ ρ .! l ≈ f t@ from @ϕ .! l ≈ f@ and @ρ .! l ≈ t@
apHas :: forall l f ϕ t ρ. (ϕ .! l  f, ρ .! l  t) :- (Ap ϕ ρ .! l  f t, Ap ϕ ρ .- l  Ap (ϕ .- l) (ρ .- l))
apHas = Sub $ UNSAFE.unsafeCoerce $ Dict @(ϕ .! l  f, ρ .! l  t)

-- | This allows us to derive @ApSingle r x .! l ≈ f x@ from @r .! l ≈ f@
apSingleHas :: forall x l f r. (r .! l  f) :- (ApSingle r x .! l  f x, ApSingle r x .- l  ApSingle (r .- l) x)
apSingleHas = Sub $ UNSAFE.unsafeCoerce $ Dict @(r .! l  f)

-- | Proof that the 'Map' type family preserves labels and their ordering.
mapExtendSwap :: forall f  τ r. Dict (Extend  (f τ) (Map f r)  Map f (Extend  τ r))
mapExtendSwap = UNSAFE.unsafeCoerce $ Dict @Unconstrained

-- | Proof that the 'Ap' type family preserves labels and their ordering.
apExtendSwap :: forall  f fs τ r. Dict (Extend  (f τ) (Ap fs r)  Ap (Extend  f fs) (Extend  τ r))
apExtendSwap = UNSAFE.unsafeCoerce $ Dict @Unconstrained

-- | Proof that the 'ApSingle' type family preserves labels and their ordering.
apSingleExtendSwap :: forall τ  f r. Dict (Extend  (f τ) (ApSingle r τ)  ApSingle (Extend  f r) τ)
apSingleExtendSwap = UNSAFE.unsafeCoerce $ Dict @Unconstrained

-- | Proof that the 'Ap' type family preserves labels and their ordering.
zipExtendSwap :: forall  τ1 r1 τ2 r2. Dict (Extend  (τ1, τ2) (Zip r1 r2)  Zip (Extend  τ1 r1) (Extend  τ2 r2))
zipExtendSwap = UNSAFE.unsafeCoerce $ Dict @Unconstrained

-- | Map preserves uniqueness of labels.
uniqueMap :: forall f r. Dict (AllUniqueLabels (Map f r)  AllUniqueLabels r)
uniqueMap = UNSAFE.unsafeCoerce $ Dict @Unconstrained

-- | Ap preserves uniqueness of labels.
uniqueAp :: forall fs r. Dict (AllUniqueLabels (Ap fs r)  AllUniqueLabels r)
uniqueAp = UNSAFE.unsafeCoerce $ Dict @Unconstrained

-- | ApSingle preserves uniqueness of labels.
uniqueApSingle :: forall x r. Dict (AllUniqueLabels (ApSingle r x)  AllUniqueLabels r)
uniqueApSingle = UNSAFE.unsafeCoerce $ Dict @Unconstrained

-- | Zip preserves uniqueness of labels.
uniqueZip :: forall r1 r2. Dict (AllUniqueLabels (Zip r1 r2)  (AllUniqueLabels r1, AllUniqueLabels r2))
uniqueZip = UNSAFE.unsafeCoerce $ Dict @Unconstrained