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

{-|
Module      : VM.Instruction.Stack
Description : Implementation of stack management opcodes.
Stability   : stable

Handles instructions that modify the stack structure itself (PUSH, POP, DUP).
This module relies on 'VM.BytecodeReader' to decode operands of varying sizes.
-}
module VM.Instruction.Stack
    ( instPush
    , instPop
    , instDup
    , instSwap
    , instCast
    , instTypeOf
    , instSizeOf
    ) where

import Data.Word (Word8)
import qualified Data.Vector as V

import Common.Type.Integer (IntValue(..))
import VM.VMState (VirtualMachine)
import VM.VMValue
    ( VMValue(..)
    , castValue
    , getValueName
    , stringToValue
    )
import VM.Bytecode.Reader
    ( readByte
    , readInt8
    , readWord8
    , readInt16
    , readWord16
    , readInt32
    , readWord32
    , readInt64
    , readWord64
    )
import VM.VMStack
    ( stackPush
    , stackPop
    , stackTop
    )

-- | Helper function to parse and push a value based on its TypeID.
--
-- @args
--   - typeId: The 8-bit identifier representing the type of the value to read.
--
-- @details
--   Matches the 'typeId' against supported types defined in the ASM spec,
--   consumes the appropriate number of bytes from the reader, constructs
--   the 'VMValue', and pushes it onto the stack.
--
--   **Supported TypeIDs:**
--   * 0x00: Bool (1 byte)
--   * 0x01: Int8 (1 byte)
--   * 0x02: UInt8 (1 byte)
--   * 0x03: Int16 (2 bytes)
--   * 0x04: UInt16 (2 bytes)
--   * 0x05: Int32 (4 bytes)
--   * 0x06: UInt32 (4 bytes)
--   * 0x07: Int64 (8 bytes)
--   * 0x08: UInt64 (8 bytes)
--   * 0x09: Char (1 byte, treated as Int8)
--   * 0x10: UChar (1 byte, treated as UInt8)
--
-- @throws
--   Error "VM Error: Unsupported PUSH TypeID" if the provided TypeID is unknown.
--
stackPushValue :: Word8 -> VirtualMachine ()
stackPushValue :: Word8 -> VirtualMachine ()
stackPushValue Word8
0x00 = do v <- VirtualMachine Word8
readByte; stackPush (VBool (v /= 0))
stackPushValue Word8
0x01 = do v <- VirtualMachine Int8
readInt8; stackPush (VInt (I8 v))
stackPushValue Word8
0x02 = do v <- VirtualMachine Word8
readWord8; stackPush (VInt (UI8 v))
stackPushValue Word8
0x03 = do v <- VirtualMachine Int16
readInt16; stackPush (VInt (I16 v))
stackPushValue Word8
0x04 = do v <- VirtualMachine Word16
readWord16; stackPush (VInt (UI16 v))
stackPushValue Word8
0x05 = do
    v <- VirtualMachine Int
readInt32
    stackPush (VInt (I32 (fromIntegral v))) -- Int32 cast
stackPushValue Word8
0x06 = do v <- VirtualMachine Word32
readWord32; stackPush (VInt (UI32 v))
stackPushValue Word8
0x07 = do v <- VirtualMachine Int64
readInt64; stackPush (VInt (I64 v))
stackPushValue Word8
0x08 = do v <- VirtualMachine Word64
readWord64; stackPush (VInt (UI64 v))
stackPushValue Word8
0x09 = do v <- VirtualMachine Int8
readInt8; stackPush (VInt (IChar v))
stackPushValue Word8
0x10 = do v <- VirtualMachine Word8
readWord8; stackPush (VInt (UIChar v))
stackPushValue Word8
t = [Char] -> VirtualMachine ()
forall a. HasCallStack => [Char] -> a
error ([Char] -> VirtualMachine ()) -> [Char] -> VirtualMachine ()
forall a b. (a -> b) -> a -> b
$ [Char]
"VM Error: Unsupported PUSH TypeID: 0x" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Word8 -> [Char]
forall a. Show a => a -> [Char]
show Word8
t

-- | Implements the PUSH instruction (Opcode 0x01).
--
-- @details
--   This is the entry point for the PUSH operation. It performs the following steps:
--   1. Reads the **TypeID** byte from the bytecode stream.
--   2. Delegates the reading of the value and the stack push to 'stackPushValue'.
--
--   This separation allows for a cleaner implementation and easier extension
--   of supported types in the future.
--
instPush :: VirtualMachine ()
instPush :: VirtualMachine ()
instPush = do
    typeId <- VirtualMachine Word8
readByte
    stackPushValue typeId

-- | Implements the POP instruction (Opcode 0x02).
--
-- @details
--   Removes the top element of the stack and discards it.
--   Commonly used to clean up the stack after an expression evaluation
--   where the result is not needed (e.g., `func();` where return is ignored).
--
-- @throws
--   Error "Stack Underflow" if the stack is empty.
--
instPop :: VirtualMachine ()
instPop :: VirtualMachine ()
instPop = do
    _ <- VirtualMachine VMValue
stackPop
    return ()

-- | Implements the DUP instruction (Opcode 0x03).
--
-- @details
--   Duplicates the top element of the stack.
--   Useful for operations that consume a value but need to preserve it
--   for a subsequent operation.
--   State change: `[..., A] -> [..., A, A]`
--
-- @throws
--   Error "Stack Underflow" if the stack is empty.
--
instDup :: VirtualMachine ()
instDup :: VirtualMachine ()
instDup = do
    v <- VirtualMachine VMValue
stackTop
    stackPush v

-- | Implements the SWAP instruction (Opcode 0x04).
--
-- @details
--   Swaps the top two elements of the stack.
--   Stack: [..., A, B] -> [..., B, A]
--
instSwap :: VirtualMachine ()
instSwap :: VirtualMachine ()
instSwap = do
    b <- VirtualMachine VMValue
stackPop
    a <- stackPop
    stackPush b
    stackPush a

-- | Implements CAST (Opcode 0x80).
--
-- @details
--   Reads a TypeID and tries to convert the top of stack to that type.
--
instCast :: VirtualMachine ()
instCast :: VirtualMachine ()
instCast = do
    typeId <- VirtualMachine Word8
readByte
    v <- stackPop
    stackPush (castValue typeId v)

-- | Implements TYPEOF (Opcode 0x81).
--
-- @details
--   Pops a value from the stack, determines its runtime type,
--   and pushes a string representation (e.g., "int", "list").
--
--   Stack: [value] -> ["type_name"]
--
instTypeOf :: VirtualMachine ()
instTypeOf :: VirtualMachine ()
instTypeOf = do
    v <- VirtualMachine VMValue
stackPop
    let typeVal = VMValue -> Text
getValueName VMValue
v
    stackPush (stringToValue typeVal)

-- | Implements SIZEOF (Opcode 0x82).
--
-- @details
--   Pops a value and pushes its length.
--   - List: Number of elements.
--   - Struct: Number of fields.
--   - Others: 0 (or error, depending on strictness choice).
--
-- @throws
--   Error if the value is not a container (List/Struct).
--
instSizeOf :: VirtualMachine ()
instSizeOf :: VirtualMachine ()
instSizeOf = do
    v <- VirtualMachine VMValue
stackPop
    case v of
        VList Vector VMValue
vec -> VMValue -> VirtualMachine ()
stackPush (IntValue -> VMValue
VInt (Int64 -> IntValue
I64 (Int -> Int64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Vector VMValue -> Int
forall a. Vector a -> Int
V.length Vector VMValue
vec))))
        VStruct Vector VMValue
vec -> VMValue -> VirtualMachine ()
stackPush (IntValue -> VMValue
VInt (Int64 -> IntValue
I64 (Int -> Int64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Vector VMValue -> Int
forall a. Vector a -> Int
V.length Vector VMValue
vec))))
        VMValue
_ -> [Char] -> VirtualMachine ()
forall a. HasCallStack => [Char] -> a
error [Char]
"VM Error: sizeof expects a List or Struct"