///|
pub(all) struct BasicBlock {
  mut llvm_bb : @IR.BasicBlock?
  label : String
  mut insts : Array[Instruction]
  parent : Function
  preds : Array[BasicBlock]
  succs : Array[BasicBlock]
  phi_nodes : Array[(@IR.PHINode, Operand)] // (phi node, operand)
  mut live_in : Set[Operand]
  mut live_out : Set[Operand]
}

///|
pub impl Eq for BasicBlock with equal(self, other) {
  self.label == other.label
}

///|
pub fn BasicBlock::new(func : Function, label : String) -> BasicBlock {
  BasicBlock::{
    llvm_bb: None,
    label,
    insts: Array::new(),
    parent: func,
    preds: Array::new(),
    succs: Array::new(),
    phi_nodes: Array::new(),
    live_in: Set::new(),
    live_out: Set::new(),
  }
}

///|
pub fn BasicBlock::push(self : Self, inst : Instruction) -> Unit {
  self.insts.push(inst)
}

///|
pub fn BasicBlock::push_inst_before_terminator(
  self : Self,
  inst : Instruction,
) -> Unit {
  if self.insts.is_empty() {
    self.insts.push(inst)
    return
  }
  let last_inst = self.insts.last().unwrap()
  if last_inst.opcode.is_terminator() {
    self.insts.insert(self.insts.length() - 1, inst)
  } else {
    self.insts.push(inst)
  }
}

///|
pub fn BasicBlock::insert_inst_before(
  self : Self,
  inst : Instruction,
  before~ : Instruction,
) -> Unit? {
  if inst.bb != before.bb {
    return None
  }
  let index = self.insts.search_by(i => i == before)
  if index is None {
    return None
  }
  self.insts.insert(index.unwrap(), inst)
  Some(())
}

///|
pub fn BasicBlock::insert_inst_after(
  self : Self,
  inst : Instruction,
  after~ : Instruction,
) -> Unit? {
  if inst.bb != after.bb {
    return None
  }
  let index = self.insts.search_by(i => i == after)
  if index is None {
    return None
  }
  self.insts.insert(index.unwrap() + 1, inst)
  Some(())
}

///|
pub fn BasicBlock::contains_virtual_reg(self : Self) -> Bool {
  for inst in self.insts {
    if inst.contains_virtual_reg() {
      return true
    }
  }
  false
}

///|
///
/// Return True is the live_out set was changed
pub fn BasicBlock::update_live_out(
  self : Self,
  live_out : Set[Operand],
) -> Bool {
  if self.live_out == live_out {
    return false
  }
  self.live_out = live_out
  true
}

///|
pub fn BasicBlock::update_live_in(self : Self, live_in : Set[Operand]) -> Bool {
  if self.live_in == live_in {
    return false
  }
  self.live_in = live_in
  true
}

///|
pub fn BasicBlock::compute_live_out_from_succs(self : Self) -> Set[Operand] {
  let live_out : Set[Operand] = Set::new()
  for succ in self.succs {
    succ.live_in.each(v => live_out.add(v))
  }
  live_out
}

///|
pub impl Show for BasicBlock with output(self, logger) {
  if !self.preds.is_empty() {
    let pred_labels = self.preds.map(b => b.label).join(", ")
    logger.write_string("; preds: \{pred_labels}\n")
  }
  let inst_strs : Array[(String, String)] = Array::new()
  let label_str = "\{self.label}:"
  let live_in_notes = if !self.live_in.is_empty() {
    let str = self.live_in.iter().map(op => "\{op}").join(", ")
    "; live in: \{str}"
  } else {
    ""
  }
  inst_strs.push((label_str, live_in_notes))
  for inst in self.insts {
    let inst_str = "  \{inst}"
    let live_str = if !inst.live_in.is_empty() {
      let str = inst.live_in.iter().map(op => "\{op}").join(", ")
      "# live in: \{str}"
    } else {
      ""
    }
    inst_strs.push((inst_str, live_str))
  }
  fn max(a : Int, b : Int) {
    if a > b {
      a
    } else {
      b
    }
  }

  let max_inst_len = inst_strs.map(p => p.0.length()).fold(init=0, max)
  for inst_str_pair in inst_strs {
    let (inst_str, live_str) = inst_str_pair
    if live_str is "" {
      logger.write_string("\{inst_str}\n")
      continue
    }
    let padding = " ".repeat(max_inst_len - inst_str.length() + 8)
    logger.write_string("\{inst_str}\{padding}\{live_str}\n")
  }
}