///|
/// Block of instructions in a function.
///
/// **Note**:
///
/// Use `Function::addBasicBlock` to create a new `BasicBlock`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [])
///   let fval = mod.addFunction(fty, "ret_42")
///   let bb = fval.addBasicBlock(name="entry")
///   builder.setInsertPoint(bb)
///   let forty_two = ctx.getConstInt32(42)
///   let _ = builder.createRet(forty_two)
///   inspect(
///     bb,
///     content=(
///       #|entry:
///       #|  ret i32 42
///       #|
///     ),
///   )
/// }
/// ```
pub struct BasicBlock {
  uid : UInt64
  users : Array[&User]
  parent : Function
  mut name : String?
  mut head : &Instruction?
  id : Int
  preds : Array[BasicBlock]
}

///|
fn BasicBlock::new(parent : Function, name~ : String?) -> BasicBlock {
  let uid = valueUIDAssigner.assign()
  BasicBlock::{
    uid,
    users: [],
    parent,
    name,
    head: None,
    id: parent.getNumBasicBlocks(),
    preds: [],
  }
}

///|
/// Get the first instruction in the basic block.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "ret_42")
///   let arg0 = fval.getArg(0).unwrap()
///   let arg1 = fval.getArg(1).unwrap()
///   let bb = fval.addBasicBlock(name="entry")
///   builder.setInsertPoint(bb)
///   let sum1 = builder.createAdd(arg0, arg1, name="sum1")
///   let sum2 = builder.createAdd(arg0, arg1, name="sum2")
///   let mul = builder.createMul(sum1, sum2, name="mul")
///   let _ = builder.createRet(mul)
///   inspect(bb.firstInst().unwrap(), content="  %sum1 = add i32 %0, %1")
/// }
/// ```
pub fn BasicBlock::firstInst(self : BasicBlock) -> &Instruction? {
  self.head
}

///|
/// Get the last instruction in the basic block.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "ret_42")
///   let arg0 = fval.getArg(0).unwrap()
///   let arg1 = fval.getArg(1).unwrap()
///   let bb = fval.addBasicBlock(name="entry")
///   builder.setInsertPoint(bb)
///   let sum1 = builder.createAdd(arg0, arg1, name="sum1")
///   let sum2 = builder.createAdd(arg0, arg1, name="sum2")
///   let mul = builder.createMul(sum1, sum2, name="mul")
///   let _ = builder.createRet(mul)
///   inspect(bb.lastInst().unwrap(), content="  ret i32 %mul")
/// }
/// ```
pub fn BasicBlock::lastInst(self : BasicBlock) -> &Instruction? {
  loop self.head {
    Some(n) if n.next() is Some(n_next) => continue Some(n_next)
    Some(n) => break Some(n)
    None => break None
  }
}

///|
pub fn BasicBlock::instIter(self : BasicBlock) -> Iter[&Instruction] {
  let mut next_inst = self.head
  Iter::new(fn() {
    match next_inst {
      Some(inst) => {
        next_inst = inst.next()
        Some(inst)
      }
      None => None
    }
  })
}

///|
pub fn BasicBlock::getParent(self : BasicBlock) -> Function {
  self.parent
}

///|
pub fn BasicBlock::getModule(self : BasicBlock) -> Module {
  self.parent.getModule()
}

///|
pub fn BasicBlock::getTerminator(self : BasicBlock) -> &Instruction? {
  match self.lastInst() {
    Some(inst) if inst.isTerminator() => Some(inst)
    _ => None
  }
}

///|
pub fn BasicBlock::getSuccessors(self : BasicBlock) -> Array[BasicBlock] {
  match self.getTerminator() {
    Some(term) =>
      match term.asInstEnum() {
        BranchInst({ trueBlock: Some(true_bb), falseBlock: Some(false_bb), .. }) =>
          [true_bb, false_bb]
        BranchInst({ trueBlock: Some(true_bb), falseBlock: None, .. }) =>
          [true_bb]
        SwitchInst({ defaultDest, cases, .. }) => {
          let succs : Array[BasicBlock] = [defaultDest]
          for case in cases {
            let (_, bb) = case
            succs.push(bb)
          }
          succs
        }
        _ => []
      }
    None => []
  }
}

///|
pub fn BasicBlock::removeFromParent(self : BasicBlock) -> Unit {
  self.parent.removeBasicBlock(self)
}

///|
pub fn BasicBlock::getLabel(self : BasicBlock) -> String {
  match self.getNameOrSlot() {
    Some(Left(name)) => "\{name}"
    Some(Right(slot)) => "\{slot}"
    None => ""
  }
}

///|
pub impl Value for BasicBlock with getValueBase(self) {
  ValueBase::{
    uid: self.uid,
    vty: self.parent.getContext().getLabelTy(),
    users: self.users,
  }
}

///|
pub impl Value for BasicBlock with getName(self) {
  self.name
}

///|
pub impl Value for BasicBlock with getValueRepr(self) {
  match self.getNameOrSlot() {
    Some(Left(name)) => "%\{name}"
    Some(Right(slot)) => "%\{slot}"
    None => ""
  }
}

///|
pub impl Value for BasicBlock with setName(self, name) {
  if name is "" {
    let msg = "Misuse `BasicBlock::setName`: name cannot be empty."
    raise LLVMValueError(msg)
  }
  if isInValidName(name) {
    let msg = "Misuse `BasicBlock::setName`: " +
      "name '\{name}' contains illegal characters, " +
      "only alphanumeric characters and underscores are allowed."
    raise LLVMValueError(msg)
  }
  let symbols = self.getParent().symbols
  guard not(symbols.contains(name)) else {
    let msg = "Misuse `BasicBlock::setName`: " +
      "name '\{name}' already exists in the parent function"
    raise LLVMValueError(msg)
  }
  self.name = Some(name)
  symbols.set(name, self)
}

///|
pub impl Value for BasicBlock with removeName(self) {
  match self.name {
    Some(name) => {
      self.parent.symbols.remove(name)
      self.name = None
    }
    None => ()
  }
}

///|
pub impl Value for BasicBlock with getNameOrSlot(self) {
  match self.name {
    Some(name) => Some(Left(name))
    None =>
      match self.getParent().getSlot(self) {
        Some(slot) => Some(Right(slot))
        None => None
      }
  }
}

///|
pub impl Value for BasicBlock with asValueEnum(self) {
  BasicBlock(self)
}

///|
pub impl Hash for BasicBlock with hash_combine(self, hasher) {
  hasher.combine_uint64(self.getValueBase().uid)
}

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

///|
pub impl Show for BasicBlock with output(self, logger) {
  let label = self.getLabel()
  logger.write_string("\{label}:")
  if self.preds.length() > 0 {
    let str_builder = StringBuilder::new()
    str_builder.write_string("                                     ; preds = ")
    let pred_labels = self.preds.map(bb => "%\{bb.getLabel()}")
    let preds = pred_labels.join(", ")
    str_builder.write_string(preds)
    let preds_str = str_builder.to_string()
    logger.write_string("\{preds_str}\n")
  } else {
    logger.write_string("\n")
  }
  loop self.head {
    Some(i) => {
      logger.write_string("\{i}\n")
      continue i.next()
    }
    None => break
  }
}