{-# LANGUAGE TypeOperators              #-}
{-# LANGUAGE FlexibleContexts           #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Generics.EMGM.Functions.Crush
-- Copyright   :  (c) 2008 Universiteit Utrecht
-- License     :  BSD3
--
-- Maintainer  :  generics@haskell.org
-- Stability   :  experimental
-- Portability :  non-portable
--
-- Summary: Generic functions that crush a container into an iteration over
-- its elements.
--
-- Crush is a datatype-generic operation on container types. It is a
-- generalization of folds, but it is not a catamorphism. To understand how
-- crush works, one can think of it as generating a list of all elements and
-- mapping an accumulating function over each one. With this image in mind, it
-- is evident that (unlike a catamorphism) very little information can be
-- determined about the structure of the container.
--
-- The EMGM implementation of 'crush' can not inherently know the associativity
-- of the binary operator. Consequently, associativity is left as an argument,
-- but there are variants specific to left- and right-associativity for
-- convenience.
--
-- Many standard Haskell datatypes (e.g. @[]@, @Data.Tree@) are designed such
-- that a constructor with more than one argument (i.e. a product structurally
-- represented by @(:*:)@) has the element on the left and any recursive points
-- towards the right. Due to this, the right-associative functions would
-- typically produce the expected values. See examples in the comments for
-- 'flattenr' and 'firstr'.
-----------------------------------------------------------------------------

module Generics.EMGM.Functions.Crush (

  -- * Crush functions

  Crush(..),
  Assoc(..),
  crush,
  crushl,
  crushr,

  -- * Left- and right-associative derived functions
  -- | The operation of these functions changes depending on the associativity
  -- of the binary operator.

  flatten,
  flattenl,
  flattenr,

  first,
  firstl,
  firstr,

  -- * Other derived functions
  -- | The operation of these functions is independent of the associativity of
  -- the binary operator. Many of these functions are generalizations of the
  -- 'Prelude' functions of the same name

  and,
  or,
  any,
  all,
  sum,
  product,
  minimum,
  maximum,
  elem,
  notElem,

) where

import Prelude hiding (and, or, any, all, elem, notElem, sum, product, max, min, maximum, minimum)

import Generics.EMGM.Common.Base
import Generics.EMGM.Common.Representation
import Generics.EMGM.Functions.Compare

-----------------------------------------------------------------------------
-- Types
-----------------------------------------------------------------------------

-- | Associativity of the binary operator used for 'crush'
data Assoc = AssocLeft  -- ^ Left-associative
           | AssocRight -- ^ Right-associative

-- | The type of a generic function that takes an associativity and two
-- arguments of different types and returns a value of the type of the second.
newtype Crush b a = Crush { selCrush :: Assoc -> a -> b -> b }

-----------------------------------------------------------------------------
-- Generic instance declaration
-----------------------------------------------------------------------------

rconstantCrush :: Assoc -> a -> b -> b
rconstantCrush _ _ = id

rsumCrush :: Crush d a -> Crush d b -> Assoc -> a :+: b -> d -> d
rsumCrush ra _  asc (L a) = selCrush ra asc a
rsumCrush _  rb asc (R b) = selCrush rb asc b

rprodCrush :: Crush d a -> Crush d b -> Assoc -> a :*: b -> d -> d
rprodCrush ra rb asc@AssocLeft  (a :*: b) = selCrush rb asc b . selCrush ra asc a
rprodCrush ra rb asc@AssocRight (a :*: b) = selCrush ra asc a . selCrush rb asc b

rconCrush :: ConDescr -> Crush d a -> Assoc -> a -> d -> d
rconCrush _ = selCrush

rtypeCrush :: EP b a -> Crush d a -> Assoc -> b -> d -> d
rtypeCrush ep ra asc = selCrush ra asc . from ep

instance Generic (Crush b) where
  rconstant      = Crush rconstantCrush
  rsum     ra rb = Crush (rsumCrush ra rb)
  rprod    ra rb = Crush (rprodCrush ra rb)
  rcon  cd ra    = Crush (rconCrush cd ra)
  rtype ep ra    = Crush (rtypeCrush ep ra)

-----------------------------------------------------------------------------
-- Exported functions
-----------------------------------------------------------------------------

-- | Apply a function (@a -> b -> b@) to each element (@a@) of a container (@f
-- a@) and an accumulator value (@b@) to produce an accumulated result (@b@).
--
-- This is the most general form in which you must specify the associativity.
-- You may prefer to use 'crushr' or 'crushl'.
crush ::
  (FRep (Crush b) f)
  => Assoc         -- ^ Associativity of the binary operator (left or right).
  -> (a -> b -> b) -- ^ Binary operator on @a@-elements with an accumulator.
  -> b             -- ^ The initial @b@-value for the binary operator.
  -> f a           -- ^ Container of @a@-values.
  -> b             -- ^ The result after applying the above operator on all
                   -- @a@-values.
crush asc f z x = selCrush (frep (Crush f')) asc x z
  where f' _ = f -- necessary to skip the asc arg

-- | A right-associative variant of 'crush'.
crushr :: (FRep (Crush b) f) => (a -> b -> b) -> b -> f a -> b
crushr = crush AssocRight

-- | A left-associative variant of 'crush'.
crushl :: (FRep (Crush b) f) => (a -> b -> b) -> b -> f a -> b
crushl = crush AssocLeft

-- | Flatten the elements of a container into a list.
--
-- This is the most general form in which you must specify the associativity.
-- You may prefer to use 'flattenr' or 'flattenl'.
flatten :: (FRep (Crush [a]) f) => Assoc -> f a -> [a]
flatten asc = crush asc (:) []

-- | A right-associative variant of 'flatten'.
--
-- Note that, for a list @ls :: [a]@, @flattenr ls == ls@.
flattenr :: (FRep (Crush [a]) f) => f a -> [a]
flattenr = flatten AssocRight

-- | A left-associative variant of 'flatten'.
--
-- Note that, for a list @ls :: [a]@, @flattenl ls == reverse ls@.
flattenl :: (FRep (Crush [a]) f) => f a -> [a]
flattenl = flatten AssocLeft

-- | Extract the first element of a container. If the container is empty, return
-- @Nothing@.
--
-- This is the most general form in which you must specify the associativity.
-- You may prefer to use 'firstr' or 'firstl'.
first :: (FRep (Crush [a]) f) => Assoc -> f a -> Maybe a
first asc as = case flatten asc as of
                 []  -> Nothing
                 a:_ -> Just a

-- | A right-associative variant of 'first'.
--
-- Note that, for a list @ls :: [a]@, @fromJust (firstr ls) == head ls@.
firstr :: (FRep (Crush [a]) f) => f a -> Maybe a
firstr = first AssocRight

-- | A left-associative variant of 'first'.
--
-- Note that, for a list @ls :: [a]@, @fromJust (firstl ls) == last ls@.
firstl :: (FRep (Crush [a]) f) => f a -> Maybe a
firstl = first AssocLeft

-- | Determine if an element is a member of a container. This is a
-- generalization of the 'Prelude' function of the same name.
elem :: (Rep Compare a, FRep (Crush Bool) f) => a -> f a -> Bool
elem x = any (eq x)

-- | Determine if an element is not a member of a container. This is a
-- generalization of the 'Prelude' function of the same name.
notElem :: (Rep Compare a, FRep (Crush Bool) f) => a -> f a -> Bool
notElem x = all (neq x)

-- | Compute the sum of all elements in a container. This is a generalization of
-- the 'Prelude' function of the same name.
sum :: (Num a, FRep (Crush a) f) => f a -> a
sum = crushr (+) 0

-- | Compute the product of all elements in a container. This is a
-- generalization of the 'Prelude' function of the same name.
product :: (Num a, FRep (Crush a) f) => f a -> a
product = crushr (*) 1

-- | Determine the maximum element of a container. If the container is empty,
-- return 'Nothing'. This is a generalization of the 'Prelude' function of the
-- same name.
maximum :: (Rep Compare a, FRep (Crush (Maybe a)) f) => f a -> Maybe a
maximum = crushr f Nothing
  where f x Nothing  = Just x
        f x (Just y) = Just $ max x y

-- | Determine the minimum element of a container. If the container is empty,
-- return 'Nothing'. This is a generalization of the 'Prelude' function of the
-- same name.
minimum :: (Rep Compare a, FRep (Crush (Maybe a)) f) => f a -> Maybe a
minimum = crushr f Nothing
  where f x Nothing  = Just x
        f x (Just y) = Just $ min x y

-- | Compute the conjunction of all elements in a container. This is a
-- generalization of the 'Prelude' function of the same name.
and :: (FRep (Crush Bool) f) => f Bool -> Bool
and = crushr (&&) True

-- | Compute the disjunction of all elements in a container. This is a
-- generalization of the 'Prelude' function of the same name.
or :: (FRep (Crush Bool) f) => f Bool -> Bool
or = crushr (||) False

-- | Determine if any element in a container satisfies the predicate @p@. This
-- is a generalization of the 'Prelude' function of the same name.
any :: (FRep (Crush Bool) f) => (a -> Bool) -> f a -> Bool
any p = crushr (\x b -> b || p x) False

-- | Determine if all elements in a container satisfy the predicate @p@. This
-- is a generalization the 'Prelude' function of the same name.
all :: (FRep (Crush Bool) f) => (a -> Bool) -> f a -> Bool
all p = crushr (\x b -> b && p x) True