module Exp where

import Test.HUnit

import Data.Maybe (fromMaybe)

-- Tests
allTests :: Test
allTests = TestList [
    TestLabel "testEval1" testEval1,
    TestLabel "testEval2" testEval2,
    TestLabel "testEval3" testEval3
  ]

data Exp = ExpAdd Exp Exp
         | ExpMult Exp Exp
         | ExpDiv Exp Exp
         | Const Integer

---------------------------------------------
-- eval1
---------------------------------------------

eval1 :: Exp -> Maybe Integer
eval1 exp =
  case exp of
    ExpAdd exp1 exp2 ->
      let outcomeExp1 = eval1 exp1
          outcomeExp2 = eval1 exp2
          in
            case outcomeExp1 of
              Nothing -> Nothing
              _ -> case outcomeExp2 of
                Nothing -> Nothing
                _ -> Just (fromMaybe 0 outcomeExp1 + fromMaybe 0 outcomeExp2)
    ExpMult exp1 exp2 ->
      let outcomeExp1 = eval1 exp1
          outcomeExp2 = eval1 exp2
          in
            case outcomeExp1 of
              Nothing -> Nothing
              _ -> case outcomeExp2 of
                Nothing -> Nothing
                _ -> Just (fromMaybe 0 outcomeExp1 * fromMaybe 0 outcomeExp2)
    ExpDiv exp1 exp2 ->
      let outcomeExp1 = eval1 exp1
          outcomeExp2 = eval1 exp2
          in
            case outcomeExp1 of
              Nothing -> Nothing
              _ -> case outcomeExp2 of
                Nothing -> Nothing
                Just 0 -> Nothing
                _ -> Just (fromMaybe 0 outcomeExp1 `div` fromMaybe 0 outcomeExp2)
    Const value -> Just value

---------------------------------------------
-- Test eval1
---------------------------------------------
testEval1 :: Test
testEval1 = TestCase $ do
  assertEqual "test1" (Just 4) (eval1 (Const 4))
  assertEqual "test2" (Nothing) (eval1 (ExpDiv (ExpAdd (Const 4) (Const 5)) (Const 0)))
  assertEqual "test3" (Just 2) (eval1 (ExpDiv (ExpAdd (Const 4) (Const 10)) (Const 7)))

---------------------------------------------
-- eval2
---------------------------------------------
eval2 :: Exp -> Maybe Integer
eval2 exp =
  case exp of
    ExpAdd exp1 exp2 -> do
      outcomeExp1 <- eval2 exp1
      outcomeExp2 <- eval2 exp2
      pure (outcomeExp1 + outcomeExp2)
    ExpMult exp1 exp2 -> do
      outcomeExp1 <- eval2 exp1
      outcomeExp2 <- eval2 exp2
      pure (outcomeExp1 * outcomeExp2)
    ExpDiv exp1 exp2 -> do
      outcomeExp1 <- eval2 exp1
      outcomeExp2 <- eval2 exp2
      if outcomeExp2 == 0
        then Nothing
        else pure (outcomeExp1 `div` outcomeExp2)
    Const x ->
      pure x

---------------------------------------------
-- Test eval2
---------------------------------------------
testEval2 :: Test
testEval2 = TestCase $ do
  assertEqual "test1" (Just 4) (eval2 (Const 4))
  assertEqual "test2" (Nothing) (eval2 (ExpDiv (ExpAdd (Const 4) (Const 5)) (Const 0)))
  assertEqual "test3" (Just 2) (eval2 (ExpDiv (ExpAdd (Const 4) (Const 10)) (Const 7)))

---------------------------------------------
-- eval3
---------------------------------------------
eval3 :: Exp -> Maybe Integer
eval3 exp = 
  case exp of
    ExpAdd exp1 exp2 ->
      eval3 exp1 >>= \x1 ->
      eval3 exp2 >>= \x2 ->
      pure (x1 + x2)
    ExpMult exp1 exp2 ->
      eval3 exp1 >>= \x1 ->
      eval3 exp2 >>= \x2 ->
      pure (x1 * x2)
    ExpDiv exp1 exp2 ->
      eval3 exp1 >>= \x1 ->
      eval3 exp2 >>= \x2 ->
      if x2 == 0
        then Nothing
        else pure (x1 `div` x2)
    Const x ->
      pure x

---------------------------------------------
-- Test eval3
---------------------------------------------
testEval3 :: Test
testEval3 = TestCase $ do
  assertEqual "test1" (Just 4) (eval3 (Const 4))
  assertEqual "test2" Nothing (eval3 (ExpDiv (ExpAdd (Const 4) (Const 5)) (Const 0)))
  assertEqual "test3" (Just 2) (eval3 (ExpDiv (ExpAdd (Const 4) (Const 10)) (Const 7)))
