{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StrictData #-}

module Refact.Options (Options (..), optionsWithHelp) where

import Data.Char (isDigit)
import Options.Applicative
import Refact.Compat (MonadFail')
import Refact.Internal (Verbosity (..))
import Text.Read (readMaybe)

data Options = Options
  { -- | Where to process hints
    optionsTarget :: Maybe FilePath,
    -- | The refactorings to process
    optionsRefactFile :: Maybe FilePath,
    optionsInplace :: Bool,
    -- | Whether to overwrite the file inplace
    optionsOutput :: Maybe FilePath,
    optionsVerbosity :: Verbosity,
    -- | Ask before applying each hint
    optionsStep :: Bool,
    optionsDebug :: Bool,
    optionsRoundtrip :: Bool,
    optionsVersion :: Bool,
    optionsLanguage :: [String],
    optionsPos :: Maybe (Int, Int)
  }

options :: Parser Options
options = do
  optionsTarget <- optional (argument str (metavar "TARGET"))
  optionsRefactFile <-
    option (Just <$> str) $
      mconcat
        [ long "refact-file",
          value Nothing,
          help $
            "A file which specifies which refactorings to perform. "
              ++ "If not specified, it will be read from stdin, in which case TARGET must be specified."
        ]
  optionsInplace <-
    switch $
      mconcat
        [ long "inplace",
          short 'i',
          help "Whether to overwrite the target inplace"
        ]
  optionsOutput <-
    optional . strOption $
      mconcat
        [ long "output",
          short 'o',
          help "Name of the file to output to",
          metavar "FILE"
        ]
  optionsVerbosity <-
    option (str >>= parseVerbosity) $
      mconcat
        [ long "verbosity",
          short 'v',
          value Normal,
          help "Specify verbosity, 0, 1 or 2. The default is 1 and 0 is silent."
        ]
  optionsStep <-
    switch $
      mconcat
        [ short 's',
          long "step",
          help "Ask before applying each refactoring"
        ]
  optionsDebug <-
    switch $
      mconcat
        [ long "debug",
          help "Output the GHC AST for debugging",
          internal
        ]
  optionsRoundtrip <-
    switch $
      mconcat
        [ long "roundtrip",
          help "Run ghc-exactprint on the file",
          internal
        ]
  optionsVersion <-
    switch $
      mconcat
        [ long "version",
          help "Display version number"
        ]
  optionsLanguage <-
    many . strOption $
      mconcat
        [ long "language",
          short 'X',
          help "Language extensions (e.g. LambdaCase, RankNTypes)",
          metavar "Extensions"
        ]
  optionsPos <-
    option (Just <$> (str >>= parsePos)) $
      mconcat
        [ long "pos",
          value Nothing,
          metavar "<line>,<col>",
          help "Apply hints relevant to a specific position"
        ]
  pure Options {..}

optionsWithHelp :: ParserInfo Options
optionsWithHelp =
  info (helper <*> options) $
    mconcat
      [ fullDesc,
        progDesc "Automatically perform refactorings on haskell source files",
        header "refactor"
      ]

parseVerbosity :: Monad m => String -> m Verbosity
parseVerbosity =
  pure . \case
    "0" -> Silent
    "1" -> Normal
    "2" -> Loud
    _ -> Normal

parsePos :: MonadFail' m => String -> m (Int, Int)
parsePos s =
  case span isDigit s of
    (line, ',' : col) ->
      case (,) <$> readMaybe line <*> readMaybe col of
        Just l -> pure l
        Nothing -> fail "Invalid input"
    _ -> fail "Invalid input"
