{-|
Description : Splitting lists on sublists
-}
module Language.Haskell.Formatter.Toolkit.Splitter (separate) where
import qualified Control.Applicative as Applicative
import qualified Data.List as List
import qualified Data.Monoid as Monoid
import qualified Language.Haskell.Formatter.Toolkit.ListTool as ListTool
import qualified Language.Haskell.Formatter.Toolkit.Visit as Visit

{-| Strategy to split a list on sublists. -}
data Splitter a = Splitter{delimiterPolicy :: DelimiterPolicy,
                           delimiterQueue :: [[a]]}
                    deriving (Eq, Ord, Show)

{-| What to do with the delimiters? -}
data DelimiterPolicy = Drop
                     | Separate
                     | MergeLeft
                     | MergeRight
                         deriving (Eq, Ord, Show)

{-| @separate d l@ splits @l@ on the delimiters @d@, which are matched in the
    given order. The delimiters are not kept.

    >>> separate ["pineapple", "pine"] "0pineapple1"
    ["0","1"]
    >>> separate ["pine", "pineapple"] "0pineapple1"
    ["0","apple1"] -}
separate :: Eq a => [[a]] -> [a] -> [[a]]
separate = split . createSplitter Drop
  where createSplitter rawDelimiterPolicy rawDelimiterQueue
          = Splitter{delimiterPolicy = rawDelimiterPolicy,
                     delimiterQueue = rawDelimiterQueue}

{-| @split s l@ splits @l@ according to the strategy @s@. -}
split :: Eq a => Splitter a -> [a] -> [[a]]
split splitter list
  = case delimiterPolicy splitter of
        Drop -> ListTool.takeEvery period parts
        Separate -> parts
        MergeLeft -> ListTool.concatenateRuns period parts
        MergeRight -> ListTool.concatenateShiftedRuns period shift parts
          where shift = 1
  where period = 2
        parts = rawSplit (delimiterQueue splitter) list

{-| @rawSplit s l@ splits @l@ on the sublists @s@, keeping the separators.

    prop> odd . length $ separate ["apple", "pine"] l -}
rawSplit :: Eq a => [[a]] -> [a] -> [[a]]
rawSplit delimiters = move [] []
  where move parts left [] = Monoid.mappend parts [left]
        move parts left right@(first : rest)
          = case stripFirstPrefix delimiters right of
                Nothing -> move parts (Monoid.mappend left [first]) rest
                Just (delimiter, suffix) -> move
                                              (Monoid.mappend parts
                                                 [left, delimiter])
                                              []
                                              suffix

{-| @stripFirstPrefix p l@ returns the first element of @p@ which is a prefix of
    @l@ and the rest of @l@. It returns 'Nothing' if there is no such element.

    >>> stripFirstPrefix ["\LF", "\CR\LF", "\CR"] "\CR\LFpine"
    Just ("\r\n","pine")
    >>> stripFirstPrefix ["apple"] "pineapple"
    Nothing -}
stripFirstPrefix :: Eq a => [[a]] -> [a] -> Maybe ([a], [a])
stripFirstPrefix prefixes list = Visit.findJust strip prefixes
  where strip prefix = (,) prefix Applicative.<$> List.stripPrefix prefix list