{-# LANGUAGE BangPatterns, CPP, DefaultSignatures, DerivingVia, LambdaCase #-}
{-# LANGUAGE OverloadedStrings, QuantifiedConstraints, StandaloneDeriving  #-}
{-# LANGUAGE TemplateHaskell, TypeOperators                                #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Control.Subcategory.Foldable
  ( CFoldable(..),
    ctoList,
    CTraversable(..),
    CFreeMonoid(..),
    cfromList,
    cfolded, cfolding,
    cctraverseFreeMonoid,
    cctraverseZipFreeMonoid
  ) where
import           Control.Applicative                  (ZipList, getZipList)
import           Control.Arrow                        (first, second, (***))
import qualified Control.Foldl                        as L
import           Control.Monad                        (forM)
import           Control.Subcategory.Applicative
import           Control.Subcategory.Functor
import           Control.Subcategory.Pointed
import           Control.Subcategory.Wrapper.Internal
import           Control.Subcategory.Zip
import           Data.Coerce
import           Data.Complex                         (Complex)
import           Data.Foldable
import           Data.Functor.Const                   (Const)
import           Data.Functor.Contravariant           (Contravariant, contramap,
                                                       phantom)
import           Data.Functor.Identity                (Identity)
import qualified Data.Functor.Product                 as SOP
import qualified Data.Functor.Sum                     as SOP
import qualified Data.HashMap.Strict                  as HM
import qualified Data.HashSet                         as HS
import qualified Data.IntMap.Strict                   as IM
import qualified Data.IntSet                          as IS
import           Data.Kind                            (Type)
import           Data.List                            (uncons)
import           Data.List                            (intersperse)
import           Data.List                            (nub)
import qualified Data.List                            as List
import           Data.List.NonEmpty                   (NonEmpty)
import qualified Data.List.NonEmpty                   as NE
import qualified Data.Map                             as M
import           Data.Maybe
import           Data.Monoid
import qualified Data.Monoid                          as Mon
import           Data.MonoTraversable                 hiding (WrappedMono,
                                                       unwrapMono)
import           Data.Ord                             (Down)
import qualified Data.Primitive.Array                 as A
import qualified Data.Primitive.PrimArray             as PA
import qualified Data.Primitive.SmallArray            as SA
import           Data.Proxy                           (Proxy)
import           Data.Semigroup                       (Arg, Max (..), Min (..),
                                                       Option)
import qualified Data.Semigroup                       as Sem
import qualified Data.Sequence                        as Seq
import           Data.Sequences                       (IsSequence (indexEx))
import qualified Data.Sequences                       as MT
import qualified Data.Set                             as Set
import qualified Data.Text                            as T
import qualified Data.Vector                          as V
import qualified Data.Vector.Algorithms.Intro         as AI
import qualified Data.Vector.Primitive                as P
import qualified Data.Vector.Storable                 as S
import qualified Data.Vector.Unboxed                  as U
import           Foreign.Ptr                          (Ptr)
import qualified GHC.Exts                             as GHC
import           GHC.Generics
import           Language.Haskell.TH                  hiding (Type)
import           Language.Haskell.TH.Syntax           hiding (Type)
import qualified VectorBuilder.Builder                as VB
import qualified VectorBuilder.Vector                 as VB

-- See Note [Function coercion]
(#.) :: Coercible b c => (b -> c) -> (a -> b) -> (a -> c)
(#.) _f = coerce
{-# INLINE (#.) #-}

ctoList :: (CFoldable f, Dom f a) => f a -> [a]
{-# INLINE [1] ctoList #-}
ctoList = cbasicToList

cfromList :: (CFreeMonoid f, Dom f a) => [a] -> f a
{-# INLINE [1] cfromList #-}
cfromList = cbasicFromList

-- | Fold-optic for 'CFoldable' instances.
--   In the terminology of lens, cfolded is a constrained
--   variant of @folded@ optic.
--
--  @
--    cfolded :: (CFoldable t, Dom t a) => Fold (t a) a
--  @
cfolded
  :: (CFoldable t, Dom t a)
  => forall f. (Contravariant f, Applicative f) => (a -> f a) -> t a -> f (t a)
{-# INLINE cfolded #-}
cfolded = (contramap (const ()) .) . ctraverse_

class Constrained f => CFoldable f where
  {-# MINIMAL cfoldMap | cfoldr #-}
  cfoldMap :: (Dom f a, Monoid w) => (a -> w) -> f a -> w
  {-# INLINE [1] cfoldMap #-}
  cfoldMap f = cfoldr (mappend . f) mempty

  cfoldMap' :: (Dom f a, Monoid m) => (a -> m) -> f a -> m
  {-# INLINE [1] cfoldMap' #-}
  cfoldMap' f = cfoldl' (\ acc a -> acc <> f a) mempty

  cfold :: (Dom f w, Monoid w) => f w -> w
  cfold = cfoldMap id

  {-# INLINE [1] cfold #-}
  cfoldr :: (Dom f a) => (a -> b -> b) -> b -> f a -> b
  {-# INLINE [1] cfoldr #-}
  cfoldr f z t = appEndo (cfoldMap (Endo #. f) t) z

  cfoldlM
    :: (Monad m, Dom f b)
    => (a -> b -> m a) -> a -> f b -> m a
  {-# INLINE [1] cfoldlM #-}
  cfoldlM f z0 xs = cfoldr f' return xs z0
    where f' x k z = f z x >>= k

  cfoldlM'
    :: (Monad m, Dom f b)
    => (a -> b -> m a) -> a -> f b -> m a
  {-# INLINE [1] cfoldlM' #-}
  cfoldlM' f z0 xs = cfoldr' f' return xs z0
    where f' !x k z = do
            !i <- f z x
            k i

  cfoldrM
    :: (Monad m, Dom f a)
    => (a -> b -> m b) -> b -> f a -> m b
  {-# INLINE [1] cfoldrM #-}
  cfoldrM f z0 xs = cfoldl c return xs z0
    where c k x z = f x z >>= k

  cfoldrM'
    :: (Monad m, Dom f a)
    => (a -> b -> m b) -> b -> f a -> m b
  {-# INLINE [1] cfoldrM' #-}
  cfoldrM' f z0 xs = cfoldl' c return xs z0
    where c k !x z = do
            !i <- f x z
            k i
  cfoldl
      :: (Dom f a)
      => (b -> a -> b) -> b -> f a -> b
  {-# INLINE [1] cfoldl #-}
  cfoldl f z t = appEndo (getDual (cfoldMap (Dual . Endo . flip f) t)) z

  cfoldr' :: (Dom f a) => (a -> b -> b) -> b -> f a -> b
  {-# INLINE [1] cfoldr' #-}
  cfoldr' f z0 xs = cfoldl f' id xs z0
      where f' k x z = k $! f x z

  cfoldl' :: Dom f a => (b -> a -> b) -> b -> f a -> b
  {-# INLINE [1] cfoldl' #-}
  cfoldl' f z0 xs = cfoldr f' id xs z0
    where f' x k z = k $! f z x

  cbasicToList :: Dom f a => f a -> [a]
  {-# INLINE cbasicToList #-}
  cbasicToList = cfoldr (:) []

  cfoldr1 :: Dom f a => (a -> a -> a) -> f a -> a
  {-# INLINE [1] cfoldr1 #-}
  cfoldr1 f xs = fromMaybe (errorWithoutStackTrace "cfoldr1: empty structure")
                    (cfoldr mf Nothing xs)
      where
        mf x m = Just $
          case m of
            Nothing -> x
            Just y  -> f x y

  cfoldl1 :: Dom f a => (a -> a -> a) -> f a -> a
  {-# INLINE [1] cfoldl1 #-}
  cfoldl1 f xs = fromMaybe (errorWithoutStackTrace "cfoldl1: empty structure")
                  (cfoldl mf Nothing xs)
    where
      mf m y = Just $
        case m of
          Nothing -> y
          Just x  -> f x y

  cindex :: Dom f a => f a -> Int -> a
  cindex xs n = case cfoldl' go (Left' 0) xs of
    Right' x -> x
    Left'{} -> errorWithoutStackTrace $ "cindex: index out of bound " ++ show n
    where
      go (Left' i) x
        | i == n = Right' x
        | otherwise = Left' (i + 1)
      go r@Right'{} _ = r

  cnull :: Dom f a => f a -> Bool
  cnull = cfoldr (const $ const False) True

  clength :: Dom f a => f a -> Int
  {-# INLINE [1] clength #-}
  clength = cfoldl' (\c _ -> c + 1) 0

  cany :: Dom f a => (a -> Bool) -> f a -> Bool
  {-# INLINE [1] cany #-}
  cany p = cfoldl' (\b -> (||) b . p) False

  call :: Dom f a => (a -> Bool) -> f a -> Bool
  {-# INLINE [1] call #-}
  call p = cfoldl' (\b -> (&&) b . p) True

  celem :: (Eq a, Dom f a) => a -> f a -> Bool
  {-# INLINE [1] celem #-}
  celem = cany . (==)

  cnotElem :: (Eq a, Dom f a) => a -> f a -> Bool
  {-# INLINE [1] cnotElem #-}
  cnotElem = call . (/=)

  cminimum :: (Ord a, Dom f a) => f a -> a
  {-# INLINE [1] cminimum #-}
  cminimum =
    getMin
    . fromMaybe (errorWithoutStackTrace "minimum: empty structure")
    . cfoldMap (Just . Min)

  cmaximum :: (Ord a, Dom f a) => f a -> a
  {-# INLINE [1] cmaximum #-}
  cmaximum =
    getMax
    . fromMaybe (errorWithoutStackTrace "cmaximum: empty structure")
    . cfoldMap (Just . Max)

  csum :: (Num a, Dom f a) => f a -> a
  {-# INLINE [1] csum #-}
  csum = getSum #. cfoldMap Sum

  cproduct :: (Num a, Dom f a) => f a -> a
  {-# INLINE [1] cproduct #-}
  cproduct = getProduct #. cfoldMap Product

  cctraverse_
    :: (CApplicative g, CPointed g, Dom g (), Dom f a, Dom g b)
    => (a -> g b)
    -> f a -> g ()
  {-# INLINE [1] cctraverse_ #-}
  cctraverse_ f = cfoldr c (cpure ())
    where
      {-# INLINE c #-}
      c x k = f x .> k

  ctraverse_
    :: (Applicative g, Dom f a)
    => (a -> g b)
    -> f a -> g ()
  {-# INLINE [1] ctraverse_ #-}
  ctraverse_ f = cfoldr c (pure ())
    where
      {-# INLINE c #-}
      c x k = f x *> k

  clast :: Dom f a => f a -> a
  {-# INLINE [1] clast #-}
  clast = fromJust . L.foldOver cfolded L.last

  chead :: Dom f a => f a -> a
  {-# INLINE [1] chead #-}
  chead = fromJust . L.foldOver cfolded L.head

  cfind :: Dom f a => (a -> Bool) -> f a -> Maybe a
  {-# INLINE [1] cfind #-}
  cfind = \p -> getFirst . cfoldMap (\x -> First $ if p x then Just x else Nothing)

  cfindIndex :: Dom f a => (a -> Bool) -> f a -> Maybe Int
  {-# INLINE [1] cfindIndex #-}
  cfindIndex = \p -> L.foldOver cfolded (L.findIndex p)

  cfindIndices :: Dom f a => (a -> Bool) -> f a -> [Int]
  {-# INLINE [1] cfindIndices #-}
  cfindIndices = \p -> List.findIndices p . ctoList

  celemIndex :: (Dom f a, Eq a) => a -> f a -> Maybe Int
  {-# INLINE [0] celemIndex #-}
  celemIndex = cfindIndex . (==)

  celemIndices :: (Dom f a, Eq a) => a -> f a -> [Int]
  {-# INLINE [0] celemIndices #-}
  celemIndices = cfindIndices . (==)

data Eith' a b = Left' !a | Right' !b

instance Traversable f => CTraversable (WrapFunctor f) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}

instance Foldable f => CFoldable (WrapFunctor f) where
  cfoldMap = foldMap
  {-# INLINE [1] cfoldMap #-}
#if MIN_VERSION_base(4,13,0)
  cfoldMap' = foldMap'
  {-# INLINE [1] cfoldMap' #-}
#endif
  cfold = fold
  {-# INLINE [1] cfold #-}
  cfoldr = foldr
  {-# INLINE [1] cfoldr #-}
  cfoldr' = foldr'
  {-# INLINE [1] cfoldr' #-}
  cfoldl = foldl
  {-# INLINE [1] cfoldl #-}
  cfoldl' = foldl'
  {-# INLINE [1] cfoldl' #-}
  cbasicToList = toList
  {-# INLINE [1] cbasicToList #-}
  cfoldr1 = foldr1
  {-# INLINE [1] cfoldr1 #-}
  cfoldl1 = foldl1
  {-# INLINE [1] cfoldl1 #-}
  cfoldlM = foldlM
  {-# INLINE [1] cfoldlM #-}
  cfoldrM = foldrM
  {-# INLINE [1] cfoldrM #-}
  cnull = null
  {-# INLINE [1] cnull #-}
  clength = length
  {-# INLINE [1] clength #-}
  cany = any
  {-# INLINE [1] cany #-}
  call = all
  {-# INLINE [1] call #-}
  celem = elem
  {-# INLINE [1] celem #-}
  cnotElem = notElem
  {-# INLINE [1] cnotElem #-}
  cminimum = minimum
  {-# INLINE [1] cminimum #-}
  cmaximum = maximum
  {-# INLINE [1] cmaximum #-}
  csum = sum
  {-# INLINE [1] csum #-}
  cproduct = product
  {-# INLINE [1] cproduct #-}
  ctraverse_ = traverse_
  {-# INLINE [1] ctraverse_ #-}
  cfind = find
  {-# INLINE [1] cfind #-}
  cfindIndex = L.fold . L.findIndex
  {-# INLINE [1] cfindIndex #-}
  celemIndex = L.fold . L.elemIndex
  {-# INLINE [1] celemIndex #-}

{-# RULES
"cfind/List"
  cfind = find @[]

"cfindIndex/List"
  cfindIndex = List.findIndex

"cfindIndices/List"
  cfindIndices = List.findIndices

"celemIndex/List"
  celemIndex = List.elemIndex

"celemIndices/List"
  celemIndices = List.elemIndices

"cfindIndex/List"
  cfindIndex = Seq.findIndexL

"cfindIndices/Seq"
  cfindIndices = Seq.findIndicesL

"celemIndex/Seq"
  celemIndex = Seq.elemIndexL

"celemIndices/Seq"
  celemIndices = Seq.elemIndicesL

  #-}

{-# RULES
"cctraverse_/traverse_"
  forall (f :: Applicative f => a -> f b) (tx :: Foldable t => t a).
  cctraverse_ f tx = traverse_ f tx
  #-}

{-# RULES
"cindex/List"
  cindex = (!!)
  #-}

class (CFunctor f, CFoldable f) => CTraversable f where
  -- | __N.B.__ If we require @g@ to be 'CApplicative'
  --   we cannot directly lift plain 'Traversable' to 'CTraversable'.
  --   This is rather annoying, so we require the strongest possible
  --   constraint to @g@ here.
  ctraverse
    :: (Dom f a, Dom f b, Applicative g)
    => (a -> g b) -> f a -> g (f b)

deriving via WrapFunctor []
  instance CFoldable []
{-# RULES
"ctoList/List"
  ctoList = id
"cfromList/List"
  cbasicFromList = id
"clast/List"
  clast = last
"chead/List"
  chead = head
  #-}

instance CTraversable [] where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor Maybe
  instance CFoldable Maybe
instance CTraversable Maybe where
  ctraverse = traverse
deriving via WrapFunctor (Either e)
  instance CFoldable (Either e)
instance CTraversable (Either e) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor IM.IntMap
  instance CFoldable IM.IntMap
instance CTraversable IM.IntMap where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor (M.Map k)
  instance CFoldable (M.Map k)
instance Ord k => CTraversable (M.Map k) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor (HM.HashMap k)
  instance CFoldable (HM.HashMap k)
instance CTraversable (HM.HashMap k) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor Seq.Seq
  instance CFoldable Seq.Seq
instance CTraversable Seq.Seq where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
{-# RULES
"cindex/Seq"
  cindex = Seq.index
  #-}

deriving via WrapFunctor Par1
  instance CFoldable Par1
instance CTraversable Par1 where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor NonEmpty
  instance CFoldable NonEmpty
instance CTraversable NonEmpty where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
{-# RULES
"cindex/NonEmpty"
  cindex = (NE.!!)
  #-}

deriving via WrapFunctor Down
  instance CFoldable Down
instance CTraversable Down where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor Mon.Last
  instance CFoldable Mon.Last
instance CTraversable Mon.Last where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor Mon.First
  instance CFoldable Mon.First
instance CTraversable Mon.First where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor Sem.Last
  instance CFoldable Sem.Last
instance CTraversable Sem.Last where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor Sem.First
  instance CFoldable Sem.First
instance CTraversable Sem.First where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor Identity
  instance CFoldable Identity
instance CTraversable Identity where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor ZipList
  instance CFoldable ZipList
instance CTraversable ZipList where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
{-# RULES
"cindex/ZipList"
  cindex = (!!) . getZipList
  #-}

deriving via WrapFunctor Option
  instance CFoldable Option
instance CTraversable Option where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor Min
  instance CFoldable Min
instance CTraversable Min where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor Max
  instance CFoldable Max
instance CTraversable Max where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor Complex
  instance CFoldable Complex
instance CTraversable Complex where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor (V1 :: Type -> Type)
  instance CFoldable (V1 :: Type -> Type)
instance CTraversable (V1 :: Type -> Type) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor (U1 :: Type -> Type)
  instance CFoldable (U1 :: Type -> Type)
instance CTraversable (U1 :: Type -> Type) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor ((,) a)
  instance CFoldable ((,) a)
instance CTraversable ((,) a) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor (Proxy :: Type -> Type)
  instance CFoldable (Proxy :: Type -> Type)
instance CTraversable (Proxy :: Type -> Type) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor (Arg a)
  instance CFoldable (Arg a)
instance CTraversable (Arg a) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor (Rec1 (f :: Type -> Type))
  instance Foldable f => CFoldable (Rec1 (f :: Type -> Type))
deriving via WrapFunctor (URec Char :: Type -> Type)
  instance CFoldable (URec Char :: Type -> Type)
instance CTraversable (URec Char :: Type -> Type) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor (URec Double :: Type -> Type)
  instance CFoldable (URec Double :: Type -> Type)
instance CTraversable (URec Double :: Type -> Type) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor (URec Float :: Type -> Type)
  instance CFoldable (URec Float :: Type -> Type)
instance CTraversable (URec Float :: Type -> Type) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor (URec Int :: Type -> Type)
  instance CFoldable (URec Int :: Type -> Type)
instance CTraversable (URec Int :: Type -> Type) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor (URec Word :: Type -> Type)
  instance CFoldable (URec Word :: Type -> Type)
instance CTraversable (URec Word :: Type -> Type) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor (URec (Ptr ()) :: Type -> Type)
  instance CFoldable (URec (Ptr ()) :: Type -> Type)
instance CTraversable (URec (Ptr ()) :: Type -> Type) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving newtype
  instance CFoldable f => CFoldable (Alt f)
deriving newtype
  instance CFoldable f => CFoldable (Ap f)
deriving via WrapFunctor (Const m :: Type -> Type)
  instance CFoldable (Const m :: Type -> Type)
instance CTraversable (Const m :: Type -> Type) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}
deriving via WrapFunctor (K1 i c :: Type -> Type)
  instance CFoldable (K1 i c :: Type -> Type)
instance CTraversable (K1 i c :: Type -> Type) where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}

instance (CFoldable f, CFoldable g) => CFoldable (f :+: g) where
  {-# INLINE [1] cfoldMap #-}
  cfoldMap f = \case
    L1 x -> cfoldMap f x
    R1 x -> cfoldMap f x

  {-# INLINE [1] cfoldr #-}
  cfoldr f z = \case
    L1 x -> cfoldr f z x
    R1 x -> cfoldr f z x

  cfoldMap' = \f -> \case
    L1 x -> cfoldMap' f x
    R1 x -> cfoldMap' f x
  {-# INLINE [1] cfoldMap' #-}
  cfold = \case
    L1 x -> cfold x
    R1 x -> cfold x
  {-# INLINE [1] cfold #-}
  cfoldr' = \f z -> \case
    L1 x -> cfoldr' f z x
    R1 x -> cfoldr' f z x
  {-# INLINE [1] cfoldr' #-}
  cfoldl = \f z -> \case
    L1 x -> cfoldl f z x
    R1 x -> cfoldl f z x
  {-# INLINE [1] cfoldl #-}
  cfoldl' = \f z -> \case
    L1 x -> cfoldl' f z x
    R1 x -> cfoldl' f z x
  {-# INLINE [1] cfoldl' #-}
  cbasicToList = \case
    L1 x -> ctoList x
    R1 x -> ctoList x
  {-# INLINE cbasicToList #-}
  cfoldr1 = \f -> \case
    L1 x -> cfoldr1 f x
    R1 x -> cfoldr1 f x
  {-# INLINE [1] cfoldr1 #-}
  cfoldl1 = \f -> \case
    L1 x -> cfoldl1 f x
    R1 x -> cfoldl1 f x
  {-# INLINE [1] cfoldl1 #-}
  cnull = \case
    L1 x -> cnull x
    R1 x -> cnull x
  {-# INLINE [1] cnull #-}
  clength = \case
    L1 x -> clength x
    R1 x -> clength x
  {-# INLINE [1] clength #-}
  cany = \f -> \case
    L1 x -> cany f x
    R1 x -> cany f x
  {-# INLINE [1] cany #-}
  call = \f -> \case
    L1 x -> call f x
    R1 x -> call f x
  {-# INLINE [1] call #-}
  celem = \x -> \case
    L1 xs -> celem x xs
    R1 xs -> celem x xs
  {-# INLINE [1] celem #-}
  cminimum = \case
    L1 xs -> cminimum xs
    R1 xs -> cminimum xs
  {-# INLINE [1] cminimum #-}
  cmaximum = \case
    L1 xs -> cmaximum xs
    R1 xs -> cmaximum xs
  {-# INLINE [1] cmaximum #-}
  csum = \case
    L1 xs -> csum xs
    R1 xs -> csum xs
  {-# INLINE [1] csum #-}
  cproduct = \case
    L1 xs -> cproduct xs
    R1 xs -> cproduct xs
  {-# INLINE [1] cproduct #-}
  ctraverse_ f = \case
    L1 xs -> ctraverse_ f xs
    R1 xs -> ctraverse_ f xs
  {-# INLINE [1] ctraverse_ #-}

instance (CTraversable f, CTraversable g) => CTraversable (f :+: g) where
  ctraverse f = \case
    L1 xs -> L1 <$> ctraverse f xs
    R1 xs -> R1 <$> ctraverse f xs
  {-# INLINE [1] ctraverse #-}

instance (CFoldable f, CFoldable g) => CFoldable (f :*: g) where
  {-# INLINE [1] cfoldMap #-}
  cfoldMap f (l :*: r) = cfoldMap f l <> cfoldMap f r

  cfoldMap' f (l :*: r) = cfoldMap' f l <> cfoldMap' f r
  {-# INLINE [1] cfoldMap' #-}
  cfold (l :*: r) = cfold l <> cfold r
  {-# INLINE [1] cfold #-}
  cnull (l :*: r) = cnull l && cnull r
  {-# INLINE [1] cnull #-}
  clength (l :*: r) = clength l + clength r
  {-# INLINE [1] clength #-}
  cany f (l :*: r) = cany f l || cany f r
  {-# INLINE [1] cany #-}
  call f (l :*: r) = call f l && call f r
  {-# INLINE [1] call #-}
  celem x (l :*: r) = celem x l || celem x r
  {-# INLINE [1] celem #-}
  csum (l :*: r) = csum l + csum r
  {-# INLINE [1] csum #-}
  cproduct (l :*: r) = cproduct l * cproduct r
  {-# INLINE [1] cproduct #-}
  ctraverse_ f (l :*: r) = ctraverse_ f l *> ctraverse_ f r
  {-# INLINE [1] ctraverse_ #-}

instance (CTraversable f, CTraversable g) => CTraversable (f :*: g) where
  ctraverse f (l :*: r) =
    (:*:) <$> ctraverse f l <*> ctraverse f r

instance (CFoldable f, CFoldable g) => CFoldable (SOP.Sum f g) where
  {-# INLINE [1] cfoldMap #-}
  cfoldMap f = \case
    SOP.InL x -> cfoldMap f x
    SOP.InR x -> cfoldMap f x

  {-# INLINE [1] cfoldr #-}
  cfoldr f z = \case
    SOP.InL x -> cfoldr f z x
    SOP.InR x -> cfoldr f z x

  cfoldMap' = \f -> \case
    SOP.InL x -> cfoldMap' f x
    SOP.InR x -> cfoldMap' f x
  {-# INLINE [1] cfoldMap' #-}
  cfold = \case
    SOP.InL x -> cfold x
    SOP.InR x -> cfold x
  {-# INLINE [1] cfold #-}
  cfoldr' = \f z -> \case
    SOP.InL x -> cfoldr' f z x
    SOP.InR x -> cfoldr' f z x
  {-# INLINE [1] cfoldr' #-}
  cfoldl = \f z -> \case
    SOP.InL x -> cfoldl f z x
    SOP.InR x -> cfoldl f z x
  {-# INLINE [1] cfoldl #-}
  cfoldl' = \f z -> \case
    SOP.InL x -> cfoldl' f z x
    SOP.InR x -> cfoldl' f z x
  {-# INLINE [1] cfoldl' #-}
  cbasicToList = \case
    SOP.InL x -> ctoList x
    SOP.InR x -> ctoList x
  {-# INLINE cbasicToList #-}
  cfoldr1 = \f -> \case
    SOP.InL x -> cfoldr1 f x
    SOP.InR x -> cfoldr1 f x
  {-# INLINE [1] cfoldr1 #-}
  cfoldl1 = \f -> \case
    SOP.InL x -> cfoldl1 f x
    SOP.InR x -> cfoldl1 f x
  {-# INLINE [1] cfoldl1 #-}
  cnull = \case
    SOP.InL x -> cnull x
    SOP.InR x -> cnull x
  {-# INLINE [1] cnull #-}
  clength = \case
    SOP.InL x -> clength x
    SOP.InR x -> clength x
  {-# INLINE [1] clength #-}
  cany = \f -> \case
    SOP.InL x -> cany f x
    SOP.InR x -> cany f x
  {-# INLINE [1] cany #-}
  call = \f -> \case
    SOP.InL x -> call f x
    SOP.InR x -> call f x
  {-# INLINE [1] call #-}
  celem = \x -> \case
    SOP.InL xs -> celem x xs
    SOP.InR xs -> celem x xs
  {-# INLINE [1] celem #-}
  cminimum = \case
    SOP.InL xs -> cminimum xs
    SOP.InR xs -> cminimum xs
  {-# INLINE [1] cminimum #-}
  cmaximum = \case
    SOP.InL xs -> cmaximum xs
    SOP.InR xs -> cmaximum xs
  {-# INLINE [1] cmaximum #-}
  csum = \case
    SOP.InL xs -> csum xs
    SOP.InR xs -> csum xs
  {-# INLINE [1] csum #-}
  cproduct = \case
    SOP.InL xs -> cproduct xs
    SOP.InR xs -> cproduct xs
  {-# INLINE [1] cproduct #-}
  ctraverse_ f = \case
    SOP.InL xs -> ctraverse_ f xs
    SOP.InR xs -> ctraverse_ f xs
  {-# INLINE [1] ctraverse_ #-}

instance (CTraversable f, CTraversable g) => CTraversable (SOP.Sum f g) where
  ctraverse f = \case
    SOP.InL xs -> SOP.InL <$> ctraverse f xs
    SOP.InR xs -> SOP.InR <$> ctraverse f xs
  {-# INLINE [1] ctraverse #-}

instance (CFoldable f, CFoldable g) => CFoldable (SOP.Product f g) where
  {-# INLINE [1] cfoldMap #-}
  cfoldMap f (SOP.Pair l r) = cfoldMap f l <> cfoldMap f r

  cfoldMap' f (SOP.Pair l r) = cfoldMap' f l <> cfoldMap' f r
  {-# INLINE [1] cfoldMap' #-}
  cfold (SOP.Pair l r) = cfold l <> cfold r
  {-# INLINE [1] cfold #-}
  cnull (SOP.Pair l r) = cnull l && cnull r
  {-# INLINE [1] cnull #-}
  clength (SOP.Pair l r) = clength l + clength r
  {-# INLINE [1] clength #-}
  cany f (SOP.Pair l r) = cany f l || cany f r
  {-# INLINE [1] cany #-}
  call f (SOP.Pair l r) = call f l && call f r
  {-# INLINE [1] call #-}
  celem x (SOP.Pair l r) = celem x l || celem x r
  {-# INLINE [1] celem #-}
  csum (SOP.Pair l r) = csum l + csum r
  {-# INLINE [1] csum #-}
  cproduct (SOP.Pair l r) = cproduct l * cproduct r
  {-# INLINE [1] cproduct #-}
  ctraverse_ f (SOP.Pair l r) =
    ctraverse_ f l *> ctraverse_ f r
  {-# INLINE ctraverse_ #-}

deriving via WrapFunctor SA.SmallArray instance CFoldable SA.SmallArray
deriving via WrapFunctor A.Array instance CFoldable A.Array

instance CFoldable PA.PrimArray where
  cfoldr = PA.foldrPrimArray
  {-# INLINE [1] cfoldr #-}
  cfoldl' = PA.foldlPrimArray'
  {-# INLINE [1] cfoldl' #-}
  cfoldlM' = PA.foldlPrimArrayM'
  {-# INLINE [1] cfoldlM' #-}
  cfoldl = PA.foldlPrimArray
  {-# INLINE [1] cfoldl #-}
  clength = PA.sizeofPrimArray
  {-# INLINE [1] clength #-}
  csum = PA.foldlPrimArray' (+) 0
  {-# INLINE [1] csum #-}
  cproduct = PA.foldlPrimArray' (*) 1
  {-# INLINE [1] cproduct #-}
  ctraverse_ = PA.traversePrimArray_
  {-# INLINE [1] ctraverse_ #-}

instance CTraversable PA.PrimArray where
  ctraverse = PA.traversePrimArray
  {-# INLINE [1] ctraverse #-}

instance CTraversable SA.SmallArray where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}

instance CTraversable A.Array where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}

instance (CTraversable f, CTraversable g) => CTraversable (SOP.Product f g) where
  {-# INLINE [1] ctraverse #-}
  ctraverse f (SOP.Pair l r) =
    SOP.Pair <$> ctraverse f l <*> ctraverse f r

instance CFoldable Set.Set where
  cfoldMap = ofoldMap
  {-# INLINE [1] cfoldMap #-}
  cfoldr = Set.foldr
  {-# INLINE [1] cfoldr #-}
  cfoldl = Set.foldl
  {-# INLINE [1] cfoldl #-}
  cfoldr' = Set.foldr'
  {-# INLINE [1] cfoldr' #-}
  cfoldl' = Set.foldl'
  {-# INLINE [1] cfoldl' #-}
  cminimum = Set.findMin
  {-# INLINE [1] cminimum #-}
  cmaximum = Set.findMax
  {-# INLINE [1] cmaximum #-}
  celem = Set.member
  {-# INLINE [1] celem #-}
  cnotElem = Set.notMember
  {-# INLINE [1] cnotElem #-}
  cbasicToList = Set.toList
  {-# INLINE cbasicToList #-}
  celemIndex = Set.lookupIndex
  {-# INLINE [1] celemIndex #-}
  cindex = flip Set.elemAt
  {-# INLINE [1] cindex #-}

instance CTraversable Set.Set where
  -- TODO: more efficient implementation
  ctraverse f =
      fmap Set.fromList
    . traverse f
    . Set.toList
  {-# INLINE [1] ctraverse #-}

instance CFoldable HS.HashSet where
  cfoldMap = ofoldMap
  {-# INLINE [1] cfoldMap #-}
  cfoldr = HS.foldr
  {-# INLINE [1] cfoldr #-}
  cfoldl' = HS.foldl'
  {-# INLINE [1] cfoldl' #-}
  celem = HS.member
  {-# INLINE [1] celem #-}
  cbasicToList = HS.toList
  {-# INLINE cbasicToList #-}

instance CTraversable HS.HashSet where
  -- TODO: more efficient implementation
  ctraverse f =
      fmap HS.fromList
    . traverse f
    . HS.toList
  {-# INLINE [1] ctraverse #-}

{-# RULES
"celem/IntSet"
  celem = coerce
    @(Int -> IS.IntSet -> Bool)
    @(Int -> WrapMono IS.IntSet Int -> Bool)
    IS.member
"cnotElem/IntSet"
  cnotElem = coerce
    @(Int -> IS.IntSet -> Bool)
    @(Int -> WrapMono IS.IntSet Int -> Bool)
    IS.notMember
"cmaximum/IntSet"
  cmaximum = coerce @_ @(WrapMono IS.IntSet Int -> Int)
    IS.findMax
"cminimum/IntSet"
  cminimum = coerce @(IS.IntSet -> Int) @(WrapMono IS.IntSet Int -> Int)
    IS.findMin
  #-}

instance MonoFoldable mono => CFoldable (WrapMono mono) where
  cfoldMap = ofoldMap
  {-# INLINE [1] cfoldMap #-}
  cfold = ofold
  {-# INLINE [1] cfold #-}
  cfoldr = ofoldr
  {-# INLINE [1] cfoldr #-}
  cfoldl' = ofoldl'
  {-# INLINE [1] cfoldl' #-}
  cfoldlM = ofoldlM
  {-# INLINE [1] cfoldlM #-}
  cbasicToList = otoList
  {-# INLINE cbasicToList #-}
  cfoldr1 = ofoldr1Ex
  {-# INLINE [1] cfoldr1 #-}
  cnull = onull
  {-# INLINE [1] cnull #-}
  clength = olength
  {-# INLINE [1] clength #-}
  cany = oany
  {-# INLINE [1] cany #-}
  call = oall
  {-# INLINE [1] call #-}
  celem = oelem
  {-# INLINE [1] celem #-}
  cnotElem = onotElem
  {-# INLINE [1] cnotElem #-}
  cminimum = minimumEx
  {-# INLINE [1] cminimum #-}
  cmaximum = maximumEx
  {-# INLINE [1] cmaximum #-}
  csum = osum
  {-# INLINE [1] csum #-}
  cproduct = oproduct
  {-# INLINE [1] cproduct #-}
  ctraverse_ = otraverse_
  {-# INLINE [1] ctraverse_ #-}

instance MonoTraversable mono => CTraversable (WrapMono mono) where
  ctraverse = \f -> fmap WrapMono . otraverse f . unwrapMono

instance CFoldable V.Vector where
  {-# INLINE [1] cfoldMap #-}
  cfoldMap = foldMap
  {-# INLINE [1] cfoldr #-}
  cfoldr = V.foldr
  {-# INLINE [1] cfoldr' #-}
  cfoldr' = V.foldr'
  {-# INLINE [1] cfoldl #-}
  cfoldl = V.foldl
  {-# INLINE [1] cfoldl' #-}
  cfoldl' = V.foldl'
  {-# INLINE cfoldlM #-}
  cfoldlM = V.foldM
  {-# INLINE cfoldlM' #-}
  cfoldlM' = V.foldM'
  {-# INLINE [1] cindex #-}
  cindex = (V.!)
  {-# INLINE [1] celem #-}
  celem = V.elem
  {-# INLINE [1] cnotElem #-}
  cnotElem = V.notElem
  {-# INLINE [1] cany #-}
  cany = V.any
  {-# INLINE [1] call #-}
  call = V.all
  {-# INLINE [1] cfoldl1 #-}
  cfoldl1 = V.foldl1
  {-# INLINE [1] cfoldr1 #-}
  cfoldr1 = V.foldr1
  {-# INLINE [1] csum #-}
  csum = V.sum
  {-# INLINE [1] cproduct #-}
  cproduct = V.product
  {-# INLINE [1] cmaximum #-}
  cmaximum = V.maximum
  {-# INLINE [1] cminimum #-}
  cminimum = V.minimum
  {-# INLINE cbasicToList #-}
  cbasicToList = V.toList
  {-# INLINE [1] clast #-}
  clast = V.last
  {-# INLINE [1] chead #-}
  chead = V.head
  {-# INLINE [1] cfind #-}
  cfind = V.find
  {-# INLINE [1] cfindIndex #-}
  cfindIndex = V.findIndex
  {-# INLINE [1] cfindIndices #-}
  cfindIndices = fmap V.toList . V.findIndices
  {-# INLINE [1] celemIndex #-}
  celemIndex = V.elemIndex
  {-# INLINE [1] celemIndices #-}
  celemIndices = fmap V.toList . V.elemIndices

instance CFoldable U.Vector where
  {-# INLINE [1] cfoldMap #-}
  cfoldMap = ofoldMap
  {-# INLINE [1] cfoldr #-}
  cfoldr = U.foldr
  {-# INLINE [1] cfoldr' #-}
  cfoldr' = U.foldr'
  {-# INLINE [1] cfoldl #-}
  cfoldl = U.foldl
  {-# INLINE [1] cfoldl' #-}
  cfoldl' = U.foldl'
  {-# INLINE cfoldlM #-}
  cfoldlM = U.foldM
  {-# INLINE cfoldlM' #-}
  cfoldlM' = U.foldM'
  {-# INLINE [1] cindex #-}
  cindex = (U.!)
  {-# INLINE [1] celem #-}
  celem = U.elem
  {-# INLINE [1] cnotElem #-}
  cnotElem = U.notElem
  {-# INLINE [1] cany #-}
  cany = U.any
  {-# INLINE [1] call #-}
  call = U.all
  {-# INLINE [1] cfoldl1 #-}
  cfoldl1 = U.foldl1
  {-# INLINE [1] cfoldr1 #-}
  cfoldr1 = U.foldr1
  {-# INLINE [1] csum #-}
  csum = U.sum
  {-# INLINE [1] cproduct #-}
  cproduct = U.product
  {-# INLINE [1] cmaximum #-}
  cmaximum = U.maximum
  {-# INLINE [1] cminimum #-}
  cminimum = U.minimum
  {-# INLINE cbasicToList #-}
  cbasicToList = U.toList
  {-# INLINE [1] clast #-}
  clast = U.last
  {-# INLINE [1] chead #-}
  chead = U.head
  {-# INLINE [1] cfind #-}
  cfind = U.find
  {-# INLINE [1] cfindIndex #-}
  cfindIndex = U.findIndex
  {-# INLINE [1] cfindIndices #-}
  cfindIndices = fmap U.toList . U.findIndices
  {-# INLINE [1] celemIndex #-}
  celemIndex = U.elemIndex
  {-# INLINE [1] celemIndices #-}
  celemIndices = fmap U.toList . U.elemIndices

instance CFoldable S.Vector where
  {-# INLINE [1] cfoldr #-}
  cfoldr = S.foldr
  {-# INLINE [1] cfoldr' #-}
  cfoldr' = S.foldr'
  {-# INLINE [1] cfoldl #-}
  cfoldl = S.foldl
  {-# INLINE [1] cfoldl' #-}
  cfoldl' = S.foldl'
  {-# INLINE cfoldlM #-}
  cfoldlM = S.foldM
  {-# INLINE cfoldlM' #-}
  cfoldlM' = S.foldM'
  {-# INLINE [1] cindex #-}
  cindex = (S.!)
  {-# INLINE [1] celem #-}
  celem = S.elem
  {-# INLINE [1] cnotElem #-}
  cnotElem = S.notElem
  {-# INLINE [1] cany #-}
  cany = S.any
  {-# INLINE [1] call #-}
  call = S.all
  {-# INLINE [1] cfoldl1 #-}
  cfoldl1 = S.foldl1
  {-# INLINE [1] cfoldr1 #-}
  cfoldr1 = S.foldr1
  {-# INLINE [1] csum #-}
  csum = S.sum
  {-# INLINE [1] cproduct #-}
  cproduct = S.product
  {-# INLINE [1] cmaximum #-}
  cmaximum = S.maximum
  {-# INLINE [1] cminimum #-}
  cminimum = S.minimum
  {-# INLINE cbasicToList #-}
  cbasicToList = S.toList
  {-# INLINE [1] clast #-}
  clast = S.last
  {-# INLINE [1] chead #-}
  chead = S.head
  {-# INLINE [1] cfind #-}
  cfind = S.find
  {-# INLINE [1] cfindIndex #-}
  cfindIndex = S.findIndex
  {-# INLINE [1] cfindIndices #-}
  cfindIndices = fmap S.toList . S.findIndices
  {-# INLINE [1] celemIndex #-}
  celemIndex = S.elemIndex
  {-# INLINE [1] celemIndices #-}
  celemIndices = fmap S.toList . S.elemIndices

instance CFoldable P.Vector where
  {-# INLINE [1] cfoldr #-}
  cfoldr = P.foldr
  {-# INLINE [1] cfoldr' #-}
  cfoldr' = P.foldr'
  {-# INLINE [1] cfoldl #-}
  cfoldl = P.foldl
  {-# INLINE [1] cfoldl' #-}
  cfoldl' = P.foldl'
  {-# INLINE cfoldlM #-}
  cfoldlM = P.foldM
  {-# INLINE cfoldlM' #-}
  cfoldlM' = P.foldM'
  {-# INLINE [1] cindex #-}
  cindex = (P.!)
  {-# INLINE [1] celem #-}
  celem = P.elem
  {-# INLINE [1] cnotElem #-}
  cnotElem = P.notElem
  {-# INLINE [1] cany #-}
  cany = P.any
  {-# INLINE [1] call #-}
  call = P.all
  {-# INLINE [1] cfoldl1 #-}
  cfoldl1 = P.foldl1
  {-# INLINE [1] cfoldr1 #-}
  cfoldr1 = P.foldr1
  {-# INLINE [1] csum #-}
  csum = P.sum
  {-# INLINE [1] cproduct #-}
  cproduct = P.product
  {-# INLINE [1] cmaximum #-}
  cmaximum = P.maximum
  {-# INLINE [1] cminimum #-}
  cminimum = P.minimum
  {-# INLINE cbasicToList #-}
  cbasicToList = P.toList
  {-# INLINE [1] clast #-}
  clast = P.last
  {-# INLINE [1] chead #-}
  chead = P.head
  {-# INLINE [1] cfind #-}
  cfind = P.find
  {-# INLINE [1] cfindIndex #-}
  cfindIndex = P.findIndex
  {-# INLINE [1] cfindIndices #-}
  cfindIndices = fmap P.toList . P.findIndices
  {-# INLINE [1] celemIndex #-}
  celemIndex = P.elemIndex
  {-# INLINE [1] celemIndices #-}
  celemIndices = fmap P.toList . P.elemIndices

instance CTraversable V.Vector where
  ctraverse = traverse
  {-# INLINE [1] ctraverse #-}

instance CTraversable U.Vector where
  ctraverse = \f -> fmap S.convert . traverse f . U.convert @_ @_ @V.Vector
  {-# INLINE [1] ctraverse #-}

instance CTraversable S.Vector where
  ctraverse = \f -> fmap S.convert . traverse f . U.convert @_ @_ @V.Vector
  {-# INLINE [1] ctraverse #-}

instance CTraversable P.Vector where
  ctraverse = \f -> fmap P.convert . traverse f . U.convert @_ @_ @V.Vector
  {-# INLINE [1] ctraverse #-}

{-# RULES
"cindex/IsSequence" forall (xs :: (MT.Index mono ~ Int, IsSequence mono) => WrapMono mono b).
  cindex xs = withMonoCoercible (coerce @(mono -> Int -> Element mono) indexEx xs)
  #-}

{-# RULES
"cfromList/ctoList" [~1]
  cfromList . ctoList = id
"cfromList/ctoList" [~1] forall xs.
  cfromList (ctoList xs) = xs
  #-}

{-# RULES
"ctoList/cfromList" [~1]
  ctoList . cfromList = id
"ctoList/cfromList" forall xs.
  ctoList (cfromList xs) = xs
  #-}
-- | Free monoid functor from fullsubcategory.
--   It must be a pointed foldable functor with the property
--   that for any 'Monoid' @w@ and @f :: a -> w@,
--   @'cfoldMap' f@ must be a monoid homomorphism and the following
--   must be hold:
--
--    @
--       'cfoldMap' f . 'cpure' == f
--    @
--
--   Hence, @'Set's@ cannot be a free monoid functor;
class (CFunctor f, forall x. Dom f x => Monoid (f x), CPointed f, CFoldable f)
  => CFreeMonoid f where
  cbasicFromList :: Dom f a => [a] -> f a
  cbasicFromList = foldr ((<>) . cpure) mempty
  {-# INLINE cbasicFromList #-}

  ccons :: Dom f a => a -> f a -> f a
  {-# INLINE [1] ccons #-}
  ccons = (<>) . cpure

  csnoc :: Dom f a => f a -> a -> f a
  {-# INLINE [1] csnoc #-}
  csnoc = (. cpure) . (<>)

  {- |
    The 'cfromListN' function takes the input list's length as a hint. Its behaviour should be equivalent to 'cfromList'. The hint can be used to construct the structure l more efficiently compared to 'cfromList'.
    If the given hint does not equal to the input list's length the behaviour of fromListN is not specified.
  -}
  cfromListN :: Dom f a => Int -> [a] -> f a
  cfromListN = const cfromList
  {-# INLINE [1] cfromListN #-}

  ctake :: Dom f a => Int -> f a -> f a
  {-# INLINE [1] ctake #-}
  ctake n = cfromList . take n . ctoList

  cdrop :: Dom f a => Int -> f a -> f a
  {-# INLINE [1] cdrop #-}
  cdrop n = cfromList . drop n . ctoList

  cinit :: Dom f a => f a -> f a
  {-# INLINE [1] cinit #-}
  cinit = cfromList . init . ctoList

  ctail :: Dom f a => f a -> f a
  ctail = cfromList . tail . ctoList

  csplitAt :: Dom f a => Int -> f a -> (f a, f a)
  {-# INLINE [1] csplitAt #-}
  csplitAt n = (\(a, b) -> (cfromList a, cfromList b)) . splitAt n . ctoList

  creplicate :: Dom f a => Int -> a -> f a
  {-# INLINE [1] creplicate #-}
  creplicate n = cfromList . replicate n

  cgenerate :: Dom f a => Int -> (Int -> a) -> f a
  {-# INLINE [1] cgenerate #-}
  cgenerate = \n f ->
    cfromList [f i | i <- [0.. n - 1]]

  cgenerateM :: (Dom f a, Monad m) => Int -> (Int -> m a) -> m (f a)
  {-# INLINE [1] cgenerateM #-}
  cgenerateM = \n f ->
    cfromList <$> mapM f [0..n-1]

  cgenerateA :: (Dom f a, Applicative g) => Int -> (Int -> g a) -> g (f a)
  {-# INLINE [1] cgenerateA #-}
  cgenerateA = \n f ->
    cfromList <$> traverse f [0..n-1]

  cuncons :: Dom f a => f a -> Maybe (a, f a)
  {-# INLINE [1] cuncons #-}
  cuncons = fmap (second cfromList) . uncons . ctoList

  cunsnoc :: Dom f a => f a -> Maybe (f a, a)
  {-# INLINE [1] cunsnoc #-}
  cunsnoc = fmap (first cfromList) . MT.unsnoc . ctoList

  creverse :: Dom f a => f a -> f a
  {-# INLINE [1] creverse #-}
  creverse = cfromList . reverse . ctoList

  cintersperse :: Dom f a => a -> f a -> f a
  cintersperse = \a -> cfromList . intersperse a . ctoList

  cnub :: (Dom f a, Eq a) => f a -> f a
  {-# INLINE [1] cnub #-}
  cnub = cfromList . nub . ctoList

  cnubOrd :: (Dom f a, Ord a) => f a -> f a
  {-# INLINE [1] cnubOrd #-}
  cnubOrd = cfromList . L.foldOver cfolded L.nub

  csort :: (Dom f a, Ord a) => f a -> f a
  {-# INLINE [1] csort #-}
  csort = cfromList . List.sort . ctoList

  csortBy :: (Dom f a) => (a -> a -> Ordering) -> f a -> f a
  {-# INLINE [1] csortBy #-}
  csortBy = \f -> cfromList . List.sortBy f . ctoList

  cinsert :: (Dom f a, Ord a) => a -> f a -> f a
  {-# INLINE [1] cinsert #-}
  cinsert = \a -> cfromList . List.insert a . ctoList

  cinsertBy :: (Dom f a) => (a -> a -> Ordering) -> a -> f a -> f a
  {-# INLINE [1] cinsertBy #-}
  cinsertBy = \f a -> cfromList . List.insertBy f a . ctoList

  ctakeWhile :: Dom f a => (a -> Bool) -> f a -> f a
  {-# INLINE [1] ctakeWhile #-}
  ctakeWhile = \f -> cfromList . takeWhile f . ctoList

  cdropWhile :: Dom f a => (a -> Bool) -> f a -> f a
  {-# INLINE [1] cdropWhile #-}
  cdropWhile = \f -> cfromList . dropWhile f . ctoList

  cspan :: Dom f a => (a -> Bool) -> f a -> (f a, f a)
  {-# INLINE [1] cspan #-}
  cspan = \f -> (cfromList *** cfromList) . span f . ctoList

  cbreak :: Dom f a => (a -> Bool) -> f a -> (f a, f a)
  {-# INLINE [1] cbreak #-}
  cbreak = \f -> (cfromList *** cfromList) . break f . ctoList

  cfilter :: Dom f a => (a -> Bool) -> f a -> f a
  {-# INLINE [1] cfilter #-}
  cfilter = \f -> cfromList . filter f . ctoList

  cpartition :: Dom f a => (a -> Bool) -> f a -> (f a, f a)
  {-# INLINE [1] cpartition #-}
  cpartition = \f -> (cfromList *** cfromList) . List.partition f . ctoList

  -- TODO: more ListLike equivalent functions here

instance CFreeMonoid [] where
  cbasicFromList = id
  {-# INLINE cbasicFromList #-}
  cfromListN = take
  {-# INLINE [1] cfromListN #-}
  ccons = (:)
  {-# INLINE [1] ccons #-}
  csnoc = \xs x -> xs ++ [x]
  {-# INLINE [1] csnoc #-}
  ctake = take
  {-# INLINE [1] ctake #-}
  cdrop = drop
  {-# INLINE [1] cdrop #-}
  cinit = init
  {-# INLINE [1] cinit #-}
  ctail = tail
  {-# INLINE [1] ctail #-}
  csplitAt = splitAt
  {-# INLINE [1] csplitAt #-}
  creplicate = replicate
  {-# INLINE [1] creplicate #-}
  cgenerateM = \n f -> mapM f [0..n-1]
  {-# INLINE [1] cgenerateM #-}
  cgenerateA = \n f -> traverse f [0..n-1]
  {-# INLINE [1] cgenerateA #-}
  cuncons = uncons
  {-# INLINE [1] cuncons #-}
  cunsnoc = MT.unsnoc
  {-# INLINE [1] cunsnoc #-}
  creverse = reverse
  {-# INLINE [1] creverse #-}
  cintersperse = intersperse
  {-# INLINE [1] cintersperse #-}
  cnub = cnub
  {-# INLINE [1] cnub #-}
  csort = List.sort
  {-# INLINE [1] csort #-}
  csortBy = List.sortBy
  {-# INLINE [1] csortBy #-}
  ctakeWhile = takeWhile
  {-# INLINE [1] ctakeWhile #-}
  cdropWhile = dropWhile
  {-# INLINE [1] cdropWhile #-}
  cspan = span
  {-# INLINE [1] cspan #-}
  cbreak = break
  {-# INLINE [1] cbreak #-}
  cfilter = filter
  {-# INLINE [1] cfilter #-}
  cpartition = List.partition
  {-# INLINE [1] cpartition #-}

fmap concat $ forM
  [''V.Vector, ''U.Vector, ''S.Vector, ''P.Vector]
  $ \vecTy@(Name _ (NameG _ pkg modl0@(ModName mn))) ->
    let modl = maybe modl0 (ModName . T.unpack)
          $ T.stripSuffix ".Base" $ T.pack mn
        modFun fun = varE $
          Name (OccName fun) (NameG VarName pkg modl)
    in [d|
    instance CFreeMonoid $(conT vecTy) where
      cbasicFromList = $(modFun "fromList")
      {-# INLINE cbasicFromList #-}
      cfromListN = $(modFun "fromListN")
      {-# INLINE [1] cfromListN #-}
      ccons = $(modFun "cons")
      {-# INLINE [1] ccons #-}
      csnoc = $(modFun "snoc")
      {-# INLINE [1] csnoc #-}
      ctake = $(modFun "take")
      {-# INLINE [1] ctake #-}
      cdrop = $(modFun "drop")
      {-# INLINE [1] cdrop #-}
      cinit = $(modFun "init")
      {-# INLINE [1] cinit #-}
      ctail = $(modFun "tail")
      {-# INLINE [1] ctail #-}
      csplitAt = $(modFun "splitAt")
      {-# INLINE [1] csplitAt #-}
      creplicate = $(modFun "replicate")
      {-# INLINE [1] creplicate #-}
      cgenerate = $(modFun "generate")
      {-# INLINE [1] cgenerate #-}
      cgenerateM = $(modFun "generateM")
      {-# INLINE [1] cgenerateM #-}
      cgenerateA = \n f ->
        fmap VB.build
        $ getAp $ foldMap (Ap . fmap VB.singleton . f) [0..n-1]
      {-# INLINE [1] cgenerateA #-}
      cuncons = \xs ->
        if $(modFun "null") xs
        then Nothing
        else Just ($(modFun "head") xs, $(modFun "tail") xs)
      {-# INLINE [1] cuncons #-}
      cunsnoc = \xs ->
        if $(modFun "null") xs
        then Nothing
        else Just ($(modFun "init") xs, $(modFun "last") xs)
      {-# INLINE [1] cunsnoc #-}
      creverse = $(modFun "reverse")
      {-# INLINE [1] creverse #-}
      cnubOrd = $(modFun "uniq") . $(modFun "modify") AI.sort
      {-# INLINE cnubOrd #-}
      csort = $(modFun "modify") AI.sort
      {-# INLINE [1] csort #-}
      csortBy = \f -> $(modFun "modify") $ AI.sortBy f
      {-# INLINE [1] csortBy #-}
      ctakeWhile = $(modFun "takeWhile")
      {-# INLINE [1] ctakeWhile #-}
      cdropWhile = $(modFun "dropWhile")
      {-# INLINE [1] cdropWhile #-}
      cspan = $(modFun "span")
      {-# INLINE [1] cspan #-}
      cbreak = $(modFun "break")
      {-# INLINE [1] cbreak #-}
      cfilter = $(modFun "filter")
      {-# INLINE [1] cfilter #-}
      cpartition = $(modFun "partition")
      {-# INLINE [1] cpartition #-}
    |]

instance CFreeMonoid PA.PrimArray where
  cbasicFromList = PA.primArrayFromList
  {-# INLINE cbasicFromList #-}
  cfromListN = PA.primArrayFromListN
  {-# INLINE [1] cfromListN #-}
  cgenerate = PA.generatePrimArray
  {-# INLINE [1] cgenerate #-}
  cgenerateM = PA.generatePrimArrayA
  {-# INLINE [1] cgenerateM #-}
  cgenerateA = PA.generatePrimArrayA
  {-# INLINE [1] cgenerateA #-}
  cfilter = PA.filterPrimArray
  {-# INLINE [1] cfilter #-}
  creplicate = PA.replicatePrimArray
  {-# INLINE [1] creplicate #-}

instance CFreeMonoid SA.SmallArray where
  cbasicFromList = SA.smallArrayFromList
  {-# INLINE cbasicFromList #-}
  cfromListN = SA.smallArrayFromListN
  {-# INLINE [1] cfromListN #-}

instance CFreeMonoid A.Array where
  cbasicFromList = A.fromList
  {-# INLINE cbasicFromList #-}
  cfromListN = A.fromListN
  {-# INLINE [1] cfromListN #-}
instance CFreeMonoid Seq.Seq where
  cbasicFromList = Seq.fromList
  {-# INLINE cbasicFromList #-}
  cfromListN = GHC.fromListN
  {-# INLINE [1] cfromListN #-}

instance MT.IsSequence mono
      => CFreeMonoid (WrapMono mono) where
  cbasicFromList = coerce $ MT.fromList @mono
  {-# INLINE cbasicFromList #-}
  cfromListN = \n -> coerce $ MT.take (fromIntegral n) . MT.fromList @mono
  {-# INLINE [1] cfromListN #-}
  ctake = coerce . MT.take @mono . fromIntegral
  {-# INLINE [1] ctake #-}
  cdrop = coerce . MT.drop @mono . fromIntegral
  {-# INLINE [1] cdrop #-}
  ccons = coerce $ MT.cons @mono
  {-# INLINE ccons #-}
  csnoc = coerce $ MT.snoc @mono
  {-# INLINE [1] csnoc #-}
  cuncons = coerce $ MT.uncons @mono
  {-# INLINE [1] cuncons #-}
  cunsnoc = coerce $ MT.unsnoc @mono
  {-# INLINE [1] cunsnoc #-}
  ctail = coerce $ MT.tailEx @mono
  {-# INLINE [1] ctail #-}
  cinit = coerce $ MT.initEx @mono
  {-# INLINE [1] cinit #-}
  csplitAt = coerce $ \(n :: Int) ->
      MT.splitAt @mono (fromIntegral n :: MT.Index mono)
  {-# INLINE [1] csplitAt #-}
  creplicate = coerce $ \(n :: Int) ->
      MT.replicate @mono (fromIntegral n :: MT.Index mono)
  {-# INLINE [1] creplicate #-}
  creverse = coerce $ MT.reverse @mono
  {-# INLINE [1] creverse #-}
  cintersperse = coerce $ MT.intersperse @mono
  {-# INLINE [1] cintersperse #-}
  csort = coerce $ MT.sort @mono
  {-# INLINE [1] csort #-}
  csortBy = coerce $ MT.sortBy @mono
  {-# INLINE [1] csortBy #-}
  ctakeWhile = coerce $ MT.takeWhile @mono
  {-# INLINE [1] ctakeWhile #-}
  cdropWhile = coerce $ MT.dropWhile @mono
  {-# INLINE [1] cdropWhile #-}
  cbreak = coerce $ MT.break @mono
  {-# INLINE [1] cbreak #-}
  cspan = coerce $ MT.span @mono
  {-# INLINE [1] cspan #-}
  cfilter = coerce $ MT.filter @mono
  {-# INLINE [1] cfilter #-}
  cpartition = coerce $ MT.partition @mono
  {-# INLINE [1] cpartition #-}

cctraverseFreeMonoid
  ::  ( CFreeMonoid t, CApplicative f, CPointed f,
        Dom t a, Dom f (t b), Dom f b, Dom t b,
        Dom f (t b, t b)
      )
  => (a -> f b) -> t a -> f (t b)
cctraverseFreeMonoid f =
  runCApp . cfoldMap (CApp . cmap cpure . f)

cctraverseZipFreeMonoid
  :: ( CFreeMonoid t, CRepeat f,
        Dom t a, Dom f (t b), Dom f b, Dom t b,
        Dom f (t b, t b)
      )
  => (a -> f b) -> t a -> f (t b)
cctraverseZipFreeMonoid f =
  runCZippy . cfoldMap (CZippy . cmap cpure . f)

-- | Lifts 'CFoldable' along given function.
--
--  @
--    cfolding :: (CFoldable t, Dom t a) => (s -> t a) -> Fold s a
--  @
cfolding
  :: (CFoldable t, Dom t a, Contravariant f, Applicative f)
  => (s -> t a)
  -> (a -> f a) -> s -> f s
{-# INLINE cfolding #-}
cfolding = \sfa agb -> phantom . ctraverse_ agb . sfa


-------------------------------------------------------------------------------
-- |
-- Module    :  Classifiers.RL.Control.MonadMWCRandom
-- Copyright :  (c) Sentenai 2017
-- License   :  BSD3
-- Maintainer:  sam@sentenai.com
-- Stability :  experimental
-- Portability: non-portable
--
-- typeclass to remove extraneous mwc-random functions
-------------------------------------------------------------------------------
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
module Control.MonadMWCRandom
  ( MonadMWCRandom(..)
  , MonadMWCRandomIO
  , MWCRand
  , MWCRandT(..)
  , runMWCRand
  , runMWCRandT
  -- * re-exports from System.Random.MWC
  , GenIO
  -- * wrappers for System.Random.MWC
  , uniform
  , uniformR
  -- * wrappers for Statistics.Distribution
  , Control.MonadMWCRandom.genContVar
  -- * extras
  , sampleFrom
  , Variate
  , _uniform
  ) where

import System.Random.MWC (GenIO, Variate)
import qualified System.Random.MWC as MWC
import qualified Statistics.Distribution as Stats
import Control.MonadEnv (MonadEnv(..), Obs, Initial)
import Control.Monad.Identity
import Control.Monad.IO.Class
import Control.Monad.Trans.Reader
import Control.Monad.Trans.Writer
import Control.Monad.Trans.State
import Control.Monad.Trans
import Control.Monad.Trans.RWS (RWST)
import Control.Exception.Safe
import Control.Monad.Primitive (PrimState, PrimMonad)

-- | a convenience helper to reference the underlying System.Random.MWC function
_uniform :: (PrimMonad m, Variate a) => MWC.Gen (PrimState m) -> m a
_uniform = MWC.uniform

-- | MonadMWCRandom for public use. FIXME: use with PrimState so that we can use ST
class Monad m => MonadMWCRandom m where
  getGen :: m GenIO

-- | A convenience type constraint with MonadMWCRandom and MonadIO.
type MonadMWCRandomIO m = (MonadIO m, MonadMWCRandom m)

-------------------------------------------------------------------------------

instance MonadMWCRandom m => MonadMWCRandom (StateT s m) where
  getGen :: StateT s m GenIO
  getGen = lift getGen

instance MonadMWCRandom m => MonadMWCRandom (ReaderT s m) where
  getGen :: ReaderT s m GenIO
  getGen = lift getGen

instance (Monoid w, MonadMWCRandom m) => MonadMWCRandom (WriterT w m) where
  getGen :: WriterT w m GenIO
  getGen = lift getGen

instance (Monoid w, MonadMWCRandom m) => MonadMWCRandom (RWST r w s m) where
  getGen :: RWST r w s m GenIO
  getGen = lift getGen

-- | in the end, we can always use IO to get our generator, but we will create a
-- new generator on each use.
instance MonadMWCRandom IO where
  getGen :: IO GenIO
  getGen = MWC.createSystemRandom

-------------------------------------------------------------------------------

-- | uniform referencing MonadMWCRandom's generator
uniform :: (MonadIO m, MonadMWCRandom m, Variate a) => m a
uniform = getGen >>= liftIO . MWC.uniform

-- | uniformR referencing MonadMWCRandom's generator
uniformR :: (MonadIO m, MonadMWCRandom m, Variate a) => (a, a) -> m a
uniformR r = getGen >>= liftIO . MWC.uniformR r

-- | genContVar referencing MonadMWCRandom's generator
genContVar :: (MonadIO m, MonadMWCRandom m, Stats.ContGen d) => d -> m Double
genContVar d = getGen >>= liftIO . Stats.genContVar d

-- ========================================================================= --
-- * Utility functions functions

-- | Sample a single index from a list of weights, converting the list into
-- a distribution
sampleFrom :: (MonadIO m, MonadMWCRandom m) => [Double] -> m (Int, [Double])
sampleFrom xs = fmap ((,dist) . choose) uniform
  where
    dist :: [Double]
    dist = fmap (/ total) xs

    total :: Double
    total = sum xs

    choose :: Double -> Int
    choose n =
      -- Return the head index (unsafeHead is safe since the last elem's snd must be 1.0)
      fst . head .

      -- Drop while the cumulative sum is < the given value
      dropWhile ((< n) . snd) .

      -- Pair each elem with its index
      zip [0..] .

      -- Transform list of probabilities to cumulative sum
      scanl1 (+) $ dist

-- ========================================================================= --
-- * A concrete type for MonadMWCRandom

-- | a wrapper to share a generator without using reader
newtype MWCRandT m a = MWCRandT { getMWCRandT :: ReaderT GenIO m a }
  deriving (Functor, Applicative, Monad, MonadTrans, MonadThrow, MonadIO)

-- | unwrap MonadMWCRandom
runMWCRandT :: MWCRandT m a -> GenIO -> m a
runMWCRandT = runReaderT . getMWCRandT

-- | simple type alias for transformer-less variant
type MWCRand = MWCRandT Identity

-- | run a transformerless MWC-random Monad
runMWCRand :: MWCRand a -> GenIO -> a
runMWCRand = runMWCRand

-- | instance declaration of MonadMWCRandom for MWCRandT
instance Monad m => MonadMWCRandom (MWCRandT m) where
  getGen :: MWCRandT m GenIO
  getGen = MWCRandT ask

-- | An instance which allows for an environment to hold a reference to a shared
-- MWC-random generator
instance MonadEnv m s a r => MonadEnv (MWCRandT m) s a r where
  reset :: MWCRandT m (Initial s)
  reset = lift reset

  step :: a -> MWCRandT m (Obs r s)
  step a = lift $ step a