///|
/// The instruction types stored in an RFC 3284 code-table entry.
pub(all) enum InstructionKind {
  Noop
  Add
  Run
  Copy
} derive(Eq, Debug)

///|
/// One instruction triple from a code-table entry.
///
/// A zero size means that the actual size follows the code byte as an RFC
/// unsigned integer. The mode is meaningful only for COPY.
pub(all) struct CodeInstruction {
  kind : InstructionKind
  size : Int
  mode : Int
} derive(Eq, Debug)

///|
/// A code-table entry containing one required instruction and an optional
/// second instruction represented by NOOP.
pub(all) struct CodeTableEntry {
  first : CodeInstruction
  second : CodeInstruction
} derive(Eq, Debug)

///|
/// Number of recent addresses in the standard near cache.
pub const DEFAULT_NEAR_CACHE_SIZE : Int = 4

///|
/// Number of 256-entry banks in the standard same cache.
pub const DEFAULT_SAME_CACHE_SIZE : Int = 3

///|
/// Number of legal COPY address modes in the standard table.
pub const DEFAULT_ADDRESS_MODE_COUNT : Int = 9

///|
fn code_instruction(
  kind : InstructionKind,
  size : Int,
  mode : Int,
) -> CodeInstruction {
  { kind, size, mode }
}

///|
fn noop_instruction() -> CodeInstruction {
  code_instruction(Noop, 0, 0)
}

///|
fn single_entry(
  kind : InstructionKind,
  size : Int,
  mode : Int,
) -> CodeTableEntry {
  { first: code_instruction(kind, size, mode), second: noop_instruction() }
}

///|
fn paired_entry(
  first_kind : InstructionKind,
  first_size : Int,
  first_mode : Int,
  second_kind : InstructionKind,
  second_size : Int,
  second_mode : Int,
) -> CodeTableEntry {
  {
    first: code_instruction(first_kind, first_size, first_mode),
    second: code_instruction(second_kind, second_size, second_mode),
  }
}

///|
fn append_single_instructions(table : Array[CodeTableEntry]) -> Unit {
  table.push(single_entry(Run, 0, 0))
  table.push(single_entry(Add, 0, 0))
  for size = 1; size <= 17; size = size + 1 {
    table.push(single_entry(Add, size, 0))
  }
  for mode = 0; mode < DEFAULT_ADDRESS_MODE_COUNT; mode = mode + 1 {
    table.push(single_entry(Copy, 0, mode))
    for size = 4; size <= 18; size = size + 1 {
      table.push(single_entry(Copy, size, mode))
    }
  }
}

///|
fn append_add_copy_pairs(table : Array[CodeTableEntry]) -> Unit {
  for mode = 0; mode <= 5; mode = mode + 1 {
    for add_size = 1; add_size <= 4; add_size = add_size + 1 {
      for copy_size = 4; copy_size <= 6; copy_size = copy_size + 1 {
        table.push(paired_entry(Add, add_size, 0, Copy, copy_size, mode))
      }
    }
  }
  for mode = 6; mode < DEFAULT_ADDRESS_MODE_COUNT; mode = mode + 1 {
    for add_size = 1; add_size <= 4; add_size = add_size + 1 {
      table.push(paired_entry(Add, add_size, 0, Copy, 4, mode))
    }
  }
}

///|
fn append_copy_add_pairs(table : Array[CodeTableEntry]) -> Unit {
  for mode = 0; mode < DEFAULT_ADDRESS_MODE_COUNT; mode = mode + 1 {
    table.push(paired_entry(Copy, 4, mode, Add, 1, 0))
  }
}

///|
/// Builds the RFC 3284 default 256-entry instruction code table.
///
/// A fresh array is returned on every call so callers may inspect or transform
/// it without mutating decoder state.
pub fn default_code_table() -> Array[CodeTableEntry] {
  let table : Array[CodeTableEntry] = []
  append_single_instructions(table)
  append_add_copy_pairs(table)
  append_copy_add_pairs(table)
  table
}

///|
/// Looks up a standard code byte while validating the byte-sized index.
pub fn default_code_entry(index : Int) -> CodeTableEntry raise VcdiffError {
  if index < 0 || index >= 256 {
    raise InvalidInstruction(
      offset=-1,
      reason="default code index must be in the range 0..255",
    )
  }
  default_code_table()[index]
}

///|
/// Returns whether a code triple is internally valid for the default format.
pub fn CodeInstruction::is_valid(self : CodeInstruction) -> Bool {
  if self.size < 0 || self.size > 255 {
    return false
  }
  match self.kind {
    Noop => self.size == 0 && self.mode == 0
    Add | Run => self.mode == 0
    Copy => self.mode >= 0 && self.mode < DEFAULT_ADDRESS_MODE_COUNT
  }
}

///|
/// Returns one stable uppercase instruction name for diagnostics and reports.
pub fn InstructionKind::name(self : InstructionKind) -> String {
  match self {
    Noop => "NOOP"
    Add => "ADD"
    Run => "RUN"
    Copy => "COPY"
  }
}