{-# LANGUAGE CPP                   #-}
{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE EmptyCase             #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds             #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TypeOperators         #-}
{-# OPTIONS_GHC -fno-warn-orphans  #-}
-- | Lenses for "Generics.SOP"
--
-- Orphan instances:
--
-- @
-- 'Wrapped' ('SOP' f xss) -- Also 'Rewrapped'
-- 'Wrapped' ('POP' f xss)
-- 'Field1' ('NP' f (x ': zs)) ('NP' f (y ': zs)) (f x) (f y) -- 'Field2' etc.
-- 'Field1' ('POP' f (x ': zs)) ('NP' f (y ': zs)) (NP f x) (NP f y)
-- @
module Generics.SOP.Lens (
    rep,
    -- * SOP & POP
    sop, pop,
    unsop, unpop,
    -- * Functors
    isoI, isoK,
    uni, unk,
    -- * Products
    singletonP,
    unSingletonP,
    headLens,
    tailLens,
    -- * Sums
    singletonS,
    unSingletonS,
    _Z,
    _S,
    -- * DatatypeInfo
    Generics.SOP.Lens.moduleName,
    Generics.SOP.Lens.datatypeName,
    Generics.SOP.Lens.constructorInfo,
    Generics.SOP.Lens.constructorName,
#if MIN_VERSION_generics_sop(0,5,0)
    Generics.SOP.Lens.strictnessInfo,
#endif
    ) where

import Control.Lens
import Generics.SOP hiding (from)
import qualified Generics.SOP as SOP

#if MIN_VERSION_generics_sop(0,5,0)
import Generics.SOP.Metadata
#endif

rep :: Generic a => Iso' a (Rep a)
rep = iso SOP.from SOP.to

-------------------------------------------------------------------------------
-- SOP & POP
-------------------------------------------------------------------------------

sop ::
    forall (f :: k -> *) xss yss.
    Iso (NS (NP f) xss) (NS (NP f) yss) (SOP f xss) (SOP f yss)
sop = iso SOP unSOP

unsop ::
    forall (f :: k -> *) xss yss.
    Iso (SOP f xss) (SOP f yss) (NS (NP f) xss) (NS (NP f) yss)
unsop = from sop

pop ::
    forall (f :: k -> *) xss yss.
    Iso (NP (NP f) xss) (NP (NP f) yss) (POP f xss) (POP f yss)
pop = iso POP unPOP

unpop ::
    forall (f :: k -> *) xss yss.
    Iso (POP f xss) (POP f yss) (NP (NP f) xss) (NP (NP f) yss)
unpop = from pop

instance (t ~ SOP f xss) => Rewrapped (SOP f xss) t
instance Wrapped (SOP f xss) where
    type Unwrapped (SOP f xss) = NS (NP f) xss
    _Wrapped' = from sop

instance (t ~ POP f xss) => Rewrapped (POP f xss) t
instance Wrapped (POP f xss) where
    type Unwrapped (POP f xss) = NP (NP f) xss
    _Wrapped' = from pop

-------------------------------------------------------------------------------
-- Basic functors
-------------------------------------------------------------------------------

isoI :: Iso a b (I a) (I b)
isoI = iso I unI

uni :: Iso (I a) (I b) a b
uni = iso unI I

isoK :: Iso a b (K a c) (K b c)
isoK = iso K unK

unk :: Iso (K a c) (K b c) a b
unk = iso unK K

instance (t ~ I a) => Rewrapped (I a) t
instance Wrapped (I a) where
    type Unwrapped (I a) = a
    _Wrapped' = from isoI

instance (t ~ K a b) => Rewrapped (K a b) t
instance Wrapped (K a b) where
    type Unwrapped (K a b) = a
    _Wrapped' = from isoK

-------------------------------------------------------------------------------
-- Products
-------------------------------------------------------------------------------

singletonP ::
    forall (f :: k -> *) x y.
    Iso (f x) (f y) (NP f '[x]) (NP f '[y])
singletonP = iso s g
  where
    g :: NP f '[y] -> f y
    g (y  :* Nil)   = y
#if __GLASGOW_HASKELL__ < 800
    g _ = error "singletonP"
#endif

    s :: f x -> NP f '[x]
    s x = x :* Nil

unSingletonP ::
    forall (f :: k -> *) x y.
    Iso (NP f '[x]) (NP f '[y]) (f x) (f y)
unSingletonP = from singletonP

headLens ::
    forall (f :: k -> *) x y zs.
    Lens (NP f (x ': zs)) (NP f (y ': zs)) (f x) (f y)
headLens = lens g s
  where
    g :: NP f (x ': zs) -> f x
    g (x  :* _zs)   = x

    s :: NP f (x ': zs) -> f y -> NP f (y ': zs)
    s (_x :*  zs) y = y :* zs

tailLens ::
    forall (f :: k -> *) x ys zs.
    Lens (NP f (x ': ys)) (NP f (x ': zs)) (NP f ys) (NP f zs)
tailLens = lens g s
  where
    g :: NP f (x ': ys) -> NP f ys
    g (_x :*  ys)    = ys

    s :: NP f (x ': ys) -> NP f zs -> NP f (x ': zs)
    s (x  :* _ys) zs = x :* zs

instance Field1 (NP f (x ': zs)) (NP f (y ': zs)) (f x) (f y) where _1 = headLens
instance Field1 (POP f (x ': zs)) (POP f (y ': zs)) (NP f x) (NP f y) where _1 = from pop . _1

instance Field2 (NP f (a ': x ': zs)) (NP f (a ': y ': zs)) (f x) (f y) where _2 = tailLens . _1
instance Field2 (POP f (a ': x ': zs)) (POP f (a ': y ': zs)) (NP f x) (NP f y) where _2 = from pop . _2

instance Field3 (NP f (a ': b ': x ': zs)) (NP f (a ': b ': y ': zs)) (f x) (f y) where _3 = tailLens . _2
instance Field3 (POP f (a ': b ': x ': zs)) (POP f (a ': b ': y ': zs)) (NP f x) (NP f y) where _3 = from pop . _3

instance Field4 (NP f (a ': b ': c ': x ': zs)) (NP f (a ': b ': c ': y ': zs)) (f x) (f y) where _4 = tailLens . _3
instance Field4 (POP f (a ': b ': c ': x ': zs)) (POP f (a ': b ': c ': y ': zs)) (NP f x) (NP f y) where _4 = from pop . _4

instance Field5 (NP f (a ': b ': c ': d ': x ': zs)) (NP f (a ': b ': c ': d ': y ': zs)) (f x) (f y) where _5 = tailLens . _4
instance Field5 (POP f (a ': b ': c ': d ': x ': zs)) (POP f (a ': b ': c ': d ': y ': zs)) (NP f x) (NP f y) where _5 = from pop . _5

instance Field6 (NP f (a ': b ': c ': d ': e ': x ': zs)) (NP f (a ': b ': c ': d ': e ': y ': zs)) (f x) (f y) where _6 = tailLens . _5
instance Field6 (POP f (a ': b ': c ': d ': e ': x ': zs)) (POP f (a ': b ': c ': d ': e ': y ': zs)) (NP f x) (NP f y) where _6 = from pop . _6

instance Field7 (NP f' (a ': b ': c ': d ': e ': f ': x ': zs)) (NP f' (a ': b ': c ': d ': e ': f ': y ': zs)) (f' x) (f' y) where _7 = tailLens . _6
instance Field7 (POP f' (a ': b ': c ': d ': e ': f ': x ': zs)) (POP f' (a ': b ': c ': d ': e ': f ': y ': zs)) (NP f' x) (NP f' y) where _7 = from pop . _7

instance Field8 (NP f' (a ': b ': c ': d ': e ': f ': g ': x ': zs)) (NP f' (a ': b ': c ': d ': e ': f ': g ': y ': zs)) (f' x) (f' y) where _8 = tailLens . _7
instance Field8 (POP f' (a ': b ': c ': d ': e ': f ': g ': x ': zs)) (POP f' (a ': b ': c ': d ': e ': f ': g ': y ': zs)) (NP f' x) (NP f' y) where _8 = from pop . _8

instance Field9 (NP f' (a ': b ': c ': d ': e ': f ': g ': h ': x ': zs)) (NP f' (a ': b ': c ': d ': e ': f ': g ': h ': y ': zs)) (f' x) (f' y) where _9 = tailLens . _8
instance Field9 (POP f' (a ': b ': c ': d ': e ': f ': g ': h ': x ': zs)) (POP f' (a ': b ': c ': d ': e ': f ': g ': h ': y ': zs)) (NP f' x) (NP f' y) where _9 = from pop . _9

-------------------------------------------------------------------------------
-- Sums
-------------------------------------------------------------------------------

singletonS ::
    forall (f :: k -> *) x y.
    Iso (f x) (f y) (NS f '[x]) (NS f '[y])
singletonS = iso s g
  where
    g :: NS f '[y] -> f y
    g (Z y)   = y
#if __GLASGOW_HASKELL__ < 800
    g _ = error "singletonS"
#else
    g (S x) = case x of {}
#endif

    s :: f x -> NS f '[x]
    s x = Z x

unSingletonS ::
    forall (f :: k -> *) x y.
    Iso (NS f '[x]) (NS f '[y]) (f x) (f y)
unSingletonS = from singletonS

_Z ::
    forall (f :: k -> *) x y zs.
    Prism (NS f (x ': zs)) (NS f (y ': zs)) (f x) (f y)
_Z = prism Z p
  where
    p :: NS f (x ': zs) -> Either (NS f (y ': zs)) (f x)
    p (Z x)  = Right x
    p (S xs) = Left (S xs)

_S ::
    forall (f :: k -> *) x ys zs.
    Prism (NS f (x ': ys)) (NS f (x ': zs)) (NS f ys) (NS f zs)
_S = prism S p
  where
    p :: NS f (x ': ys) -> Either (NS f (x ': zs)) (NS f ys)
    p (Z x)  = Left $ Z x
    p (S xs) = Right $ xs

-------------------------------------------------------------------------------
-- DatatypeInfo
-------------------------------------------------------------------------------

moduleName :: Lens' (DatatypeInfo xss) ModuleName
moduleName = lens g s
  where
    g :: DatatypeInfo xss -> ModuleName
#if MIN_VERSION_generics_sop(0,5,0)
    g (ADT m _ _ _)   = m
#else
    g (ADT m _ _)     = m
#endif
    g (Newtype m _ _) = m

    s :: DatatypeInfo xss -> ModuleName -> DatatypeInfo xss
#if MIN_VERSION_generics_sop(0,5,0)
    s (ADT _ n cs ss) m = ADT m n cs ss
#else
    s (ADT _ n cs)    m = ADT m n cs
#endif
    s (Newtype _ n c) m = Newtype m n c

datatypeName :: Lens' (DatatypeInfo xss) DatatypeName
datatypeName = lens g s
  where
    g :: DatatypeInfo xss -> DatatypeName
#if MIN_VERSION_generics_sop(0,5,0)
    g (ADT _ n _ _)   = n
#else
    g (ADT _ n _)     = n
#endif
    g (Newtype _ n _) = n

    s :: DatatypeInfo xss -> DatatypeName -> DatatypeInfo xss
#if MIN_VERSION_generics_sop(0,5,0)
    s (ADT m _ cs ss) n = ADT m n cs ss
#else
    s (ADT m _ cs)    n = ADT m n cs
#endif
    s (Newtype m _ c) n = Newtype m n c

constructorInfo :: Lens' (DatatypeInfo xss) (NP ConstructorInfo xss)
constructorInfo = lens g s
  where
    g :: DatatypeInfo xss -> NP ConstructorInfo xss
#if MIN_VERSION_generics_sop(0,5,0)
    g (ADT _ _ cs _)  = cs
#else
    g (ADT _ _ cs)    = cs
#endif
    g (Newtype _ _ c) = c :* Nil

    s :: DatatypeInfo xss -> NP ConstructorInfo xss -> DatatypeInfo xss
#if MIN_VERSION_generics_sop(0,5,0)
    s (ADT m n _ ss)  cs          = ADT m n cs ss
#else
    s (ADT m n _)     cs          = ADT m n cs
#endif
    s (Newtype m n _) (c :* Nil)  = Newtype m n c
#if __GLASGOW_HASKELL__ < 800
    s _ _ = error "constructorInfo set: impossible happened"
#endif

-- | /Note:/ 'Infix' constructor has operator as a 'ConstructorName'. Use as
-- setter with care.
constructorName :: Lens' (ConstructorInfo xs) ConstructorName
constructorName f (Constructor n      ) = (\ n' -> Constructor n'      ) `fmap` f n
constructorName f (Infix       n a fix) = (\ n' -> Infix       n' a fix) `fmap` f n
constructorName f (Record      n finfo) = (\ n' -> Record      n' finfo) `fmap` f n

#if MIN_VERSION_generics_sop(0,5,0)
-- | Strictness info is only aviable for 'ADT' data. This combinator is available only with @generics-sop@ 0.5 or later.
strictnessInfo :: Traversal' (DatatypeInfo xss) (POP StrictnessInfo xss)
strictnessInfo _ di@(Newtype {}) = pure di
strictnessInfo f (ADT m n cs ss) = ADT m n cs <$> f ss
#endif