{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE UnicodeSyntax #-}
-- | Parser for text format for DL data structure, for example
--
-- @
-- c/
-- ..x
-- ..y
-- ....n
-- ....n
-- ....
-- ..z
-- ....t
-- ....t
-- ....
-- d/
-- @
--
-- where '.' stands for space is equivalent of
--
-- @
-- do directory \"c\" $ do
--      file_ \"x\"
--      file \"y\" \"n\nn\n\"
--      file \"z\" \"t\nt\n\"
--    directory_ \"d\"
-- @
--
module System.Directory.Layout.Parser
  ( layout, layout'
  ) where

import Control.Applicative
import Control.Arrow (left)
import Control.Monad (guard)
import Data.Functor.Identity (Identity)
import Data.List (intercalate)

import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import           Text.Parsec hiding ((<|>), many)
import           Text.Parsec.Text ()
import           Text.Parsec.Text.Lazy ()

import System.Directory.Layout.Internal


-- | lazy 'Text' parser
layout  LT.Text  Either String Layout
layout = glayout


-- | strict 'Text' parser
layout'  T.Text  Either String Layout
layout' = glayout


glayout  Stream s Identity Char  s  Either String Layout
glayout = left show . parse (sequence_ <$> many (p_any 0)) "(layout parser)"


p_any  Stream s Identity Char  Int  Parsec s u Layout
p_any n = try (p_directory n) <|> p_file n


p_directory  Stream s Identity Char  Int  Parsec s u Layout
p_directory n = do
  name  p_directory_name
  inner  sequence_ <$> try (inners p_any n) <|> return (E ())
  return $ D name inner (E ())


p_file  Stream s Identity Char  Int  Parsec s u Layout
p_file n = do
  name  p_file_name
  inner  Just . T.intercalate "\n" <$> try (inners (const p_text) n) <|> return Nothing
  return $ F name inner (E ())


inners  Stream s Identity Char  (Int  Parsec s u a)  Int  Parsec s u [a]
inners p n = do
  indent  length <$> many (char ' ')
  guard (indent > n)
  (:) <$> p indent <*> generous_many (string (replicate indent ' ') *> p indent)


generous_many  Parsec s u a  Parsec s u [a]
generous_many p = f
 where
  f = try ((:) <$> p <*> f) <|> return []
{-# INLINE generous_many #-}


p_directory_name  Stream s Identity Char  Parsec s u String
p_directory_name = some (noneOf "/\n") <* char '/' <* char '\n'


p_file_name  Stream s Identity Char  Parsec s u String
p_file_name = intercalate "." <$> ((some (noneOf "/.\n") `sepBy` char '.') <* char '\n')


p_text  Stream s Identity Char  Parsec s u T.Text
p_text = T.pack <$> many (noneOf "\n") <* char '\n'