{-# LANGUAGE CPP #-}

{- |
   The HList library

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

   Type-indexed co-products. The public interface is exposed
   in <Data-HList-CommonMain.html#t:TIC CommonMain#TIC>

   (There are other ways: see ConsUnion.hs, for example)
-}

module Data.HList.TIC where

import Data.HList.TIP
import Data.HList.FakePrelude
import Data.HList.HListPrelude

import Data.HList.Record
import Data.HList.Variant
import Data.HList.HList

import Data.HList.HArray

import Data.Array (Ix)

import Text.ParserCombinators.ReadP
import LensDefs

-- --------------------------------------------------------------------------
-- | A datatype for type-indexed co-products. A 'TIC' is just a 'Variant',
-- where the elements of the type-level list @\"l\"@ are in the form
-- @Tagged x x@.

newtype TIC (l :: [*]) = TIC (Variant l)

deriving instance Eq (Variant l) => Eq (TIC l)
deriving instance Ord (Variant l) => Ord (TIC l)
deriving instance Ix (Variant l) => Ix (TIC l)
deriving instance Bounded (Variant l) => Bounded (TIC l)
deriving instance Enum (Variant l) => Enum (TIC l)
deriving instance Monoid (Variant l) => Monoid (TIC l)


instance HMapAux Variant f xs ys => HMapAux TIC f xs ys where
    hMapAux f (TIC a) = TIC (hMapAux f a)

-- | @Iso (TIC s) (TIC t) (Variant s) (Variant t)@
--
-- 'typeIndexed' may be more appropriate
ticVariant x = isoNewtype (\(TIC a) -> a) TIC x

-- | @Iso' (TIC s) (Variant s)@
ticVariant' x = simple (ticVariant x)


-- --------------------------------------------------------------------------

{- | Conversion between type indexed collections ('TIC' and 'TIP')
and the corresponding collection that has other label types ('Variant'
and 'Record' respectively)

See 'typeIndexed''

-}
class TypeIndexed r tr | r -> tr, tr -> r where
    -- | @Iso (r s) (r t) (tr a) (tr b)@
    typeIndexed :: forall p f s t a b.
       (TypeIndexedCxt s t a b, Profunctor p, Functor f) =>
      p (tr (TagR a)) (f (tr (TagR b))) -> p (r s) (f (r t))

type TypeIndexedCxt s t a b =
 (HMapCxt HList TaggedFn b t,
  RecordValues s, RecordValues t,
  a ~ RecordValuesR s,
  b ~ RecordValuesR t,
  SameLabels s t,
  SameLength s t,
  SameLength b a,
  {- to use castVariant instead of unsafeCastVariant
  RecordValuesR (TagR a) ~ a,
  RecordValuesR (TagR b) ~ b,
  SameLength (TagR a) s,
  SameLength (TagR b) t,
  -}
  Coercible (TagR b) t,
  Coercible (TagR a) s,
  HAllTaggedLV s,
  HRLabelSet t,
  TagUntag a,
  TagUntag b)

instance TypeIndexed Record TIP where
    typeIndexed = sameLength . unlabeled . fromTipHList
      where fromTipHList = iso (TIP . hTagSelf) (\(TIP a) -> hUntagSelf a)

instance TypeIndexed Variant TIC where
    typeIndexed = isoNewtype unsafeCastVariant unsafeCastVariant
                . isoNewtype TIC (\(TIC a) -> a)

{- |

@'Iso'' ('Variant' s) ('TIC' a)@

@'Iso'' ('Record' s) ('TIP' a)@

where @s@ has a type like @'[Tagged \"x\" Int]@, and
@a@ has a type like @'[Tagged Int Int]@.
-}
typeIndexed' x = simple (typeIndexed x)

-- --------------------------------------------------------------------------
-- | Public constructor (or, open union's injection function)

mkTIC' :: forall i l proxy.
         ( HTypeIndexed l
         , MkVariant i i l
         )
      => i
      -> proxy l -- ^ the ordering of types in the @l :: [*]@ matters.
                 -- This argument is intended to fix the ordering
                 -- it can be a Record, Variant, TIP, Proxy
      -> TIC l

mkTIC' i p = TIC (mkVariant (Label :: Label i) i p)

-- | make a TIC that contains one element
mkTIC1 :: forall i. MkVariant i i '[Tagged i i] => i -> TIC '[Tagged i i]
mkTIC1 i = TIC (mkVariant1 (Label :: Label i) i)

-- | make a TIC for use in contexts where the result type is fixed
mkTIC i = mkTIC' i Proxy


-- --------------------------------------------------------------------------
-- | Public destructor (or, open union's projection function)
instance HasField o (Variant l) (Maybe o) =>
      HasField o (TIC l) (Maybe o) where
    hLookupByLabel l (TIC i) = hLookupByLabel l i


instance (HasField o (TIC l) mo, mo ~ Maybe o) => HOccurs mo (TIC l) where
    hOccurs = hLookupByLabel (Label :: Label o)


-- | similar to 'HPrism'
class TICPrism s t a b | s a b -> t, t a b -> s where
  ticPrism :: (SameLength s t, Choice p, Applicative f)
      => (a `p` f b) -> (TIC s `p` f (TIC t))

instance (
    MkVariant b b t,
    HasField a (Variant s) (Maybe a),
    SameLength s t,

    HFindLabel b t n,
    HFindLabel a s n,

    HUpdateAtHNatR n (Tagged b b) s ~ t,
    HUpdateAtHNatR n (Tagged a a) t ~ s

    ) => TICPrism s t a b where
  ticPrism = ticVariant . prism (\b -> mkVariant (Label :: Label b) b Proxy)
      (\s -> case hLookupByLabel (Label :: Label a) s of
          Just a -> Right a
          Nothing -> Left (unsafeCastVariant s :: Variant t))

-- | @Prism' (TIC s) a@
ticPrism' :: forall s t a b. (HPrism a s t a b, a~b, s~t)
  => (forall f p. (Applicative f, Choice p) => (a `p` f b) -> (TIC s `p` f (TIC t)))
ticPrism' = ticVariant . hPrism (Label :: Label a)


-- --------------------------------------------------------------------------
-- | TICs are not opaque

instance ShowVariant l => Show (TIC l)
 where
  showsPrec _ (TIC v) = ("TIC{"++) . showVariant v . ('}':)


instance (ReadVariant l, HAllTaggedEq l, HRLabelSet l) => Read (TIC l)
 where
   readsPrec _ = readP_to_S $ do
     _ <- string "TIC{"
     r <- readVariant
     _ <- string "}"
     return (TIC r)


{- |
> Nothing .*. x = x
> Just a .*. y = mkTIC a
-}
instance (me ~ Maybe e, HOccursNot (Tagged e e) l)
       => HExtend me (TIC l) where
    type HExtendR me (TIC l) = TIC (Tagged (UnMaybe me) (UnMaybe me) ': l)
    Just e .*. _ = TIC (unsafeMkVariant 0 e)
    Nothing .*. TIC x = TIC (extendVariant x)