{- 
    Copyright 2013-2022 Mario Blazevic

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

-- | This module defines the monoid transformer data type 'Concat'.
-- 

{-# LANGUAGE Haskell2010, DeriveDataTypeable #-}

module Data.Monoid.Instances.Concat (
   Concat, concatenate, extract, force
   )
where

import Control.Applicative -- (Applicative(..))
import Control.Arrow (first)
import Data.Data (Data, Typeable)
import qualified Data.Foldable as Foldable
import qualified Data.List as List
import Data.String (IsString(..))
import Data.Semigroup (Semigroup(..))
import Data.Monoid (Monoid(..), First(..), Sum(..))
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.Monoid.Factorial as Factorial
import qualified Data.Monoid.Textual as Textual
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
import qualified Data.Text as Text

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

-- | @'Concat'@ is a transparent monoid transformer. The behaviour of the @'Concat' a@ instances of monoid subclasses is
-- identical to the behaviour of their @a@ instances, up to the 'pure' isomorphism.
--
-- The only purpose of 'Concat' then is to change the performance characteristics of various operations. Most
-- importantly, injecting a monoid into 'Concat' has the effect of making 'mappend' a constant-time operation. The
-- `splitPrimePrefix` and `splitPrimeSuffix` operations are amortized to constant time, provided that only one or the
-- other is used. Using both operations alternately will trigger the worst-case behaviour of O(n).
--
data Concat a = Leaf a
              | Concat a :<> Concat a
              deriving (Data, Show, Typeable)

{-# DEPRECATED concatenate, extract "Concat is not wrapping Seq any more, don't use concatenate nor extract." #-}
concatenate :: PositiveMonoid a => Seq a -> Concat a
concatenate q
   | Foldable.all null q = mempty
   | otherwise = Foldable.foldr (\a c-> if null a then c else Leaf a <> c) mempty q

extract :: Concat a -> Seq a
extract = Seq.fromList . Foldable.toList

force :: Semigroup a => Concat a -> a
force (Leaf x) = x
force (x :<> y) = force x <> force y

instance (Eq a, Semigroup a) => Eq (Concat a) where
   x == y = force x == force y

instance (Ord a, Semigroup a) => Ord (Concat a) where
   compare x y = compare (force x) (force y)

instance Functor Concat where
   fmap f (Leaf x) = Leaf (f x)
   fmap f (l :<> r) = fmap f l :<> fmap f r

instance Applicative Concat where
   pure = Leaf
   Leaf f <*> x = f <$> x
   (f1 :<> f2) <*> x = (f1 <*> x) :<> (f2 <*> x)

instance Foldable.Foldable Concat where
   fold (Leaf x) = x
   fold (x :<> y) = Foldable.fold x `mappend` Foldable.fold y
   foldMap f (Leaf x) = f x
   foldMap f (x :<> y) = Foldable.foldMap f x `mappend` Foldable.foldMap f y
   foldl f a (Leaf x) = f a x
   foldl f a (x :<> y) = Foldable.foldl f (Foldable.foldl f a x) y
   foldl' f a (Leaf x) = f a x
   foldl' f a (x :<> y) = let a' = Foldable.foldl' f a x in a' `seq` Foldable.foldl' f a' y
   foldr f a (Leaf x) = f x a
   foldr f a (x :<> y) = Foldable.foldr f (Foldable.foldr f a y) x
   foldr' f a (Leaf x) = f x a
   foldr' f a (x :<> y) = let a' = Foldable.foldr' f a y in Foldable.foldr' f a' x

instance PositiveMonoid a => Semigroup (Concat a) where
   x <> y 
      | null x = y
      | null y = x
      | otherwise = x :<> y

instance PositiveMonoid a => Monoid (Concat a) where
   mempty = Leaf mempty
   mappend = (<>)

instance PositiveMonoid a => MonoidNull (Concat a) where
   null (Leaf x) = null x
   null _ = False

instance PositiveMonoid a => PositiveMonoid (Concat a)

instance (LeftReductive a, StableFactorial a, PositiveMonoid a) => LeftReductive (Concat a) where
   stripPrefix (Leaf x) (Leaf y) = Leaf <$> stripPrefix x y
   stripPrefix (xp :<> xs) y = stripPrefix xp y >>= stripPrefix xs
   stripPrefix x (yp :<> ys) = case (stripPrefix x yp, stripPrefix yp x)
                               of (Just yps, _) -> Just (yps <> ys)
                                  (Nothing, Nothing) -> Nothing
                                  (Nothing, Just xs) -> stripPrefix xs ys

instance (RightReductive a, StableFactorial a, PositiveMonoid a) => RightReductive (Concat a) where
   stripSuffix (Leaf x) (Leaf y) = Leaf <$> stripSuffix x y
   stripSuffix (xp :<> xs) y = stripSuffix xs y >>= stripSuffix xp
   stripSuffix x (yp :<> ys) = case (stripSuffix x ys, stripSuffix ys x)
                               of (Just ysp, _) -> Just (yp <> ysp)
                                  (Nothing, Nothing) -> Nothing
                                  (Nothing, Just xp) -> stripSuffix xp yp

instance (LeftGCDMonoid a, StableFactorial a, PositiveMonoid a) => LeftGCDMonoid (Concat a) where
   stripCommonPrefix (Leaf x) (Leaf y) = map3 Leaf (stripCommonPrefix x y)
   stripCommonPrefix (xp :<> xs) y
      | null xps = (xp <> xsp, xss, yss)
      | otherwise = (xpp, xps <> xs, ys)
      where (xpp, xps, ys) = stripCommonPrefix xp y
            (xsp, xss, yss) = stripCommonPrefix xs ys
   stripCommonPrefix x (yp :<> ys)
      | null yps = (yp <> ysp, xss, yss)
      | otherwise = (ypp, xs, yps <> ys)
      where (ypp, xs, yps) = stripCommonPrefix x yp
            (ysp, xss, yss) = stripCommonPrefix xs ys

instance (RightGCDMonoid a, StableFactorial a, PositiveMonoid a) => RightGCDMonoid (Concat a) where
   stripCommonSuffix (Leaf x) (Leaf y) = map3 Leaf (stripCommonSuffix x y)
   stripCommonSuffix (xp :<> xs) y
      | null xsp = (xpp, ypp, xps <> xs)
      | otherwise = (xp <> xsp, yp, xss)
      where (xsp, yp, xss) = stripCommonSuffix xs y
            (xpp, ypp, xps) = stripCommonSuffix xp yp
   stripCommonSuffix x (yp :<> ys)
      | null ysp = (xpp, ypp, yps <> ys)
      | otherwise = (xp, yp <> ysp, yss)
      where (xp, ysp, yss) = stripCommonSuffix x ys
            (xpp, ypp, yps) = stripCommonSuffix xp yp

instance (Factorial a, PositiveMonoid a) => Factorial (Concat a) where
   factors c = toList c []
      where toList (Leaf x) rest
               | null x = rest
               | otherwise = (Leaf <$> factors x) ++ rest
            toList (x :<> y) rest = toList x (toList y rest)
   primePrefix (Leaf x) = Leaf (primePrefix x)
   primePrefix (x :<> _) = primePrefix x
   primeSuffix (Leaf x) = Leaf (primeSuffix x)
   primeSuffix (_ :<> y) = primeSuffix y

   foldl f = Foldable.foldl g
      where g = Factorial.foldl (\a-> f a . Leaf)
   foldl' f = Foldable.foldl' g
      where g = Factorial.foldl' (\a-> f a . Leaf)
   foldr f = Foldable.foldr g
      where g a b = Factorial.foldr (f . Leaf) b a
   foldMap f = Foldable.foldMap (Factorial.foldMap (f . Leaf))
   length x = getSum $ Foldable.foldMap (Sum . length) x
   reverse (Leaf x) = Leaf (reverse x)
   reverse (x :<> y) = reverse y :<> reverse x

instance (FactorialMonoid a, PositiveMonoid a) => FactorialMonoid (Concat a) where
   splitPrimePrefix (Leaf x) = map2 Leaf <$> splitPrimePrefix x
   splitPrimePrefix (x :<> y) = ((<> y) <$>) <$> splitPrimePrefix x
   splitPrimeSuffix (Leaf x) = map2 Leaf <$> splitPrimeSuffix x
   splitPrimeSuffix (x :<> y) = first (x <>) <$> splitPrimeSuffix y
   span p (Leaf x) = map2 Leaf (Factorial.span (p . Leaf) x)
   span p (x :<> y)
      | null xs = (x <> yp, ys)
      | otherwise = (xp, xs :<> y)
      where (xp, xs) = Factorial.span p x
            (yp, ys) = Factorial.span p y
   spanMaybe s0 f (Leaf x) = first2 Leaf (Factorial.spanMaybe s0 (\s-> f s . Leaf) x)
   spanMaybe s0 f (x :<> y)
      | null xs = (x :<> yp, ys, s2)
      | otherwise = (xp, xs :<> y, s1)
      where (xp, xs, s1) = Factorial.spanMaybe s0 f x
            (yp, ys, s2) = Factorial.spanMaybe s1 f y
   spanMaybe' s0 f c = seq s0 $
      case c
      of Leaf x -> first2 Leaf (Factorial.spanMaybe' s0 (\s-> f s . Leaf) x)
         x :<> y -> let (xp, xs, s1) = Factorial.spanMaybe' s0 f x
                        (yp, ys, s2) = Factorial.spanMaybe' s1 f y
                    in if null xs then (x :<> yp, ys, s2) else (xp, xs :<> y, s1)

   split p = Foldable.foldr splitNext [mempty]
      where splitNext a ~(xp:xs) =
               let as = Leaf <$> Factorial.split (p . Leaf) a
               in if null xp
                  then as ++ xs
                  else init as ++ (last as <> xp):xs
   splitAt 0 c = (mempty, c)
   splitAt n (Leaf x) = map2 Leaf (Factorial.splitAt n x)
   splitAt n (x :<> y)
      | k < n = (x :<> yp, ys)
      | k > n = (xp, xs :<> y)
      | otherwise = (x, y)
      where k = length x
            (yp, ys) = splitAt (n - k) y
            (xp, xs) = splitAt n x

instance (Factorial a, PositiveMonoid a) => StableFactorial (Concat a)

instance (IsString a) => IsString (Concat a) where
   fromString s = Leaf (fromString s)

instance (Eq a, TextualMonoid a, StableFactorial a, PositiveMonoid a) => TextualMonoid (Concat a) where
   fromText t = Leaf (fromText t)
   singleton = Leaf . singleton
   splitCharacterPrefix (Leaf x) = (Leaf <$>) <$> splitCharacterPrefix x
   splitCharacterPrefix (x :<> y) = ((<> y) <$>) <$> splitCharacterPrefix x
   characterPrefix (Leaf x) = characterPrefix x
   characterPrefix (x :<> _) = characterPrefix x
   map f x = map f <$> x
   toString ft x = List.concatMap (toString $ ft . Leaf) (Foldable.toList x)
   toText ft x = Text.concat (toText (ft . Leaf) <$> Foldable.toList x)

   foldl ft fc = Foldable.foldl g
      where g = Textual.foldl (\a-> ft a . Leaf) fc
   foldl' ft fc = Foldable.foldl' g
      where g = Textual.foldl' (\a-> ft a . Leaf) fc
   foldr ft fc = Foldable.foldr g
      where g a b = Textual.foldr (ft . Leaf) fc b a
   any p = Foldable.any (any p)
   all p = Foldable.all (all p)

   span pt pc (Leaf x) = map2 Leaf (Textual.span (pt . Leaf) pc x)
   span pt pc (x :<> y)
      | null xs = (x <> yp, ys)
      | otherwise = (xp, xs :<> y)
      where (xp, xs) = Textual.span pt pc x
            (yp, ys) = Textual.span pt pc y
   span_ bt pc (Leaf x) = map2 Leaf (Textual.span_ bt pc x)
   span_ bt pc (x :<> y)
      | null xs = (x <> yp, ys)
      | otherwise = (xp, xs :<> y)
      where (xp, xs) = Textual.span_ bt pc x
            (yp, ys) = Textual.span_ bt pc y
   break pt pc = Textual.span (not . pt) (not . pc)
   takeWhile_ bt pc = fst . span_ bt pc
   dropWhile_ bt pc = snd . span_ bt pc
   break_ bt pc = span_ (not bt) (not . pc)

   spanMaybe s0 ft fc (Leaf x) = first2 Leaf (Textual.spanMaybe s0 (\s-> ft s . Leaf) fc x)
   spanMaybe s0 ft fc (x :<> y)
      | null xs = (x :<> yp, ys, s2)
      | otherwise = (xp, xs :<> y, s1)
      where (xp, xs, s1) = Textual.spanMaybe s0 ft fc x
            (yp, ys, s2) = Textual.spanMaybe s1 ft fc y
   spanMaybe' s0 ft fc c = seq s0 $
      case c
      of Leaf x -> first2 Leaf (Textual.spanMaybe' s0 (\s-> ft s . Leaf) fc x)
         x :<> y -> let (xp, xs, s1) = Textual.spanMaybe' s0 ft fc x
                        (yp, ys, s2) = Textual.spanMaybe' s1 ft fc y
                    in if null xs then (x :<> yp, ys, s2) else (xp, xs :<> y, s1)
   spanMaybe_ s0 fc (Leaf x) = first2 Leaf (Textual.spanMaybe_ s0 fc x)
   spanMaybe_ s0 fc (x :<> y)
      | null xs = (x :<> yp, ys, s2)
      | otherwise = (xp, xs :<> y, s1)
      where (xp, xs, s1) = Textual.spanMaybe_ s0 fc x
            (yp, ys, s2) = Textual.spanMaybe_ s1 fc y
   spanMaybe_' s0 fc c = seq s0 $
      case c
      of Leaf x -> first2 Leaf (Textual.spanMaybe_' s0 fc x)
         x :<> y -> let (xp, xs, s1) = Textual.spanMaybe_' s0 fc x
                        (yp, ys, s2) = Textual.spanMaybe_' s1 fc y
                    in if null xs then (x :<> yp, ys, s2) else (xp, xs :<> y, s1)

   split p = Foldable.foldr splitNext [mempty]
      where splitNext a ~(xp:xs) =
               let as = Leaf <$> Textual.split p a
               in if null xp
                  then as ++ xs
                  else init as ++ (last as <> xp):xs
   find p x = getFirst $ Foldable.foldMap (First . find p) x
   elem i = Foldable.any (Textual.elem i)

-- Utility functions

map2 :: (a -> b) -> (a, a) -> (b, b)
map2 f (x, y) = (f x, f y)

map3 :: (a -> b) -> (a, a, a) -> (b, b, b)
map3 f (x, y, z) = (f x, f y, f z)

first2 :: (a -> b) -> (a, a, c) -> (b, b, c)
first2 f (x, y, z) = (f x, f y, z)
