{-# LANGUAGE UndecidableInstances , FlexibleContexts , MultiParamTypeClasses , FlexibleInstances , GeneralizedNewtypeDeriving, TypeOperators, ScopedTypeVariables, CPP #-}
#if __GLASGOW_HASKELL__ >= 702
{-# LANGUAGE Trustworthy #-}
#endif

#ifndef MIN_VERSION_semigroups
#define MIN_VERSION_semigroups(x,y,z) 1
#endif

-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Semigroup.Reducer
-- Copyright   :  (c) Edward Kmett 2009
-- License     :  BSD3
-- Maintainer  :  ekmett@gmail.com
-- Stability   :  experimental
-- Portability :  non-portable (MPTCs)
--
-- A @c@-'Reducer' is a 'Semigroup' with a canonical mapping from @c@ to the Semigroup.
--
-----------------------------------------------------------------------------

module Data.Semigroup.Reducer
  ( Reducer(..)
  , foldMapReduce, foldMapReduce1
  , foldReduce, foldReduce1
  , pureUnit
  , returnUnit
  , Count(..)
  ) where

#if __GLASGOW_HASKELL__ < 710
import Control.Applicative
#endif

import qualified Data.Monoid as Monoid
import Data.Semigroup as Semigroup
import Data.Semigroup.Foldable
import Data.Semigroup.Instances ()
import Data.Hashable

#if __GLASGOW_HASKELL__ < 710
import Data.Foldable
#endif

import Data.FingerTree

import qualified Data.Sequence as Seq
import Data.Sequence (Seq)
import qualified Data.Set as Set
import Data.Set (Set)
import qualified Data.IntSet as IntSet
import Data.IntSet (IntSet)
import qualified Data.IntMap as IntMap
import Data.IntMap (IntMap)
import qualified Data.Map as Map
import Data.Map (Map)
import qualified Data.HashMap.Lazy as HashMap
import Data.HashMap.Lazy (HashMap)

#ifdef LANGUAGE_DeriveDataTypeable
import Data.Data
#endif

--import Text.Parsec.Prim

-- | This type may be best read infix. A @c `Reducer` m@ is a 'Semigroup' @m@ that maps
-- values of type @c@ through @unit@ to values of type @m@. A @c@-'Reducer' may also
-- supply operations which tack-on another @c@ to an existing 'Monoid' @m@ on the left
-- or right. These specialized reductions may be more efficient in some scenarios
-- and are used when appropriate by a 'Generator'. The names 'cons' and 'snoc' work
-- by analogy to the synonymous operations in the list monoid.
--
-- This class deliberately avoids functional-dependencies, so that () can be a @c@-Reducer
-- for all @c@, and so many common reducers can work over multiple types, for instance,
-- First and Last may reduce both @a@ and 'Maybe' @a@. Since a 'Generator' has a fixed element
-- type, the input to the reducer is generally known and extracting from the monoid usually
-- is sufficient to fix the result type. Combinators are available for most scenarios where
-- this is not the case, and the few remaining cases can be handled by using an explicit
-- type annotation.
--
-- Minimal definition: 'unit'
class Semigroup m => Reducer c m where
  -- | Convert a value into a 'Semigroup'
  unit :: c -> m
  -- | Append a value to a 'Semigroup' for use in left-to-right reduction
  snoc :: m -> c -> m
  -- | Prepend a value onto a 'Semigroup' for use during right-to-left reduction
  cons :: c -> m -> m

  snoc m = (<>) m . unit
  cons = (<>) . unit

-- | Apply a 'Reducer' to a 'Foldable' container, after mapping the contents into a suitable form for reduction.
foldMapReduce :: (Foldable f, Monoid m, Reducer e m) => (a -> e) -> f a -> m
foldMapReduce f = foldMap (unit . f)

foldMapReduce1 :: (Foldable1 f, Reducer e m) => (a -> e) -> f a -> m
foldMapReduce1 f = foldMap1 (unit . f)

-- | Apply a 'Reducer' to a 'Foldable' mapping each element through 'unit'
foldReduce :: (Foldable f, Monoid m, Reducer e m) => f e -> m
foldReduce = foldMap unit

-- | Apply a 'Reducer' to a 'Foldable1' mapping each element through 'unit'
foldReduce1 :: (Foldable1 f, Reducer e m) => f e -> m
foldReduce1 = foldMap1 unit

returnUnit :: (Monad m, Reducer c n) => c -> m n
returnUnit = return . unit

pureUnit :: (Applicative f, Reducer c n) => c -> f n
pureUnit = pure . unit

newtype Count = Count { getCount :: Int } deriving
  ( Eq, Ord, Show, Read
#ifdef LANGUAGE_DeriveDataTypeable
  , Data, Typeable
#endif
  )

instance Hashable Count where
  hashWithSalt n = hashWithSalt n . getCount

instance Semigroup Count where
  Count a <> Count b = Count (a + b)
#if MIN_VERSION_semigroups(0,17,0)
  stimes n (Count a) = Count $ fromIntegral n * a
#else
  times1p n (Count a) = Count $ (fromIntegral n + 1) * a
#endif

instance Monoid Count where
  mempty = Count 0
  Count a `mappend` Count b = Count (a + b)

instance Reducer a Count where
  unit _ = Count 1
  Count n `snoc` _ = Count (n + 1)
  _ `cons` Count n = Count (n + 1)

instance (Reducer c m, Reducer c n) => Reducer c (m,n) where
  unit x = (unit x,unit x)
  (m,n) `snoc` x = (m `snoc` x, n `snoc` x)
  x `cons` (m,n) = (x `cons` m, x `cons` n)

instance (Reducer c m, Reducer c n, Reducer c o) => Reducer c (m,n,o) where
  unit x = (unit x,unit x, unit x)
  (m,n,o) `snoc` x = (m `snoc` x, n `snoc` x, o `snoc` x)
  x `cons` (m,n,o) = (x `cons` m, x `cons` n, x `cons` o)

instance (Reducer c m, Reducer c n, Reducer c o, Reducer c p) => Reducer c (m,n,o,p) where
  unit x = (unit x,unit x, unit x, unit x)
  (m,n,o,p) `snoc` x = (m `snoc` x, n `snoc` x, o `snoc` x, p `snoc` x)
  x `cons` (m,n,o,p) = (x `cons` m, x `cons` n, x `cons` o, x `cons` p)

instance Reducer c [c] where
  unit = return
  cons = (:)
  xs `snoc` x = xs ++ [x]

instance Reducer c () where
  unit _ = ()
  _ `snoc` _ = ()
  _ `cons` _ = ()

instance Reducer Bool Any where
  unit = Any

instance Reducer Bool All where
  unit = All

instance Reducer (a -> a) (Endo a) where
  unit = Endo

instance Semigroup a => Reducer a (Dual a) where
  unit = Dual

instance Num a => Reducer a (Sum a) where
  unit = Sum

instance Num a => Reducer a (Product a) where
  unit = Product

instance Ord a => Reducer a (Min a) where
  unit = Min

instance Ord a => Reducer a (Max a) where
  unit = Max

instance Reducer (Maybe a) (Monoid.First a) where
  unit = Monoid.First

instance Reducer a (Semigroup.First a) where
  unit = Semigroup.First

instance Reducer (Maybe a) (Monoid.Last a) where
  unit = Monoid.Last

instance Reducer a (Semigroup.Last a) where
  unit = Semigroup.Last

instance Measured v a => Reducer a (FingerTree v a) where
  unit = singleton
  cons = (<|)
  snoc = (|>)

--instance (Stream s m t, Reducer c a) => Reducer c (ParsecT s u m a) where
--    unit = return . unit

instance Reducer a (Seq a) where
  unit = Seq.singleton
  cons = (Seq.<|)
  snoc = (Seq.|>)

instance Reducer Int IntSet where
  unit = IntSet.singleton
  cons = IntSet.insert
  snoc = flip IntSet.insert -- left bias irrelevant

instance Ord a => Reducer a (Set a) where
  unit = Set.singleton
  cons = Set.insert
  -- pedantic about order in case 'Eq' doesn't implement structural equality
  snoc s m | Set.member m s = s
           | otherwise = Set.insert m s

instance Reducer (Int, v) (IntMap v) where
  unit = uncurry IntMap.singleton
  cons = uncurry IntMap.insert
  snoc = flip . uncurry . IntMap.insertWith $ const id

instance Ord k => Reducer (k, v) (Map k v) where
  unit = uncurry Map.singleton
  cons = uncurry Map.insert
  snoc = flip . uncurry . Map.insertWith $ const id


instance (Eq k, Hashable k) => Reducer (k, v) (HashMap k v) where
  unit = uncurry HashMap.singleton
  cons = uncurry HashMap.insert
  snoc = flip . uncurry . HashMap.insertWith $ const id

instance Monoid m => Reducer m (WrappedMonoid m) where
  unit = WrapMonoid