import Maybe
import Lang

-- pridame zmenu promennych

data Result x = Chyba String | Hodnota x deriving (Show)

instance Monad Result where
  Chyba s >>=  _ = Chyba s
  Hodnota a >>= f = f a
  return x = Hodnota x
  fail s = Chyba s

data Vypocet s x = V (s->(s, Result x))
unV (V vyp) = vyp

instance Monad (Vypocet s) where
  V vyp1 >>= f = V spoj
    where
      spoj stav = let (stav', val) = vyp1 stav
                   in case val of
		        Chyba ch -> (stav', Chyba ch)
			Hodnota x -> unV (f x) stav'
  return x = V (\s -> (s,return x))
  fail ch = V (\s -> (s,fail ch))

class MonadRead m s | m -> s where
  get :: m s

instance MonadRead (Vypocet s) s where
  get = V (\s -> (s,return s))

class MonadState m s | m -> s where
  put :: s -> m ()

instance MonadState (Vypocet s) s where
  put s = V (\_ -> (s,return ()))

runVypocet :: Vypocet s x -> s -> (s, Result x)
runVypocet (V f) state = f state

eval::(Monad m, MonadRead m Values, MonadState m Values) => Expr->m Integer
eval (Plus e1 e2) = 
  do
   r1 <- eval e1
   r2 <- eval e2
   return (r1 + r2)
eval (Minus e1 e2) =
  do
   r1 <- eval e1
   r2 <- eval e2
   return (r1 - r2)
eval (Mul e1 e2) = 
  do
   r1 <- eval e1
   r2 <- eval e2
   return (r1 * r2)
eval (Div e1 e2) =
  do
   r1 <- eval e1
   r2 <- eval e2
   if r2 == 0 then fail "Deleni nulou" else return (r1 `div` r2)
eval (Mod e1 e2) =
  do
   r1 <- eval e1
   r2 <- eval e2 
   if r2 == 0 then fail "Deleni nulou" else return (r1 `mod` r2)
eval (Negate e) =
  do
   r <- eval e
   return (negate r)
eval (Num n) = return n
eval (Var s) =
  do
    ohodnoceni <- get
    case lookup s ohodnoceni of
      Just x -> return x
      Nothing -> fail ("Neznama promenna " ++ s)
eval (Assign s e) =
  do
    r <- eval e
    ohodnoceni <- get
    put (update ohodnoceni s r)
    return r

update::Values->Variable->Integer->Values
update [] s v = [(s,v)]
update ((s1,v1):t) s v
  | s == s1 = (s,v) : t
  | otherwise = (s1, v1) : update t s v

