{-# LANGUAGE OverloadedStrings #-}
module WildBind.SeqSpec (main,spec) where

import Control.Applicative ((<*>))
import Control.Monad (forM_)
import Control.Monad.IO.Class (liftIO)
import qualified Control.Monad.Trans.State as State
import Data.Monoid ((<>))
import Data.IORef (modifyIORef, newIORef, readIORef)
import Test.Hspec

import WildBind.Binding
  ( binds, on, run, as,
    boundActions, actDescription,
    boundInputs,
    Binding,
    justBefore
  )
import WildBind.Description (ActionDescription)
import WildBind.Seq
  ( prefix,
    toSeq, fromSeq,
    withPrefix, withCancel,
    reviseSeq
  )

import WildBind.ForTest
  ( SampleInput(..), SampleState(..),
    evalStateEmpty, execAll,
    boundDescs, curBoundInputs, curBoundDescs, curBoundDesc,
    checkBoundInputs,
    checkBoundDescs,
    checkBoundDesc,
    withRefChecker
  )

main :: IO ()
main = hspec spec

spec :: Spec
spec = do
  spec_prefix
  spec_SeqBinding
  spec_reviseSeq

spec_prefix :: Spec
spec_prefix = describe "prefix" $ do
  let base_b = binds $ do
        on SIa `as` "a" `run` return ()
        on SIb `as` "b" `run` return ()
  specify "no prefix" $ do
    let b = prefix [] [] base_b
    boundDescs b (SS "") `shouldMatchList`
      [ (SIa, "a"),
        (SIb, "b")
      ]
  specify "one prefix" $ evalStateEmpty $ do
    State.put $ prefix [] [SIc] base_b
    checkBoundInputs (SS "") [SIc]
    execAll (SS "") [SIc] 
    checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")]
    execAll (SS "") [SIc]
    checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")]
    execAll (SS "") [SIa]
    checkBoundInputs (SS "") [SIc]
  specify "two prefixes" $ evalStateEmpty $ do
    State.put $ prefix [] [SIc, SIb] base_b
    checkBoundInputs (SS "") [SIc]
    execAll (SS "") [SIc]
    checkBoundInputs (SS "") [SIb]
    execAll (SS "") [SIb]
    checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")]
    execAll (SS "") [SIa]
    checkBoundInputs (SS "") [SIc]
  specify "cancel binding" $ evalStateEmpty $ do
    State.put $ prefix [SIa] [SIc, SIb] base_b
    checkBoundInputs (SS "") [SIc]
    execAll (SS "") [SIc] -- there is no cancel binding at the top level.
    checkBoundInputs (SS "") [SIa, SIb]
    checkBoundDesc (SS "") SIa "cancel"
    execAll (SS "") [SIa]
    checkBoundInputs (SS "") [SIc]
    execAll (SS "") [SIc, SIb]
    checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")]  -- cancel binding should be weak and overridden.
    execAll (SS "") [SIb]
    checkBoundInputs (SS "") [SIc]
    execAll (SS "") [SIc, SIb]
    checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")]
    execAll (SS "") [SIa]
    checkBoundInputs (SS "") [SIc]


spec_SeqBinding :: Spec
spec_SeqBinding = describe "SeqBinding" $ do
  let b_a = binds $ on SIa `as` "a" `run` return ()
      b_b = binds $ on SIb `as` "b" `run` return ()
  describe "withPrefix" $ do
    it "should allow nesting" $ evalStateEmpty $ do
      State.put $ fromSeq $ withPrefix [SIb] $ withPrefix [SIc] $ withPrefix [SIa] $ toSeq (b_a <> b_b)
      checkBoundInputs (SS "") [SIb]
      execAll (SS "") [SIb]
      checkBoundInputs (SS "") [SIc]
      execAll (SS "") [SIc]
      checkBoundInputs (SS "") [SIa]
      execAll (SS "") [SIa]
      checkBoundDescs (SS "") [(SIa, "a"), (SIb, "b")]
      execAll (SS "") [SIa]
      checkBoundInputs (SS "") [SIb]
  describe "mappend" $ do
    it "should be able to combine SeqBindings with different prefixes." $ evalStateEmpty $ do
      State.put $ fromSeq $ withPrefix [SIc] $ ( (withPrefix [SIa, SIc] $ toSeq $ b_a)
                                                 <> (withPrefix [SIa] $ toSeq $ b_b)
                                               )
      checkBoundInputs (SS "") [SIc]
      execAll (SS "") [SIc]
      checkBoundInputs (SS "") [SIa]
      execAll (SS "") [SIa]
      checkBoundInputs (SS "") [SIc, SIb]
      checkBoundDesc (SS "") SIb "b"
      execAll (SS "") [SIb]
      checkBoundInputs (SS "") [SIc]
      execAll (SS "") [SIc, SIa]
      checkBoundInputs (SS "") [SIc, SIb]
      execAll (SS "") [SIc]
      checkBoundDescs (SS "") [(SIa, "a")]
      execAll (SS "") [SIa]
      checkBoundInputs (SS "") [SIc]
  describe "withCancel" $ do
    it "should weakly add 'cancel' binding when at least one prefix is kept in the state." $ evalStateEmpty $ do
      State.put $ fromSeq $ withPrefix [SIa, SIc] $ withCancel [SIa, SIb, SIc] $ ( toSeq b_a
                                                                                   <> (withPrefix [SIc] $ toSeq b_b)
                                                                                 )
      let checkPrefixOne = do
            checkBoundInputs (SS "") [SIa]
            execAll (SS "") [SIa]
            checkBoundInputs (SS "") [SIa, SIb, SIc]
            forM_ [SIa, SIb] $ \c -> checkBoundDesc (SS "") c "cancel"
      checkPrefixOne
      execAll (SS "") [SIa]
      checkPrefixOne
      execAll (SS "") [SIc]
      checkBoundInputs (SS "") [SIa, SIb, SIc]
      checkBoundDesc (SS "") SIa "a"
      checkBoundDesc (SS "") SIb "cancel"
      execAll (SS "") [SIa]
      checkPrefixOne
      execAll (SS "") [SIc, SIb]
      checkPrefixOne
      execAll (SS "") [SIc, SIc]
      checkBoundDescs (SS "") [(SIa, "cancel"), (SIb, "b"), (SIc, "cancel")]
      execAll (SS "") [SIb]
      checkPrefixOne
      
spec_reviseSeq :: Spec
spec_reviseSeq = describe "reviseSeq" $ do
  it "should allow access to prefix keys input so far" $ evalStateEmpty $ withRefChecker [] $ \out checkOut -> do
    act_out <- liftIO $ newIORef ("" :: String)
    let sb = withCancel [SIa] $ withPrefix [SIa, SIb, SIc] $ toSeq $ base_b
        base_b = binds $ on SIb `as` "B" `run` modifyIORef act_out (++ "B executed")
        rev ps _ _ = justBefore $ modifyIORef out (++ [ps])
    State.put $ fromSeq $ reviseSeq rev sb
    execAll (SS "") [SIa, SIa]
    checkOut [[], [SIa]]
    execAll (SS "") [SIa, SIb, SIc]
    checkOut [[], [SIa], [], [SIa], [SIa, SIb]]
    liftIO $ readIORef act_out `shouldReturn` ""
    execAll (SS "") [SIb]
    checkOut [[], [SIa], [], [SIa], [SIa, SIb], [SIa, SIb, SIc]]
    liftIO $ readIORef act_out `shouldReturn` "B executed"
  it "should allow unbinding" $ evalStateEmpty $ do
    let sb = withPrefix [SIa]
             ( toSeq ba
               <> (withPrefix [SIb] $ toSeq bab)
               <> (withPrefix [SIa] $ toSeq baa)
             )
        ba  = binds $ on SIc `as` "c on a" `run` return ()
        bab = binds $ on SIc `as` "c on ab" `run` return ()
        baa = binds $ do
          on SIc `as` "c on aa" `run` return ()
          on SIb `as` "b on aa" `run` return ()
        rev ps _ i act = if (ps == [SIa] && i == SIb) || (ps == [SIa,SIa] && i == SIc)
                         then Nothing
                         else Just act
    State.put $ fromSeq $ reviseSeq rev sb
    checkBoundInputs (SS "") [SIa]
    execAll (SS "") [SIa]
    checkBoundInputs (SS "") [SIa, SIc] -- SIb should be canceled
    execAll (SS "") [SIa]
    checkBoundDescs (SS "") [(SIb, "b on aa")] -- SIc should be canceled
    execAll (SS "") [SIb]
    checkBoundInputs (SS "") [SIa]
    
    
