module Specs where

import Test.Hspec.Internal
import Test.Hspec.QuickCheck
import Test.Hspec.HUnit ()
import System.IO
import System.Environment
import System.Exit
import qualified Test.HUnit as HUnit

main :: IO ()
main = do
  ar <- getArgs
  b <- case ar of
    ["README"] -> withFile "README" WriteMode (\ h -> hPutStrLn h preable >> hHspec h specs)
    [filename] -> withFile filename WriteMode (\ h -> hHspec h specs)
    _          -> hspecB specs
  exitWith $ toExitCode b

preable :: String
preable = unlines [
    "hspec aims to be a simple, extendable, and useful tool for Behavior Driven Development in Haskell.", "",
    "",
    "Step 1, write descriptions and examples of your desired behavior",
    "> specs = describe \"myabs\" [",
    ">   it \"returns the original number when given a positive input\"",
    ">     (myabs 1 == 1),",
    "> ",
    ">   it \"returns a positive number when given a negative input\"",
    ">     (myabs (-1) == 1),",
    "> ",
    ">   it \"returns zero when given zero\"",
    ">     (myabs 0 == 0)",
    ">   ]",
    "",
    "Step 2, write whatever you are describing",
    "> myabs n = undefined",
    "",
    "Step 3, watch your examples fail",
    "> hspec specs",
    "myabs",
    " x returns the original number when given a positive input [1]",
    " x returns a positive number when given a negative input [2]",
    " x returns zero when given zero [3]",
    "",
    "1) myabs returns the original number when given a positive input FAILED",
    "Prelude.undefined",
    "",
    "2) myabs returns a positive number when given a negative input FAILED",
    "Prelude.undefined",
    "",
    "3) myabs returns zero when given zero FAILED",
    "Prelude.undefined",
    "",
    "Finished in 0.0002 seconds",
    "",
    "3 examples, 3 failures",
    "",
    "Step 4, implement your desired behavior",
    "> myabs n = if n < 0 then negate n else n",
    "",
    "Step 5, watch your examples pass",
    "> hspec specs",
    "myabs",
    " - returns the original number when given a positive input",
    " - returns a positive number when given a negative input",
    " - returns zero when given zero",
    "",
    "Finished in 0.0000 seconds",
    "",
    "3 examples, 0 failures",
    "",
    "",
    "",
    "",
    "Here's the report of hspec's behavior:" ]

specs :: IO [Spec]
specs = do
  exampleSpecs <- describe "Example" [
    it "pass" (Success),
    it "fail 1" (Fail "fail message"),
    it "pending" (Pending "pending message"),
    it "fail 2" (HUnit.assertEqual "assertEqual test" 1 (2::Int)),
    it "exceptions" (undefined :: Bool),
    it "quickcheck" (property $ \ i -> i == (i+1::Integer))]

  let report = pureHspec exampleSpecs

  -- uncomment the next line to aid in debugging
  --mapM_ putStrLn $ ["-- START example specs --"] ++ report ++ ["-- END example specs --"]

  -- the real specs
  descriptions [

    describe "the \"describe\" function" [
        it "takes a description of what the behavior is for"
            ((=="Example") . name . head $ exampleSpecs),

        it "groups behaviors for what's being described"
            (all ((=="Example").name) exampleSpecs)
    ],
    describe "the \"it\" function" [
        it "takes a description of a desired behavior"
            (requirement (Spec "Example" "whatever" Success) == "whatever" ),

        it "takes an example of that behavior"
            (result (Spec "Example" "whatever" Success) == Success),

        it "can use a Bool, HUnit Test, QuickCheck property, or \"pending\" as an example"
            (True),

        it "will treat exceptions as failures"
            (any (==" x exceptions [3]") report)
    ],
    describe "the \"hspec\" function" [
        it "displays a header for each thing being described"
            (any (=="Example") report),

        it "displays one row for each behavior"
            (HUnit.assertEqual "" 29 (length report)),
            -- 19 = group header + behaviors + blank lines + time + error messsages + pending message + example/failures footer

        it "displays a '-' for successfull examples"
            (any (==" - pass") report),

        it "displays an 'x' for failed examples"
            (any (==" x fail 1 [1]") report),

        it "displays a list of failed examples"
            (any (=="1) Example fail 1 FAILED") report),

        it "displays available details for failed examples"
            (any (=="fail message") report),

        it "displays a '-' for pending examples"
            (any (==" - pending") report ),

        it "displays a '#' and an additional message for pending examples"
            (any (=="     # pending message") report ),

        it "summarizes the time it takes to finish"
            (any (=="Finished in 0.0000 seconds") (pureHspec exampleSpecs)),

        it "summarizes the number of examples and failures"
            (any (=="6 examples, 4 failures") (pureHspec exampleSpecs)),

        it "outputs to stdout"
            (True)
    ],
    describe "Bool as an example" [
        it "is just an expression that evaluates to a Bool"
            (True)
    ],
    describe "HUnit TestCase as an example" [
        it "is specified with the HUnit \"TestCase\" data constructor"
            (HUnit.TestCase $ HUnit.assertBool "example" True),

        it "is the assumed example for IO() actions"
            (HUnit.assertBool "example" True),

        it "will show the failed assertion text if available (e.g. assertBool)"
            (HUnit.TestCase $ do
              innerSpecs <- describe "" [ it "" (HUnit.assertBool "trivial" False)]
              let innerReport = pureHspec innerSpecs
              HUnit.assertBool "should find assertion text" $ any (=="trivial") innerReport),

        it "will show the failed assertion expected and actual values if available (e.g. assertEqual)"
            (HUnit.TestCase $ do
              innerSpecs <- describe "" [ it "" (HUnit.assertEqual "trivial" (1::Int) 2)]
              let innerReport = pureHspec innerSpecs
              HUnit.assertBool "should find assertion text" $ any (=="trivial") innerReport
              HUnit.assertBool "should find 'expected: 1'" $ any (=="expected: 1") innerReport
              HUnit.assertBool "should find ' but got: 2'" $ any (==" but got: 2") innerReport)
    ],
    describe "QuickCheck property as an example" [
        it "is specified with the \"property\" function"
            (property $ \ b -> b || True),

        it "will show what falsified it"
            (any (=="0") report)
        ],
    describe "pending as an example" [
        it "is specified with the \"pending\" function and an explanation"
            (pending "message" == Pending "message"),

        it "accepts a message to display in the report"
            (any (== "     # pending message") report)
        ],
    describe "hspecB" [
        it "returns true if no examples failed"
            (HUnit.TestCase $ do
              ss <- describe "" [it "" Success]
              HUnit.assertEqual "no errors" True (snd $ pureHspecB ss)),

        it "returns false if any examples failed"
            (HUnit.TestCase $ do
              ss <- describe "" [it "" (Fail "test")]
              HUnit.assertEqual "one error" False (snd $ pureHspecB ss))
    ],
    describe "quantify (an internal function)" [
        it "returns an amount and a word given an amount and word"
            (quantify (1::Int) "thing" == "1 thing"),

        it "returns a singular word given the number 1"
            (quantify (1::Int) "thing" == "1 thing"),

        it "returns a plural word given a number greater than 1"
            (quantify (2::Int) "thing" == "2 things"),

        it "returns a plural word given the number 0"
            (quantify (0::Int) "thing" == "0 things")
    ]]