{-# LANGUAGE TemplateHaskell, CPP #-}
module Main where

import qualified Examples.Hello as Hello
import qualified Examples.Commands as Commands
import qualified Examples.Cabal as Cabal
import qualified Examples.Alternatives as Alternatives

import Control.Monad
import Data.List
import Options.Applicative
import System.Exit
import Test.HUnit
import Test.Framework.Providers.HUnit
import Test.Framework.TH.Prime

#if __GLASGOW_HASKELL__ <= 702
import Data.Monoid
(<>) :: Monoid a => a -> a -> a
(<>) = mappend
#endif

run :: ParserInfo a -> [String] -> Either ParserFailure a
run = execParserPure (prefs idm)

assertLeft :: Show b => Either a b -> (a -> Assertion) -> Assertion
assertLeft x f = either f err x
  where
    err b = assertFailure $ "expected Left, got " ++ show b

assertRight :: Either ParserFailure b -> (b -> Assertion) -> Assertion
assertRight x f = either err f x
  where
    err (ParserFailure e _) = do
      msg <- e "test"
      assertFailure $ "unexpected parse error\n" ++ msg

assertHasLine :: String -> String -> Assertion
assertHasLine l s
  | l `elem` lines s = return ()
  | otherwise = assertFailure $ "expected line:\n\t" ++ l ++ "\nnot found"

checkHelpText :: Show a => String -> ParserInfo a -> [String] -> Assertion
checkHelpText name p args = do
  let result = run p args
  assertLeft result $ \(ParserFailure err code) -> do
    expected <- readFile $ "tests/" ++ name ++ ".err.txt"
    msg <- err name
    expected @=? msg
    ExitFailure 1 @=? code

case_hello :: Assertion
case_hello = checkHelpText "hello" Hello.opts ["--help"]

case_modes :: Assertion
case_modes = checkHelpText "commands" Commands.opts ["--help"]

case_cabal_conf :: Assertion
case_cabal_conf = checkHelpText "cabal" Cabal.pinfo ["configure", "--help"]

case_args :: Assertion
case_args = do
  let result = run Commands.opts ["hello", "foo", "bar"]
  case result of
    Left _ ->
      assertFailure "unexpected parse error"
    Right (Commands.Hello args) ->
      ["foo", "bar"] @=? args
    Right Commands.Goodbye ->
      assertFailure "unexpected result: Goodbye"

case_args_opts :: Assertion
case_args_opts = do
  let result = run Commands.opts ["hello", "foo", "--bar"]
  case result of
    Left _ -> return ()
    Right (Commands.Hello xs) ->
      assertFailure $ "unexpected result: Hello " ++ show xs
    Right Commands.Goodbye ->
      assertFailure "unexpected result: Goodbye"

case_args_ddash :: Assertion
case_args_ddash = do
  let result = run Commands.opts ["hello", "foo", "--", "--bar", "baz"]
  case result of
    Left _ ->
      assertFailure "unexpected parse error"
    Right (Commands.Hello args) ->
      ["foo", "--bar", "baz"] @=? args
    Right Commands.Goodbye ->
      assertFailure "unexpected result: Goodbye"

case_alts :: Assertion
case_alts = do
  let result = run Alternatives.opts ["-b", "-a", "-b", "-a", "-a", "-b"]
  case result of
    Left _ -> assertFailure "unexpected parse error"
    Right xs -> [b, a, b, a, a, b] @=? xs
      where a = Alternatives.A
            b = Alternatives.B

case_show_default :: Assertion
case_show_default = do
  let p = option ( short 'n'
                <> help "set count"
                <> value (0 :: Int)
                <> showDefault)
      i = info (p <**> helper) idm
      result = run i ["--help"]
  case result of
    Left (ParserFailure err _) -> do
      msg <- err "test"
      assertHasLine
        "  -n ARG                   set count (default: 0)"
        msg
    Right r  -> assertFailure $ "unexpected result: " ++ show r

case_alt_cont :: Assertion
case_alt_cont = do
  let p = Alternatives.a <|> Alternatives.b
      i = info p idm
      result = run i ["-a", "-b"]
  case result of
    Left _ -> return ()
    Right r -> assertFailure $ "unexpected result: " ++ show r

case_alt_help :: Assertion
case_alt_help = do
  let p = p1 <|> p2 <|> p3
      p1 = (Just . Left)
        <$> strOption ( long "virtual-machine"
                     <> metavar "VM"
                     <> help "Virtual machine name" )
      p2 = (Just . Right)
        <$> strOption ( long "cloud-service"
                     <> metavar "CS"
                     <> help "Cloud service name" )
      p3 = flag' Nothing ( long "dry-run" )
      i = info (p <**> helper) idm
  checkHelpText "alt" i ["--help"]

case_nested_commands :: Assertion
case_nested_commands = do
  let p3 = strOption (short 'a' <> metavar "A")
      p2 = subparser (command "b" (info p3 idm))
      p1 = subparser (command "c" (info p2 idm))
      i = info (p1 <**> helper) idm
  checkHelpText "nested" i ["c", "b"]

case_many_args :: Assertion
case_many_args = do
  let p = arguments str idm
      i = info p idm
      nargs = 20000
      result = run i (replicate nargs "foo")
  case result of
    Left _ -> assertFailure "unexpected parse error"
    Right xs -> nargs @=? length xs

case_disambiguate :: Assertion
case_disambiguate = do
  let p =   flag' (1 :: Int) (long "foo")
        <|> flag' 2 (long "bar")
        <|> flag' 3 (long "baz")
      i = info p idm
      result = execParserPure (prefs disambiguate) i ["--f"]
  case result of
    Left _ -> assertFailure "unexpected parse error"
    Right val -> 1 @=? val

case_ambiguous :: Assertion
case_ambiguous = do
  let p =   flag' (1 :: Int) (long "foo")
        <|> flag' 2 (long "bar")
        <|> flag' 3 (long "baz")
      i = info p idm
      result = execParserPure (prefs disambiguate) i ["--ba"]
  case result of
    Left _ -> return ()
    Right val -> assertFailure $ "unexpected result " ++ show val

case_completion :: Assertion
case_completion = do
  let p = (,)
        <$> strOption (long "foo"<> value "")
        <*> strOption (long "bar"<> value "")
      i = info p idm
      result = run i ["--bash-completion-index", "0"]
  case result of
    Left (ParserFailure err code) -> do
      ExitSuccess @=? code
      completions <- lines <$> err "test"
      ["--foo", "--bar"] @=? completions
    Right val ->
      assertFailure $ "unexpected result " ++ show val

case_bind_usage :: Assertion
case_bind_usage = do
  let p = arguments str (metavar "ARGS...")
      i = info (p <**> helper) briefDesc
      result = run i ["--help"]
  case result of
    Left (ParserFailure err _) -> do
      text <- head . lines <$> err "test"
      "Usage: test [ARGS...]" @=? text
    Right val ->
      assertFailure $ "unexpected result " ++ show val

case_issue_19 :: Assertion
case_issue_19 = do
  let p = option
        ( short 'x'
       <> reader (fmap Just . str)
       <> value Nothing )
      i = info (p <**> helper) idm
      result = run i ["-x", "foo"]
  case result of
    Left _ -> assertFailure "unexpected parse error"
    Right r -> Just "foo" @=? r

case_arguments1_none :: Assertion
case_arguments1_none = do
  let p = arguments1 str idm
      i = info (p <**> helper) idm
      result = run i []
  assertLeft result $ \(ParserFailure _ _) -> return ()

case_arguments1_some :: Assertion
case_arguments1_some = do
  let p = arguments1 str idm
      i = info (p <**> helper) idm
      result = run i ["foo", "--", "bar", "baz"]
  case result of
    Left _ -> assertFailure "unexpected parse error"
    Right r -> ["foo", "bar", "baz"] @=? r

case_arguments_switch :: Assertion
case_arguments_switch = do
  let p =  switch (short 'x')
        *> arguments str idm
      i = info p idm
      result = run i ["--", "-x"]
  assertRight result $ \args -> ["-x"] @=? args

case_issue_35 :: Assertion
case_issue_35 = do
  let p =  flag' True (short 't' <> hidden)
       <|> flag' False (short 'f')
      i = info p idm
      result = run i []
  case result of
    Left (ParserFailure err _) -> do
      text <- head . lines <$> err "test"
      "Usage: test -f" @=? text
    Right val ->
      assertFailure $ "unexpected result " ++ show val

case_backtracking :: Assertion
case_backtracking = do
  let p2 = switch (short 'a')
      p1 = (,)
        <$> subparser (command "c" (info p2 idm))
        <*> switch (short 'b')
      i = info (p1 <**> helper) idm
      result = execParserPure (prefs noBacktrack) i ["c", "-b"]
  assertLeft result $ \ _ -> return ()

case_error_context :: Assertion
case_error_context = do
  let p = pk <$> option (long "port")
             <*> option (long "key")
      i = info p idm
      result = run i ["--port", "foo", "--key", "291"]
  case result of
    Left (ParserFailure err _) -> do
      msg <- err "test"
      let errMsg = head $ lines msg
      assertBool "no context in error message (option)"
                 ("port" `isInfixOf` errMsg)
      assertBool "no context in error message (value)"
                 ("foo" `isInfixOf` errMsg)
    Right val ->
      assertFailure $ "unexpected result " ++ show val
  where
    pk :: Int -> Int -> (Int, Int)
    pk = (,)

condr :: MonadPlus m => (Int -> Bool) -> String -> m Int
condr f arg = do
  x <- auto arg
  guard (f (x :: Int))
  return x

case_arg_order_1 :: Assertion
case_arg_order_1 = do
  let p = (,)
          <$> argument (condr even) idm
          <*> argument (condr odd) idm
      i = info p idm
      result = run i ["3", "6"]
  assertLeft result $ \_ -> return ()

case_arg_order_2 :: Assertion
case_arg_order_2 = do
  let p = (,,)
        <$> argument (condr even) idm
        <*> option (reader (condr even) <> short 'a')
        <*> option (reader (condr odd) <> short 'b')
      i = info p idm
      result = run i ["2", "-b", "3", "-a", "6"]
  case result of
    Left _ -> assertFailure "unexpected parse error"
    Right res -> (2, 6, 3) @=? res

case_arg_order_3 :: Assertion
case_arg_order_3 = do
  let p = (,)
          <$> (  argument (condr even) idm
             <|> option (short 'n') )
          <*> argument (condr odd) idm
      i = info p idm
      result = run i ["-n", "3", "5"]
  case result of
    Left _ ->
      assertFailure "unexpected parse error"
    Right res -> (3, 5) @=? res

case_issue_47 :: Assertion
case_issue_47 = do
  let p = nullOption (long "test" <> reader r <> value 9) :: Parser Int
      r _ = readerError "error message"
      result = run (info p idm) ["--test", "x"]
  assertLeft result $ \(ParserFailure err _) -> do
    text <- head . lines <$> err "test"
    assertBool "no error message"
               ("error message" `isInfixOf` text)

case_issue_50 :: Assertion
case_issue_50 = do
  let p = argument str (metavar "INPUT")
          <* switch (long "version")
      result = run (info p idm) ["--version", "test"]
  assertRight result $ \r -> "test" @=? r

case_intersperse_1 :: Assertion
case_intersperse_1 = do
  let p = arguments str (metavar "ARGS")
          <* switch (short 'x')
      result = execParserPure (prefs noIntersperse)
                              (info p idm)
                              ["a", "-x", "b"]
  assertRight result $ \args -> ["a", "-x", "b"] @=? args

main :: IO ()
main = $(defaultMainGenerator)
