{-# LANGUAGE AllowAmbiguousTypes   #-}
{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE KindSignatures        #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TypeApplications      #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TypeOperators         #-}
{-# LANGUAGE UndecidableInstances  #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Generics.Sum.Internal.Typed
-- Copyright   :  (C) 2019 Csongor Kiss
-- License     :  BSD3
-- Maintainer  :  Csongor Kiss <kiss.csongor.kiss@gmail.com>
-- Stability   :  experimental
-- Portability :  non-portable
--
-- Derive constructor-field-type-based prisms generically.
--
-----------------------------------------------------------------------------

module Data.Generics.Sum.Internal.Typed
  ( GAsType (..)
  ) where

import Data.Kind
import GHC.Generics
import Data.Tagged

import Data.Generics.Internal.Families
import Data.Generics.Product.Internal.HList
import Data.Generics.Internal.Profunctor.Iso
import Data.Generics.Internal.Profunctor.Prism

-- |As 'AsType' but over generic representations as defined by "GHC.Generics".
class GAsType (f :: Type -> Type) (as :: [Type]) where
  _GTyped :: Prism (f x) (f x) (HList as) (HList as)
-- We create this specialised version as we use it in the subtype prism
-- If we don't create it, the opportunity for specialisation is only
-- created after specialisation happens, I think a late specialisation pass
-- would pick up this case.
{-# SPECIALISE _GTyped  :: (Tagged b b -> Tagged t t) #-}


instance
  ( GIsList f f as as
  ) => GAsType (M1 C meta f) as where
  _GTyped = mIso . glist
  {-# INLINE[0] _GTyped #-}

instance GSumAsType (HasPartialTypeP a l) l r a => GAsType (l :+: r) a where
  _GTyped = _GSumTyped @(HasPartialTypeP a l)
  {-# INLINE[0] _GTyped #-}

instance GAsType f a => GAsType (M1 D meta f) a where
  _GTyped = mIso . _GTyped
  {-# INLINE[0] _GTyped #-}

class GSumAsType (contains :: Bool) l r (a :: [Type]) where
  _GSumTyped :: Prism ((l :+: r) x) ((l :+: r) x) (HList a) (HList a)

instance GAsType l a => GSumAsType 'True l r a where
  _GSumTyped = left . _GTyped
  {-# INLINE[0] _GSumTyped #-}

instance GAsType r a => GSumAsType 'False l r a where
  _GSumTyped = right . _GTyped
  {-# INLINE[0] _GSumTyped #-}