-- | Generalized lenses
--
-- Intended to be imported qualified
--
-- > import Generics.SOP.Lens as GLens
--
module Generics.SOP.Lens (
    -- * Generalized lenses
    GLens
  , lens
  , get
  , modify
  , set
    -- * Conversion
  , fromLens
  , fromIso
  , toLens
    -- * Generic computation of lenses for record type
  , glenses
    -- * Labels for the representation types
  , np
  , rep
  , sop
  , head
  , tail
  , i
  ) where

import Prelude hiding (id, (.), curry, uncurry, const, head, tail)
import Control.Arrow
import Control.Category
import Data.Label.Mono (Lens)
import Data.Label.Point (Iso(..))
import qualified Data.Label.Mono as Lens

import Generics.SOP

{-------------------------------------------------------------------------------
  Generalized lens using two categories
-------------------------------------------------------------------------------}

-- | GLens generalizes a monomorphic lens by allowing for different categories
-- for the getter and modifier
data GLens r w a b = GLens (r a b) (w (w b b, a) a)

instance (Category r, ArrowApply w) => Category (GLens r w) where
  id = GLens id app
  (GLens f m) . (GLens g n) = GLens (f . g) (uncurry (curry n . curry m))

lens :: r a b -> w (w b b, a) a -> GLens r w a b
lens = GLens

get :: GLens r w a b -> r a b
get (GLens f _) = f

modify :: GLens r w a b -> w (w b b, a) a
modify (GLens _ g) = g

set :: Arrow w => GLens r w a b -> w (b, a) a
set l = modify l . first (arr const)

{-------------------------------------------------------------------------------
  Conversion
-------------------------------------------------------------------------------}

fromLens :: (Arrow r, ArrowApply w) => Lens (->) a b -> GLens r w a b
fromLens l =
  GLens (arr (Lens.get l))
        (uncurry $ \h -> arr (Lens.set l) . (h . arr (Lens.get l) &&& id))

fromIso :: (Arrow r, ArrowApply w) => Iso (->) a b -> GLens r w a b
fromIso (Iso f g) = GLens (arr f) (uncurry $ \h -> arr g . h . arr f)

toLens :: GLens cat cat a b -> Lens cat a b
toLens (GLens f g) = Lens.lens f g

{-------------------------------------------------------------------------------
  Generic computation of all lenses for a record type
-------------------------------------------------------------------------------}

glenses :: forall r w a xs. (Generic a, Code a ~ '[xs], Arrow r, ArrowApply w) => NP (GLens r w a) xs
glenses = case sList :: SList (Code a) of
            SCons -> hliftA (\l -> l . sop . rep) np
#if __GLASGOW_HASKELL__ < 800
            _     -> error "inaccessible"
#endif

{-------------------------------------------------------------------------------
  Generalized lenses for representation types
-------------------------------------------------------------------------------}

np :: forall r w xs. (Arrow r, ArrowApply w, SListI xs) => NP (GLens r w (NP I xs)) xs
np = case sList :: SList xs of
      SNil  -> Nil
      SCons -> i . head :* hliftA (. tail) np

rep :: (Arrow r, ArrowApply w, Generic a) => GLens r w a (Rep a)
rep = fromIso $ Iso from to

sop :: (Arrow r, ArrowApply w) => GLens r w (SOP f '[xs]) (NP f xs)
sop = fromIso $ Iso (\(SOP (Z x)) -> x) (SOP . Z)

head :: (Arrow r, ArrowApply w) => GLens r w (NP f (x ': xs)) (f x)
head = fromLens $ Lens.lens (\(x :* _) -> x) (\(f, x :* xs) -> (f x :* xs))

tail :: (Arrow r, ArrowApply w) => GLens r w (NP f (x ': xs)) (NP f xs)
tail = fromLens $ Lens.lens (\(_ :* xs) -> xs) (\(f, x :* xs) -> (x :* f xs))

i :: (Arrow r, ArrowApply w) => GLens r w (I a) a
i = fromIso $ Iso unI I

{-------------------------------------------------------------------------------
  Auxiliary
-------------------------------------------------------------------------------}

const :: Arrow arr => c -> arr b c
const a = arr (\_ -> a)

curry :: Arrow cat => cat (a, b) c -> (a -> cat b c)
curry m a = m . (const a &&& id)

uncurry :: ArrowApply cat => (a -> cat b c) -> cat (a, b) c
uncurry a = app . arr (first a)