{- sv2v
 - Author: Zachary Snow <zach@zachjs.com>
 -
 - conversion entry point
 -}

import System.Directory (doesFileExist)
import System.IO (hPrint, hPutStrLn, stderr, stdout)
import System.Exit (exitFailure, exitSuccess)

import Control.Monad (filterM, when, zipWithM_)
import Data.List (elemIndex, intercalate)

import Convert (convert)
import Job (readJob, Job(..), Write(..))
import Language.SystemVerilog.AST
import Language.SystemVerilog.Parser (parseFiles)

splitDefine :: String -> (String, String)
splitDefine str =
    case elemIndex '=' str of
        Nothing -> (str, "")
        Just idx -> (take idx str, drop (idx + 1) str)

isInterface :: Description -> Bool
isInterface (Part _ _ Interface _ _ _ _ ) = True
isInterface _ = False

isPackage :: Description -> Bool
isPackage Package{} = True
isPackage _ = False

isComment :: Description -> Bool
isComment (PackageItem (Decl CommentDecl{})) = True
isComment _ = False

emptyWarnings :: AST -> AST -> IO ()
emptyWarnings before after =
    if all isComment before || not (all isComment after) then
        return ()
    else if any isInterface before then
        hPutStrLn stderr $ "Warning: Source includes an interface but output is"
            ++ " empty because there is no top-level module which has no ports"
            ++ " which are interfaces."
    else if any isPackage before then
        hPutStrLn stderr $ "Warning: Source includes packages but no modules."
            ++ " Please convert packages alongside the modules that use them."
    else
        return ()

rewritePath :: FilePath -> IO FilePath
rewritePath path = do
    when (end /= ext) $ do
        hPutStrLn stderr $ "Refusing to write adjacent to " ++ show path
            ++ " because that path does not end in " ++ show ext
        exitFailure
    return $ base ++ ".v"
    where
        ext = ".sv"
        (base, end) = splitAt (length path - length ext) path

writeOutput :: Write -> [FilePath] -> [AST] -> IO ()
writeOutput _ [] [] =
    hPutStrLn stderr "Warning: No input files specified (try `sv2v --help`)"
writeOutput Stdout _ asts =
    hPrint stdout $ concat asts
writeOutput (File f) _ asts =
    writeFile f $ show $ concat asts
writeOutput Adjacent inPaths asts = do
    outPaths <- mapM rewritePath inPaths
    badPaths <- filterM doesFileExist outPaths
    when (not $ null badPaths) $ do
        hPutStrLn stderr $ "Refusing to write output because the following"
            ++ " files would be overwritten: " ++ intercalate ", " badPaths
        exitFailure
    let results = map (++ "\n") $ map show asts
    zipWithM_ writeFile outPaths results

main :: IO ()
main = do
    job <- readJob
    -- parse the input files
    let defines = map splitDefine $ define job
    result <- parseFiles (incdir job) defines (siloed job)
        (skipPreprocessor job) (files job)
    case result of
        Left msg -> do
            hPutStrLn stderr msg
            exitFailure
        Right asts -> do
            -- convert the files if requested
            let asts' = if passThrough job
                            then asts
                            else convert (exclude job) asts
            emptyWarnings (concat asts) (concat asts')
            -- write the converted files out
            writeOutput (write job) (files job) asts'
            exitSuccess