use std::io::{self, Read};

use crate::shared::Operation;

pub struct Interpreter {
    pub memory: [u8; 30000],
    pub data_ptr: usize,
    pub instr_ptr: usize,
    pub program: Vec<Operation>,
}

impl Interpreter {
    pub fn run(&mut self) {
        while self.instr_ptr < self.program.len() {
            let operation = &self.program[self.instr_ptr];

            match operation {
                Operation::Next => self.data_ptr += 1,
                Operation::Prev => self.data_ptr -= 1,
                Operation::Increment => {
                    self.memory[self.data_ptr] = self.memory[self.data_ptr].wrapping_add(1);
                }
                Operation::Decrement => {
                    self.memory[self.data_ptr] = self.memory[self.data_ptr].wrapping_sub(1);
                }
                Operation::Output => print!("{}", self.memory[self.data_ptr] as char),
                Operation::Input => {
                    let mut buffer = [0u8; 1];
                    if io::stdin().read(&mut buffer).is_ok() {
                        self.memory[self.data_ptr] = buffer[0];
                    } else {
                        self.memory[self.data_ptr] = 0;
                    }
                }
                Operation::JumpIfZero(target) => {
                    if self.memory[self.data_ptr] == 0 {
                        self.instr_ptr = *target;
                        continue;
                    }
                }
                Operation::JumpIfNonzero(target) => {
                    if self.memory[self.data_ptr] != 0 {
                        self.instr_ptr = *target;
                        continue;
                    }
                }
            }

            self.instr_ptr += 1;
        }
    }
}

pub fn parse_code(code: &str) -> Vec<Operation> {
    let mut operaions: Vec<Operation> = Vec::new();

    let mut stack: Vec<usize> = Vec::new();

    for token in code.chars() {
        match token {
            '>' => operaions.push(Operation::Next),
            '<' => operaions.push(Operation::Prev),
            '+' => operaions.push(Operation::Increment),
            '-' => operaions.push(Operation::Decrement),
            '.' => operaions.push(Operation::Output),
            ',' => operaions.push(Operation::Input),
            '[' => {
                stack.push(operaions.len());
                operaions.push(Operation::JumpIfZero(0));
            }
            ']' => {
                let start_idx = stack.pop().unwrap();
                let end_idx = operaions.len();

                if let Operation::JumpIfZero(ref mut target) = operaions[start_idx] {
                    *target = end_idx;
                }

                operaions.push(Operation::JumpIfNonzero(start_idx));
            }
            _ => {}
        }
    }
    return operaions;
}
