use std::collections::HashMap;
use osiris_data::data::identification::Address;
use osiris_process::compare::Compare;
use osiris_process::operation::error::{OperationError, OperationResult};
use osiris_process::operation::{Operation, OperationSet};
use osiris_process::operation::scheme::{ArgumentScheme, ArgumentType, InstructionScheme, OperationId};
use osiris_process::processor::Cpu;
use crate::logic::FALSE;
pub const SET_MASK: u16 = 0x0100;
pub const JUMP: OperationId = OperationId::new(SET_MASK | 0x01);
pub const CALL: OperationId = OperationId::new(SET_MASK | 0x02);
pub const CALL_RETURN: OperationId = OperationId::new(SET_MASK | 0x03);
pub const GOTO: OperationId = OperationId::new(SET_MASK | 0x04);
pub const GOSUB: OperationId = OperationId::new(SET_MASK | 0x05);
pub const LOOP: OperationId = OperationId::new(SET_MASK | 0x08);
pub const NEXT: OperationId = OperationId::new(SET_MASK | 0x09);
pub const GOTO_IF: OperationId = OperationId::new(SET_MASK | 0x0A);
pub const GOSUB_IF: OperationId = OperationId::new(SET_MASK | 0x0B);
pub const GOTO_CHECK: OperationId = OperationId::new(SET_MASK | 0x0C);
pub const GOSUB_CHECK: OperationId = OperationId::new(SET_MASK | 0x0D);
pub const SKIP_IF: OperationId = OperationId::new(SET_MASK | 0x0E);
pub const SKIP_CHECK: OperationId = OperationId::new(SET_MASK | 0x0F);
pub const HALT: OperationId = OperationId::new(SET_MASK | 0xFF);
fn _jump(cpu: &mut Cpu, address: Address) -> OperationResult<()> {
match cpu.point_instruction(address) {
Ok(_) => Ok(()),
Err(err) => Err(OperationError::MemoryError(err)),
}
}
fn _goto(cpu: &mut Cpu, current: Address, target: u32) -> OperationResult<()> {
_jump(cpu, Address::new(0xFFFFFFFF_00000000 & current.to_u64() | target as u64))
}
pub fn jump(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
scheme.argument.get_no_argument()?;
_jump(cpu, Address::from_word(cpu.bank_get(scheme.target)))
}
pub fn call(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
cpu.stack_push(cpu.state.current.to_word());
jump(cpu, scheme)
}
pub fn call_return(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
scheme.argument.get_no_argument()?;
match cpu.stack_pop() {
None => { Err(OperationError::CannotReturnFromEmptyStack) }
Some(addr) => {
let result = _jump(cpu, Address::from_word(addr));
cpu.state.current.increment();
result
}
}
}
pub fn goto(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
let target = scheme.argument.get_one_u32()?;
_goto(cpu, cpu.state.current, target)
}
pub fn gosub(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
cpu.stack_push(cpu.state.current.to_word());
goto(cpu, scheme)
}
pub fn loop_init(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
scheme.argument.get_no_argument()?;
cpu.state.operation.counter = cpu.bank_get(scheme.target);
cpu.debug("for", format!("{:016x}", cpu.state.operation.counter.to_u64()), "⚙️".to_string());
Ok(())
}
pub fn next(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
cpu.decrease_loop_counter();
if cpu.state.operation.counter.to_u64() > 0 {
goto(cpu, scheme)?;
}
Ok(())
}
fn fn_if(state: i64, operator: u16) -> OperationResult<bool> {
let cmp_op = Compare::from(operator);
match cmp_op {
None => Err(OperationError::Panic(format!("Invalid argument for comparison : {}", operator)))?,
Some(op) => Ok(op.is(state))
}
}
pub fn goto_if(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
let target = scheme.argument.get_one_u32()?;
let state = cpu.state.operation.compare;
if fn_if(state, scheme.target.to_u16())? {
_goto(cpu, cpu.state.current, target)
} else { Ok(()) }
}
pub fn gosub_if(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
cpu.stack_push(cpu.state.current.to_word());
goto_if(cpu, scheme)
}
pub fn goto_check(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
let argument = scheme.argument.get_one_u32()?;
if cpu.bank_get(scheme.target) != FALSE {
_goto(cpu, cpu.state.current, argument)?;
}
Ok(())
}
pub fn gosub_check(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
let argument = scheme.argument.get_one_u32()?;
if cpu.bank_get(scheme.target) != FALSE {
cpu.stack_push(cpu.state.current.to_word());
_goto(cpu, cpu.state.current, argument)?;
}
Ok(())
}
pub fn skip_if(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
scheme.argument.get_no_argument()?;
let state = cpu.state.operation.compare;
if fn_if(state, scheme.target.to_u16())? {
cpu.state.flag_skip = true;
}
Ok(())
}
pub fn skip_check(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
scheme.argument.get_no_argument()?;
if cpu.bank_get(scheme.target) != FALSE {
cpu.state.flag_skip = true;
}
Ok(())
}
pub fn halt(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
scheme.argument.get_no_argument()?;
cpu.halt();
Ok(())
}
pub fn operation_set() -> OperationSet {
let mut set: OperationSet = HashMap::new();
set.insert(
JUMP,
Operation::new(JUMP, "jump".to_string(), true, ArgumentType::NoArgument, jump),
);
set.insert(
CALL,
Operation::new(CALL, "call".to_string(), true, ArgumentType::NoArgument, call),
);
set.insert(
CALL_RETURN,
Operation::new(CALL_RETURN, "return".to_string(), false, ArgumentType::NoArgument, call_return),
);
set.insert(
GOTO,
Operation::new(GOTO, "goto".to_string(), false, ArgumentType::OneU32, goto),
);
set.insert(
GOSUB,
Operation::new(GOSUB, "gosub".to_string(), false, ArgumentType::OneU32, gosub),
);
set.insert(
LOOP,
Operation::new(LOOP, "loop".to_string(), true, ArgumentType::NoArgument, loop_init),
);
set.insert(
NEXT,
Operation::new(NEXT, "next".to_string(), false, ArgumentType::OneU32, next),
);
set.insert(
GOTO_IF,
Operation::new(GOTO_IF, "goto-if".to_string(), true, ArgumentType::OneU32, goto_if),
);
set.insert(
GOSUB_IF,
Operation::new(GOSUB_IF, "gosub-if".to_string(), true, ArgumentType::OneU32, gosub_if),
);
set.insert(
GOTO_CHECK,
Operation::new(GOTO_CHECK, "goto-check".to_string(), true, ArgumentType::OneU32, goto_check),
);
set.insert(
GOSUB_CHECK,
Operation::new(GOSUB_CHECK, "gosub-check".to_string(), true, ArgumentType::OneU32, gosub_check),
);
set.insert(
SKIP_IF,
Operation::new(SKIP_IF, "skip-if".to_string(), true, ArgumentType::NoArgument, skip_if),
);
set.insert(
SKIP_CHECK,
Operation::new(SKIP_CHECK, "skip-check".to_string(), true, ArgumentType::NoArgument, skip_check),
);
set.insert(
HALT,
Operation::new(HALT, "halt".to_string(), false, ArgumentType::NoArgument, halt),
);
set
}