// Text disassembly for the supported instruction subset.

fn signed(value : Int) -> String {
  if value >= 0 {
    "+" + value.to_string()
  } else {
    value.to_string()
  }
}

fn reg(index : Int) -> String {
  "r" + index.to_string()
}

fn alu_name(base : Int) -> String {
  if base == 0x07 {
    "add"
  } else if base == 0x17 {
    "sub"
  } else if base == 0x27 {
    "mul"
  } else if base == 0x37 {
    "div"
  } else if base == 0x47 {
    "or"
  } else if base == 0x57 {
    "and"
  } else if base == 0x67 {
    "lsh"
  } else if base == 0x77 {
    "rsh"
  } else if base == 0x87 {
    "neg"
  } else if base == 0x97 {
    "mod"
  } else if base == 0xa7 {
    "xor"
  } else if base == 0xb7 {
    "mov"
  } else if base == 0xc7 {
    "arsh"
  } else {
    "alu"
  }
}

fn is_alu64(base : Int) -> Bool {
  base == 0x07 || base == 0x17 || base == 0x27 || base == 0x37 ||
  base == 0x47 || base == 0x57 || base == 0x67 || base == 0x77 ||
  base == 0x87 || base == 0x97 || base == 0xa7 || base == 0xb7 || base == 0xc7
}

fn is_alu32(base : Int) -> Bool {
  base == 0x04 || base == 0x14 || base == 0x24 || base == 0x34 ||
  base == 0x44 || base == 0x54 || base == 0x64 || base == 0x74 ||
  base == 0x84 || base == 0x94 || base == 0xa4 || base == 0xb4 || base == 0xc4
}

fn jump_name(base : Int) -> String {
  if base == 0x15 {
    "jeq"
  } else if base == 0x25 {
    "jgt"
  } else if base == 0x35 {
    "jge"
  } else if base == 0x45 {
    "jset"
  } else if base == 0x55 {
    "jne"
  } else if base == 0x65 {
    "jsgt"
  } else if base == 0x75 {
    "jsge"
  } else if base == 0xa5 {
    "jlt"
  } else if base == 0xb5 {
    "jle"
  } else if base == 0xc5 {
    "jslt"
  } else if base == 0xd5 {
    "jsle"
  } else {
    "jmp"
  }
}

fn jump_name32(base : Int) -> String {
  if base == 0x16 {
    "jeq32"
  } else if base == 0x26 {
    "jgt32"
  } else if base == 0x36 {
    "jge32"
  } else if base == 0x46 {
    "jset32"
  } else if base == 0x56 {
    "jne32"
  } else if base == 0x66 {
    "jsgt32"
  } else if base == 0x76 {
    "jsge32"
  } else if base == 0xa6 {
    "jlt32"
  } else if base == 0xb6 {
    "jle32"
  } else if base == 0xc6 {
    "jslt32"
  } else if base == 0xd6 {
    "jsle32"
  } else {
    "jmp32"
  }
}

fn load_name(op : Int) -> String {
  if op == 0x61 {
    "ldxw"
  } else if op == 0x69 {
    "ldxh"
  } else if op == 0x71 {
    "ldxb"
  } else {
    "ldxdw"
  }
}

fn store_name(op : Int) -> String {
  if op == 0x63 {
    "stxw"
  } else if op == 0x6b {
    "stxh"
  } else if op == 0x73 {
    "stxb"
  } else {
    "stxdw"
  }
}

fn store_imm_name(op : Int) -> String {
  if op == 0x62 {
    "stw"
  } else if op == 0x6a {
    "sth"
  } else if op == 0x72 {
    "stb"
  } else {
    "stdw"
  }
}

pub fn disassemble_one(ins : Insn) -> String {
  let op = ins.op
  let base = op & 0xf7
  if is_alu64(base) || is_alu32(base) {
    let suffix = if is_alu64(base) { "64" } else { "32" }
    let name = alu_name(base) + suffix
    if base == 0x87 || base == 0x84 {
      name + " " + reg(ins.dst)
    } else if (op & 0x08) != 0 {
      name + " " + reg(ins.dst) + ", " + reg(ins.src)
    } else {
      name + " " + reg(ins.dst) + ", " + ins.imm.to_string()
    }
  } else if op == 0x61 || op == 0x69 || op == 0x71 || op == 0x79 {
    load_name(op) + " " + reg(ins.dst) + ", [" + reg(ins.src) +
    signed(ins.off) + "]"
  } else if op == 0x81 || op == 0x89 || op == 0x91 {
    let name = if op == 0x81 {
      "ldxsw"
    } else if op == 0x89 {
      "ldxsh"
    } else {
      "ldxsb"
    }
    name + " " + reg(ins.dst) + ", [" + reg(ins.src) + signed(ins.off) + "]"
  } else if op == 0x63 || op == 0x6b || op == 0x73 || op == 0x7b {
    store_name(op) + " [" + reg(ins.dst) + signed(ins.off) + "], " +
    reg(ins.src)
  } else if op == 0x62 || op == 0x6a || op == 0x72 || op == 0x7a {
    store_imm_name(op) + " [" + reg(ins.dst) + signed(ins.off) + "], " +
    ins.imm.to_string()
  } else if op == 0x05 {
    "ja " + signed(ins.off)
  } else if op == 0x06 {
    "ja32 " + signed(ins.off)
  } else if op == 0x15 || op == 0x1d || op == 0x25 || op == 0x2d ||
    op == 0x35 || op == 0x3d || op == 0x45 || op == 0x4d ||
    op == 0x55 || op == 0x5d || op == 0x65 || op == 0x6d ||
    op == 0x75 || op == 0x7d || op == 0xa5 || op == 0xad ||
    op == 0xb5 || op == 0xbd || op == 0xc5 || op == 0xcd ||
    op == 0xd5 || op == 0xdd {
    let source = if (op & 0x08) != 0 {
      reg(ins.src)
    } else {
      ins.imm.to_string()
    }
    jump_name(base) + " " + reg(ins.dst) + ", " + source + ", " +
    signed(ins.off)
  } else if is_jmp32(op) {
    let source = if (op & 0x08) != 0 {
      reg(ins.src)
    } else {
      ins.imm.to_string()
    }
    jump_name32(op & 0xf7) + " " + reg(ins.dst) + ", " + source + ", " +
    signed(ins.off)
  } else if op == 0x85 {
    if ins.src == 1 {
      "call " + signed(ins.imm)
    } else {
      "call helper " + ins.imm.to_string()
    }
  } else if op == 0xd4 || op == 0xdc {
    (if op == 0xd4 { "le" } else { "be" }) + ins.imm.to_string() + " " +
    reg(ins.dst)
  } else if op == 0xd7 {
    "bswap " + reg(ins.dst)
  } else if op == 0x95 {
    "exit"
  } else if op == 0x18 {
    "lddw " + reg(ins.dst) + ", 0x" + ins.imm.to_string()
  } else {
    ".insn op=0x" + op.to_string() + " dst=" + ins.dst.to_string() + " src=" +
    ins.src.to_string() + " off=" + ins.off.to_string() + " imm=" +
    ins.imm.to_string()
  }
}

pub fn disassemble(program : Array[Insn]) -> Array[String] {
  let out : Array[String] = Array::new()
  let mut i = 0
  while i < program.length() {
    let ins = program[i]
    if ins.op == 0x18 && i + 1 < program.length() {
      let value = (ins.imm.to_int64().reinterpret_as_uint64()).land(
        0xffffffffUL,
      ).lor(
        (program[i + 1].imm.to_int64().reinterpret_as_uint64()).land(
          0xffffffffUL,
        ).shl(32),
      )
      out.push("lddw " + reg(ins.dst) + ", 0x" + value.to_string(radix=16))
      i = i + 2
    } else {
      out.push(disassemble_one(ins))
      i = i + 1
    }
  }
  out
}

fn is_conditional_jump(op : Int) -> Bool {
  op == 0x15 || op == 0x1d || op == 0x25 || op == 0x2d ||
  op == 0x35 || op == 0x3d || op == 0x45 || op == 0x4d ||
  op == 0x55 || op == 0x5d || op == 0x65 || op == 0x6d ||
  op == 0x75 || op == 0x7d || op == 0xa5 || op == 0xad ||
  op == 0xb5 || op == 0xbd || op == 0xc5 || op == 0xcd ||
  op == 0xd5 || op == 0xdd || is_jmp32(op)
}

pub fn disassemble_with_targets(program : Array[Insn]) -> Array[String] {
  let out : Array[String] = Array::new()
  let mut i = 0
  while i < program.length() {
    let ins = program[i]
    if ins.op == 0x18 && i + 1 < program.length() {
      let value = (ins.imm.to_int64().reinterpret_as_uint64()).land(
        0xffffffffUL,
      ).lor(
        (program[i + 1].imm.to_int64().reinterpret_as_uint64()).land(
          0xffffffffUL,
        ).shl(32),
      )
      out.push("lddw " + reg(ins.dst) + ", 0x" + value.to_string(radix=16))
      i = i + 2
    } else {
      let mut line = disassemble_one(ins)
      if ins.op == 0x05 || is_conditional_jump(ins.op) {
        line = line + " -> " + (i + 1 + ins.off).to_string()
      } else if ins.op == 0x85 && ins.src == 1 {
        line = line + " -> " + (i + 1 + ins.imm).to_string()
      }
      out.push(line)
      i = i + 1
    }
  }
  out
}