{-
-- EPITECH PROJECT, 2025
-- Glados
-- File description:
-- Variable Instructions
-}

{-|
Module      : VM.Instruction.Variable
Description : Implementation of local variable access instructions.
Stability   : stable

Handles LOAD_LOCAL and STORE_LOCAL operations.
These instructions access the stack relative to the current Frame Pointer (FP).
This allows functions to access their arguments (negative offsets) and 
local variables (positive offsets) regardless of the absolute stack depth.
-}
module VM.Instruction.Variable
    ( instLoadLocal
    , instStoreLocal
    , instLoadCapture
    , instStoreCapture
    , instLoadGlobal
    , instStoreGlobal
    ) where

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

import VM.VMValue (VMValue)
import VM.VMState (VirtualMachine, VMState(..))
import VM.Bytecode.Reader (readInt32)
import VM.VMStack (stackPush, stackPop)

-- | Implements LOAD_LOCAL (Opcode 0x50).
--
-- @details
--   1. Reads the signed 32-bit index (offset) from the bytecode.
--   2. Calculates the absolute index: Frame Pointer (FP) + Offset.
--   3. Copies the value at that index to the top of the stack.
--
--   This instruction is used to retrieve arguments and local variables.
--
-- @throws
--   Error "VM Error: LOAD_LOCAL out of bounds" if the calculated index 
--   is outside the valid stack range.
--
instLoadLocal :: VirtualMachine ()
instLoadLocal :: VirtualMachine ()
instLoadLocal = do
    idx <- VirtualMachine Int
readInt32
    vm <- get
    let baseIdx = VMState -> Int
baseVStackIndex VMState
vm Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
idx
    let stack = VMState -> Vector VMValue
vStack VMState
vm
    case baseIdx >= 0 && baseIdx < V.length stack of
        Bool
False -> [Char] -> VirtualMachine ()
forall a. HasCallStack => [Char] -> a
error ([Char] -> VirtualMachine ()) -> [Char] -> VirtualMachine ()
forall a b. (a -> b) -> a -> b
$ [Char]
"VM Error: LOAD_LOCAL out of bounds (Index: " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++
            Int -> [Char]
forall a. Show a => a -> [Char]
show Int
baseIdx [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
", Size: " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Int -> [Char]
forall a. Show a => a -> [Char]
show (Vector VMValue -> Int
forall a. Vector a -> Int
V.length Vector VMValue
stack) [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
")"
        Bool
True -> VMValue -> VirtualMachine ()
stackPush (Vector VMValue
stack Vector VMValue -> Int -> VMValue
forall a. Vector a -> Int -> a
V.! Int
baseIdx)

-- | Helper to format and throw the Out of Bounds error.
--
-- @args
--   - idx: The invalid index accessed.
--   - size: The current stack size.
--
throwStoreError :: Int -> Int -> VirtualMachine ()
throwStoreError :: Int -> Int -> VirtualMachine ()
throwStoreError Int
idx Int
size = [Char] -> VirtualMachine ()
forall a. HasCallStack => [Char] -> a
error ([Char] -> VirtualMachine ()) -> [Char] -> VirtualMachine ()
forall a b. (a -> b) -> a -> b
$
    [Char]
"VM Error: STORE_LOCAL out of bounds (Idx: " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Int -> [Char]
forall a. Show a => a -> [Char]
show Int
idx [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++
    [Char]
", Size: " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Int -> [Char]
forall a. Show a => a -> [Char]
show Int
size [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
")"

-- | Safe helper to update the stack at a specific index.
--
-- @args
--   - vm: Current VM state.
--   - idx: Absolute index to write to.
--   - val: Value to store.
--
-- @details
--   - If idx < length: Overwrites existing value.
--   - If idx == length: Appends value (stack growth).
--   - Otherwise: Throws an Out of Bounds error.
--
updateLocalVar :: VMState -> Int -> VMValue -> VirtualMachine ()
updateLocalVar :: VMState -> Int -> VMValue -> VirtualMachine ()
updateLocalVar VMState
vm Int
idx VMValue
val
    | Int
idx Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
0 = Int -> Int -> VirtualMachine ()
throwStoreError Int
idx (Vector VMValue -> Int
forall a. Vector a -> Int
V.length (VMState -> Vector VMValue
vStack VMState
vm))
    | Int
idx Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Vector VMValue -> Int
forall a. Vector a -> Int
V.length (VMState -> Vector VMValue
vStack VMState
vm) =
        VMState -> VirtualMachine ()
forall s (m :: * -> *). MonadState s m => s -> m ()
put (VMState -> VirtualMachine ()) -> VMState -> VirtualMachine ()
forall a b. (a -> b) -> a -> b
$ VMState
vm { vStack = V.snoc (vStack vm) val }
    | Int
idx Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Vector VMValue -> Int
forall a. Vector a -> Int
V.length (VMState -> Vector VMValue
vStack VMState
vm) =
        VMState -> VirtualMachine ()
forall s (m :: * -> *). MonadState s m => s -> m ()
put (VMState -> VirtualMachine ()) -> VMState -> VirtualMachine ()
forall a b. (a -> b) -> a -> b
$ VMState
vm { vStack = (vStack vm) V.// [(idx, val)] }
    | Bool
otherwise = Int -> Int -> VirtualMachine ()
throwStoreError Int
idx (Vector VMValue -> Int
forall a. Vector a -> Int
V.length (VMState -> Vector VMValue
vStack VMState
vm))

-- | Implements STORE_LOCAL (Opcode 0x51).
--
-- @details
--   1. Reads the relative index.
--   2. Pops the value to store.
--   3. Calculates the absolute index (FP + offset).
--   4. Delegates to 'updateLocalVar' to handle stack growth or update.
--
instStoreLocal :: VirtualMachine ()
instStoreLocal :: VirtualMachine ()
instStoreLocal = do
    idx <- VirtualMachine Int
readInt32
    v <- stackPop
    vm <- get
    let absIdx = VMState -> Int
baseVStackIndex VMState
vm Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
idx
    updateLocalVar vm absIdx v

-- | Implements LOAD_CAPTURE (Opcode 0x54).
--
-- @details
--   Accesses a variable in the current function's captured environment.
--   1. Reads the capture index (Int32).
--   2. Pushes the value at 'env[index]' onto the stack.
--
instLoadCapture :: VirtualMachine ()
instLoadCapture :: VirtualMachine ()
instLoadCapture = do
    idx <- VirtualMachine Int
readInt32
    vm <- get
    let currEnv = VMState -> Vector VMValue
env VMState
vm
    case idx >= 0 && idx < V.length currEnv of
        Bool
False -> [Char] -> VirtualMachine ()
forall a. HasCallStack => [Char] -> a
error ([Char] -> VirtualMachine ()) -> [Char] -> VirtualMachine ()
forall a b. (a -> b) -> a -> b
$ [Char]
"VM Error: LOAD_CAPTURE out of bounds (" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++
            Int -> [Char]
forall a. Show a => a -> [Char]
show Int
idx [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
")"
        Bool
True -> VMValue -> VirtualMachine ()
stackPush (Vector VMValue
currEnv Vector VMValue -> Int -> VMValue
forall a. Vector a -> Int -> a
V.! Int
idx)

-- | Implements STORE_CAPTURE (Opcode 0x55).
--
-- @details
--   Updates a variable in the current function's captured environment.
--   1. Reads the capture index.
--   2. Pops the new value.
--   3. Writes to 'env[index]'.
--
instStoreCapture :: VirtualMachine ()
instStoreCapture :: VirtualMachine ()
instStoreCapture = do
    idx <- VirtualMachine Int
readInt32
    val <- stackPop
    vm <- get
    let currEnv = VMState -> Vector VMValue
env VMState
vm
    case idx >= 0 && idx < V.length currEnv of
        Bool
False -> [Char] -> VirtualMachine ()
forall a. HasCallStack => [Char] -> a
error ([Char] -> VirtualMachine ()) -> [Char] -> VirtualMachine ()
forall a b. (a -> b) -> a -> b
$ [Char]
"VM Error: STORE_CAPTURE out of bounds (" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++
            Int -> [Char]
forall a. Show a => a -> [Char]
show Int
idx [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
")"
        Bool
True -> VMState -> VirtualMachine ()
forall s (m :: * -> *). MonadState s m => s -> m ()
put (VMState -> VirtualMachine ()) -> VMState -> VirtualMachine ()
forall a b. (a -> b) -> a -> b
$ VMState
vm { env = currEnv V.// [(idx, val)] }

-- | Implements LOAD_GLOBAL (Opcode 0x52).
--
-- @details
--   Accesses the Global Environment (Heap) at the given index.
--
instLoadGlobal :: VirtualMachine ()
instLoadGlobal :: VirtualMachine ()
instLoadGlobal = do
    idx <- VirtualMachine Int
readInt32
    vm <- get
    let gEnv = VMState -> Vector VMValue
globalEnv VMState
vm
    case idx >= 0 && idx < V.length gEnv of
        Bool
False -> [Char] -> VirtualMachine ()
forall a. HasCallStack => [Char] -> a
error ([Char] -> VirtualMachine ()) -> [Char] -> VirtualMachine ()
forall a b. (a -> b) -> a -> b
$ [Char]
"VM Error: LOAD_GLOBAL out of bounds (" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++
            Int -> [Char]
forall a. Show a => a -> [Char]
show Int
idx [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
")"
        Bool
True -> VMValue -> VirtualMachine ()
stackPush (Vector VMValue
gEnv Vector VMValue -> Int -> VMValue
forall a. Vector a -> Int -> a
V.! Int
idx)

-- | Implements STORE_GLOBAL (Opcode 0x53).
--
-- @details
--   Stores the top of the stack into the Global Environment at 'idx'.
--
instStoreGlobal :: VirtualMachine ()
instStoreGlobal :: VirtualMachine ()
instStoreGlobal = do
    idx <- VirtualMachine Int
readInt32
    v <- stackPop
    vm <- get
    let gEnv = VMState -> Vector VMValue
globalEnv VMState
vm
    case idx >= 0 && idx < V.length gEnv of
        Bool
False -> [Char] -> VirtualMachine ()
forall a. HasCallStack => [Char] -> a
error ([Char] -> VirtualMachine ()) -> [Char] -> VirtualMachine ()
forall a b. (a -> b) -> a -> b
$ [Char]
"VM Error: STORE_GLOBAL out of bounds (" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++
            Int -> [Char]
forall a. Show a => a -> [Char]
show Int
idx [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
")"
        Bool
True -> VMState -> VirtualMachine ()
forall s (m :: * -> *). MonadState s m => s -> m ()
put (VMState -> VirtualMachine ()) -> VMState -> VirtualMachine ()
forall a b. (a -> b) -> a -> b
$ VMState
vm { globalEnv = gEnv V.// [(idx, v)] }