
import Control.Monad (when)
import System.Environment
import System.Console.GetOpt
import System.FilePath
import System.IO

import Language.SSVM.Types
import Language.SSVM.Parser
import Language.SSVM.Interpreter
import Language.SSVM.Binary

-- | Command-line flag
data Flag =
    Mode Mode
  | Output FilePath
  deriving (Eq, Show)

-- | Working mode
data Mode =
    Interpret
  | Trace
  | TraceBytecode
  | Compile
  | Run
  | Decompile
  | Help
  deriving (Eq, Show)

-- | config
data RunMode = RunMode {
  mode :: Mode,
  inputFile :: FilePath,
  outputFile :: Maybe FilePath }
  deriving (Eq, Show)

-- | Default run mode
defaultMode :: RunMode
defaultMode = RunMode {
  mode = Run,
  inputFile = "-",
  outputFile = Nothing }

-- | Command-line options description
options :: [OptDescr Flag]
options = [
  Option "c" ["compile"]   (NoArg $ Mode Compile)   "compile source code to bytecode",
  Option "i" ["interpret"] (NoArg $ Mode Interpret) "interpret source code",
  Option "e" ["run"]       (NoArg $ Mode Run)       "run compiled bytecode",
  Option "d" ["decompile"] (NoArg $ Mode Decompile) "decompile bytecode into pseudo source code",
  Option "t" ["trace"]     (NoArg $ Mode Trace)     "interpret and trace source code ",
  Option ""  ["trace-bytecode"] (NoArg $ Mode TraceBytecode)     "run and trace bytecode ",
  Option "o" ["output"]    (ReqArg Output "FILE")   "set output file name",
  Option "h" ["help"]      (NoArg $ Mode Help)      "show this help and exit" ]

usage :: String
usage = usageInfo header options
  where
    header = "Usage: ssvm [MODE] [-o OUTPUT] FILE"

flags2runmode :: [Flag] -> RunMode
flags2runmode flags = foldl go defaultMode flags
  where
    go acc (Mode m)      = acc {mode = m}
    go acc (Output path) = acc {outputFile = Just path}

parseCmdLine :: [String] -> Either String RunMode
parseCmdLine args =
  case getOpt Permute options args of
    (flags, [], []) | Mode Help `elem` flags -> Right (flags2runmode flags)
                    | otherwise              -> Left "No input file"
    (flags, [file], []) -> Right $ (flags2runmode flags) {inputFile = file}
    (_, (_:_:_), [])    -> Left "More than one input file"
    (_, _, errs)        -> Left $ unlines errs ++ usage

doInterpret :: FilePath -> IO ()
doInterpret path = do
  mbCode <- parseSourceFile path
  case mbCode of
    Left err -> fail (show err)
    Right code -> runVM (interpret code)

doTrace :: FilePath -> IO ()
doTrace path = do
  mbCode <- parseSourceFile path
  case mbCode of
    Left err -> fail (show err)
    Right code -> traceVM (interpret code)

doTraceBytecode :: FilePath -> IO ()
doTraceBytecode path = do
  code <- loadCode path
  traceVM (interpret code)

doCompile :: FilePath -> Maybe FilePath -> IO ()
doCompile src mbdst = do
  let dst = case mbdst of
              Just x -> x
              Nothing -> replaceExtension src ".bytecode"
  mbCode <- parseSourceFile src
  case mbCode of
    Left err -> fail (show err)
    Right code -> dumpCode dst code

doRun :: FilePath -> IO ()
doRun path = do
  code <- loadCode path
  runVM (interpret code)

doDecompile :: FilePath -> IO ()
doDecompile path = do
    code <- loadCode path
    putStrLn $ showCode code

main = do
  args <- getArgs
  case parseCmdLine args of
    Left err -> error err
    Right m -> do
      term <- hIsTerminalDevice stdout
      when term $
        hSetBuffering stdout NoBuffering
      case mode m of
        Interpret -> doInterpret (inputFile m)
        Compile   -> doCompile (inputFile m) (outputFile m)
        Run       -> doRun (inputFile m)
        Decompile -> doDecompile (inputFile m) 
        Trace     -> doTrace (inputFile m) 
        TraceBytecode -> doTraceBytecode (inputFile m) 
        Help      -> putStrLn usage

