use crate::message::*;
use crate::alu::{AluProvider, Digit};
use p101_is::*;
use std::sync::mpsc;
use std::collections::HashMap;

#[derive(PartialEq, Eq, Hash, Debug)]
pub enum FuncName {
    CopyDecimal,
    CopyToA,
    CopyM,
    Print,
    Newline,
    Reset,
    SwapA,
    DrExchange,
    Add,
    Subtract,
    Multiply,
    Divide,
    Abs,
    Sqr,
    Label,
    Jump,
    ConditionalJump,
    CaiStart,
    ClearEntry,
    ClearM,
    WriteToM,
    EnteringDecimals,
    InsertSign,
    ClearMemory,
    StartStop
}

pub type Callback = fn(&mut TestAlu) -> Result<(), String>;

const COMPONENT_NAME: &'static str = "TestAlu";
const MEM_SIZE: usize = 120;

pub struct TestAlu {
    pub mem: [Option<Instruction>; MEM_SIZE],
    pub ip: usize,
    pub system: mpsc::Sender<Message>,
    pub callback: HashMap<FuncName, Callback>,
    pub log: Vec<String>
}

impl TestAlu {
    fn call(&mut self, func_name: FuncName) -> Result<(), String> {
        match self.callback.get(&func_name) {
            Some(func) => func(self),
            None => Ok(())
        }
    }
}

impl AluProvider for TestAlu {
    fn update_memory(&mut self, program: Vec<Instruction>) -> Result<(), String> {
        let mut addr = 0;

        for i in &program {
            self.mem[addr] = Some(*i);
            addr += 1;
        }

        Ok(())
    }
    
    fn read_instruction(&self, address: usize) -> Option<Instruction> {
        self.mem[address]
    }

    fn read_ip(&mut self) -> usize {
        self.ip
    }
    
    fn increment_ip(&mut self, step: usize) -> Result<(), String> {
        self.ip += step;
        Ok(())
    }

    fn copy_decimal(&mut self) -> Result<(), String> {
        self.call(FuncName::CopyDecimal)
    }
    
    fn copy_to_a(&mut self, operand: Register) -> Result<(), String> {
        self.log.push(format!("copy_to_a({:?})", operand));
        self.call(FuncName::CopyToA)
    }
    
    fn copy_m(&mut self, operand: Register) -> Result<(), String> {
        self.log.push(format!("copy_m({:?})", operand));
        self.call(FuncName::CopyM)
    }
    
    fn print(&mut self, operand: Register) -> Result<(), String> {
        self.log.push(format!("print({:?})", operand));
        self.call(FuncName::Print)
    }
    
    fn newline(&mut self) -> Result<(), String> {
        self.call(FuncName::Newline)
    }
    
    fn reset(&mut self, operand: Register) -> Result<(), String> {
        self.log.push(format!("reset({:?})", operand));
        self.call(FuncName::Reset)
    }
    
    fn swap_a(&mut self, operand: Register) -> Result<(), String> {
        self.log.push(format!("swap_a({:?})", operand));
        self.call(FuncName::SwapA)
    }
    
    fn dr_exchange(&mut self) -> Result<(), String> {
        self.log.push("dr_exchange".to_string());
        self.call(FuncName::DrExchange)
    }
    
    fn add(&mut self, operand: Register) -> Result<(), String> {
        self.log.push(format!("add({:?})", operand));
        self.call(FuncName::Add)
    }
    
    fn subtract(&mut self, operand: Register) -> Result<(), String> {
        self.log.push(format!("sub({:?})", operand));
        self.call(FuncName::Subtract)
    }
    
    fn multiply(&mut self, operand: Register) -> Result<(), String> {
        self.log.push(format!("mul({:?})", operand));
        self.call(FuncName::Multiply)
    }
    
    fn divide(&mut self, operand: Register) -> Result<(), String> {
        self.log.push(format!("div({:?})", operand));
        self.call(FuncName::Divide)
    }
    
    fn abs(&mut self) -> Result<(), String> {
        self.call(FuncName::Abs)
    }

    fn sqr(&mut self, operand: Register) -> Result<(), String> {
        self.log.push(format!("sqr({:?})", operand));
        self.call(FuncName::Sqr)
    }

    fn label(&mut self) -> Result<(), String> {
        self.call(FuncName::Label)
    }

    fn jump(&mut self, _: Origin) -> Result<(), String> {
        self.call(FuncName::Jump)
    }

    fn conditional_jump(&mut self, _: ConditionalOrigin) -> Result<(), String> {
        self.call(FuncName::ConditionalJump)
    }

    fn cai_start(&mut self) -> Result<(), String> {
        self.call(FuncName::CaiStart)
    }

    fn clear_entry(&mut self) -> Result<(), String> {
        self.log.push("clear_entry".to_string());
        self.call(FuncName::ClearEntry)
    }

    fn clear_m(&mut self) -> Result<(), String> {
        self.log.push("clear_m".to_string());
        self.call(FuncName::ClearM)
    }

    fn write_to_m(&mut self, value: Digit) -> Result<(), String> {
        self.log.push(format!("write_to_m({})", value));
        self.call(FuncName::WriteToM)
    }

    fn entering_decimals(&mut self, _value: bool) {
        self.log.push("entering_decimals".to_string());
        let _ = self.call(FuncName::EnteringDecimals);
    }

    fn invert_sign(&mut self) {
        let _ = self.call(FuncName::InsertSign);
    }

    fn clear_memory(&mut self) {
        self.log.push("clear_memory".to_string());
        let _ = self.call(FuncName::ClearMemory);
    }

    fn start_stop(&mut self) -> Result<(), String> {
        self.log.push("start_stop".to_string());
        self.call(FuncName::StartStop)
    }

    fn get_provider_name(&self) -> &'static str {
       COMPONENT_NAME
    }
}

pub struct TestAluBuilder {
    mem: [Option<Instruction>; MEM_SIZE],
    ip: usize,
    system: mpsc::Sender<Message>,
    callback: HashMap<FuncName, Callback>,
}

impl TestAluBuilder {
    pub fn new(system: mpsc::Sender<Message>) -> Self {
        Self {
            mem: [None; MEM_SIZE],
            ip: 0,
            system,
            callback: HashMap::new(),
        }
    }

    pub fn instr(mut self, addr: usize, instr: Instruction) -> Self {
        self.mem[addr] = Some(instr);
        self
    }

    pub fn ip(mut self, ip: usize) -> Self {
        self.ip = ip;
        self
    }

    pub fn cb(mut self, func_name: FuncName, func: Callback) -> Self {
        let _ = self.callback.insert(func_name, func);
        self
    }

    pub fn build(self) -> TestAlu {
        TestAlu {
            mem: self.mem,
            ip: self.ip,
            system: self.system,
            callback: self.callback,
            log: Vec::new()
        }
    }
}
