module WildBind.ForTest
       ( SampleInput(..),
         SampleState(..),
         SampleBackState(..),
         inputAll,
         execAll,
         evalStateEmpty,
         boundDescs,
         boundDescs',
         curBoundInputs,
         curBoundDescs,
         curBoundDesc,
         checkBoundInputs,
         checkBoundDescs,
         checkBoundDesc,
         withRefChecker
       ) where

import Control.Applicative ((<$>), (<*>), pure)
import Control.Monad (join)
import Control.Monad.IO.Class (liftIO, MonadIO)
import qualified Control.Monad.Trans.State as State
import Data.IORef (IORef, newIORef, readIORef)
import Data.Monoid (mempty)
import Test.QuickCheck (Arbitrary(arbitrary,shrink), arbitraryBoundedEnum)
import Test.Hspec (shouldReturn, shouldMatchList, shouldBe)

import qualified WildBind.Binding as WB
import qualified WildBind.Description as WBD

data SampleInput = SIa | SIb | SIc
                 deriving (Show, Eq, Ord, Enum, Bounded)

instance Arbitrary SampleInput where
  arbitrary = arbitraryBoundedEnum

data SampleState = SS { unSS :: String }
                 deriving (Show, Eq, Ord)

instance Arbitrary SampleState where
  arbitrary = SS <$> arbitrary
  shrink (SS s) = SS <$> shrink s


data SampleBackState = SB { unSB :: Int }
                     deriving (Show, Eq, Ord)

instance Enum SampleBackState where
  toEnum = SB
  fromEnum = unSB


inputAll :: Ord i => WB.Binding s i -> s -> [i] -> IO (WB.Binding s i)
inputAll b _ [] = return b
inputAll binding state (i:rest) = case WB.boundAction binding state i of
  Nothing -> inputAll binding state rest
  Just act -> join $ inputAll <$> WB.actDo act <*> return state <*> return rest

execAll :: Ord i => s -> [i] -> State.StateT (WB.Binding s i) IO ()
execAll state inputs = do
  b <- State.get
  next_b <- liftIO $ inputAll b state inputs
  State.put next_b

evalStateEmpty :: State.StateT (WB.Binding SampleState SampleInput) IO () -> IO ()
evalStateEmpty s = State.evalStateT s mempty

toDesc :: (i, WB.Action m a) -> (i, WBD.ActionDescription)
toDesc (i, act) = (i, WB.actDescription act)

boundDescs :: WB.Binding s i -> s -> [(i, WBD.ActionDescription)]
boundDescs b s = map toDesc $ WB.boundActions b s

boundDescs' :: WB.Binding' bs fs i -> bs -> fs -> [(i, WBD.ActionDescription)]
boundDescs' b bs fs = map toDesc $ WB.boundActions' b bs fs

curBoundInputs :: s -> State.StateT (WB.Binding s i) IO [i]
curBoundInputs s = State.gets WB.boundInputs <*> pure s

curBoundDescs :: s -> State.StateT (WB.Binding s i) IO [(i, WBD.ActionDescription)]
curBoundDescs s = State.gets boundDescs <*> pure s

curBoundDesc :: Ord i => s -> i -> State.StateT (WB.Binding s i) IO (Maybe WBD.ActionDescription)
curBoundDesc s i = (fmap . fmap) WB.actDescription $ State.gets WB.boundAction <*> pure s <*> pure i

checkBoundInputs :: (Eq i, Show i) => s -> [i] -> State.StateT (WB.Binding s i) IO ()
checkBoundInputs fs expected = liftIO . (`shouldMatchList` expected) =<< curBoundInputs fs

checkBoundDescs :: (Eq i, Show i) => s -> [(i, WBD.ActionDescription)] -> State.StateT (WB.Binding s i) IO ()
checkBoundDescs fs expected = liftIO . (`shouldMatchList` expected) =<< curBoundDescs fs

checkBoundDesc :: (Ord i) => s -> i -> WBD.ActionDescription -> State.StateT (WB.Binding s i) IO ()
checkBoundDesc fs input expected = liftIO . (`shouldBe` Just expected) =<< curBoundDesc fs input
  
withRefChecker :: (Eq a, Show a, MonadIO m)
               => a
               -> (IORef a -> (a -> m ()) -> m ())
               -> m ()
withRefChecker init_ref action = do
  out <- liftIO $ newIORef init_ref
  let checkOut expected = liftIO $ readIORef out `shouldReturn` expected
  action out checkOut
