{-# OPTIONS_GHC -fno-warn-type-defaults #-}
{-# LANGUAGE RecordWildCards #-}

import Data.Foldable     (for_)
import Test.Hspec        (Spec, describe, it, shouldBe)
import Test.Hspec.Runner (configFastFail, defaultConfig, hspecWith)
import Test.QuickCheck
import Text.Printf       (printf)

import DND (Character(..), ability, modifier, character)

data Case = Case { input    :: Int
                 , expected :: Int
                 }

type Ability = Character -> Int

main :: IO ()
main = hspecWith defaultConfig {configFastFail = True} specs

specs :: Spec
specs = do
  describe "modifier" $ for_ cases test

  describe "ability" $ it "generates values within range" $
    property (forAll ability abilityWithinRange)

  describe "character" $ it "generates valid characters" $
    property (forAll character characterIsValid)
  where
    test Case{..} = it description assertion
      where
        description = printf "computes the modifier for %2d to be %2d" input expected
        assertion = modifier input `shouldBe` expected

    cases :: [Case]
    cases = map (uncurry Case)
      [ (18, 4), (17, 3), (16, 3), (15, 2), (14, 2)
      , (13, 1), (12, 1), (11, 0), (10, 0), (9, -1)
      , (8, -1), (7, -2), (6, -2), (5, -3), (4, -3)
      , (3, -4) ]

    abilityWithinRange :: Int -> Bool
    abilityWithinRange ability' = ability' >= 3 && ability' <= 18

    characterIsValid :: Character -> Property
    characterIsValid character' =
      conjoin $
        hitpointsAddUp character' :
        map (abilityWithinRange' character') abilities
      where
        abilities = [ ("strength", strength)
                    , ("dexterity", dexterity)
                    , ("constitution", constitution)
                    , ("intelligence", intelligence)
                    , ("wisdom", wisdom)
                    , ("charisma", charisma) ]

    abilityWithinRange' :: Character -> (String, Ability) -> Property
    abilityWithinRange' character' (name, ability') =
      counterexample msg (abilityWithinRange (ability' character'))
      where msg = "The '" ++ name ++ "' ability is out of range"

    hitpointsAddUp :: Character -> Property
    hitpointsAddUp Character{..} =
      counterexample msg (hitpoints === 10 + modifier constitution)
      where msg = "The 'hitpoints' are not 10 + constitution modifier"

-- 03af5a6297d5ab68e15332da12cc303938866d7f
