{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}

import Options.Applicative.Simple hiding(action)
import GHC.IO.Handle
import System.IO
import System.Environment
import Control.Exception
import Control.Monad
import System.Directory
import System.Exit
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Monoid ((<>))


shouldBe :: (Show a, Eq a) => a -> a -> IO ()
shouldBe actual expected
  | expected == actual = return ()
  | otherwise = do
    putStrLn $ "expected: " ++ show expected
    putStrLn $ "actual  : " ++ show actual
    exitFailure

catchReturn :: Exception e => IO e -> IO e
catchReturn io = io `catch` return

catchExitCode :: IO () -> IO ExitCode
catchExitCode action = catchReturn $ do
  action
  return ExitSuccess

data FakeHandles = FakeHandles
  { fakeIn  :: Handle
  , fakeOut :: Handle
  , fakeErr :: Handle
  , realIn  :: Handle
  , realOut :: Handle
  , realErr :: Handle
  }

openFile' :: FilePath -> IO Handle
openFile' path = do
  removeIfExists path
  openFile path ReadWriteMode

removeIfExists :: FilePath -> IO ()
removeIfExists path = do
  exists <- doesFileExist path
  when exists $ do
    removeFile path

stdinFile :: FilePath
stdinFile = ".tmp.stdin"

stdoutFile :: FilePath
stdoutFile = ".tmp.stdout"

stderrFile :: FilePath
stderrFile = ".tmp.stderr"

beforeFH :: IO FakeHandles
beforeFH = do
  realIn <- hDuplicate stdin
  realOut <- hDuplicate stdout
  realErr <- hDuplicate stderr

  fakeIn <- openFile stdinFile ReadWriteMode
  fakeOut <- openFile' stdoutFile
  fakeErr <- openFile' stderrFile

  hDuplicateTo fakeIn stdin
  hDuplicateTo fakeOut stdout
  hDuplicateTo fakeErr stderr

  return FakeHandles{..}

afterFH :: FakeHandles -> IO ()
afterFH FakeHandles{..} = do
  hDuplicateTo realIn stdin
  hDuplicateTo realOut stdout
  hDuplicateTo realErr stderr

  hClose fakeIn
  hClose fakeOut
  hClose fakeErr

withFakeHandles :: IO a -> IO a
withFakeHandles = bracket beforeFH afterFH . const

withStdIn :: ByteString -> IO ()
  -> IO (ByteString, ByteString, ExitCode)
withStdIn inBS action = do
  BS.writeFile stdinFile inBS
  withFakeHandles $ do
    _ <- catchExitCode action
    hFlush stdout
    hFlush stderr
  out <- BS.readFile stdoutFile
  err <- BS.readFile stderrFile

  removeIfExists stdinFile
  removeIfExists stdoutFile
  removeIfExists stderrFile

  return (out, err, ExitSuccess)


main :: IO ()
main = do
  (out, err, exitCode) <- withStdIn ""
    $ withArgs ["--version"]
    $ simpleProg
  exitCode `shouldBe` ExitSuccess
  err `shouldBe` ""
  out `shouldBe` "version\n"

  (out', err', exitCode') <- withStdIn ""
    $ withArgs ["--summary"]
    $ summaryProg
  exitCode' `shouldBe` ExitSuccess
  err' `shouldBe` ""
  out' `shouldBe` "A program summary\n"

  return ()


simpleProg :: IO ()
simpleProg = do
  ((), ()) <- simpleOptions "version" "header" "desc" (pure ()) empty
  return ()

parserWithSummary :: Parser ()
parserWithSummary = summaryOption <*> pure () where
  summaryOption = infoOption "A program summary"
    $ long "summary"
   <> help "Show program summary"

summaryProg :: IO ()
summaryProg = do
  ((), ()) <- simpleOptions "version" "header" "desc" parserWithSummary empty
  return ()
