{-# LANGUAGE TypeSynonymInstances, FlexibleInstances,
    PatternGuards #-}

module Text.Pandoc.Readers.Docx.Reducible ( concatReduce
                                          , (<+>)
                                          )
       where


import Text.Pandoc.Builder
import Data.List
import Data.Sequence (ViewR(..), ViewL(..), viewl, viewr)
import qualified Data.Sequence as Seq (null)

data Modifier a = Modifier (a -> a)
                | AttrModifier (Attr -> a -> a) Attr
                | NullModifier

class (Eq a) => Modifiable a where
  modifier :: a -> Modifier a
  innards :: a -> a
  getL    :: a -> (a, a)
  getR    :: a -> (a, a)
  spaceOut :: a -> (a, a, a)

spaceOutL :: (Monoid a, Modifiable a) => a -> (a, a)
spaceOutL ms = (l, stack fs (m' <> r))
  where (l, m, r) = spaceOut ms
        (fs, m')  = unstack m

spaceOutR :: (Monoid a, Modifiable a) => a -> (a, a)
spaceOutR ms = (stack fs (l <> m'), r)
  where (l, m, r) = spaceOut ms
        (fs, m')  = unstack m

instance (Monoid a, Show a) => Show (Modifier a) where
  show (Modifier f) = show $ f mempty
  show (AttrModifier f attr) = show $ f attr mempty
  show (NullModifier) = "NullModifier"

instance (Monoid a, Eq a) => Eq (Modifier a) where
  (Modifier f) == (Modifier g) = (f mempty == g mempty)
  (AttrModifier f attr) == (AttrModifier g attr') = (f attr mempty == g attr' mempty)
  (NullModifier) == (NullModifier) = True
  _ == _ = False

instance Modifiable Inlines where
  modifier ils = case viewl (unMany ils) of
    (x :< xs) | Seq.null xs -> case x of
      (Emph _)        -> Modifier emph
      (Strong _)      -> Modifier strong
      (SmallCaps _)   -> Modifier smallcaps
      (Strikeout _)   -> Modifier strikeout
      (Superscript _) -> Modifier superscript
      (Subscript _)   -> Modifier subscript
      (Span attr _)   -> AttrModifier spanWith attr
      _               -> NullModifier
    _ -> NullModifier

  innards ils = case viewl (unMany ils) of
    (x :< xs) | Seq.null xs -> case x of
      (Emph lst)        -> fromList lst
      (Strong lst)      -> fromList lst
      (SmallCaps lst)   -> fromList lst
      (Strikeout lst)   -> fromList lst
      (Superscript lst) -> fromList lst
      (Subscript lst)   -> fromList lst
      (Span _ lst)      -> fromList lst
      _        -> ils
    _          -> ils

  getL ils = case viewl $ unMany ils of
    (s :< sq) -> (singleton s, Many sq)
    _          -> (mempty, ils)

  getR ils = case viewr $ unMany ils of
    (sq :> s) -> (Many sq, singleton s)
    _          -> (ils, mempty)

  spaceOut ils =
    let (fs, ils') = unstack ils
        contents = unMany ils'
        left  = case viewl contents of
          (Space :< _) -> space
          _            -> mempty
        right = case viewr contents of
          (_ :> Space) -> space
          _            -> mempty in
    (left, (stack fs $ trimInlines .Many $ contents), right)

instance Modifiable Blocks where
  modifier blks = case viewl (unMany blks) of
    (x :< xs) | Seq.null xs -> case x of
      (BlockQuote _) -> Modifier blockQuote
      -- (Div attr _)   -> AttrModifier divWith attr
      _               -> NullModifier
    _ -> NullModifier

  innards blks = case viewl (unMany blks) of
    (x :< xs) | Seq.null xs -> case x of
      (BlockQuote lst) -> fromList lst
      -- (Div attr lst)   -> fromList lst
      _        -> blks
    _          -> blks

  spaceOut blks = (mempty, blks, mempty)

  getL ils = case viewl $ unMany ils of
    (s :< sq) -> (singleton s, Many sq)
    _          -> (mempty, ils)

  getR ils = case viewr $ unMany ils of
    (sq :> s) -> (Many sq, singleton s)
    _          -> (ils, mempty)


unstack :: (Modifiable a) => a -> ([Modifier a], a)
unstack ms = case modifier ms of
  NullModifier -> ([], ms)
  _            -> (f : fs, ms') where
    f = modifier ms
    (fs, ms') = unstack $ innards ms

stack :: (Monoid a, Modifiable a) => [Modifier a] -> a -> a
stack [] ms = ms
stack (NullModifier : fs) ms = stack fs ms
stack ((Modifier f) : fs) ms =
  if isEmpty ms
  then stack fs ms
  else f $ stack fs ms
stack ((AttrModifier f attr) : fs) ms = f attr $ stack fs ms

isEmpty :: (Monoid a, Eq a) => a -> Bool
isEmpty x = x == mempty


combine :: (Monoid a, Modifiable a, Eq a) => a -> a -> a
combine x y =
  let (xs', x') = getR x
      (y', ys') = getL y
  in
   xs' <> (combineSingleton x' y') <> ys'

isAttrModifier :: Modifier a -> Bool
isAttrModifier (AttrModifier _ _) = True
isAttrModifier _                  = False

combineSingleton :: (Monoid a, Modifiable a, Eq a) => a -> a -> a
combineSingleton x y =
  let (xfs, xs) = unstack x
      (yfs, ys) = unstack y
      shared = xfs `intersect` yfs
      x_remaining = xfs \\ shared
      y_remaining = yfs \\ shared
      x_rem_attr = filter isAttrModifier x_remaining
      y_rem_attr = filter isAttrModifier y_remaining
  in
   case null shared of
     True | isEmpty xs && isEmpty ys ->
            stack (x_rem_attr ++ y_rem_attr) mempty
          | isEmpty xs ->
            let (sp, y') = spaceOutL y in
            (stack x_rem_attr mempty) <> sp <> y'
          | isEmpty ys ->
            let (x', sp) = spaceOutR x in
            x' <> sp <> (stack y_rem_attr mempty)
          | otherwise ->
              let (x', xsp) = spaceOutR x
                  (ysp, y') = spaceOutL y
              in
               x' <> xsp <> ysp <> y'
     False -> stack shared $
              combine
              (stack x_remaining xs)
              (stack y_remaining ys)

(<+>) :: (Monoid a, Modifiable a, Eq a) => a -> a -> a
x <+> y = combine x y

concatReduce :: (Monoid a, Modifiable a) => [a] -> a
concatReduce xs = foldl combine mempty xs