///|
pub(all) enum FixedConstraintOperand {
  Virtual(Location?, Int)
  Physical(PhysicalReg, Int)
} derive(Eq, Debug)

///|
pub(all) struct FixedConstraint {
  operand : FixedConstraintOperand
  required : PhysicalReg
} derive(Eq, Debug)

///|
pub(all) struct FixedConstraintInst {
  block_index : Int
  inst_index : Int
  uses : Array[FixedConstraint]
  defs : Array[FixedConstraint]
} derive(Debug)

///|
pub(all) struct FixedConstraintMove {
  from : Location
  to : Location
  class_tag : Int
} derive(Eq, Debug)

///|
pub(all) struct FixedConstraintInstEdits {
  block_index : Int
  inst_index : Int
  before : Array[FixedConstraintMove]
  after : Array[FixedConstraintMove]
} derive(Debug)

///|
fn fixed_constraint_same_reg(a : PhysicalReg, b : PhysicalReg) -> Bool {
  a.id == b.id
}

///|
fn fixed_constraint_use_move(
  constraint : FixedConstraint,
) -> FixedConstraintMove? {
  match constraint.operand {
    Virtual(location, class_tag) =>
      match location {
        Some(Reg(preg)) =>
          if fixed_constraint_same_reg(preg, constraint.required) {
            None
          } else {
            Some({ from: Reg(preg), to: Reg(constraint.required), class_tag })
          }
        Some(Spill(slot)) =>
          Some({ from: Spill(slot), to: Reg(constraint.required), class_tag })
        None => None
      }
    Physical(preg, class_tag) =>
      if fixed_constraint_same_reg(preg, constraint.required) {
        None
      } else {
        Some({ from: Reg(preg), to: Reg(constraint.required), class_tag })
      }
  }
}

///|
fn fixed_constraint_def_move(
  constraint : FixedConstraint,
) -> FixedConstraintMove? {
  match constraint.operand {
    Virtual(location, class_tag) =>
      match location {
        Some(Reg(preg)) =>
          if fixed_constraint_same_reg(preg, constraint.required) {
            None
          } else {
            Some({ from: Reg(constraint.required), to: Reg(preg), class_tag })
          }
        Some(Spill(slot)) =>
          Some({ from: Reg(constraint.required), to: Spill(slot), class_tag })
        None => None
      }
    Physical(_, _) => None
  }
}

///|
pub fn plan_fixed_constraint_edits(
  insts : Array[FixedConstraintInst],
) -> Array[FixedConstraintInstEdits] {
  let edits : Array[FixedConstraintInstEdits] = []
  for inst in insts {
    let before : Array[FixedConstraintMove] = []
    let after : Array[FixedConstraintMove] = []
    for constraint in inst.uses {
      if fixed_constraint_use_move(constraint) is Some(mv) {
        before.push(mv)
      }
    }
    for constraint in inst.defs {
      if fixed_constraint_def_move(constraint) is Some(mv) {
        after.push(mv)
      }
    }
    if !before.is_empty() || !after.is_empty() {
      edits.push({
        block_index: inst.block_index,
        inst_index: inst.inst_index,
        before,
        after,
      })
    }
  }
  edits
}