{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE FlexibleContexts #-}

module Text.Docvim.Visitor (endSection, extract, extractBlocks) where

import Control.Applicative
import Control.Monad
import Control.Monad.Writer
import Data.Data.Lens
import Text.Docvim.AST
import qualified Data.DList as DList

-- | Returns True if a node marks the end of a region/block/section.
endSection :: Node -> Bool
endSection = \case
  CommandAnnotation {}   -> True
  CommandsAnnotation     -> True
  FooterAnnotation       -> True
  FunctionAnnotation _   -> True
  FunctionsAnnotation    -> True
  MappingAnnotation _    -> True
  MappingsAnnotation     -> True
  OptionAnnotation {}    -> True
  OptionsAnnotation      -> True
  PluginAnnotation {}    -> True
  _                      -> False

extract :: ([Node] -> ([[a]], [Node])) -> Node -> (Node, [a])
extract extractNodes = toList . runWriter . postorder uniplate extractor
  where
    toList (ast, dlist) = (ast, concat $ DList.toList dlist)
    extractor (DocBlock nodes) = do
      let (extracted, remainder) = extractNodes nodes
      tell (DList.fromList extracted)
      return (DocBlock remainder)
    extractor node = return node

extractBlocks :: Alternative f => (a -> Maybe (a -> Bool)) -> [a] -> (f [a], [a])
extractBlocks start = go
  where
    go     [] = (empty, [])
    go (x:xs) = maybe noExtract extract' (start x)
      where
        noExtract = (extracted, x:unextracted)
          where
            ~(extracted, unextracted) = go xs
        extract' stop = (pure (x:block) <|> extracted, unextracted)
          where
            ~(block, remainder) = break stop xs
            ~(extracted, unextracted) = go remainder

postorder :: Monad m => ((a -> m c) -> (a -> m b)) -> (b -> m c) -> (a -> m c)
postorder t f = go
  where
    go = t go >=> f