{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}

{-
   The HList library

   (C) 2004, Oleg Kiselyov, Ralf Laemmel, Keean Schupke

   Extensible records -- operations that (may) require GHC

   See Record.hs for the base module.
-}

module Data.HList.GhcRecord where

import Data.HList.FakePrelude
import Data.HList.HListPrelude
import Data.HList.HArray
import Data.HList.Record
import Data.Typeable


{-----------------------------------------------------------------------------}

-- A variation on update.
-- Replace a proxy by a value of the proxied type.
-- The signature is inferred
hUnproxyLabel :: (HUpdateAtHNat n (LVPair l a) t l', HFind l ls n,
                               RecordLabels t ls,
                               HasField l t (Proxy a)) =>
                                             l -> a -> Record t -> Record l'
hUnproxyLabel l v r = hUpdateAtLabel l v r
 where
  tpe :: a -> Proxy a -> ()
  tpe _ _ = ()
  _ = tpe v (hLookupByLabel l r)


{-----------------------------------------------------------------------------}

-- Test for values; refuse proxies

hasNoProxies :: HasNoProxies r
             => Record r -> ()
hasNoProxies = const ()


data ProxyFound x
class HasNoProxies l
instance HasNoProxies HNil
instance Fail (ProxyFound x) => HasNoProxies (HCons (Proxy x) l)
instance Fail (ProxyFound x) => HasNoProxies (HCons (LVPair lab (Proxy x)) l)
instance HasNoProxies l => HasNoProxies (HCons e l)


{-----------------------------------------------------------------------------}

-- Narrow a record to a different record type

-- First is the `monadic' version, which returns the `failure indictator'
-- (HNothing) if the narrowing fails because the source does not have
-- all the fields for the target.
class  NarrowM a b res | a b -> res where
    narrowM :: Record a -> Record b -> res

instance NarrowM a HNil (HJust (Record HNil)) where
    narrowM _ _ = HJust emptyRecord

instance (H2ProjectByLabels (HCons l HNil) a rin rout,
          NarrowM' rin rout b res)
    => NarrowM a (HCons (LVPair l v) b) res where
    narrowM (Record a) _ = narrowM' rin rout (undefined::b)
     where
        (rin,rout) = h2projectByLabels (undefined::(HCons l HNil)) a

class  NarrowM' rin rout b res | rin rout b -> res where
    narrowM' :: rin -> rout -> b -> res

instance NarrowM' HNil rout b HNothing where
    narrowM' _ _ _ = HNothing

instance (NarrowM rout b res',
          NarrowM'' f res' res)
    => NarrowM' (HCons f HNil) rout b res where
    narrowM' (HCons f HNil) rout b =
        narrowM'' f (narrowM (Record rout) (Record b))

class  NarrowM'' f r r' | f r -> r' where
    narrowM'' :: f -> r -> r'

instance NarrowM'' f HNothing HNothing where
    narrowM'' _ _ = HNothing

instance NarrowM'' f (HJust (Record r)) (HJust (Record (HCons f r))) where
    narrowM'' f (HJust (Record r)) = HJust (Record (HCons f r))


class  Narrow a b
 where narrow :: Record a -> Record b

instance Narrow a HNil
 where   narrow _ = emptyRecord

instance ( Narrow rout r'
         , H2ProjectByLabels (HCons l HNil) r (HCons (LVPair l v) HNil) rout
         ) => Narrow r (HCons (LVPair l v) r')
  where
    narrow (Record r) = Record (HCons f r')
      where
        (HCons f HNil,rout) = h2projectByLabels (undefined::(HCons l HNil)) r
        (Record r')    = narrow (Record rout)


{-----------------------------------------------------------------------------}

-- Narrow two records to their least-upper bound

class LubNarrow a b c | a b -> c
 where
  lubNarrow :: a -> b -> (c,c)

instance ( RecordLabels a la
         , RecordLabels b lb
         , HTIntersect la lb lc
         , H2ProjectByLabels lc a c aout
         , H2ProjectByLabels lc b c bout
         , HRLabelSet c
         )
      => LubNarrow (Record a) (Record b) (Record c)
 where
  lubNarrow ra@(Record _) rb@(Record _) =
     ( hProjectByLabels (undefined::lc) ra
     , hProjectByLabels (undefined::lc) rb
     )


{-----------------------------------------------------------------------------}

-- List constructors that also LUB together

data NilLub
nilLub :: NilLub
nilLub = undefined

class ConsLub h t l | h t -> l
 where
  consLub :: h -> t -> l

instance ConsLub e  NilLub [e]
 where
  consLub h _ = [h]

instance LubNarrow e0 e1 e2 => ConsLub e0 [e1] [e2]
 where
  consLub h t = fst (head z) : map snd (tail z)
   where
    z = map (lubNarrow h) (undefined:t)


{-----------------------------------------------------------------------------}

-- Extension of lubNarrow to a heterogeneous list

class HLub l e | l -> e
 where
  hLub :: l -> [e]

instance ( LubNarrow h h' e
         )
      => HLub (HCons h (HCons h' HNil)) e
 where
  hLub (HCons h (HCons h' _)) = [fst ee, snd ee]
   where
    ee = lubNarrow h h'

instance ( HLub (HCons h (HCons h'' t)) e'
         , HLub (HCons h' (HCons h'' t)) e''
         , LubNarrow e' e'' e
         , HLub (HCons e (HCons h'' t)) e
         )
      => HLub (HCons h (HCons h' (HCons h'' t))) e
 where
  hLub (HCons h (HCons h' t)) = fst e : ( snd e : tail r )
   where
    e' = hLub (HCons h t)
    e'' = hLub (HCons h' t)
    e = lubNarrow (head e') (head e'')
    r = hLub (HCons (fst e) t)



{-----------------------------------------------------------------------------}
-- Record equivalence modulo field order
-- Decide if two records r1 and r2 are identical or differ only in the order
-- of their fields.
-- If the two record types are indeed equivalent, return the witness of
-- their equivalence, (HJust (r1->r2,r2->r1)). If they are not equivalent,
-- return HNothing
-- The function equivR does not examine the values of its arguments:
-- it needs only their types.

-- The algorithm is simple: two records are equivalent if one can be narrowed
-- to the other, and vice versa. The narrowing coercions are the desired
-- witnesses.
-- The obvious optimization is to check first if two records are of the same
-- type. That requires TypeEq however. Perhaps we shouldn't use it here.
-- Use of the record narrowing tacitly assumes that the label of a record
-- field uniquely determines the type of the field value. Therefore, we
-- should not use equivR on two records with inconsistent labeling...

class RecordEquiv r1 r2 res | r1 r2 -> res where
    equivR :: Record r1 -> Record r2 -> res


{-
instance (TypeEq r1 r2 b, RecordEquiv' b r1 r2 res)
    => RecordEquiv r1 r2 res where
    equivR _ _ = equivR' (undefined::b) (undefined::r1) (undefined::r2)
-- Two records have the same type: the fast path
instance RecordEquiv' HTrue r r
                      (HJust (Record r->Record r,Record r->Record r)) where
    equivR' _ _ _ = HJust (id,id)
-}

instance (NarrowM r1 r2 r12, NarrowM r2 r1 r21,
          RecordEquiv' (Record r1->r12) (Record r2->r21) res)
    => RecordEquiv r1 r2 res where
    equivR r1 r2 = equivR' r1p r2p
     where r1p (r1 :: Record r1) = narrowM r1 r2
           r2p (r2 :: Record r2) = narrowM r2 r1

class RecordEquiv' pj1 pj2 res | pj1 pj2 -> res where
    equivR' :: pj1 -> pj2 -> res

instance RecordEquiv' (r1->HJust r2) (r2->HJust r1) (HJust (r1->r2,r2->r1))
    where
    equivR' r12 r21 = HJust (unj.r12,unj.r21)
     where unj (HJust x) = x

-- r2 has something that r1 doesn't
instance RecordEquiv' (r1->HNothing) pj2 HNothing where
    equivR' _ _ = HNothing

-- r1 is a strict superset of r2
instance RecordEquiv' (r1->HJust r2) (r2->HNothing) HNothing where
    equivR' _ _ = HNothing


{-----------------------------------------------------------------------------}
-- Typeable instances

hNilTcName :: TyCon
hNilTcName = mkTyCon "HList.HNil"
instance Typeable HNil
 where
  typeOf _ = mkTyConApp hNilTcName []

hConsTcName :: TyCon
hConsTcName = mkTyCon "HList.HCons"
instance (Typeable x, Typeable y) => Typeable (HCons x y)
 where
  typeOf ~(HCons x y)
   = mkTyConApp hConsTcName [ typeOf x, typeOf y ]

recordTcName :: TyCon
recordTcName = mkTyCon "HList.Record"
instance Typeable x => Typeable (Record x)
 where
  typeOf ~(Record x)
   = mkTyConApp recordTcName [ typeOf x ]

hFieldTcName :: TyCon
hFieldTcName = mkTyCon "HList.F"
instance (Typeable x, Typeable y) => Typeable (LVPair x y)
 where
  typeOf _
   = mkTyConApp hFieldTcName [ typeOf (undefined::x), typeOf (undefined::y)  ]

proxyTcName :: TyCon
proxyTcName = mkTyCon "HList.Proxy"
instance Typeable x => Typeable (Proxy x)
 where
  typeOf (_::Proxy x)
   = mkTyConApp proxyTcName [ typeOf (undefined::x) ]


{-----------------------------------------------------------------------------}