{-# LANGUAGE DefaultSignatures    #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE TypeApplications     #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE UndecidableInstances #-}

{-|
Many of the common things we want to do with String like structures are just properties of MonoFoldable.
This module provides a classes for MonoFoldables that contain Char as Stringlike structures.

The motivation here is to have re-usable functions abstracted over common things we get from Foldable.
The reason we can't just use the mono-traversable package, is that it's ByteString instances are MonoFoldables containing @Word8@, an the goal here is to normalize the API around Char.

For example:

@
  isFiveChars :: FoldableString s => s -> Bool
  isFiveChars s = length s == 5
@

Which will work with all 5 string types

The motivation here is to have re-usable functions abstracted over common things we get from Foldable.

For example:

@
  isFiveChars :: FoldableString s => s -> Bool
  isFiveChars s = length s == 5
@

Which will work with all 5 string types.
.

-}

module Text.Foldable
  ( module Data.MonoTraversable
  , FunctorString (..)
  , FoldableString (..)
  , TraverseString (..)) where

import qualified Data.ByteString            as BS
import qualified Data.ByteString.Char8      as BSC
import qualified Data.ByteString.Lazy       as BL
import qualified Data.ByteString.Lazy.Char8 as BLC
import qualified Data.Char                  as C
import           Data.MonoTraversable
import qualified Data.String                as S
import qualified Data.Text                  as TS
import qualified Data.Text.Encoding         as TSE
import qualified Data.Text.Lazy             as TL
import qualified Data.Text.Lazy.Encoding    as TLE

import           Text.Isomorphic

{-| MonoFunctor containing Char -}
class FunctorString s where
  map :: (Char -> Char) -> s -> s
  default map :: (MonoFunctor s, Element s ~ Char) => (Char -> Char) -> s -> s
  map = omap

instance FunctorString String
instance FunctorString TS.Text
instance FunctorString TL.Text
instance FunctorString BS.ByteString where map = BSC.map
instance FunctorString BL.ByteString where map = BLC.map

{-| MonoFoldable containing Char -}
class FoldableString s where
  foldMap :: (Char -> s) -> s -> s
  default foldMap :: (MonoFoldable s, Element s ~ Char, Monoid s) => (Char -> s) -> s -> s
  foldMap = ofoldMap
  foldr   :: (Char -> a -> a) -> a -> s -> a
  default foldr :: (MonoFoldable s, Element s ~ Char) => (Char -> a -> a) -> a -> s -> a
  foldr = ofoldr
  foldl'  :: (a -> Char -> a) -> a -> s -> a
  default foldl' :: (MonoFoldable s, Element s ~ Char) => (a -> Char -> a) -> a -> s -> a
  foldl' = ofoldl'
  all     :: (Char -> Bool) -> s -> Bool
  default all :: (MonoFoldable s, Element s ~ Char) => (Char -> Bool) -> s -> Bool
  all = oall
  any     :: (Char -> Bool) -> s -> Bool
  default any :: (MonoFoldable s, Element s ~ Char) => (Char -> Bool) -> s -> Bool
  any = oany
  null    :: s -> Bool
  default null :: (MonoFoldable s) => s -> Bool
  null = onull
  length  :: s -> Int
  default length :: (MonoFoldable s) => s -> Int
  length = olength
  elem    :: Char -> s -> Bool
  default elem :: (MonoFoldable s, Element s ~ Char) => Char -> s -> Bool
  elem = oelem
  maximum :: s -> Char
  default maximum :: (MonoFoldable s, Element s ~ Char) => s -> Char
  maximum = maximumEx
  minimum :: s -> Char
  default minimum :: (MonoFoldable s, Element s ~ Char) => s -> Char
  minimum = minimumEx

instance FoldableString String
instance FoldableString TS.Text
instance FoldableString TL.Text
instance FoldableString BS.ByteString where
  foldMap = BSC.concatMap
  foldr   = BSC.foldr
  foldl'  = BSC.foldl'
  all     = BSC.all
  any     = BSC.any
  elem    = BSC.elem
  maximum = BSC.maximum
  minimum = BSC.minimum
instance FoldableString BL.ByteString where
  foldMap = BLC.concatMap
  foldr   = BLC.foldr
  foldl'  = BLC.foldl'
  all     = BLC.all
  any     = BLC.any
  elem    = BLC.elem
  maximum = BLC.maximum
  minimum = BLC.minimum

{-| MonoTraversable containing Char -}
class (FunctorString s, FoldableString s) => TraverseString s where
  traverse :: Applicative f => (Char -> f Char) -> s -> f s
  default traverse :: (MonoTraversable s, Element s ~ Char, Applicative f) => (Char -> f Char) -> s -> f s
  traverse = otraverse

instance TraverseString String
instance TraverseString TS.Text
instance TraverseString TL.Text
instance TraverseString BS.ByteString where
  traverse f = fmap to . Prelude.traverse f . (to @ BS.ByteString @ String)
instance TraverseString BL.ByteString where
  traverse f = fmap to . Prelude.traverse f . (to @ BL.ByteString @ String)