-- | This module defines a generator for @getopt@ based command
-- line argument parsing.  Each option is associated with arbitrary
-- Python code that will perform side effects, usually by setting some
-- global variables.
module Futhark.CodeGen.Backends.GenericPython.Options
  ( Option (..),
    OptionArgument (..),
    generateOptionParser,
  )
where

import Data.Text qualified as T
import Futhark.CodeGen.Backends.GenericPython.AST

-- | Specification if a single command line option.  The option must
-- have a long name, and may also have a short name.
--
-- When the statement is being executed, the argument (if any) will be
-- stored in the variable @optarg@.
data Option = Option
  { optionLongName :: T.Text,
    optionShortName :: Maybe Char,
    optionArgument :: OptionArgument,
    optionAction :: [PyStmt]
  }

-- | Whether an option accepts an argument.
data OptionArgument
  = NoArgument
  | RequiredArgument String
  | OptionalArgument

-- | Generate option parsing code that accepts the given command line options.  Will read from @sys.argv@.
--
-- If option parsing fails for any reason, the entire process will
-- terminate with error code 1.
generateOptionParser :: [Option] -> [PyStmt]
generateOptionParser options =
  [ Assign
      (Var "parser")
      ( Call
          (Var "argparse.ArgumentParser")
          [ ArgKeyword "description" $
              String "A compiled Futhark program."
          ]
      )
  ]
    ++ map parseOption options
    ++ [ Assign (Var "parser_result") $
           Call (Var "vars") [Arg $ Call (Var "parser.parse_args") [Arg $ Var "sys.argv[1:]"]]
       ]
    ++ map executeOption options
  where
    parseOption option =
      Exp $
        Call (Var "parser.add_argument") $
          map (Arg . String) name_args ++ argument_args
      where
        name_args =
          maybe
            id
            (\x l -> ("-" <> T.singleton x) : l)
            (optionShortName option)
            ["--" <> optionLongName option]
        argument_args = case optionArgument option of
          RequiredArgument t ->
            [ ArgKeyword "action" (String "append"),
              ArgKeyword "default" $ List [],
              ArgKeyword "type" $ Var t
            ]
          NoArgument ->
            [ ArgKeyword "action" (String "append_const"),
              ArgKeyword "default" $ List [],
              ArgKeyword "const" None
            ]
          OptionalArgument ->
            [ ArgKeyword "action" (String "append"),
              ArgKeyword "default" $ List [],
              ArgKeyword "nargs" $ String "?"
            ]

    executeOption option =
      For
        "optarg"
        ( Index (Var "parser_result") $ IdxExp $ String $ fieldName option
        )
        $ optionAction option

    fieldName = T.map escape . optionLongName
      where
        escape '-' = '_'
        escape c = c
