{-# LANGUAGE NoMonomorphismRestriction #-}

module Data.Functor.Utils (module Data.Functor.Utils, module X) where

import Prelude hiding ((.))
import GHC.Exts (Constraint)
import Data.Traversable (mapM)
import Data.Functor.Compose as X (Compose(Compose), getCompose)
import Control.Lens


-- === Multi-constraint type families === --

type family Functors lst :: Constraint where
    Functors '[]       = ()
    Functors (f ': fs) = (Functor f, Functors fs)

type family Applicatives lst :: Constraint where
    Applicatives '[]       = ()
    Applicatives (f ': fs) = (Applicative f, Applicatives fs)


-- === Nested fmaps === --

fmap0                                  ::                               (a -> b) ->                    a     ->                    b
(.)                          , ()     :: Functor  f1                => (a -> b) ->                 f1 a     ->                 f1 b
fmap2, (.:)  , (<<$>>)       , (∘∘)    :: Functors '[f1,f2]          => (a -> b) ->             f2 (f1 a)    ->             f2 (f1 b)
fmap3, (.:.) , (<<<$>>>)     , (∘∘∘)   :: Functors '[f1,f2,f3]       => (a -> b) ->         f3 (f2 (f1 a))   ->         f3 (f2 (f1 b))
fmap4, (.::) , (<<<<$>>>>)   , (∘∘∘∘)  :: Functors '[f1,f2,f3,f4]    => (a -> b) ->     f4 (f3 (f2 (f1 a)))  ->     f4 (f3 (f2 (f1 b)))
fmap5, (.::.), (<<<<<$>>>>>) , (∘∘∘∘∘) :: Functors '[f1,f2,f3,f4,f5] => (a -> b) -> f5 (f4 (f3 (f2 (f1 a)))) -> f5 (f4 (f3 (f2 (f1 b))))

fmap0 = ($)        ; {-# INLINE fmap0 #-}
fmap1 = fmap       ; {-# INLINE fmap1 #-}
fmap2 = fmap.fmap  ; {-# INLINE fmap2 #-}
fmap3 = fmap.fmap2 ; {-# INLINE fmap3 #-}
fmap4 = fmap.fmap3 ; {-# INLINE fmap4 #-}
fmap5 = fmap.fmap4 ; {-# INLINE fmap5 #-}

-- === Dot operators === --

infixr 9 .
infixr 8 .:
infixr 8 .:.
infixr 8 .::
infixr 8 .::.
(.)      = fmap  ; {-# INLINE (.)      #-}
(.:)     = fmap2 ; {-# INLINE (.:)     #-}
(.:.)    = fmap3 ; {-# INLINE (.:.)    #-}
(.::)    = fmap4 ; {-# INLINE (.::)    #-}
(.::.)   = fmap5 ; {-# INLINE (.::.)   #-}

-- === UTF8 operators === --

infixr 9 
infixr 8 ∘∘
infixr 8 ∘∘∘
infixr 8 ∘∘∘∘
infixr 8 ∘∘∘∘∘
()      = fmap  ; {-# INLINE (∘)     #-}
(∘∘)     = fmap2 ; {-# INLINE (∘∘)    #-}
(∘∘∘)    = fmap3 ; {-# INLINE (∘∘∘)   #-}
(∘∘∘∘)   = fmap4 ; {-# INLINE (∘∘∘∘)  #-}
(∘∘∘∘∘)  = fmap5 ; {-# INLINE (∘∘∘∘∘) #-}

-- === Applicative operators === --

infixl 4 <<$>>
infixl 4 <<<$>>>
infixl 4 <<<<$>>>>
infixl 4 <<<<<$>>>>>
(<<$>>)       = fmap2 ; {-# INLINE (<<$>>)       #-}
(<<<$>>>)     = fmap3 ; {-# INLINE (<<<$>>>)     #-}
(<<<<$>>>>)   = fmap4 ; {-# INLINE (<<<<$>>>>)   #-}
(<<<<<$>>>>>) = fmap5 ; {-# INLINE (<<<<<$>>>>>) #-}

infixl 4 <<*>>
infixl 4 <<<*>>>
infixl 4 <<<<*>>>>
infixl 4 <<<<<*>>>>>
(<<*>>)       :: Applicatives '[f1, f2]             =>             f2 (f1 (a -> b))    ->             f2 (f1 a)    ->             f2 (f1 b)
(<<<*>>>)     :: Applicatives '[f1, f2, f3]         =>         f3 (f2 (f1 (a -> b)))   ->         f3 (f2 (f1 a))   ->         f3 (f2 (f1 b))
(<<<<*>>>>)   :: Applicatives '[f1, f2, f3, f4]     =>     f4 (f3 (f2 (f1 (a -> b))))  ->     f4 (f3 (f2 (f1 a)))  ->     f4 (f3 (f2 (f1 b)))
(<<<<<*>>>>>) :: Applicatives '[f1, f2, f3, f4, f5] => f5 (f4 (f3 (f2 (f1 (a -> b))))) -> f5 (f4 (f3 (f2 (f1 a)))) -> f5 (f4 (f3 (f2 (f1 b))))
(<<*>>)       = (<*>) . fmap (<*>)       ; {-# INLINE (<<*>>)       #-}
(<<<*>>>)     = (<*>) . fmap (<<*>>)     ; {-# INLINE (<<<*>>>)     #-}
(<<<<*>>>>)   = (<*>) . fmap (<<<*>>>)   ; {-# INLINE (<<<<*>>>>)   #-}
(<<<<<*>>>>>) = (<*>) . fmap (<<<<*>>>>) ; {-# INLINE (<<<<<*>>>>>) #-}


-- === Functors remembering call args === --

infixl 4 |$
infixl 4 $|
(|$) :: (a -> b) -> a -> (a, b)
($|) :: (a -> b) -> a -> (b, a)
f |$ a = (a, f a) ; {-# INLINE (|$) #-}
f $| a = (f a, a) ; {-# INLINE ($|) #-}

infixl 4 <|$>
infixl 4 <$|>
(<|$>) :: Functor f => (a -> b) -> f a -> f (a, b)
(<$|>) :: Functor f => (a -> b) -> f a -> f (b, a)
f <|$> a = (f |$) <$> a ; {-# INLINE (<|$>) #-}
f <$|> a = (f $|) <$> a ; {-# INLINE (<$|>) #-}


-- === Functor composition === --

composed :: Iso' (f (g a)) (Compose f g a)
composed = iso Compose getCompose ; {-# INLINE composed #-}


-- FIXME[WD]: Think if lenses doesnt provide any counterpart to it.
-- === Nested functors === --

-- nested lenses
-- | following functions are usefull when operating on nested structures with lenses, for example
-- | given function foo :: a -> m (n a) and a lens l :: Lens' x a, we can use
-- | nested l foo to get signature of x -> m (n x)

-- nested :: (Functor m, Functor n) => Lens a b c d -> (c -> m (n d)) -> (a -> m (n b))
-- nested :: (Functor f10, Functor f0) => (f0 (Compose m1 n1 a1) -> f10 (Compose m n a)) -> f0 (m1 (n1 a1)) -> f10 (m (n a))
nested l f = getCompose . l (fmap Compose f) ; {-# INLINE nested #-}