package vm

import "core:container/queue"
import "core:fmt"

import syn "../syntax-tree"

VM :: struct {
	code:  []syn.TOKEN,
	ip:    u8,
	stack: ^queue.Queue(syn.TOKEN),
}

new_vm :: proc() -> ^VM {
	when ODIN_DEBUG {
		fmt.println("Created VM")
	}
	vm: ^VM = new(VM)
	vm.ip = 0
	stack := new(queue.Queue(syn.TOKEN))
	queue.init(stack)
	vm.stack = stack
	return vm
}

load_code :: proc(vm: ^VM, code: []syn.TOKEN) {
	vm.code = code
}

delete_vm :: proc(vm: ^VM) {
	when ODIN_DEBUG {
		fmt.println("Deleted VM")
	}
	delete(vm.code)
	queue.destroy(vm.stack)
	free(vm)
}

execute :: proc(vm: ^VM) -> f64 {
	using queue
	for token in vm.code {
		#partial switch token.type {
		case syn.TOKEN_TYPE.NUMBER:
			push_back(vm.stack, token)
		case syn.TOKEN_TYPE.OPERATOR:
			y := pop_back(vm.stack)
			x := pop_back(vm.stack)
			switch token.value {
			case syn.OPERATOR_TYPE.PLUS:
				push_back(vm.stack, vm_add(&x, &y))
			case syn.OPERATOR_TYPE.MINUS:
				push_back(vm.stack, vm_sub(&x, &y))
			case syn.OPERATOR_TYPE.MULTIPLY:
				push_back(vm.stack, vm_mul(&x, &y))
			case syn.OPERATOR_TYPE.DIVIDE:
				push_back(vm.stack, vm_div(&x, &y))
			}
		}

	}

	return pop_back(vm.stack).value.(f64)
}

@(private)
vm_add :: #force_inline proc(x: ^syn.TOKEN, y: ^syn.TOKEN) -> syn.TOKEN {
	op1 := x.value.(f64)
	op2 := y.value.(f64)

	return syn.TOKEN{syn.TOKEN_TYPE.NUMBER, op1 + op2}
}

@(private)
vm_sub :: #force_inline proc(x: ^syn.TOKEN, y: ^syn.TOKEN) -> syn.TOKEN {
	op1 := x.value.(f64)
	op2 := y.value.(f64)

	return syn.TOKEN{syn.TOKEN_TYPE.NUMBER, op1 - op2}
}

@(private)
vm_mul :: #force_inline proc(x: ^syn.TOKEN, y: ^syn.TOKEN) -> syn.TOKEN {
	op1 := x.value.(f64)
	op2 := y.value.(f64)

	return syn.TOKEN{syn.TOKEN_TYPE.NUMBER, op1 * op2}
}

@(private)
vm_div :: #force_inline proc(x: ^syn.TOKEN, y: ^syn.TOKEN) -> syn.TOKEN {
	op1 := x.value.(f64)
	op2 := y.value.(f64)

	return syn.TOKEN{syn.TOKEN_TYPE.NUMBER, op1 / op2}
}