{-# LANGUAGE RankNTypes           #-}
{-# LANGUAGE FlexibleContexts     #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE GADTs                #-}
{-# LANGUAGE TypeOperators        #-}
{-# LANGUAGE DataKinds            #-}
{-# LANGUAGE PolyKinds            #-}
{-# LANGUAGE ScopedTypeVariables  #-}
{-# LANGUAGE TypeApplications     #-}
-- | A collection of combinators
--   for operating over sums of products.
module Generics.MRSOP.Base.Combinators where

import Data.Function (on)

import Control.Monad.Identity

import Generics.MRSOP.Base.Universe
import Generics.MRSOP.Base.Class
import Generics.MRSOP.Util

-- * Equality
--
-- $equality
--
-- Compares two elements for equality.

-- |Given a way to compare the constant types
--  within two values, compare the outer values for
--  syntatical equality.
geq :: forall ki fam codes ix
     . (Family ki fam codes)
    => (forall k . ki k -> ki k -> Bool)
    -> El fam ix -> El fam ix -> Bool
geq kp = eqFix kp `on` dfrom

-- * Compos
--
-- $compos
--
-- Applies a morphism everywhere in a structure.
--
-- Conceptually one can think of 'compos' as
-- having type @(b -> b) -> a -> a@. The semantics
-- is applying the morphism over @b@s wherever possible
-- inside a value of type @a@.
--
-- For our case, we need @a@ and @b@ to be elements of
-- the same family.

-- |Given a morphism for the @iy@ element of the family,
--  applies it everywhere in another element of
--  the family.
composM :: forall ki fam codes ix m
         . (Monad m , Family ki fam codes, IsNat ix)
        => (forall iy . IsNat iy => SNat iy -> El fam iy -> m (El fam iy))
        -> El fam ix -> m (El fam ix)
composM f = (sto @fam <$>)
          . mapRepM (\x -> f (getElSNat x) x)
          . sfrom @fam

-- |Non monadic version from above.
compos :: forall ki fam codes ix
        . (Family ki fam codes, IsNat ix)
       => (forall iy . IsNat iy => SNat iy -> El fam iy -> El fam iy)
       -> El fam ix -> El fam ix
compos f = runIdentity . composM (\iy -> return . f iy)

-- * Crush
--
-- $crush
--
-- Crush will collapse an entire value given only
-- an action to perform on the leaves and a way
-- of combining results.

-- | 'crushM' Applies its first parameter to all leaves,
--   combines the results with its second parameter.
crushM :: forall ki fam codes ix r m
        . (Monad m , Family ki fam codes)
       => (forall k. ki k -> m r) -> ([r] -> m r)
       -> El fam ix -> m r
crushM kstep combine
  = elimRep kstep (crushM kstep combine) (combine <.> sequence) . sfrom

-- | Non-monadic version
crush :: forall ki fam codes ix r
       . (Family ki fam codes)
      => (forall k. ki k -> r) -> ([r] -> r)
      -> El fam ix -> r
crush kstep combine = runIdentity . crushM (return . kstep) (return . combine)