{-
-- EPITECH PROJECT, 2025
-- Glados
-- File description:
-- VM Stack
-}

{-|
Module      : VM.VMStack
Description : Low-level stack manipulation primitives.
Stability   : stable

This module provides the core abstractions to interact with the VM's operand stack.
It abstracts the underlying 'Vector' implementation and handles underflow safety checks.
-}
module VM.VMStack
    ( stackPush
    , stackPop
    , stackTop
    ) where

import Control.Monad.State.Strict (get, put, modify)
import qualified Data.Vector as V

import VM.VMState (VMState(..), VirtualMachine)
import VM.VMValue (VMValue)

-- | Pushes a value onto the top of the global value stack.
--
-- @args
--   - val: The 'VMValue' to push.
--
-- @details
--   Modifies the VM state by appending the value to the end of the 'vStack' vector.
--   This operation is generally O(1).
--
stackPush :: VMValue -> VirtualMachine ()
stackPush :: VMValue -> VirtualMachine ()
stackPush VMValue
val = (VMState -> VMState) -> VirtualMachine ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify ((VMState -> VMState) -> VirtualMachine ())
-> (VMState -> VMState) -> VirtualMachine ()
forall a b. (a -> b) -> a -> b
$ \VMState
s -> VMState
s { vStack = V.snoc (vStack s) val }

-- | Removes and returns the value at the top of the stack.
--
-- @details
--   Retrieves the last element of the 'vStack' vector and removes it from the state.
--   Uses 'case' instead of 'if' to avoid DoAndIfThenElse extension.
--   
-- @throws
--   Error "VM Error: Stack Underflow" if the stack is empty.
--
-- @return
--   The 'VMValue' that was at the top.
--
stackPop :: VirtualMachine VMValue
stackPop :: VirtualMachine VMValue
stackPop = do
    s <- StateT VMState IO VMState
forall s (m :: * -> *). MonadState s m => m s
get
    case V.null (vStack s) of
        Bool
True -> [Char] -> VirtualMachine VMValue
forall a. HasCallStack => [Char] -> a
error [Char]
"VM Error: Stack Underflow"
        Bool
False -> VMState -> VirtualMachine ()
forall s (m :: * -> *). MonadState s m => s -> m ()
put (VMState
s { vStack = V.init (vStack s) }) VirtualMachine ()
-> VirtualMachine VMValue -> VirtualMachine VMValue
forall a b.
StateT VMState IO a -> StateT VMState IO b -> StateT VMState IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>>
            VMValue -> VirtualMachine VMValue
forall a. a -> StateT VMState IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Vector VMValue -> VMValue
forall a. Vector a -> a
V.last (VMState -> Vector VMValue
vStack VMState
s))

-- | Returns the value at the top of the stack without removing it (Peek).
--
-- @details
--   Useful for instructions like DUP or conditional jumps that need to inspect
--   the value without consuming it.
--
-- @throws
--   Error "VM Error: Stack Underflow (Top)" if the stack is empty.
--
-- @return
--   The 'VMValue' currently at the top.
--
stackTop :: VirtualMachine VMValue
stackTop :: VirtualMachine VMValue
stackTop = do
    s <- StateT VMState IO VMState
forall s (m :: * -> *). MonadState s m => m s
get
    case V.null (vStack s) of
        Bool
True -> [Char] -> VirtualMachine VMValue
forall a. HasCallStack => [Char] -> a
error [Char]
"VM Error: Stack Underflow (Top)"
        Bool
False -> VMValue -> VirtualMachine VMValue
forall a. a -> StateT VMState IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Vector VMValue -> VMValue
forall a. Vector a -> a
V.last (VMState -> Vector VMValue
vStack VMState
s))