----------------------------------------------------------------
--
-- Imparse
-- Cross-platform/-language parser generator.
--
-- Text/Imparse.hs
--   Haskell implementation of the Imparse parser parser.
--

----------------------------------------------------------------
-- Useful functions for the imparser parser generator.

module Text.Imparse
  where

import Data.Char (toUpper)
import Data.List (splitAt, elemIndex)
import System.Directory (createDirectory, removeDirectoryRecursive, doesDirectoryExist, doesFileExist, removeFile)
import System.Environment (getArgs)
import System.IO ()
import Prelude hiding (catch)
import System.IO.Error hiding (catch)
import Control.Exception (throwIO, catch)

import qualified Control.Compilation.Compile as C
import qualified Text.UxADT as U (uxadt, javaScriptModule)
import Text.RichReports (report)
import Text.Ascetic.HTML (html)

import Text.Imparse.AbstractSyntax
import Text.Imparse.Report
import Text.Imparse.Parse (parseParser)
import Text.Imparse.Analysis (Analysis, analyze)
import Text.Imparse.Compile.Haskell

----------------------------------------------------------------
-- The target of the output, as specified by the command-line
-- arguments.

type HaskellModulePrefix = String

data OutputTarget =
    HTML
  | ASCII
  | UXADT
  | HASKELL HaskellModulePrefix
  deriving Eq

emitHaskell :: [OutputTarget] -> Maybe HaskellModulePrefix
emitHaskell ots = case ots of
  []           -> Nothing
  HASKELL p :_ -> Just p
  ot:ots       -> emitHaskell ots

----------------------------------------------------------------
-- Take a file path in the form of a string, and try to parse
-- the contents of the file into abstract syntax.

parseShow :: String -> IO ()
parseShow fname =
  do { s <- readFile fname
     ; r <- return $ (parseParser s :: Either String (Parser Analysis))
     ; case r of
         Left err -> do { putStr "parse error: "; putStr err }
         Right parser ->
           do { putStr $ show parser
              }
     }

parse :: String -> IO (Maybe (Parser Analysis))
parse str =
  do { r <- return $ parseParser str
     ; case r of
         Left err -> do { putStr "parse error: "; putStr err ; return Nothing }
         Right top -> return $ Just top
     }

----------------------------------------------------------------
-- Take a file path in the form of a string, read it, and
-- process it as specified by the command line.

nothing :: IO ()
nothing = return ()

createDirectoryIfNotExists :: FilePath -> IO ()
createDirectoryIfNotExists dir = 
  do chk <- doesDirectoryExist dir
     if chk then nothing else createDirectory dir

removeIfExists :: FilePath -> IO ()
removeIfExists file = removeFile file `catch` handleExists
  where handleExists e
          | isDoesNotExistError e = return ()
          | otherwise = throwIO e

fileNamePrefix :: String -> String
fileNamePrefix s = fst $ splitAt (maybe (length s) id (elemIndex '.' s)) s

fileNameDir :: String -> String
fileNameDir s = 
  if '/' `elem` s then
    (fst $ splitAt (maybe (length s) id (elemIndex '/' s)) s) ++ "/"
  else
    ""

writeAndPutStr :: String -> String -> String -> IO ()
writeAndPutStr file ext s =
  do { writeFile (file++"."++ext) s
     ; putStr $ "  Wrote file \"" ++ file ++ "." ++ ext ++ "\".\n"
     }

procWrite :: [OutputTarget] -> Maybe String -> IO ()
procWrite outs fname =
  do { fname     <- maybe (return "") return fname
     ; txt       <- if length fname > 0 then readFile fname else return ""
     ; parser    <- parse txt
     ; case parser of
         Nothing -> return ()
         Just parser ->
           do { parser <- return $ analyze parser
              ; (fname, fdir) <- return $ (fileNamePrefix fname, fileNameDir fname)
              ; putStr "\n"

              ; if HTML `elem` outs then
                  writeAndPutStr fname "html" (show $ html $ report parser)
                else
                  do nothing

              ; if ASCII `elem` outs then
                  writeAndPutStr fname "txt" (show parser)
                else
                  do nothing

              ; if UXADT `elem` outs then
                  writeAndPutStr fname "js" (U.javaScriptModule fname (U.uxadt parser))
                else
                  do nothing
              
              ; case emitHaskell outs of
                  Nothing  -> do nothing
                  Just pre ->
                    do moduleName <- return $ (\(c:cs) -> toUpper c : cs) fname
                       writeAndPutStr (fdir ++ "AbstractSyntax") "hs" (C.extract (toAbstractSyntax pre parser) "")
                       writeAndPutStr (fdir ++ "Report") "hs" (C.extract (toRichReport pre parser) "")
                       writeAndPutStr (fdir ++ "Parse") "hs" (C.extract (toParsec pre parser) "")

              }
     }

usage :: IO ()
usage = putStr $
     "\nUsage:\n\n"
  ++ "  imparse [optional flags] path/file.p\n\n"
  ++ "Flags:\n\n"
  ++ ""

cmd :: [OutputTarget] -> [String] -> IO ()
cmd [] []            = usage
cmd ts ("-html":ss)  = cmd (HTML:ts) ss
cmd ts ("-ascii":ss) = cmd (ASCII:ts) ss
cmd ts ("-uxadt":ss) = cmd (UXADT:ts) ss
cmd ts ("-hs":p:ss)  = cmd (HASKELL p:ts) ss
cmd ts [f]           = procWrite ts (Just f)
cmd _ _              = usage

--eof