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

{-|
Module      : VM.Instruction.Arithmetic
Description : Implementation of mathematical operations.
Stability   : stable

Handles basic arithmetic. 
WARNING: Operations are performed on the top two stack elements. 
Since the stack is LIFO, the first popped element is the Right-Hand Operand.
-}
module VM.Instruction.Arithmetic
    ( instAdd
    , instSub
    , instMul
    , instDiv
    , instMod
    , binaryInstInt
    , binaryInstIntZero
    ) where

import Common.Type.Integer (IntValue(..), intValueToInt)
import VM.VMState (VirtualMachine)
import VM.VMValue (VMValue(..))
import VM.VMStack (stackPush, stackPop)

-- | Helper function for binary integer operations.
--
-- @args
--   - fn: The Haskell binary function to apply (e.g., (+), (-)).
--
-- @details
--   1. Pops the Right Operand (v2).
--   2. Pops the Left Operand (v1).
--   3. Checks if both are integers.
--   4. Applies 'fn' and pushes the result.
--
-- @throws
--   Error if operands are not VInt.
--
binaryInstInt :: (Int -> Int -> Int) -> VirtualMachine ()
binaryInstInt :: (Int -> Int -> Int) -> VirtualMachine ()
binaryInstInt Int -> Int -> Int
fn = do
    v2 <- VirtualMachine VMValue
stackPop
    v1 <- stackPop
    case (v1, v2) of
        (VInt IntValue
i1, VInt IntValue
i2) ->
            let result :: Int
result = Int -> Int -> Int
fn (IntValue -> Int
intValueToInt IntValue
i1) (IntValue -> Int
intValueToInt IntValue
i2) in
            VMValue -> VirtualMachine ()
stackPush (IntValue -> VMValue
VInt (Int64 -> IntValue
I64 (Int -> Int64
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
result)))
        (VMValue, VMValue)
_ -> [Char] -> VirtualMachine ()
forall a. HasCallStack => [Char] -> a
error [Char]
"VM Error: Arithmetic instruction expects Integers"

-- | Helper function for division and modulo with Zero Check.
--
-- @args
--   - fn: The Haskell binary function (div or mod).
--
-- @throws
--   Error "VM Error: Division by Zero" if the divisor (v2) is 0.
--
binaryInstIntZero :: (Int -> Int -> Int) -> VirtualMachine ()
binaryInstIntZero :: (Int -> Int -> Int) -> VirtualMachine ()
binaryInstIntZero Int -> Int -> Int
fn = do
    v2 <- VirtualMachine VMValue
stackPop -- Divisor (b)
    v1 <- stackPop -- Dividend (a)
    case (v1, v2) of
        (VInt IntValue
i1, VInt IntValue
i2) -> let a :: Int
a = IntValue -> Int
intValueToInt IntValue
i1
                                  b :: Int
b = IntValue -> Int
intValueToInt IntValue
i2 in
            case Int
b Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 of
                Bool
True -> [Char] -> VirtualMachine ()
forall a. HasCallStack => [Char] -> a
error [Char]
"VM Error: Division by Zero"
                Bool
False -> VMValue -> VirtualMachine ()
stackPush (IntValue -> VMValue
VInt (Int64 -> IntValue
I64 (Int -> Int64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Int -> Int
fn Int
a Int
b))))
        (VMValue, VMValue)
_ -> [Char] -> VirtualMachine ()
forall a. HasCallStack => [Char] -> a
error [Char]
"VM Error: Arithmetic instruction expects Integers"

-- | Implements Addition (Opcode 0x10).
--
-- Stack: [a, b] -> [a + b]
--
instAdd :: VirtualMachine ()
instAdd :: VirtualMachine ()
instAdd = (Int -> Int -> Int) -> VirtualMachine ()
binaryInstInt Int -> Int -> Int
forall a. Num a => a -> a -> a
(+)

-- | Implements Subtraction (Opcode 0x11).
--
-- Stack: [a, b] -> [a - b] (Note: b is popped first)
--
instSub :: VirtualMachine ()
instSub :: VirtualMachine ()
instSub = (Int -> Int -> Int) -> VirtualMachine ()
binaryInstInt (-)

-- | Implements Multiplication (Opcode 0x12).
--
-- Stack: [a, b] -> [a * b]
--
instMul :: VirtualMachine ()
instMul :: VirtualMachine ()
instMul = (Int -> Int -> Int) -> VirtualMachine ()
binaryInstInt Int -> Int -> Int
forall a. Num a => a -> a -> a
(*)

-- | Implements Division (Opcode 0x13).
--
-- Stack: [a, b] -> [a / b]
--
instDiv :: VirtualMachine ()
instDiv :: VirtualMachine ()
instDiv = (Int -> Int -> Int) -> VirtualMachine ()
binaryInstIntZero Int -> Int -> Int
forall a. Integral a => a -> a -> a
div

-- | Implements Modulo (Opcode 0x14).
--
-- Stack: [a, b] -> [a % b]
--
instMod :: VirtualMachine ()
instMod :: VirtualMachine ()
instMod = (Int -> Int -> Int) -> VirtualMachine ()
binaryInstIntZero Int -> Int -> Int
forall a. Integral a => a -> a -> a
mod