-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Barbie.Internal.Functor
--
-- GHC is at the momemt unable to derive @'Generic1' b@ for a Barbie-type
-- @b@. Instead, we use a trick by which we use the uninhabited type
-- 'Target' to identify the point where an 'f' occurs. That is, we coerce
-- a @b f@ into a @b 'Target'@, operate on the representation of this type,
-- and finally coerce back to the desired type.
----------------------------------------------------------------------------
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE TypeFamilies  #-}
{-# LANGUAGE TypeOperators #-}
module Data.Barbie.Internal.Generics
  ( Target (..)
  , unsafeTargetBarbie
  , unsafeUntarget
  , unsafeTarget
  , unsafeUntargetBarbie

  , W

  , Repl, Repl'

  , RecRep
  , RecUsage(..), NonRec(..)
  , AnnRec, DeannRec
  , toWithRecAnn
  , fromWithRecAnn

  )

where

import GHC.Generics
import Unsafe.Coerce (unsafeCoerce)

-- | We use 'Target' to identify the position in
--   in the generic representation where @f@ is used.
--   This is a hack to overcome the fact that 'Generic1'
--   does not currently work on a type @T f@ whenever
--   if 'f' is applied in 'T', which are all the interesting
--   cases!
data Target (f :: * -> *) a
  = Target (f a)

unsafeTargetBarbie :: forall t b f . b f -> b (Target t)
unsafeTargetBarbie = unsafeCoerce

unsafeUntarget :: forall t f a . Target t a -> f a
unsafeUntarget = unsafeCoerce

unsafeTarget :: forall t f a . f a -> Target t a
unsafeTarget = unsafeCoerce

unsafeUntargetBarbie :: forall t b f . b (Target t) -> b f
unsafeUntargetBarbie = unsafeCoerce

type family Repl f g rep where
    Repl f g (M1 i c x)       = M1 i c (Repl f g x)
    Repl f g V1               = V1
    Repl f g U1               = U1
    Repl (Target f) (Target g) (K1 i (Target (W f) a)) = K1 i (Target (W g) a)
    Repl f g (K1 i (f a))     = K1 i (g a)
    Repl f g (K1 i (b f))     = K1 i (b g)
    Repl f g (K1 i (h (b f))) = K1 i (h (b g))
    Repl f g (K1 i c)         = K1 i c
    Repl f g (l :+: r)        = (Repl f g l) :+: (Repl f g r)
    Repl f g (l :*: r)        = (Repl f g l) :*: (Repl f g r)


-- | We use 'RecUsage' to identify the position in the
--   generic representation where the barbie type is used
--   recursively.
newtype RecUsage a
  = RecUsage a

newtype NonRec a
  = NonRec a

type family AnnRec a rep where
  AnnRec a (M1 i c x)  = M1  i c (AnnRec a x)
  AnnRec a V1          = V1
  AnnRec a U1          = U1
  AnnRec a (K1 i a)    = K1 i (RecUsage a)
  AnnRec a (K1 i a')   = K1 i (NonRec a')
  AnnRec a (l :*: r)   = AnnRec a l :*: AnnRec a r
  AnnRec a (l :+: r)   = AnnRec a l :+: AnnRec a r

type family DeannRec rep where
  DeannRec (M1 i c x)          = M1  i c (DeannRec x)
  DeannRec V1                  = V1
  DeannRec U1                  = U1
  DeannRec (K1 i (RecUsage a)) = K1 i a
  DeannRec (K1 i (NonRec a))   = K1 i a
  DeannRec (l :*: r)           = DeannRec l :*: DeannRec r
  DeannRec (l :+: r)           = DeannRec l :+: DeannRec r

fromWithRecAnn :: Generic a => a -> RecRep a x
fromWithRecAnn = unsafeCoerce . from

toWithRecAnn :: Generic a => RecRep a x -> a
toWithRecAnn = to . unsafeCoerce

type RecRep a = AnnRec a (Rep a)

type Repl' f g rep
  = Repl f g (DeannRec rep)


-- | We use 'W' to identify usagaes of 'Wear' in the generic
--   representation of a barbie-type.
data W (f :: * -> *) a