
use ux::{u3, u25};

pub struct ParsingError;

#[derive(Debug)]
pub struct NormalInstruction {
  pub a: u3,
  pub b: u3,
  pub c: u3
}

#[derive(Debug)]
pub struct SpecialInstruction {
  pub a: u3,
  pub v: u25
}

#[derive(Debug)]
pub enum Instruction {
  ConditionalMove(NormalInstruction),
  ArrayIndex(NormalInstruction),
  ArrayAmend(NormalInstruction),
  Add(NormalInstruction),
  Multiply(NormalInstruction),
  Divide(NormalInstruction),
  NotAnd(NormalInstruction),
  Halt(NormalInstruction),
  Allocate(NormalInstruction),
  Abandon(NormalInstruction),
  Output(NormalInstruction),
  Input(NormalInstruction),
  Load(NormalInstruction),
  Ortho(SpecialInstruction)
} 

const fn u3at(d: u32, pos: u8) -> u3 {
  u3::new(((d >> pos) & 7) as u8)
}

impl Instruction {
  pub fn fmt(&self) -> &'static str {
    match self {
      Instruction::ConditionalMove(i) => "CMV",
      Instruction::ArrayIndex(i) => "IDX",
      Instruction::ArrayAmend(i) => "AMD",
      Instruction::Add(i) => "ADD",
      Instruction::Multiply(i) => "MUL",
      Instruction::Divide(i) => "DIV",
      Instruction::NotAnd(i) => "NAD",
      Instruction::Halt(i) => "HLT",
      Instruction::Allocate(i) => "ALC",
      Instruction::Abandon(i) => "ABN",
      Instruction::Output(i) => "OUT",
      Instruction::Input(i) => "INP",
      Instruction::Load(i) => "LOD",
      Instruction::Ortho(i) => "ORT"
    }
  }
  
  pub const fn parse(d: u32) -> Result<Instruction, ParsingError> {
    let itype = d >> 28;
    let (a, b, c, v) = match itype {
      13 => (u3at(d, 25), u3::new(0), u3::new(0), u25::new(d & 0x1ffffff)),
      _ => (u3at(d, 6), u3at(d, 3), u3at(d, 0), u25::new(0))
    };
    match itype {
      0 => Ok(Instruction::ConditionalMove(NormalInstruction{a: a, b: b, c: c})),
      1 => Ok(Instruction::ArrayIndex(NormalInstruction{a: a, b: b, c: c})),
      2 => Ok(Instruction::ArrayAmend(NormalInstruction{a: a, b: b, c: c})),
      3 => Ok(Instruction::Add(NormalInstruction{a: a, b: b, c: c})),
      4 => Ok(Instruction::Multiply(NormalInstruction{a: a, b: b, c: c})),
      5 => Ok(Instruction::Divide(NormalInstruction{a: a, b: b, c: c})),
      6 => Ok(Instruction::NotAnd(NormalInstruction{a: a, b: b, c: c})),
      7 => Ok(Instruction::Halt(NormalInstruction{a: a, b: b, c: c})),
      8 => Ok(Instruction::Allocate(NormalInstruction{a: a, b: b, c: c})),
      9 => Ok(Instruction::Abandon(NormalInstruction{a: a, b: b, c: c})),
     10 => Ok(Instruction::Output(NormalInstruction{a: a, b: b, c: c})),
     11 => Ok(Instruction::Input(NormalInstruction{a: a, b: b, c: c})),
     12 => Ok(Instruction::Load(NormalInstruction{a: a, b: b, c: c})),
     13 => Ok(Instruction::Ortho(SpecialInstruction{a: a, v: v})),
      _ => Err(ParsingError)
    }
  }
}