import Test.HUnit hiding (State)

import DeepControl.Applicative 
import DeepControl.Traversable (sinkmap)
import DeepControl.Monad ((>-))
import DeepControl.Monad.Morph
import DeepControl.Monad.Trans
import DeepControl.Monad.Trans.Identity

import Control.Monad.Except
import Control.Monad.State
import Control.Monad.Trans.Maybe

-----------------------------------------------
-- State

push :: a -> State [a] a
push x = do 
    xs <- get
    put (x:xs)
    return x

pop :: State [a] a
pop = do 
    xs <- get
    put (tail xs)
    return (head xs)

-- > runState (push 1 >> push 2 >> push 3) []
-- (3,[3,2,1])
-- > runState (push 1 >> push 2 >> push 3 >> pop >> pop) []
-- (2,[1])

poland :: String -> State [Double] Double
poland "+" = do 
    x <- pop
    y <- pop
    push (y + x)
poland "-" = do 
    x <- pop
    y <- pop
    push (y - x)
poland "*" = do 
    x <- pop
    y <- pop
    push (y * x)
poland "/" = do
    x <- pop
    y <- pop
    push (y / x)
poland x = push (read x :: Double)

poland_calc :: [String] -> (Double, [Double])
poland_calc xs = (sinkmap poland xs >> pop) >- \x -> runState x []

-- > poland_calc ["1","2","*"]
-- (2.0, [])
-- > poland_calc ["1","2","-"]
-- (-1.0, [])
-- > poland_calc ["1","2","+","3","*"]
-- (9.0, [])
-- > poland_calc ["1","2","+","3","*","3","/"]
-- (3.0, [])
-- > poland_calc ["1","2","+","3","*","0","/"]
-- (Infinity, [])

-----------------------------------------------
-- StateT-Maybe

pushS :: a -> StateT [a] Maybe a
pushS x = do 
    xs <- get
    put (x:xs)
    return x

popS :: StateT [a] Maybe a
popS = do 
    xs <- get
    put (tail xs)
    return (head xs)

-- > runStateT (pushT 1 >> pushT 2 >> pushT 3) []
-- Just (3,[3,2,1])
-- > runStateT (pushT 1 >> pushT 2 >> pushT 3 >> popT >> popT) []
-- Just (2,[1])

polandS :: String -> StateT [Double] Maybe Double
polandS "+" = do 
    x <- popS
    y <- popS
    pushS (y + x)
polandS "-" = do 
    x <- popS
    y <- popS
    pushS (y - x)
polandS "*" = do 
    x <- popS
    y <- popS
    pushS (y * x)
polandS "/" = do
    x <- popS
    y <- popS
    guard (x /= 0)
    pushS (y / x)
polandS x = pushS (read x :: Double)

poland_calcS :: [String] -> Maybe (Double, [Double])
poland_calcS xs = (sinkmap polandS xs >> popS) >- \x -> runStateT x []

-- > poland_calcS ["1","2","*"]
-- Just (2.0, [])
-- > poland_calcS ["1","2","-"]
-- Just (-1.0, [])
-- > poland_calcS ["1","2","+","3","*"]
-- Just (9.0, [])
-- > poland_calcS ["1","2","+","3","*","3","/"]
-- Just (3.0, [])
-- > poland_calcS ["1","2","+","3","*","0","/"]
-- Nothing

-----------------------------------------------
-- MaybeT-StateT-IO Monad

pushMS :: a -> MaybeT (StateT [a] IO) a
pushMS x = do 
    xs <- get
    put (x:xs)
    return x
popMS :: MaybeT (StateT [a] IO) a
popMS = do 
    xs <- get
    put (tail xs)
    return (head xs)

polandMS :: String -> MaybeT (StateT [Double] IO) Double
polandMS "+" = do 
    x <- popMS
    y <- popMS
    liftIO $ putStrLn (show y ++" + "++ show x ++" = "++ show (y + x))
    pushMS (y + x)
polandMS "-" = do 
    x <- popMS
    y <- popMS
    liftIO $ putStrLn (show y ++" - "++ show x ++" = "++ show (y - x))
    pushMS (y - x)
polandMS "*" = do
    x <- popMS
    y <- popMS
    liftIO $ putStrLn (show y ++" * "++ show x ++" = "++ show (y * x))
    pushMS (y * x)
polandMS "/" = do
    x <- popMS
    y <- popMS
    liftIO $ putStr (show y ++" / "++ show x ++" = ")
    guard (x /= 0)
    liftIO $ putStr (show (y / x) ++"\n")
    pushMS (y / x)
polandMS x = pushMS (read x :: Double)

poland_calcMS :: [String] -> IO (Maybe Double, [Double])
poland_calcMS xs = (sinkmap polandMS xs >> popMS) >- runMaybeT
                                               >- \x -> runStateT x []

-- > poland_calcS2 ["1","2","*"]
-- 1.0 * 2.0 = 2.0
-- (Just 2.0, [])

-----------------------------------------------
-- StateT-MaybeT-IO Monad

pushSM :: a -> StateT [a] (MaybeT IO) a
pushSM x = do 
    xs <- get
    put (x:xs)
    return x
popSM :: StateT [a] (MaybeT IO) a
popSM = do 
    xs <- get
    put (tail xs)
    return (head xs)

polandSM :: String -> StateT [Double] (MaybeT IO) Double
polandSM "+" = do 
    x <- popSM
    y <- popSM
    liftIO $ putStrLn (show y ++" + "++ show x ++" = "++ show (y + x))
    pushSM (y + x)
polandSM "-" = do 
    x <- popSM
    y <- popSM
    liftIO $ putStrLn (show y ++" - "++ show x ++" = "++ show (y - x))
    pushSM (y - x)
polandSM "*" = do
    x <- popSM
    y <- popSM
    liftIO $ putStrLn (show y ++" * "++ show x ++" = "++ show (y * x))
    pushSM (y * x)
polandSM "/" = do
    x <- popSM
    y <- popSM
    liftIO $ putStr (show y ++" / "++ show x ++" = ")
    guard (x /= 0)
    liftIO $ putStr (show (y / x) ++"\n")
    pushSM (y / x)
polandSM x = pushSM (read x :: Double)

poland_calcSM :: [String] -> IO (Maybe (Double, [Double]))
poland_calcSM xs = (sinkmap polandSM xs >> popSM) >- \x -> runStateT x []
                                               >- runMaybeT

-----------------------------------------------
-- StateT-Identity2-IO-Maybe Monad

pushSI2 :: a -> StateT [a] (IdentityT2 IO Maybe) a
pushSI2 x = do 
    xs <- get
    put (x:xs)
    return x
popSI2 :: StateT [a] (IdentityT2 IO Maybe) a
popSI2 = do 
    xs <- get
    put (tail xs)
    return (head xs)

polandSI2 :: String -> StateT [Double] (IdentityT2 IO Maybe) Double
polandSI2 "+" = do 
    x <- popSI2
    y <- popSI2
    liftIO $ putStrLn (show y ++" + "++ show x ++" = "++ show (y + x))
    pushSI2 (y + x)
polandSI2 "-" = do 
    x <- popSI2
    y <- popSI2
    liftIO $ putStrLn (show y ++" - "++ show x ++" = "++ show (y - x))
    pushSI2 (y - x)
polandSI2 "*" = do
    x <- popSI2
    y <- popSI2
    liftIO $ putStrLn (show y ++" * "++ show x ++" = "++ show (y * x))
    pushSI2 (y * x)
polandSI2 "/" = do
    x <- popSI2
    y <- popSI2
    liftIO $ putStr (show y ++" / "++ show x ++" = ")
    guard (x /= 0)
    liftIO $ putStr (show (y / x) ++"\n")
    pushSI2 (y / x)
polandSI2 x = pushSI2 (read x :: Double)

poland_calcSI2 :: [String] -> IO (Maybe (Double, [Double]))
poland_calcSI2 xs = (sinkmap polandSI2 xs >> popSI2) >- \x -> runStateT x []
                                                  >- runIdentityT2

-----------------------------------------------
-- StateT-IdentityT-MaybeT-IO

popSIM :: StateT [a] (MaybeT IO) a
popSIM = (transfold2 . runIdentityT2) |>| popSI2

polandSIM :: String -> StateT [Double] (MaybeT IO) Double
polandSIM s = (transfold2 . runIdentityT2) |>| polandSI2 s

poland_calcSIM :: [String] -> IO (Maybe (Double, [Double]))
poland_calcSIM xs = (sinkmap polandSIM xs >> popSIM) >- \x -> runStateT x []
                                                  >- runMaybeT

-----------------------------------------------
-- StateT-MaybeT-ExceptT-IO Monad

pushSME :: a -> StateT [a] (MaybeT (ExceptT () IO)) a
pushSME x = do 
    xs <- get
    put (x:xs)
    return x
popSME :: StateT [a] (MaybeT (ExceptT () IO)) a
popSME = do 
    xs <- get
    put (tail xs)
    return (head xs)

polandSME :: String -> StateT [Double] (MaybeT (ExceptT () IO)) Double
polandSME "+" = do 
    x <- popSME
    y <- popSME
    liftIO $ putStrLn (show y ++" + "++ show x ++" = "++ show (y + x))
    pushSME (y + x)
polandSME "-" = do 
    x <- popSME
    y <- popSME
    liftIO $ putStrLn (show y ++" - "++ show x ++" = "++ show (y - x))
    pushSME (y - x)
polandSME "*" = do
    x <- popSME
    y <- popSME
    liftIO $ putStrLn (show y ++" * "++ show x ++" = "++ show (y * x))
    pushSME (y * x)
polandSME "/" = do
    x <- popSME
    y <- popSME
    liftIO $ putStr (show y ++" / "++ show x ++" = ")
    guard (x /= 0)
    liftIO $ putStr (show (y / x) ++"\n")
    pushSME (y / x)
polandSME x = pushSME (read x :: Double)

poland_calcSME :: [String] -> IO (Either () (Maybe (Double, [Double])))
poland_calcSME xs = (sinkmap polandSME xs >> popSME) >- \x -> runStateT x []
                                                  >- runMaybeT
                                                  >- runExceptT

-----------------------------------------------
-- StateT-IdentityT-MaybeT-ExceptT-IO

pushSIME :: a -> StateT [a] (IdentityT (MaybeT (ExceptT () IO))) a
pushSIME x = IdentityT |>| pushSME x
popSIME :: StateT [a] (IdentityT (MaybeT (ExceptT () IO))) a
popSIME = IdentityT |>| popSME
polandSIME :: String -> StateT [Double] (IdentityT (MaybeT (ExceptT () IO))) Double
polandSIME x = IdentityT |>| polandSME x

poland_calcSIME :: [String] -> IO (Either () (Maybe (Double, [Double])))
poland_calcSIME xs = (sinkmap polandSIME xs >> popSIME) >- \x -> runStateT x []
                                                     >- runIdentityT
                                                     >- runMaybeT
                                                     >- runExceptT

-----------------------------------------------
-- StateT-IdentityT3-IO-Except-Maybe

pushSI3 :: a -> StateT [a] (IdentityT3 IO (Except ()) Maybe) a
pushSI3 x = (IdentityT3 . untransfold3 . runIdentityT) |>| pushSIME x
popSI3 :: StateT [a] (IdentityT3 IO (Except ()) Maybe) a
popSI3 = (IdentityT3 . untransfold3 . runIdentityT) |>| popSIME
polandSI3 :: String -> StateT [Double] (IdentityT3 IO (Except ()) Maybe) Double
polandSI3 x = (IdentityT3 . untransfold3 . runIdentityT) |>| polandSIME x

poland_calcSI3 :: [String] -> IO (Either () (Maybe (Double, [Double])))
poland_calcSI3 xs = (sinkmap polandSI3 xs >> popSI3) >- \x -> runStateT x []
                                                  >- runIdentityT3
                                                  >- (runExcept|$>)

----------------------------------------------------------------
-- unit test
----------------------------------------------------------------

main :: IO ()
main = do
    runTestTT tests_
    putStrLn "" >> print "---------- S -----------"
    runTestTT tests_S

    putStrLn "" >> print "---------- MS -----------"
    runTestTT tests_MS

    putStrLn "" >> print "---------- SM -----------"
    runTestTT tests_SM

    putStrLn "" >> print "---------- SI2 -----------"
    runTestTT tests_SI2
    putStrLn "" >> print "---------- SIM -----------"
    runTestTT tests_SIM
    putStrLn "" >> print "---------- SIME -----------"
    runTestTT tests_SIME
    putStrLn "" >> print "---------- SI3 -----------"
    runTestTT tests_SI3


    putStrLn "" >> print "---------------------"
    return ()

tests_ :: Test
tests_ = test [ 
      "poland" ~: do
        let actual = poland_calc ["1","2","*"]
        actual @?= (2.0, [])

        let actual = poland_calc ["1","2","+","3","*","3","/"]
        actual @?= (3.0, [])

        --actual <- poland_calc ["1","2","+","3","*","0","/"]
        --actual @?= Infinity
    ]
tests_S :: Test
tests_S = test [ 
      "polandT" ~: do
        let actual = poland_calcS ["1","2","*"]
        actual @?= Just (2.0, [])

        let actual = poland_calcS ["1","2","+","3","*","3","/"]
        actual @?= Just (3.0, [])

        let actual = poland_calcS ["1","2","+","3","*","0","/"]
        actual @?= Nothing
    ]
tests_MS :: Test
tests_MS = test [ 
      "polandMS" ~: do
        actual <- poland_calcMS ["1","2","*"]
        actual @?= (Just 2.0, [])

        actual <- poland_calcMS ["1","2","+","3","*","3","/"]
        actual @?= (Just 3.0, [])

        actual <- poland_calcMS ["1","2","+","3","*","0","/"]
        actual @?= (Nothing, [])
    ]
tests_SM :: Test
tests_SM = test [ 
      "polandSM" ~: do
        actual <- poland_calcSM ["1","2","*"]
        actual @?= Just (2.0, [])

        actual <- poland_calcSM ["1","2","+","3","*","3","/"]
        actual @?= Just (3.0, [])

        actual <- poland_calcSM ["1","2","+","3","*","0","/"]
        actual @?= Nothing
    ]
tests_SI2 :: Test
tests_SI2 = test [ 
      "polandSI2" ~: do
        actual <- poland_calcSI2 ["1","2","*"]
        actual @?= Just (2.0, [])

        actual <- poland_calcSI2 ["1","2","+","3","*","3","/"]
        actual @?= Just (3.0, [])

        actual <- poland_calcSI2 ["1","2","+","3","*","0","/"]
        actual @?= Nothing
    ]
tests_SIM :: Test
tests_SIM = test [ 
      "polandSIM" ~: do
        actual <- poland_calcSIM ["1","2","*"]
        actual @?= Just (2.0, [])

        actual <- poland_calcSIM ["1","2","+","3","*","3","/"]
        actual @?= Just (3.0, [])

        actual <- poland_calcSIM ["1","2","+","3","*","0","/"]
        actual @?= Nothing
    ]
tests_SIME :: Test
tests_SIME = test [ 
      "polandSIME" ~: do
        actual <- poland_calcSIME ["1","2","*"]
        actual @?= Right (Just (2.0, []))

        actual <- poland_calcSIME ["1","2","+","3","*","3","/"]
        actual @?= Right (Just (3.0, []))

        actual <- poland_calcSIME ["1","2","+","3","*","0","/"]
        actual @?= Right Nothing
    ]
tests_SI3 :: Test
tests_SI3 = test [ 
      "polandSI3" ~: do
        actual <- poland_calcSI3 ["1","2","*"]
        actual @?= Right (Just (2.0, []))

        actual <- poland_calcSI3 ["1","2","+","3","*","3","/"]
        actual @?= Right (Just (3.0, []))

        actual <- poland_calcSI3 ["1","2","+","3","*","0","/"]
        actual @?= Right Nothing
    ]


