content: {-# LANGUAGE OverloadedStrings #-}
  
  -- |
  -- Module      : Jikka.Main
  -- Description : is the entry point of the @jikka@ command. / @jikka@ コマンドのエントリポイントです。
  -- Copyright   : (c) Kimiyuki Onaka, 2021
  -- License     : Apache License 2.0
  -- Maintainer  : kimiyuki95@gmail.com
  -- Stability   : experimental
  -- Portability : portable
  module Jikka.Main where
  
  import Data.Maybe (fromMaybe)
  import qualified Data.Text.IO as T
  import Data.Version (showVersion)
  import qualified Jikka.CPlusPlus.Convert.BundleRuntime as BundleRuntime
  import qualified Jikka.CPlusPlus.Convert.EmbedOriginalCode as EmbedOriginalCode
  import Jikka.Common.Error
  import Jikka.Common.Format.Error (hPrintError, hPrintErrorWithText)
  import qualified Jikka.Main.Subcommand.Convert as Convert
  import qualified Jikka.Main.Subcommand.Debug as Debug
  import qualified Jikka.Main.Subcommand.Execute as Execute
  import Jikka.Main.Target
  import Paths_Jikka (version)
  import System.Console.GetOpt
  import System.Exit (ExitCode (..))
  import System.IO (hPutStr, stderr)
  
  data Flag
    = Help
    | Verbose
    | Version
    | Source String
    | Target String
    | BundleRuntimeHeaders Bool
    | EmbedOriginalCode Bool
    deriving (Eq, Ord, Show, Read)
  
  data Options = Options
    { verbose :: Bool,
      source :: Target,
      target :: Maybe Target,
      bundleRuntimeHeaders :: Bool,
      embedOriginalCode :: Bool
    }
    deriving (Eq, Ord, Show, Read)
  
  defaultOptions :: Options
  defaultOptions =
    Options
      { verbose = False,
        source = PythonTarget,
        target = Nothing,
        bundleRuntimeHeaders = True,
        embedOriginalCode = True
      }
  
  header :: String -> String
  header progName = "Usage: " ++ progName ++ " [convert | debug | execute] FILE"
  
  options :: [OptDescr Flag]
  options =
    [ Option ['h', '?'] ["help"] (NoArg Help) "",
      Option ['v'] ["verbose"] (NoArg Verbose) "",
      Option [] ["version"] (NoArg Version) "",
      Option [] ["source"] (ReqArg Source "SOURCE") "\"python\" or \"core\"",
      Option [] ["target"] (ReqArg Target "TARGET") "\"python\", \"rpython\", \"core\" or \"cxx\"",
      Option [] ["bundle-runtime-headers"] (NoArg (BundleRuntimeHeaders True)) "bundles C++ runtime headers",
      Option [] ["no-bundle-runtime-headers"] (NoArg (BundleRuntimeHeaders False)) "",
      Option [] ["embed-original-code"] (NoArg (EmbedOriginalCode True)) "embeds the original Python code",
      Option [] ["no-embed-original-code"] (NoArg (EmbedOriginalCode False)) ""
    ]
  
  main :: String -> [String] -> IO ExitCode
  main name args = do
    let usage = usageInfo (header name) options
    case getOpt Permute options args of
      (parsed, _, []) | Help `elem` parsed -> do
        putStr usage
        return ExitSuccess
      (parsed, _, []) | Version `elem` parsed -> do
        putStrLn $ 'v' : showVersion version
        return ExitSuccess
      (parsed, [subcmd, path], []) -> case parseFlags name parsed of
        Left err -> do
          hPrintError stderr err
          return $ ExitFailure 1
        Right opts -> do
          result <- runExceptT $ runSubcommand subcmd opts path
          case result of
            Left err -> do
              text <- liftIO $ T.readFile path
              hPrintErrorWithText stderr text err
              return $ ExitFailure 1
            Right () -> do
              return ExitSuccess
      (_, _, errors) | errors /= [] -> do
        forM_ errors $ \msg -> do
          let err = WithGroup CommandLineError (Error msg)
          hPrintError stderr err
        return $ ExitFailure 1
      _ -> do
        hPutStr stderr usage
        return $ ExitFailure 1
  
  parseFlags :: String -> [Flag] -> Either Error Options
  parseFlags _ = go defaultOptions
    where
      go :: Options -> [Flag] -> Either Error Options
      go opts [] = Right opts
      go opts (flag : flags) = case flag of
        Help -> throwCommandLineError "parseFlags is not called when --help is specified"
        Version -> throwCommandLineError "parseFlags is not called when --version is specified"
        Verbose -> go (opts {verbose = True}) flags
        Source source -> do
          source <- parseTarget source
          go (opts {source = source}) flags
        Target target -> do
          target <- parseTarget target
          go (opts {target = Just target}) flags
        BundleRuntimeHeaders p -> go (opts {bundleRuntimeHeaders = p}) flags
        EmbedOriginalCode p -> go (opts {embedOriginalCode = p}) flags
  
  runSubcommand :: String -> Options -> FilePath -> ExceptT Error IO ()
  runSubcommand subcmd opts path = case subcmd of
    "convert" -> do
      input <- liftIO $ T.readFile path
      let target' = fromMaybe CPlusPlusTarget (target opts)
      output <- liftEither $ Convert.run (source opts) target' path input
      output <-
        if target' == CPlusPlusTarget && bundleRuntimeHeaders opts
          then BundleRuntime.run output
          else return output
      output <-
        return $
          if target' == CPlusPlusTarget && embedOriginalCode opts
            then EmbedOriginalCode.run input output
            else output
      liftIO $ T.putStr output
    "debug" -> Debug.run path -- TODO: make this subcommand convenient
    "execute" -> Execute.run (fromMaybe CoreTarget (target opts)) path -- TODO: use source
    _ -> throwCommandLineError $ "undefined subcommand: " ++ show subcmd
  