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

pub trait Console {
  fn read(&mut self) -> Option<u8>;
  fn write(&mut self, c: u8);
}

#[derive(Debug)]
pub enum CpuError {
  ProgramEnd,
  InvalidInstruction,
  UnexpectedInput,
  InvalidArray,
  ArrayOutOfBounds,
  DivideByZero,
  Halt, 
  LargeAlloc,
  InvalidOutput
}

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


impl Cpu {
  pub fn new(pgm: Vec<u32>) -> Cpu {
    let mut arrays = HashMap::new();
    arrays.insert(0, pgm);
    Cpu {
      reg: [0;8],
      arr: arrays,
      iarr: 1,
      finger: 0,
      in_reg: None,
      out: None, 
      debug: 0
    }
  }
  
  pub fn set_debug(&mut self, debug: u32) {
    self.debug = debug;
  }
  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 handle_aix(&mut self, i: NormalInstruction) -> Result<(), CpuError> {
    let aidx = self.gr(i.b);
    let aoff = self.gr(i.c) as usize;
    
    if let Some(arr) = self.arr.get(&aidx){
      if arr.len() > aoff {
        self.sr(i.a, arr[aoff]);
        Ok(())
      } else {
        Err(CpuError::ArrayOutOfBounds)
      }
    } else {
      Err(CpuError::InvalidArray)
    }
  }
  
  fn handle_amd(&mut self, i: NormalInstruction) -> Result<(), CpuError> {
    let aidx = self.gr(i.a);
    let aoff = self.gr(i.b) as usize;
    let v = self.gr(i.c);
    // println!("amd {}[{}]={}", aidx, aoff, v);
    if let Some(arr) = self.arr.get_mut(&aidx){
      if arr.len() > aoff {
        arr[aoff] = v;
        Ok(())
      } else {
        Err(CpuError::ArrayOutOfBounds)
      }
    } else {
      Err(CpuError::InvalidArray)
    }
  }
  
  fn handle_add(&mut self, i:NormalInstruction) -> Result<(), CpuError>{
    let b = self.gr(i.b);
    let c = self.gr(i.c);
    let (a, _) = b.overflowing_add(c);
    self.sr(i.a, a);
    Ok(())
  } 
  
  fn handle_mul(&mut self, i:NormalInstruction) -> Result<(), CpuError>{
    let b = self.gr(i.b);
    let c = self.gr(i.c);
    let (a, _) = b.overflowing_mul(c);
    self.sr(i.a, a);
    Ok(())
  } 
  
  fn handle_div(&mut self, i:NormalInstruction) -> Result<(), CpuError>{
    let b = self.gr(i.b);
    let c = self.gr(i.c);
    if c == 0 {
      return Err(CpuError::DivideByZero);
    }
    let (a, _) = b.overflowing_div(c);
    self.sr(i.a, a);
    Ok(())
  } 
  
  fn handle_nand(&mut self, i:NormalInstruction) -> Result<(), CpuError>{
    let b = self.gr(i.b);
    let c = self.gr(i.c);
    let a = !(b & c);
    self.sr(i.a, a);
    Ok(())
  } 
  
  fn handle_halt(&mut self, _i:NormalInstruction) -> Result<(), CpuError>{
    Err(CpuError::Halt)
  }
  
  fn handle_alc(&mut self, i:NormalInstruction) -> Result<(), CpuError>{
    let c = self.gr(i.c) as usize;
    if c > 1048576 {
      return Err(CpuError::LargeAlloc);
    }
    let aid = self.iarr;
    self.iarr += 1;
    let v = vec![0; c];
    self.arr.insert(aid, v);
    self.sr(i.b, aid);
    //println!("alc {} -> {}", c, aid);
    Ok(())
  } 
  
  fn handle_abn(&mut self, i:NormalInstruction) -> Result<(), CpuError>{
    let c = self.gr(i.c);
    if c == 0 || ! self.arr.contains_key(&c){
      return Err(CpuError::InvalidArray);
    }
    self.arr.remove(&c);
    Ok(())
  } 
  fn handle_out(&mut self, i:NormalInstruction) -> Result<(), CpuError>{
    let c = self.gr(i.c);
    if c > 255 {
      return Err(CpuError::InvalidOutput);
    }
    self.out = Some(c as u8);
    Ok(())
  } 
  
  fn handle_inp(&mut self, i:NormalInstruction) -> Result<(), CpuError>{
    self.sr(i.c, 0xffffffff);
    self.in_reg = Some(i.c);
    Ok(())
  } 
  
  fn handle_lod(&mut self, i:NormalInstruction) -> Result<(), CpuError>{
    let aidx = self.gr(i.b);
    let aoff = self.gr(i.c);
    if let Some(arr) = self.arr.get(&aidx){
      if arr.len() > (aoff as usize) {
        if aidx > 0 {
          // println!("cloning {}", aidx);
          self.arr.insert(0, arr.clone());
        }
        self.finger = aoff;
        Ok(())
      } else {
        Err(CpuError::ArrayOutOfBounds)
      }
    } else {
      Err(CpuError::InvalidArray)
    }
  } 
  
  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),
      Instruction::ArrayIndex(i) => self.handle_aix(i),
      Instruction::ArrayAmend(i) => self.handle_amd(i),
      Instruction::Add(i) => self.handle_add(i),
      Instruction::Multiply(i) => self.handle_mul(i),
      Instruction::Divide(i) => self.handle_div(i),
      Instruction::NotAnd(i) => self.handle_nand(i),
      Instruction::Halt(i) => self.handle_halt(i),
      Instruction::Allocate(i) => self.handle_alc(i),
      Instruction::Abandon(i) => self.handle_abn(i),
      Instruction::Output(i) => self.handle_out(i),
      Instruction::Input(i) => self.handle_inp(i),
      Instruction::Load(i) => self.handle_lod(i),
      //_ => Err(CpuError::InvalidInstruction)
    }
  }
  
  pub fn step(&mut self) -> Result<(), CpuError>{
    self.out = None;
    self.in_reg = None;
    let i = self.ins()?;
    self.finger += 1;
    self.exec(i)?;
    Ok(())
  }
  
  pub fn run(&mut self, console: &mut impl Console) -> Result<(), CpuError> {
  let mut cont = true;
   
   while cont {
     if self.debug > 5 {
        println!("{:?}", self.ins());
        println!("{}", self.state());
      }
    cont = match self.step() {
      Ok(_) => true,
      Err(CpuError::ProgramEnd) => false,
      Err(e) => return Err(e)
    };
    if self.expecting_input() {
      if let Some(c) = console.read() {
        self.set_in(c)?;
      }
    };
    if let Some(c) = self.output() {
      console.write(c);
    }
  };
  Ok(())
}
}



#[cfg(test)]
mod tests{
  use super::*;
  struct NoConsole;

  impl Console for NoConsole {
    fn read(&mut self) -> Option<u8> {
      None
    }
    fn write(&mut self, _c: u8) {}
  }
  
  struct BufConsole {
    inb: Vec<u8>,
    inptr: usize,
    outb: Vec<u8>
  }
  
  impl BufConsole {
    fn make() -> BufConsole {
      BufConsole {
        inb: vec![],
        inptr:  0,
        outb: vec![]
      }
    }
  }

  impl Console for BufConsole {
    fn read(&mut self) -> Option<u8> {
      if self.inptr >= self.inb.len() {
        None
      } else {
        let r = self.inb[self.inptr];
        self.inptr += 1;
        Some(r)
      }
    }
    fn write(&mut self, c: u8) {
      self.outb.push(c);
    }
  }
  
  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<(), CpuError> {
    let pgm = vec!( ezop(13, 3, 1337, 0) );
    let mut c = Cpu::new(pgm);
    let exp = [0, 0, 0, 1337, 0, 0, 0, 0];
    c.run(&mut NoConsole{})?;
    assert_eq!(c.reg, exp);
    Ok(())
  }
  
   #[test]
  fn test_cmov() -> Result<(), CpuError> {
    let pgm = vec!( ezop(0, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 1337, 1, 0, 0, 0, 0];
    c.run(&mut NoConsole{})?;
    let exp = [0, 1337, 1337, 1, 0, 0, 0, 0];
    assert_eq!(c.reg, exp);
    Ok(())
  }
  
  #[test]
  fn test_cmov_neg() -> Result<(), CpuError> {
    let pgm = vec!( ezop(0, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 1337, 0, 0, 0, 0, 0];
    c.run(&mut NoConsole{})?;
    let exp = [0, 0, 1337, 0, 0, 0, 0, 0];
    assert_eq!(c.reg, exp);
    Ok(())
  }
  
   #[test]
  fn test_aix() -> Result<(), CpuError> {
    let pgm = vec!( ezop(1, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.arr.insert(1338, vec![0,0,0,1337]);
    c.reg = [0, 0, 1338, 3, 0, 0, 0, 0];
    c.run(&mut NoConsole{})?;
    let exp = [0, 1337, 1338, 3, 0, 0, 0, 0];
    assert_eq!(c.reg, exp);
    Ok(())
  }
  
  #[test]
  fn test_aix_noarr() -> Result<(), String> {
    let pgm = vec!( ezop(1, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 1338, 3, 0, 0, 0, 0];
    match c.run(&mut NoConsole{}) {
      Err(CpuError::InvalidArray) => Ok(()),
      Err(_) => Err(String::from("Invalid error")),
      Ok(_) => Err(String::from("Expected err got ok"))
    }
    
  }
  
    #[test]
  fn test_aix_nooff() -> Result<(), String> {
    let pgm = vec!( ezop(1, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 1338, 3, 0, 0, 0, 0];
    c.arr.insert(1338, vec![0]);
    match c.run(&mut NoConsole{}) {
      Err(CpuError::ArrayOutOfBounds) => Ok(()),
      Err(_) => Err(String::from("Invalid error")),
      Ok(_) => Err(String::from("Expected err got ok"))
    }
    
  }
  
  #[test]
  fn test_amd() -> Result<(), CpuError> {
    let pgm = vec!( ezop(2, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.arr.insert(1338, vec![0,0,0,0]);
    c.reg = [0, 1338, 1, 1336, 0, 0, 0, 0];
    c.run(&mut NoConsole{})?;
    let exp = [0, 1338, 1, 1336,
   0, 0, 0, 0];
    assert_eq!(c.reg, exp);
    if let Some(arr) = c.arr.get(&1338){
      assert_eq!(arr,&vec![0,1336,0,0] );
      Ok(())
    } else {
      Err(CpuError::InvalidArray)
    }
  }
  
    #[test]
  fn test_amd_noarr() -> Result<(), String> {
    let pgm = vec!( ezop(2, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 1338, 3, 0, 0, 0, 0, 0];
    match c.run(&mut NoConsole{}) {
      Err(CpuError::InvalidArray) => Ok(()),
      Err(_) => Err(String::from("Invalid error")),
      Ok(_) => Err(String::from("Expected err got ok"))
    }
    
  }
  
  #[test]
  fn test_amd_nooff() -> Result<(), String> {
    let pgm = vec!( ezop(2, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 1338, 3, 0, 0, 0, 0, 0];
    c.arr.insert(1338, vec![0]);
    match c.run(&mut NoConsole{}) {
      Err(CpuError::ArrayOutOfBounds) => Ok(()),
      Err(_) => Err(String::from("Invalid error")),
      Ok(_) => Err(String::from("Expected err got ok"))
    }
  }
  
  #[test]
  fn test_add() -> Result<(), CpuError> {
    let pgm = vec!( ezop(3, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 1337, 1, 0, 0, 0, 0];
    c.run(&mut NoConsole{})?;
    let exp = [0, 1338, 1337, 1, 0, 0, 0, 0];
    assert_eq!(c.reg, exp);
    Ok(())
  }
  
  #[test]
  fn test_add_ovf() -> Result<(), CpuError> {
    let pgm = vec!( ezop(3, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 1337, 0xffffffff, 0, 0, 0, 0];
    c.run(&mut NoConsole{})?;
    let exp = [0, 1336, 1337, 0xffffffff, 0, 0, 0, 0];
    assert_eq!(c.reg, exp);
    Ok(())
  }
  
   #[test]
  fn test_mul() -> Result<(), CpuError> {
    let pgm = vec!( ezop(4, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 1337, 2, 0, 0, 0, 0];
    c.run(&mut NoConsole{})?;
    let exp = [0, 2674, 1337, 2, 0, 0, 0, 0];
    assert_eq!(c.reg, exp);
    Ok(())
  }
  
  #[test]
  fn test_mul_ovf() -> Result<(), CpuError> {
    let pgm = vec!( ezop(4, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 2, 0xffffffff, 0, 0, 0, 0];
    c.run(&mut NoConsole{})?;
    let exp = [0, 0xfffffffe, 2, 0xffffffff, 0, 0, 0, 0];
    assert_eq!(c.reg, exp);
    Ok(())
  }
  
  #[test]
  fn test_div() -> Result<(), CpuError> {
    let pgm = vec!( ezop(5, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 2048, 1024, 0, 0, 0, 0];
    c.run(&mut NoConsole{})?;
    let exp = [0, 2, 2048, 1024, 0, 0, 0, 0];
    assert_eq!(c.reg, exp);
    Ok(())
  }
  
  #[test]
  fn test_div_zero() -> Result<(), String> {
    let pgm = vec!( ezop(5, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 1338, 3, 0, 0, 0, 0, 0];
    match c.run(&mut NoConsole{}) {
      Err(CpuError::DivideByZero) => Ok(()),
      Err(_) => Err(String::from("Invalid error")),
      Ok(_) => Err(String::from("Expected err got ok"))
    }
  }
  
  #[test]
  fn test_nand() -> Result<(), CpuError> {
    let pgm = vec!( ezop(6, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 0xff00ff00, 0xffff0000, 0, 0, 0, 0];
    c.run(&mut NoConsole{})?;
    let exp = [0, 0x00ffff00, 0xff00ff00, 0xffff0000,  0, 0, 0, 0];
    assert_eq!(c.reg, exp);
    Ok(())
  }

  #[test]
  fn test_halt() -> Result<(), String> {
    let pgm = vec!( ezop(7, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    match c.run(&mut NoConsole{}) {
      Err(CpuError::Halt) => Ok(()),
      Err(_) => Err(String::from("Invalid error")),
      Ok(_) => Err(String::from("Expected err got ok"))
    }
  }
  
  #[test]
  fn test_alc_large() -> Result<(), String> {
    let pgm = vec!( ezop(8, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 1338, 33, 0xffffffff, 0, 0, 0, 0];
    match c.run(&mut NoConsole{}) {
      Err(CpuError::LargeAlloc) => Ok(()),
      Err(_) => Err(String::from("Invalid error")),
      Ok(_) => Err(String::from("Expected err got ok"))
    }
  }
  
    #[test]
  fn test_alc() -> Result<(), CpuError> {
    let pgm = vec!( ezop(8, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 1338, 33, 5, 0, 0, 0, 0];
    if let Err(e) = c.run(&mut NoConsole{}) {
      return Err(e)
    }
    let exp = [0, 1338, 1, 5, 0, 0, 0, 0];
    assert_eq!(c.reg, exp);
    if let Some(arr) = c.arr.get(&1){
      assert_eq!(arr,&vec![0,0,0,0,0] );
      Ok(())
    } else {
      Err(CpuError::InvalidArray)
    }
  }
  
  #[test]
  fn test_abn() -> Result<(), CpuError> {
    let pgm = vec!( ezop(9, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.arr.insert(1338, vec![0,0,0,0]);
    c.reg = [0, 0, 0, 1338, 0, 0, 0, 0];
    c.run(&mut NoConsole{})?;
    if c.arr.contains_key(&1338) {
      Err(CpuError::InvalidArray)
    } else {
      Ok(())
    }
  }
  
  #[test]
  fn test_abn_zero() -> Result<(), String> {
    let pgm = vec!( ezop(9, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 0, 0, 0, 0, 0, 0];
    match c.run(&mut NoConsole{}) {
      Err(CpuError::InvalidArray) => Ok(()),
      Err(_) => Err(String::from("Invalid error")),
      Ok(_) => Err(String::from("Expected err got ok"))
    }
  }
  
  #[test]
  fn test_out() -> Result<(), String> {
    let pgm = vec!( ezop(10, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 0, 128, 0, 0, 0, 0];
    let mut cons = BufConsole::make();
    if let Err(e) = c.run(&mut cons) {
      return Err(format!("unexpected err {:?}", e));
    };
    assert_eq!(cons.outb, vec!(128));
    Ok(())
  }
  
  #[test]
  fn test_out_inv() -> Result<(), String> {
    let pgm = vec!( ezop(10, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 0, 1337, 0, 0, 0, 0];
    let mut cons = BufConsole::make();
    match c.run(&mut cons) {
      Err(CpuError::InvalidOutput) => Ok(()),
      Ok(_) => Err(format!("unexpected ok")),
      Err(e) => Err(format!("unexpected err {:?}", e))
    }
    
  }
  
  #[test]
  fn test_inp() -> Result<(), String> {
    let pgm = vec!( ezop(11, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 0, 0, 0, 0, 0, 0];
    let mut cons = BufConsole::make();
    cons.inb.push(128);
    if let Err(e) = c.run(&mut cons) {
      return Err(format!("unexpected err {:?}", e));
    };
    let exp = [0, 0, 0, 128, 0, 0, 0, 0];
    assert_eq!(c.reg, exp);
    Ok(())
  }
  
  #[test]
  fn test_inp_fail() -> Result<(), String> {
    let pgm = vec!( ezop(11, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 0, 0, 0, 0, 0, 0];
    let mut cons = BufConsole::make();
    if let Err(e) = c.run(&mut cons) {
      return Err(format!("unexpected err {:?}", e));
    };
    let exp = [0, 0, 0, 0xffffffff, 0, 0, 0, 0];
    assert_eq!(c.reg, exp);
    Ok(())
  }
  
  #[test]
  fn test_lod() -> Result<(), CpuError> {
    let pgm = vec!( ezop(12, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.arr.insert(1338, vec![0,0,0,1339]);
    c.reg = [0, 0, 1338, 3, 0, 0, 0, 0];
    c.step()?;
    assert_eq!(c.finger, 3);
    if let Some(arr) = c.arr.get(&0){
      assert_eq!(arr,&vec![0,0,0,1339] );
      Ok(())
    } else {
      Err(CpuError::InvalidArray)
    }
  }
  
  #[test]
  fn test_lod_inv() -> Result<(), String> {
    let pgm = vec!( ezop(12, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 1338, 3, 0, 0, 0, 0];

    match c.step() {
      Err(CpuError::InvalidArray) => Ok(()),
      Ok(_) => Err(format!("unexpected ok")),
      Err(e) => Err(format!("unexpected err {:?}", e))
    }
  }
  
    #[test]
  fn test_lod_off() -> Result<(), String> {
    let pgm = vec!( ezop(12, 1, 2, 3) );
    let mut c = Cpu::new(pgm);
    c.reg = [0, 0, 1338, 3, 0, 0, 0, 0];
    c.arr.insert(1338, vec![0]);
    match c.step() {
      Err(CpuError::ArrayOutOfBounds) => Ok(()),
      Ok(_) => Err(format!("unexpected ok")),
      Err(e) => Err(format!("unexpected err {:?}", e))
    }
  }
}