pub struct ParsingError;

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

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

#[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) -> u8 {
  ((d >> pos) & 7) as u8
}

impl Instruction {
  pub fn fmt(&self) -> String {
    match self {
      Instruction::ConditionalMove(i) => format!("CMV r{}=r{} if r{}", i.
a, i.b, i.c),
      Instruction::ArrayIndex(i) => format!("IDX r{}=r{}[r{}]", i.a, i.b, i.c) ,
      Instruction::ArrayAmend(i) => format!("AMD r{}[r{}]=r{}", i.a, i.b, i.c),
      Instruction::Add(i) => format!("ADD r{}=r{}+r{}", i.a, i.b, i.c),
      Instruction::Multiply(i) => format!("MUL r{}=r{}*r{}", i.a, i.b, i.c),
      Instruction::Divide(i) => format!("DIV r{}=r{}/r{}", i.a, i.b, i.c),
      Instruction::NotAnd(i) => format!("NAD r{}=r{} nand r{}", i.a, i.b, i.c),
      Instruction::Halt(_i) => format!("HLT"),
      Instruction::Allocate(i) => format!("ALC r{} r{}", i.b, i.c),
      Instruction::Abandon(i) => format!("ABN r{}", i.c),
      Instruction::Output(i) => format!("OUT r{}", i.c),
      Instruction::Input(i) => format!("INP r{}", i.c),
      Instruction::Load(i) => format!("LOD r{}@r{}", i.b, i.c),
      Instruction::Ortho(i) => format!("ORT r{}=0x{:x}", i.a, i.v)
    }
  }
  
  pub const fn parse(d: u32) -> Result<Instruction, ParsingError> {
    let itype = d >> 28;
    let (a, b, c, v) = match itype {
      13 => (u3at(d, 25), 0, 0, d & 0x1ffffff),
      _ => (u3at(d, 6), u3at(d, 3), u3at(d, 0), 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)
    }
  }
}