content: module SessionUIUnitTests (macroTests) where
  
  import Prelude ()
  
  import Game.LambdaHack.Core.Prelude
  
  import qualified Data.Map.Strict as M
  import           Test.Tasty
  import           Test.Tasty.HUnit
  import           Test.Tasty.QuickCheck
  
  import qualified Game.LambdaHack.Client.UI.Content.Input as IC
  import qualified Game.LambdaHack.Client.UI.HumanCmd as HumanCmd
  import qualified Game.LambdaHack.Client.UI.Key as K
  import           Game.LambdaHack.Client.UI.SessionUI
  
  import qualified Client.UI.Content.Input as Content.Input
  
  import SessionUIMock
  
  -- Run @test -p "In-game" --quickcheck-verbose@ to verify that quickcheck
  -- properties are not too often satisfied voidly.
  macroTests :: TestTree
  macroTests = testGroup "macroTests" $
    let coinput = IC.makeData Nothing Content.Input.standardKeysAndMouse
        stringToKeyMacro = KeyMacro . map (K.mkKM . (: []))
        listToKeyMacro = KeyMacro . map K.mkKM
        bindInput l input =
          let ltriple = M.fromList $ map (\(k, ks) ->
                (K.mkKM k, ([], "", HumanCmd.Macro $ map (: []) ks))) l
          in input {IC.bcmdMap = M.union ltriple $ IC.bcmdMap input}
    in [ testCase "Macro 1 from PR#192 description" $
           fst <$> unwindMacros coinput (stringToKeyMacro "'j''j'")
           @?= [ [ (Right "",     "'j''j'",  "")  ]
               , [ (Left  "",     "j''j'",   "") ]
               , [ (Left  "j",    "''j'",    "j") ]
               , [ (Right "j",    "'j'",     "j") ]
               , [ (Left  "",     "j'",      "j") ]
               , [ (Left  "j",    "'",       "j") ]
               , [ (Right "j",    "",        "j") ]
               ]
       , testCase "Macro 1 from Issue#189 description" $
           snd (last (unwindMacros (bindInput [ ("a", "'bc'V")
                                              , ("c", "'aaa'V") ] coinput)
                                   (stringToKeyMacro "a")))
           @?= "Macro looped"
       , testCase "Macro 2 from Issue#189 description" $
           snd (last (unwindMacros (bindInput [("a", "'x'")] coinput)
                                   (stringToKeyMacro "'a'")))
           @?= "x"
       , testCase "Macro 3 from Issue#189 description" $
           snd (last (unwindMacros coinput
                                   (stringToKeyMacro "'x''x'")))
           @?= "xx"
       , testCase "Macro 4 from Issue#189 description" $
           snd (last (unwindMacros coinput
                                   (stringToKeyMacro "'x''x'V")))
           @?= "xxx"
       , testCase "Macro 5 from Issue#189 description" $
           snd (last (unwindMacros coinput
                                   (stringToKeyMacro "x'x'V")))
           @?= "xxx"
       , testCase "Macro test 10" $
           snd (last (unwindMacros coinput
                                   (stringToKeyMacro "x'y'V")))
           @?= "xyy"
       , testCase "Macro test 11" $
           snd (last (unwindMacros coinput
                                   (stringToKeyMacro "'x''y'V")))
           @?= "xyy"
       , testCase "Macro test 12" $
           snd (last (unwindMacros coinput
                                   (listToKeyMacro ["x", "C-V"])))
           @?= "x"
       , testCase "Macro test 13" $
           snd (last (unwindMacros coinput
                                   (listToKeyMacro ["'", "x", "'", "C-V"])))
           @?= "xxxxxxxxxxxxxxxxxxxxxxxxxx"
       , testCase "Macro test 14" $
           snd (last (unwindMacros coinput
                                   (listToKeyMacro ["'", "x", "'", "y", "C-V"])))
           @?= "xyxxxxxxxxxxxxxxxxxxxxxxxxx"
       , testCase "Macro test 15" $
           snd (last (unwindMacros (bindInput [("a", "x")] coinput)
                                   (stringToKeyMacro "'a'V")))
           @?= "xx"
       , testCase "Macro test 16" $
           snd (last (unwindMacros (bindInput [("a", "'x'")] coinput)
                                   (stringToKeyMacro "'a'V")))
           @?= "xx"
       , testCase "Macro test 17" $
           snd (last (unwindMacros (bindInput [("a", "'x'V")] coinput)
                                   (stringToKeyMacro "a")))
           @?= "xx"
       , testCase "Macro test 18" $
           snd (last (unwindMacros (bindInput [("a", "'x'V")] coinput)
                                   (stringToKeyMacro "'a'")))
           @?= "xx"
       , testCase "Macro test 19" $
           snd (last (unwindMacros (bindInput [("a", "'x'V")] coinput)
                                   (stringToKeyMacro "'a'V")))
           @?= "xxxx"
       , testCase "Macro test 20" $
           snd (last (unwindMacros (bindInput [ ("a", "'bz'V")
                                              , ("c", "'aaa'V") ] coinput)
                                   (stringToKeyMacro "c")))
           @?= "bzbzbzbzbzbzbzbzbzbzbzbz"
       , testCase "RepeatLast test 10" $
           snd (last (unwindMacros coinput
                                   (stringToKeyMacro "x'y'v")))
           @?= "xyy"
       , testCase "RepeatLast test 11" $
           snd (last (unwindMacros coinput
                                   (stringToKeyMacro "'x'yv")))
           @?= "xyy"
       , testCase "RepeatLast test 12" $
           snd (last (unwindMacros coinput
                                   (listToKeyMacro ["v", "C-v"])))
           @?= ""
       , testCase "RepeatLast test 13" $
           snd (last (unwindMacros coinput
                                   (listToKeyMacro ["'", "x", "'", "C-v"])))
           @?= "xxxxxxxxxxxxxxxxxxxxxxxxxx"
       , testCase "RepeatLast test 14" $
           snd (last (unwindMacros coinput
                                   (listToKeyMacro ["'", "x", "'", "V", "C-v"])))
           @?= "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
       , testCase "RepeatLast test 15" $
           snd (last (unwindMacros (bindInput [("a", "x")] coinput)
                                   (stringToKeyMacro "av")))
           @?= "xx"
       , testCase "RepeatLast test 16" $
           snd (last (unwindMacros (bindInput [("a", "'x'")] coinput)
                                   (stringToKeyMacro "'a'v")))
           @?= "xx"
       , testCase "RepeatLast test 17" $
           snd (last (unwindMacros (bindInput [("a", "'x'v")] coinput)
                                   (stringToKeyMacro "a")))
           @?= "xx"
       , testCase "RepeatLast test 18" $
           snd (last (unwindMacros (bindInput [("a", "'x'v")] coinput)
                                   (stringToKeyMacro "'a'")))
           @?= "xx"
       , testCase "RepeatLast test 19" $
           snd (last (unwindMacros (bindInput [("a", "'x'v")] coinput)
                                   (stringToKeyMacro "'a'v")))
           @?= "xxxx"
       , testCase "RepeatLast test 20" $
           snd (last (unwindMacros (bindInput [ ("a", "'bz'v")
                                              , ("c", "'aaa'v") ] coinput)
                                   (stringToKeyMacro "c")))
           @?= "bzzbzzbzzbzz"
       , testCase "RepeatLast test 21" $
           snd (last (unwindMacros (bindInput [("a", "'x'V")] coinput)
                                   (stringToKeyMacro "'a'v")))
           @?= "xxxx"
       , testCase "RepeatLast test 22" $
           snd (last (unwindMacros (bindInput [("a", "'xy'V")] coinput)
                                   (stringToKeyMacro "'aa'v")))
           @?= "xyxyxyxyxyxy"
       , testCase "RepeatLast test 23" $
           snd (last (unwindMacros (bindInput [("a", "'xy'v")] coinput)
                                   (stringToKeyMacro "'aa'V")))
           @?= "xyyxyyxyyxyy"
       , testCase "RepeatLast test 24" $
           snd (last (unwindMacros (bindInput [("a", "'xy'vv")] coinput)
                                   (stringToKeyMacro "'aa'vv")))
           @?= "xyyyxyyyxyyyxyyy"
       , testCase "RepeatLast test 25" $
           snd (last (unwindMacros (bindInput [("a", "'xyv'v")] coinput)
                                   (stringToKeyMacro "'a'a'vv'")))
           @?= "xyyyxyyyxyyyxyyy"
       , testCase "RepeatLast test 26" $
           snd (last (unwindMacros (bindInput [ ("a", "'xy'v")
                                              , ("b", "'za'v")
                                              , ("c", "'ab'v") ] coinput)
                                   (stringToKeyMacro "'c'v")))
           @?= "xyyzxyyxyyzxyyxyyxyyzxyyxyyzxyyxyy"
       , testCase "RepeatLast test 27" $
           snd (last (unwindMacros (bindInput [ ("a", "'xy'V")
                                              , ("b", "'za'v")
                                              , ("c", "'ab'v") ] coinput)
                                   (stringToKeyMacro "'c'v")))
           @?= "xyxyzxyxyxyxyzxyxyxyxyxyxyzxyxyxyxyzxyxyxyxy"
       , testCase "RepeatLast test 28" $
           snd (last (unwindMacros (bindInput [ ("a", "'xy'v")
                                              , ("b", "'za'V")
                                              , ("c", "'ab'v") ] coinput)
                                   (stringToKeyMacro "'c'v")))
           @?= "xyyzxyyzxyyzxyyzxyyxyyzxyyzxyyzxyyzxyy"
       , testCase "RepeatLast test 29" $
           snd (last (unwindMacros (bindInput [ ("a", "'xy'v")
                                              , ("b", "'za'V")
                                              , ("c", "'ab'V") ] coinput)
                                   (stringToKeyMacro "'c'v")))
           @?= "xyyzxyyzxyyxyyzxyyzxyyxyyzxyyzxyyxyyzxyyzxyy"
       , testCase "RepeatLast test 30" $
           snd (last (unwindMacros (bindInput [ ("a", "'xy'v")
                                              , ("b", "'za'V")
                                              , ("c", "'ab'V") ] coinput)
                                   (stringToKeyMacro "'c'V")))
           @?= "xyyzxyyzxyyxyyzxyyzxyyxyyzxyyzxyyxyyzxyyzxyy"
       , testCase "RepeatLast test 31" $
           snd (last (unwindMacros (bindInput [ ("a", "'xy'v")
                                              , ("b", "'za'v")
                                              , ("c", "'ab'V") ] coinput)
                                   (stringToKeyMacro "'c'V")))
           @?= "xyyzxyyxyyxyyzxyyxyyxyyzxyyxyyxyyzxyyxyy"
       , testCase "RepeatLast test 32" $
           snd (last (unwindMacros (bindInput [ ("a", "'xy'v")
                                              , ("b", "'za'v") ] coinput)
                                   (stringToKeyMacro "'ab'vv")))
           @?= "xyyzxyyxyyzxyyxyyzxyyxyy"
       , testCase "RepeatLast test 33" $
           snd (last (unwindMacros (bindInput [ ("a", "'xy'V") ] coinput)
                                   (stringToKeyMacro "a'za'vvv")))
           @?= "xyxyzxyxyxyxyxyxyxyxy"
       , testCase "RepeatLast test 34" $
           snd (last (unwindMacros (bindInput [ ("a", "'xy'v")
                                              , ("c", "a'za'Vv") ] coinput)
                                   (stringToKeyMacro "'c'v")))
           @?= "xyyzxyyzxyyzxyyxyyzxyyzxyyzxyy"
       , testCase "RepeatLast test 35" $
           snd (last (unwindMacros (bindInput [ ("a", "'xy'v")
                                              , ("b", "'za'V") ] coinput)
                                   (stringToKeyMacro "'ab'Vv")))
           @?= "xyyzxyyzxyyxyyzxyyzxyyxyyzxyyzxyy"
       , testCase "RepeatLast test 36" $
           snd (last (unwindMacros (bindInput [ ("a", "'xy'v")
                                              , ("b", "za'za'") ] coinput)
                                   (stringToKeyMacro "'ab'V'ab'V")))
           @?= "xyyzxyyzxyyxyyzxyyzxyyxyyzxyyzxyyxyyzxyyzxyy"
       , testCase "RepeatLast test 37" $
           snd (last (unwindMacros (bindInput [ ("b", "z'xy'vv")
                                              , ("c", "'xyvb'V") ] coinput)
                                   (stringToKeyMacro "'c'V")))
           @?= "xyyzxyyyxyyzxyyyxyyzxyyyxyyzxyyy"
       , testCase "RepeatLast test 38" $
           snd (last (unwindMacros coinput (stringToKeyMacro "'xv'V")))
           @?= "xxxx"
       , testCase "RepeatLast test 39" $
           fst <$> unwindMacros coinput (stringToKeyMacro "'xv'V")
           @?= [[(Right   "", "'xv'V",  "")],
                [(Left    "",  "xv'V",  "")],
                [(Left   "x",   "v'V", "x")],
                [(Left   "x",   "x'V", "x")],
                [(Left  "xx",    "'V", "x")],
                [(Right "xx",     "V", "x")],
                [(Right   "",    "xx",  ""), (Right "xx",    "", "V")],
                [(Right   "",     "x", "x"), (Right "xx",    "", "V")],
                [(Right "xx",      "", "V")]]
       , testCase "RepeatLast test 40" $
           snd (last (unwindMacros coinput (stringToKeyMacro "'xy'Vv")))
           @?= "xyxyxy"
       , testCase
           "RepeatLast test 41; named macros not referentially transparent" $
           snd (last (unwindMacros (bindInput [("a", "'xy'V")] coinput)
                                   (stringToKeyMacro "av")))
           @?= "xyxyxyxy"  -- because @a@ repeated; good!
       , testCase "RepeatLast test 42" $
           snd (last (unwindMacros (bindInput [("a", "xy")] coinput)
                                   (stringToKeyMacro "'a'Vv")))
           @?= "xyxyxy"  -- because @V@ repeated; good!
       , testCase "RepeatLast test 43" $
           snd (last (unwindMacros coinput (stringToKeyMacro "'xyV'V")))
           @?= "xyxy"
       , testCase "RepeatLast test 44" $
           snd (last (unwindMacros coinput (stringToKeyMacro "'xyV'v")))
           @?= "xyxy"
       , testCase "RepeatLast test 45" $
           snd (last (unwindMacros (bindInput [("a", "xyV")] coinput)
                                   (stringToKeyMacro "'a'V")))
           @?= "xyxy"
       , testCase "RepeatLast test 46" $
           snd (last (unwindMacros (bindInput [("a", "xyV")] coinput)
                                   (stringToKeyMacro "'a'v")))
           @?= "xyxy"
       , testProperty "In-game macro and equivalent predefined macro agree" $
           forAll (listOf (elements "`ABvV")) $
             \macro ->
               let bindings = bindInput [("a", macro)] coinput
                   inGameResult =
                     snd (last (unwindMacros coinput
                                             (stringToKeyMacro macro)))
               in inGameResult
                  === snd (last (unwindMacros bindings
                                              (stringToKeyMacro "a")))
                  .&&. inGameResult =/= "Macro looped"  -- may not loop
       , testProperty "In-game and predefined with limited minimal bindings" $
           forAll (listOf (elements "````''''ABCABCABCABCvVvVvVa")) $  -- may loop
             \macro ->
               let bindings = bindInput [("a", macro)] coinput
               in snd (last (unwindMacros bindings
                                          (stringToKeyMacro macro)))
                  === snd (last (unwindMacros bindings
                                              (stringToKeyMacro "a")))
       , testProperty "In-game and predefined with limited multiple bindings" $
           forAll (listOf (elements "```ABCDvVabccc")) $
             \macro ->
               -- The macros may still loop due to mutual recursion,
               -- even though direct recursion is ruled out by filtering.
               let macroA = filter (/= 'a') macro
                   macroB = filter (/= 'b') $ take 5 $ reverse macro
                   bindings = bindInput [ ("a", macroA)
                                        , ("b", macroB)
                                        , ("c", "A'B''CD'") ]
                                        coinput
               in snd (last (unwindMacros bindings
                                          (stringToKeyMacro macroA)))
                  === snd (last (unwindMacros bindings
                                              (stringToKeyMacro "a")))
       ]
  