use crate::{
    operation::{Operation, RegisterOperation},
    runtime_state::Register,
    ExecutionResult,
};

struct Runtime {
    registers: Vec<Register>,
    code: Vec<u8>,
}

impl Runtime {
    fn new(code: Vec<u8>) -> Result<Self, String> {
        let mut code = code.into_iter();
        let Some(reg_count) = code.next() else {
            return Err("Expected number of registers, got end of file".to_string());
        };
        let mut registers: Vec<Register> = Vec::new();
        for _reg in 0..reg_count {
            let mut content = Vec::new();
            content.resize(
                ((code.next().ok_or("Expected register size, got eof")? as u32) << 8
                    | code.next().ok_or("Expected register size, got eof")? as u32)
                    .try_into()
                    .unwrap(),
                0,
            );
            registers.push(Register { content, cursor: 0 });
        }
        let code = code.collect::<Vec<u8>>();
        let remainder = code.len() % 4;
        if remainder > 0 {
            return Err(format!(
                "Invalid amounts of bytes in bytecode, {remainder} remaining"
            ));
        }
        Ok(Self { code, registers })
    }

    fn get_register(&mut self, index: u32) -> Result<&mut Register, String> {
        self.registers
            .get_mut(index as usize)
            .ok_or(format!("Register {index} not defined"))
    }

    pub fn run(mut self, debug: bool) -> Result<ExecutionResult, String> {
        let mut pc = 0;
        loop {
            if pc >= self.code.len() {
                return Ok(ExecutionResult::Full(
                    self.registers.into_iter().map(|r| r.content).collect(),
                ));
            }
            let mut jumped = false;
            let reg = self.code[pc];
            let is_reg = reg > 0;
            let reg = reg.checked_sub(1).unwrap_or_default();
            let ins_and_is_reg = self.code[pc + 1];
            let op = ins_and_is_reg.checked_shr(5).unwrap_or_default();
            let val_is_reg = ins_and_is_reg & 1 == 1;
            let val = ((self.code[pc + 2] as u32) << 8) + self.code[pc + 3] as u32;
            let num = if val_is_reg {
                self.get_register(val - 1)?.get()
            } else {
                val
            };
            match Operation::try_from_u8(op) {
                Some(Operation::Register(RegisterOperation::Right)) => {
                    let register = self.registers.get_mut(reg as usize);

                    match register {
                        Some(register) => register.right(num.max(1)),
                        None => return Err(format!("Invalid register in > instruction: {reg}")),
                    }
                }
                Some(Operation::Register(RegisterOperation::Left)) => {
                    self.get_register(reg.into())?.left(num)
                }
                Some(Operation::Take) => {
                    let val =
                        self.get_register(val - 1)?.remove() + self.get_register(reg.into())?.get();
                    self.get_register(reg.into())?.insert(val)
                }
                Some(Operation::Register(RegisterOperation::Add)) => {
                    self.get_register(reg.into())?.add(num)
                }
                Some(Operation::Register(RegisterOperation::Subtract)) => {
                    self.get_register(reg.into())?.sub(num)
                }
                Some(Operation::Jump) => {
                    if val_is_reg {
                        return Err(format!("Can't jump to register {num}"));
                    } else if num != 0 && (!is_reg || self.get_register(reg.into())?.get() > 0) {
                        pc = (num * 4) as usize;
                        jumped = true;
                    }
                }
                Some(Operation::Result) => {
                    let result = if is_reg {
                        ExecutionResult::List(self.get_register(reg.into())?.content.clone())
                    } else {
                        ExecutionResult::Number(num)
                    };
                    if debug {
                        match result {
                            ExecutionResult::Full(_) => todo!(),
                            ExecutionResult::List(list) => {
                                println!(
                                    "{}",
                                    list.into_iter()
                                        .map(|c| format!("{c}"))
                                        .collect::<Vec<String>>()
                                        .join(" ")
                                )
                            }
                            ExecutionResult::Number(num) => println!("{num}"),
                        }
                    } else {
                        return Ok(result);
                    }
                }
                None => return Err(format!("Invalid instruction: {op}")),
            };
            if !jumped {
                pc += 4;
            }
        }
    }
}

pub fn run(code: Vec<u8>, debug: bool) -> Result<ExecutionResult, String> {
    Runtime::new(code)?.run(debug)
}