{-
    Copyright 2023 Mario Blazevic

    License: BSD3 (see BSD3-LICENSE.txt file)
-}

-- | This module defines the monoid transformer data type 'Shadowed'.
{-# LANGUAGE Haskell2010, DeriveDataTypeable #-}

module Data.Monoid.Instances.PrefixMemory (
   Shadowed, shadowed, content, prefix
   )
where

import Control.Applicative -- (Applicative(..))
import qualified Data.List as List
import Data.String (IsString(fromString))

import Data.Data (Data, Typeable)
import Data.Semigroup (Semigroup(..))
import Data.Monoid (Monoid(..), Endo(..))
import Data.Semigroup.Cancellative (LeftReductive(..), RightReductive(..))
import Data.Semigroup.Factorial (Factorial(..), StableFactorial)
import Data.Monoid.GCD (LeftGCDMonoid(..), RightGCDMonoid(..))
import Data.Monoid.Null (MonoidNull(null), PositiveMonoid)
import Data.Monoid.Factorial (FactorialMonoid(..))
import Data.Monoid.Textual (TextualMonoid(..))
import qualified Data.Semigroup.Factorial as Factorial
import qualified Data.Monoid.Factorial as Factorial
import qualified Data.Monoid.Textual as Textual

import Prelude hiding (all, any, break, filter, foldl, foldl1, foldr, foldr1, lines, map, concatMap,
                       length, null, reverse, scanl, scanr, scanl1, scanr1, span, splitAt)

-- | Monoid transformer that keeps track of the former 'prefix' of its 'content'. All functions that return a suffix
-- of their argument, such as 'stripPrefix' or 'commonSuffix', preserve the discarded 'prefix'.
data Shadowed m = Shadowed{prefix :: !m,
                           -- ^ used to precede the 'content' but has been consumed
                           content :: !m
                           -- ^ the present value
                          } deriving (Data, Typeable)

-- | The constructor of a 'Shadowed' monoid, with the initial @prefix = null@
shadowed :: Monoid m => m -> Shadowed m
shadowed = Shadowed mempty

instance Eq m => Eq (Shadowed m) where
   Shadowed{content = a} == Shadowed{content = b} = a == b

instance Ord m => Ord (Shadowed m) where
   compare Shadowed{content= a} Shadowed{content= b} = compare a b

instance (MonoidNull m, Show m) => Show (Shadowed m) where
   showsPrec prec (Shadowed p c) rest
      | null p = showsPrec prec c rest
      | otherwise = "Shadowed{prefix=" <> shows p (", content=" <> shows c ("}" <> rest))

instance (MonoidNull m, StableFactorial m) => Semigroup (Shadowed m) where
   Shadowed p1 c1 <> m2@Shadowed{content = c2}
      | null c1 && null p1 = m2
      | otherwise = Shadowed p1 (c1 <> c2)
   {-# INLINE (<>) #-}

instance (MonoidNull m, StableFactorial m) => Monoid (Shadowed m) where
   mempty = shadowed mempty
   mappend = (<>)
   {-# INLINE mempty #-}
   {-# INLINE mappend #-}

instance (MonoidNull m, StableFactorial m) => MonoidNull (Shadowed m) where
   null = null . content
   {-# INLINE null #-}

instance (PositiveMonoid m, StableFactorial m) => PositiveMonoid (Shadowed m)

instance (MonoidNull m, StableFactorial m, LeftReductive m) => LeftReductive (Shadowed m) where
   t1 `isPrefixOf` t2 = content t1 `isPrefixOf` content t2
   stripPrefix (Shadowed _ c1) (Shadowed p c2) = fmap (Shadowed (p <> c1)) (stripPrefix c1 c2)
   {-# INLINE isPrefixOf #-}
   {-# INLINE stripPrefix #-}

instance (Eq m, StableFactorial m, FactorialMonoid m, LeftGCDMonoid m) => LeftGCDMonoid (Shadowed m) where
   stripCommonPrefix (Shadowed p1 c1) (Shadowed p2 c2) =
      (Shadowed prefix' common, Shadowed (p1 <> common) c1', Shadowed (p2 <> common) c2')
      where (common, c1', c2') = stripCommonPrefix c1 c2
            prefix' = if p1 == p2 then p1 <> common else common
   {-# INLINE stripCommonPrefix #-}

instance (StableFactorial m, FactorialMonoid m, RightReductive m) => RightReductive (Shadowed m) where
   isSuffixOf (Shadowed _ c1) (Shadowed _ c2) = isSuffixOf c1 c2
   stripSuffix (Shadowed _ c1) (Shadowed p c2) = fmap (Shadowed p) (stripSuffix c1 c2)
   {-# INLINE isSuffixOf #-}
   {-# INLINE stripSuffix #-}

instance (StableFactorial m, FactorialMonoid m, RightGCDMonoid m) => RightGCDMonoid (Shadowed m) where
   commonSuffix (Shadowed _ c1) (Shadowed _ c2) = shadowed suffix
      where suffix = commonSuffix c1 c2
   stripCommonSuffix (Shadowed p1 c1) (Shadowed p2 c2) =
      (Shadowed p1 c1', Shadowed p2 c2',
       shadowed suffix)
      where (c1', c2', suffix) = stripCommonSuffix c1 c2
   {-# INLINE commonSuffix #-}
   {-# INLINE stripCommonSuffix #-}

instance (FactorialMonoid m, StableFactorial m) => Factorial (Shadowed m) where
   factors (Shadowed p c) = rewrap <$> List.tail (inits c)
      where rewrap t
               | Just (p', prime) <- splitPrimeSuffix t = Shadowed (p <> p') prime
               | otherwise = error "all (not . null) . tail . inits"
   primePrefix (Shadowed p c) = Shadowed p (primePrefix c)
   foldl f a0 (Shadowed p0 c0) = fst $ Factorial.foldl f' (a0, p0) c0
      where f' (a, p) c = (f a (Shadowed p c), p <> c)
   foldl' f a0 (Shadowed p0 c0) = fst $ Factorial.foldl' f' (a0, p0) c0
      where f' (a, p) c = ((,) $! f a (Shadowed p c)) $! p <> c
   foldr f a0 (Shadowed p0 c0) = Factorial.foldr f' (const a0) c0 p0
      where f' c cont p = f (Shadowed p c) (cont $! p <> c)
   foldMap f (Shadowed p0 c) = appEndo (Factorial.foldMap f' c) (const mempty) p0
      where -- f' :: m -> Endo (Int -> m)
            f' prime = Endo (\cont p-> f (Shadowed p prime) `mappend` (cont $! p <> prime))
   length (Shadowed _ c) = length c
   reverse (Shadowed p c) = Shadowed p (Factorial.reverse c)
   {-# INLINE primePrefix #-}
   {-# INLINE foldl #-}
   {-# INLINE foldl' #-}
   {-# INLINE foldr #-}
   {-# INLINE foldMap #-}

instance (StableFactorial m, FactorialMonoid m) => FactorialMonoid (Shadowed m) where
   splitPrimePrefix (Shadowed p c) = fmap rewrap (splitPrimePrefix c)
      where rewrap (cp, cs) = (Shadowed p cp, Shadowed (p <> cp) cs)
   splitPrimeSuffix (Shadowed p c) = fmap rewrap (splitPrimeSuffix c)
      where rewrap (cp, cs) = (Shadowed p cp, Shadowed (p <> cp) cs)
   spanMaybe s0 f (Shadowed p0 c) = rewrap $ Factorial.spanMaybe (s0, p0) f' c
      where f' (s, p) prime = do s' <- f s (Shadowed p prime)
                                 let p' = p <> prime
                                 Just $! seq p' (s', p')
            rewrap (cp, cs, (s, p)) = (Shadowed p0 cp, Shadowed p cs, s)
   spanMaybe' s0 f (Shadowed p0 c) = rewrap $! Factorial.spanMaybe' (s0, p0) f' c
      where f' (s, p) prime = do s' <- f s (Shadowed p prime)
                                 let p' = p <> prime
                                 Just $! s' `seq` p' `seq` (s', p')
            rewrap (cp, cs, (s, p)) = (Shadowed p0 cp, Shadowed p cs, s)
   span f (Shadowed p0 c) = rewrap $ Factorial.spanMaybe' p0 f' c
      where f' p prime = if f (Shadowed p prime)
                         then Just $! p <> prime
                         else Nothing
            rewrap (cp, cs, p) = (Shadowed p0 cp, Shadowed p cs)
   splitAt n (Shadowed p c) = (Shadowed p cp, Shadowed (p <> cp) cs)
      where (cp, cs) = splitAt n c
   take n (Shadowed p c) = Shadowed p (Factorial.take n c)
   {-# INLINE splitPrimePrefix #-}
   {-# INLINE splitPrimeSuffix #-}
   {-# INLINE span #-}
   {-# INLINE splitAt #-}
   {-# INLINE take #-}

instance (StableFactorial m, FactorialMonoid m) => StableFactorial (Shadowed m)

instance (Monoid m, IsString m) => IsString (Shadowed m) where
   fromString = shadowed . fromString

instance (Eq m, StableFactorial m, TextualMonoid m) => TextualMonoid (Shadowed m) where
   splitCharacterPrefix (Shadowed p t) = (Shadowed p <$>) <$> Textual.splitCharacterPrefix t

   fromText = shadowed . fromText
   singleton = shadowed . singleton

   characterPrefix = characterPrefix . content

   map f (Shadowed p c) = Shadowed p (map f c)
   concatMap f (Shadowed p c) = Shadowed p (concatMap (content . f) c)
   all p = all p . content
   any p = any p . content

   foldl ft fc a0 (Shadowed p0 c0) = fst $ Textual.foldl ft' fc' (a0, p0) c0
      where ft' (a, p) c = (ft a (Shadowed p c), p <> c)
            fc' (a, p) c = (fc a c, p <> Textual.singleton c)
   foldl' ft fc a0 (Shadowed p0 c0) = fst $ Textual.foldl' ft' fc' (a0, p0) c0
      where ft' (a, p) c = ((,) $! ft a (Shadowed p c)) $! p <> c
            fc' (a, p) c = ((,) $! fc a c) $! p <> Textual.singleton c
   foldr ft fc a0 (Shadowed p0 c0) = snd $ Textual.foldr ft' fc' (p0, a0) c0
      where ft' c (p, a) = ((,) $! p <> c) $! ft (Shadowed p c) a
            fc' c (p, a) = ((,) $! p <> Textual.singleton c) $! fc c a

   scanl f ch (Shadowed p c) = Shadowed p (Textual.scanl f ch c)
   scanl1 f (Shadowed p c) = Shadowed p (Textual.scanl1 f c)
   scanr f ch (Shadowed p c) = Shadowed p (Textual.scanr f ch c)
   scanr1 f (Shadowed p c) = Shadowed p (Textual.scanr1 f c)
   mapAccumL f a0 (Shadowed p c) = fmap (Shadowed p) (Textual.mapAccumL f a0 c)
   mapAccumR f a0 (Shadowed p c) = fmap (Shadowed p) (Textual.mapAccumR f a0 c)

   spanMaybe s0 ft fc (Shadowed p0 t) = rewrap $ Textual.spanMaybe (s0, p0) ft' fc' t
      where ft' (s, p) prime = do s' <- ft s (Shadowed p prime)
                                  let p' = p <> prime
                                  Just $! seq p' (s', p')
            fc' (s, p) c = do s' <- fc s c
                              let p' = p <> Textual.singleton c
                              Just $! seq p' (s', p')
            rewrap (tp, ts, (s, p)) = (Shadowed p0 tp, Shadowed p ts, s)
   spanMaybe' s0 ft fc (Shadowed p0 t) = rewrap $! Textual.spanMaybe' (s0, p0) ft' fc' t
      where ft' (s, p) prime = do s' <- ft s (Shadowed p prime)
                                  let p' = p <> prime
                                  Just $! s' `seq` p' `seq` (s', p')
            fc' (s, p) c = do s' <- fc s c
                              let p' = p <> Textual.singleton c
                              Just $! s' `seq` p' `seq` (s', p')
            rewrap (tp, ts, (s, p)) = (Shadowed p0 tp, Shadowed p ts, s)
   span ft fc (Shadowed p0 t) = rewrap $ Textual.spanMaybe' p0 ft' fc' t
      where ft' p prime = if ft (Shadowed p prime)
                          then Just $! p <> prime
                          else Nothing
            fc' p c = if fc c
                      then Just $! p <> Textual.singleton c
                      else Nothing
            rewrap (tp, ts, p) = (Shadowed p0 tp, Shadowed p ts)

   split f (Shadowed p0 c0) = rewrap p0 (Textual.split f c0)
      where rewrap _ [] = []
            rewrap p (c:rest) = Shadowed p c : rewrap (p <> c) rest
   find p = find p . content

   foldl_ fc a0 (Shadowed _ c) = Textual.foldl_ fc a0 c
   foldl_' fc a0 (Shadowed _ c) = Textual.foldl_' fc a0 c
   foldr_ fc a0 (Shadowed _ c) = Textual.foldr_ fc a0 c

   spanMaybe_ s0 fc (Shadowed p0 t) = rewrap $ Textual.spanMaybe_' (s0, p0) fc' t
      where fc' (s, p) c = do s' <- fc s c
                              let p' = p <> Textual.singleton c
                              Just $! seq p' (s', p')
            rewrap (tp, ts, (s, p)) = (Shadowed p0 tp, Shadowed p ts, s)
   spanMaybe_' s0 fc (Shadowed p0 t) = rewrap $! Textual.spanMaybe_' (s0, p0) fc' t
      where fc' (s, p) c = do s' <- fc s c
                              let p' = p <> Textual.singleton c
                              Just $! s' `seq` p' `seq` (s', p')
            rewrap (tp, ts, (s, p)) = (Shadowed p0 tp, Shadowed p ts, s)
   span_ bt fc (Shadowed p0 t) = rewrap $ Textual.span_ bt fc t
      where rewrap (tp, ts) = (Shadowed p0 tp, Shadowed (p0 <> tp) ts)
   break_ bt fc (Shadowed p0 t) = rewrap $ Textual.break_ bt fc t
      where rewrap (tp, ts) = (Shadowed p0 tp, Shadowed (p0 <> tp) ts)
   dropWhile_ bt fc t = snd (span_ bt fc t)
   takeWhile_ bt fc (Shadowed p t) = Shadowed p (takeWhile_ bt fc t)
   toString ft (Shadowed _ t) = toString (ft . shadowed) t
   toText ft (Shadowed _ t) = toText (ft . shadowed) t

   {-# INLINE characterPrefix #-}
   {-# INLINE splitCharacterPrefix #-}
   {-# INLINE map #-}
   {-# INLINE concatMap #-}
   {-# INLINE foldl' #-}
   {-# INLINE foldr #-}
   {-# INLINABLE spanMaybe #-}
   {-# INLINABLE spanMaybe' #-}
   {-# INLINABLE span #-}
   {-# INLINE foldl_' #-}
   {-# INLINE foldr_ #-}
   {-# INLINE any #-}
   {-# INLINE all #-}
   {-# INLINABLE spanMaybe_ #-}
   {-# INLINABLE spanMaybe_' #-}
   {-# INLINE span_ #-}
   {-# INLINE break_ #-}
   {-# INLINE dropWhile_ #-}
   {-# INLINE takeWhile_ #-}
   {-# INLINE split #-}
   {-# INLINE find #-}
