///|
pub(all) enum EditPosition {
  Before(Int)
  After(Int)
  Edge(source_block~ : Int, successor_index~ : Int)
} derive(Eq, Hash, Debug)

///|
pub(all) struct AllocationEdit {
  value : VirtualReg
  from : Location
  to : Location
  position : EditPosition
} derive(Eq, Debug)

///|
pub struct OperandAssignment {
  instruction : Int
  operand : Int
  location : Location
} derive(Eq, Debug)

///|
pub struct SpillSlotSpec {
  size : Int
  alignment : Int
} derive(Eq, Debug)

///|
pub struct AllocationPlan {
  priv value_locations : Array[Location?]
  priv operand_locations : Array[Array[Location?]]
  priv edits : Array[AllocationEdit]
  priv spill_slots : Array[SpillSlotSpec]
}

///|
pub impl Debug for AllocationPlan with fn to_repr(self) {
  Repr::record({
    "value_locations": Repr(self.value_locations),
    "operand_assignments": Repr(self.operand_assignments()),
    "edits": Repr(self.edits),
    "spill_slots": Repr(self.spill_slots),
  })
}

///|
pub impl Show for AllocationPlan with fn output(self, logger) {
  logger.write_string(Repr(self).to_string())
}

///|
fn AllocationPlan::new(value_count : Int) -> AllocationPlan {
  {
    value_locations: Array::make(value_count, None),
    operand_locations: [],
    edits: [],
    spill_slots: [],
  }
}

///|
fn AllocationPlan::assign_value(
  self : AllocationPlan,
  vreg : VirtualReg,
  location : Location,
) -> Unit {
  if vreg.id >= 0 && vreg.id < self.value_locations.length() {
    self.value_locations[vreg.id] = Some(location)
  }
}

///|
fn AllocationPlan::assign_operand(
  self : AllocationPlan,
  instruction : Int,
  operand : Int,
  location : Location,
) -> Unit {
  while self.operand_locations.length() <= instruction {
    self.operand_locations.push([])
  }
  let locations = self.operand_locations[instruction]
  while locations.length() <= operand {
    locations.push(None)
  }
  locations[operand] = Some(location)
}

///|
fn AllocationPlan::add_edit(
  self : AllocationPlan,
  edit : AllocationEdit,
) -> Unit {
  self.edits.push(edit)
}

///|
fn AllocationPlan::create_spill_slot(
  self : AllocationPlan,
  size : Int,
  alignment : Int,
) -> Int {
  let slot = self.spill_slots.length()
  self.spill_slots.push({ size, alignment })
  slot
}

///|
/// Return the value's default transfer home.
///
/// Segment-specific operand assignments and edits may keep the newest value
/// elsewhere until a transition returns it here.
pub fn AllocationPlan::value_location(
  self : AllocationPlan,
  value : Int,
) -> Location? {
  self.value_locations.get(value).bind(location => location)
}

///|
pub fn AllocationPlan::operand_location(
  self : AllocationPlan,
  instruction : Int,
  operand : Int,
) -> Location? {
  self.operand_locations
  .get(instruction)
  .bind(locations => locations.get(operand).bind(location => location))
}

///|
pub fn AllocationPlan::operand_assignments(
  self : AllocationPlan,
) -> Array[OperandAssignment] {
  let assignments : Array[OperandAssignment] = []
  for instruction, locations in self.operand_locations {
    for operand, location in locations {
      if location is Some(location) {
        assignments.push({ instruction, operand, location })
      }
    }
  }
  assignments
}

///|
pub fn AllocationPlan::edits(self : AllocationPlan) -> Array[AllocationEdit] {
  self.edits.copy()
}

///|
pub fn AllocationPlan::spill_count(self : AllocationPlan) -> Int {
  self.spill_slots.length()
}

///|
pub fn AllocationPlan::spill_slot(
  self : AllocationPlan,
  index : Int,
) -> SpillSlotSpec? {
  self.spill_slots.get(index)
}