{-# LANGUAGE ParallelListComp #-}
-- | Parse and pretty print the string of help of commands 
module Helper where
import Control.Monad
import Text.ParserCombinators.Parsec.Prim
import Text.ParserCombinators.Parsec.Char
import Text.ParserCombinators.Parsec.Token
import Text.ParserCombinators.Parsec.Combinator
import Text.PrettyPrint (render,text,nest, (<>),(<+>),($$),sep)
import Data.List (transpose,find)


-- |structure for the help of a command
data CommandHelp = CommandHelp {
	name 		:: String, 	-- ^ the command name
	synopsis	:: String, 	-- ^ how to run it
	descriptions	:: [String],	-- ^ aspects
	errors		:: [String],	-- ^ errors explanations
	implementation	:: String	-- ^ implementation state
	}

instance Show CommandHelp where
	show (CommandHelp name synopsis descriptions errors implementation) = render $
		text ("Command: " ++ name ) $$ 
			nest 4 (
				text ("Synopsis: " ++ synopsis) $$
				(text ("Description: ") <> foldr1 ($$) (map text descriptions)) $$
				(text ("Errors: ") <> foldr1 ($$) (map text errors)) $$
				text ("Implementation: " ++ implementation)
				)
-- |parses a CommandHelp			
parseACommandHelp 	:: CharParser () CommandHelp 			
parseACommandHelp = do
	name <- field 0 "command"
	synopsis <- field 1 "synopsis"
	descriptions <- many (try $ field 1 "description")
	errors <- many (try $ field 1 "error")
	implementation <- field 1 "implementation"
	return $ CommandHelp name synopsis descriptions errors implementation
		where
	field n name = replicateM n tab >> string name >>  char ')' >> many space >> manyTill anyChar newline
	
-- |parses all commands help
parseCommandsHelp 	:: CharParser () [CommandHelp]
parseCommandsHelp = do
	rs <- many (try $ many emptyline >> parseACommandHelp) 
	manyTill  anyChar  eof
	return rs
		where
	emptyline = manyTill space newline

-- |run the parser against a string
run 
	:: String 			-- ^ The string to parse
	-> GenParser Char () a 		-- ^ the parser to use
	-> (a -> Maybe b)		-- ^ a function to use on the result , if it succed 
	-> Either String (Maybe b)	-- ^ the error showed if it fails or the result closed

run file p cl = either (Left . show) (Right . cl) (parse p "help parser" file)  

-- |create a nice table from lines of words
tabulate :: [[String]] -> String
tabulate = render . foldr1 ($$) . tabulate' . transpose where
	tabulate' (xs:[]) 	= map text xs
	tabulate' (xs:yss) 	= [text x $$ nest (maximum (map length xs) + 1) y| x <- xs | y <- tabulate' yss]

-- |parse a prettyprint of a list of command helps from a string
listOfCommands 	
	:: String 			-- ^ the string with the help inside
	-> Either String (Maybe String)	-- ^ a parse error or Just a prettyprint of a list of command helps
listOfCommands file = run file parseCommandsHelp (Just . tabulate . map (\c -> [synopsis c , name c]))

-- |parse a prettyprint of a list of command helps from a string
helpCommand
	:: String 			-- ^ the command name
	->  String 			-- ^ the string with the help inside
	-> Either String (Maybe String)	-- ^ a parse error or (Just the command help or Nothing if the command is missing)
helpCommand s file = run file parseCommandsHelp (\xs -> find  ((==s).name) xs >>= return . show)