use std::collections::HashMap;
use std::vec::Vec;
use crate::instruction::{Instruction, NormalInstruction, SpecialInstruction};


#[derive(Debug)]
pub enum CpuError {
  ProgramEnd,
  InvalidInstruction,
  UnexpectedInput
}

#[derive(Debug)]
pub struct Cpu {
  reg: [u32; 8],
  arr: HashMap<u32, Vec<u32>>,
  finger: u32,
  out: Option<u8>,
  in_reg: Option<u8>
}


impl Cpu {
  pub fn new(pgm: Vec<u32>) -> Cpu {
    let mut arrays = HashMap::new();
    arrays.insert(0, pgm);
    Cpu {
      reg: [0;8],
      arr: arrays,
      finger: 0,
      in_reg: None,
      out: None
    }
  }
  
  fn gr(&self, r: u8) -> u32{
    self.reg[r as usize]
  }
  
  fn sr(&mut self, r: u8, v:u32) {
    self.reg[r as usize] = v
  }
  
  fn ins(&self) -> Result<Instruction,CpuError> {
    if self.finger as usize >= self.arr[&0].len() {
      return Err(CpuError::ProgramEnd);
    }
    let d = self.arr[&0][self.finger as usize];
    if let Ok(i) = Instruction::parse(d) {
      Ok(i)
    } else {
      Err(CpuError::InvalidInstruction)
    }

  }
  pub fn output(&self) -> Option<u8> {
    self.out
  }
  
  pub fn expecting_input(&self) -> bool {
    match self.in_reg {
      None => false,
      Some(_) => true
    }
  }
  
  pub fn set_in(&mut self, value: u8) -> Result<(), CpuError> {
    match self.in_reg {
      None => Err(CpuError::UnexpectedInput),
      Some(i) => {
        self.reg[i as usize] = value as u32;
        Ok(())
      }
    }
  }
  
  pub fn state(&self) -> String {
    let istr = match self.ins() {
      Ok(i) => i.fmt(),
      Err(e) => format!("{:?}", e)
    };
    format!("CPU Reg {:?}\nCPU Finger {}\nCPU Ins {}", self.reg, self.finger, istr)
  }
  
  fn handle_ortho(&mut self, i:SpecialInstruction) -> Result<(), CpuError>{
    self.sr(i.a, i.v);
    Ok(())
  }
  
  fn handle_cmov(&mut self, i:NormalInstruction) -> Result<(), CpuError>{
    if self.gr(i.c) > 0 {
      let v = self.gr(i.b);
      self.sr(i.a, v); 
    }
    Ok(())
  }
  
  fn exec(&mut self, instruction: Instruction)-> Result<(), CpuError>{
    //println!("{:?}", instruction);
    match instruction {
      Instruction::Ortho(i) => self.handle_ortho(i),
      Instruction::ConditionalMove(i) => self.handle_cmov(i),
      _ => Err(CpuError::InvalidInstruction)
    }
  }
  
  pub fn step(&mut self) -> Result<(), CpuError>{
    self.out = None;
    self.in_reg = None;
    let i = self.ins()?;
    self.exec(i)?;
    self.finger += 1;
    Ok(())
  }
}

#[cfg(test)]
mod tests{
  use super::*;
  
  fn ezop(op:u32, a:u32, b:u32, c:u32) -> u32 {
    match op {
      13 => (op & 0xf) << 28 | (a & 7) << 25 | (b & 0x1ffffff),
      _ => (op & 0xf) << 28 | (a & 7) << 6 | (b & 7) << 3 | (c & 7)
    }
  }
  
  
  
  #[test]
  fn test_ortho() -> Result<(), String> {
    let pgm = vec!(ezop(13, 3, 1337, 0));
    let mut c = Cpu::new(pgm);
    let result = c.step();
    assert!(matches!(result, Ok(_)));
    assert_eq!(c.reg[0], 0);
    assert_eq!(c.reg[3], 1337);
    Ok(())
  }
  
   #[test]
  fn test_cmov() -> Result<(), String> {
    let pgm = vec!(
      ezop(13, 1, 65535, 0),
      ezop(13, 2, 1337, 0),
      ezop(13, 3, 1, 0),
      ezop(0, 1, 2, 3),
    );
    let mut c = Cpu::new(pgm);
    _ = c.step();
    _ = c.step();
    _ = c.step();
    let result = c.step();
    assert!(matches!(result, Ok(_)));
    assert_eq!(c.reg[0], 0);
    assert_eq!(c.reg[3], 1);
    assert_eq!(c.reg[2], 1337);
    assert_eq!(c.reg[1], 1337);
    Ok(())
  }
  
  #[test]
  fn test_cmov_neg() -> Result<(), String> {
    let pgm = vec!(
      ezop(13, 1, 65535, 0),
      ezop(13, 2, 1337, 0),
      ezop(13, 3, 0, 0),
      ezop(0, 1, 2, 3),
    );
    let mut c = Cpu::new(pgm);
    _ = c.step();
    _ = c.step();
    _ = c.step();
    let result = c.step();
    assert!(matches!(result, Ok(_)));
    assert_eq!(c.reg[0], 0);
    assert_eq!(c.reg[3], 0);
    assert_eq!(c.reg[2], 1337);
    assert_eq!(c.reg[1], 65535);
    Ok(())
  }
  
}