{-# LANGUAGE RankNTypes        #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs             #-}
{-# LANGUAGE TypeOperators     #-}
{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE PolyKinds         #-}
{-# OPTIONS_GHC -Wno-orphans   #-}
-- | Standard representation of n-ary products.
module Generics.MRSOP.Base.NP
  ( SOP.NP(..)
  , appendNP
  , listPrfNP
  , mapNP
  , mapNPM
  , elimNP
  , elimNPM
  , zipNP
  , unzipNP
  , cataNP
  , cataNPM
  , eqNP
  ) where

import           Data.SOP.NP (NP(..))
import qualified Data.SOP.NP as SOP
import Generics.MRSOP.Util

-- |@since 2.3.0
instance EqHO f => EqHO (NP f) where
  eqHO (x :* xs) (y :* ys) = eqHO x y && eqHO xs ys
  eqHO Nil       Nil       = True

-- |@since 2.3.0
instance ShowHO f => ShowHO (NP f) where
  showsPrecHO _ Nil = showString "Nil"
  showsPrecHO d (x :* xs) = showParen (d > ifx_prec) $
    showsPrecHO (ifx_prec+1) x . showString " :* " . showsPrecHO (ifx_prec+1) xs
   where ifx_prec = 5


-- * Relation to IsList predicate

-- |Append two values of type 'NP'
appendNP :: NP p xs -> NP p ys -> NP p (xs :++: ys)
appendNP Nil        ays = ays
appendNP (a :* axs) ays = a :* appendNP axs ays

-- |Proves that the index of a value of type 'NP' is a list.
--  This is useful for pattern matching on said list without
--  having to carry the product around.
listPrfNP :: NP p xs -> ListPrf xs
listPrfNP Nil       = LP_Nil
listPrfNP (_ :* xs) = LP_Cons $ listPrfNP xs

-- * Map, Elim and Zip

-- |Maps a natural transformation over a n-ary product
mapNP :: f :-> g -> NP f ks -> NP g ks
mapNP _ Nil       = Nil
mapNP f (k :* ks) = f k :* mapNP f ks

-- |Maps a monadic natural transformation over a n-ary product
mapNPM :: (Monad m) => (forall x . f x -> m (g x)) -> NP f ks -> m (NP g ks)
mapNPM _ Nil       = return Nil
mapNPM f (k :* ks) = (:*) <$> f k <*> mapNPM f ks

-- |Eliminates the product using a provided function.
elimNP :: (forall x . f x -> a) -> NP f ks -> [a]
elimNP _ Nil       = []
elimNP f (k :* ks) = f k : elimNP f ks

-- |Monadic eliminator
elimNPM :: (Monad m) => (forall x . f x -> m a) -> NP f ks -> m [a]
elimNPM _ Nil       = return []
elimNPM f (k :* ks) = (:) <$> f k <*> elimNPM f ks

-- |Combines two products into one.
zipNP :: NP f xs -> NP g xs -> NP (f :*: g) xs
zipNP Nil       Nil       = Nil
zipNP (f :* fs) (g :* gs) = (f :*: g) :* zipNP fs gs

-- |Unzips a combined product into two separate products
unzipNP :: NP (f :*: g) xs -> (NP f xs , NP g xs)
unzipNP Nil               = (Nil , Nil)
unzipNP (Pair f g :* fgs) = (f :*) *** (g :*) $ unzipNP fgs

-- * Catamorphism

-- |Consumes a value of type 'NP'.
cataNP :: (forall a as . f a  -> r as -> r (a : as))
       -> r '[]
       -> NP f xs -> r xs
cataNP _fCons fNil Nil       = fNil
cataNP fCons  fNil (k :* ks) = fCons k (cataNP fCons fNil ks)

-- |Consumes a value of type 'NP'.
cataNPM :: (Monad m)
        => (forall a as . f a  -> r as -> m (r (a : as)))
        -> m (r '[])
        -> NP f xs -> m (r xs)
cataNPM _fCons fNil Nil      = fNil
cataNPM fCons fNil (k :* ks) = cataNPM fCons fNil ks >>= fCons k


-- * Equality

-- |Compares two 'NP's pairwise with the provided function and
--  return the conjunction of the results.
eqNP :: (forall x. p x -> p x -> Bool)
     -> NP p xs -> NP p xs -> Bool
eqNP p x = and . elimNP (uncurry' p) . zipNP x