// =======================================================
// BranchInst
// =======================================================

///|
/// BranchInst represents a branch instruction that transfers control flow to different basic blocks.
///
/// **Note**:
///
/// Use `IRBuilder::createBr` for unconditional branches or `IRBuilder::createCondBr` for conditional branches.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i1_ty = ctx.getInt1Ty()
///   let void_ty = ctx.getVoidTy()
///   let fty = ctx.getFunctionType(void_ty, [i1_ty])
///   let fval = mod.addFunction(fty, "branch_demo")
///   let entry_bb = fval.addBasicBlock(name="entry")
///   let true_bb = fval.addBasicBlock(name="true_branch")
///   let false_bb = fval.addBasicBlock(name="false_branch")
///   let cond = fval.getArg(0).unwrap()
///   builder.setInsertPoint(entry_bb)
///   let cond_br = builder.createCondBr(cond, true_bb, false_bb)
///   inspect(
///     cond_br,
///     content="  br i1 %0, label %true_branch, label %false_branch",
///   )
///   assert_true(cond_br.asValueEnum() is BranchInst(_))
///   builder.setInsertPoint(true_bb)
///   let uncond_br = builder.createBr(false_bb)
///   inspect(uncond_br, content="  br label %false_branch")
/// }
/// ```
pub struct BranchInst {
  // --- ValueBase ---

  // Unique identifier
  uid : UInt64
  vty : VoidType
  condition : &Value?
  trueBlock : BasicBlock?
  falseBlock : BasicBlock?
  parent : Function

  // --- InstBase ---
  bb : Ref[BasicBlock?]
  prev : Ref[&Instruction?]
  next : Ref[&Instruction?]
}

///|
fn BranchInst::newConditional(
  condition : &Value,
  trueBlock : BasicBlock,
  falseBlock : BasicBlock,
  parent : Function,
) -> BranchInst {
  let uid = valueUIDAssigner.assign()
  let bb : Ref[BasicBlock?] = Ref::new(None)
  let prev : Ref[&Instruction?] = Ref::new(None)
  let next : Ref[&Instruction?] = Ref::new(None)
  let vty = parent.getContext().getVoidTy()
  let inst = BranchInst::{
    uid,
    vty,
    condition: Some(condition),
    trueBlock: Some(trueBlock),
    falseBlock: Some(falseBlock),
    parent,
    bb,
    prev,
    next,
  }
  condition.addUser(inst)
  trueBlock.addUser(inst)
  falseBlock.addUser(inst)
  inst
}

///|
fn BranchInst::newUnconditional(
  targetBlock : BasicBlock,
  parent : Function,
) -> BranchInst {
  let bb : Ref[BasicBlock?] = Ref::new(None)
  let prev : Ref[&Instruction?] = Ref::new(None)
  let next : Ref[&Instruction?] = Ref::new(None)
  let uid = valueUIDAssigner.assign()
  let vty = parent.getContext().getVoidTy()
  let inst = BranchInst::{
    uid,
    vty,
    condition: None,
    trueBlock: Some(targetBlock),
    falseBlock: None,
    parent,
    bb,
    prev,
    next,
  }
  targetBlock.addUser(inst)
  inst
}

///|
pub fn BranchInst::getNumSuccessors(self : Self) -> Int {
  match self.condition {
    Some(_) => 2 // conditional branch
    None => 1 // unconditional branch
  }
}

///|
pub fn BranchInst::getSuccessor(self : BranchInst, idx : Int) -> BasicBlock? {
  match idx {
    0 => self.trueBlock
    1 => self.falseBlock
    _ => None
  }
}

///|
pub fn BranchInst::isConditional(self : BranchInst) -> Bool {
  match self.condition {
    Some(_) => true
    None => false
  }
}

///|
pub fn BranchInst::isUnconditional(self : BranchInst) -> Bool {
  match self.condition {
    Some(_) => false
    None => true
  }
}

///|
pub impl Value for BranchInst with getValueBase(self) {
  ValueBase::{
    uid: self.uid,
    vty: self.getParent().getContext().getVoidTy(), // BranchInst does not have a value type
    users: [],
  }
}

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

///|
pub impl Value for BranchInst with getValueRepr(_) {
  ""
}

///|
pub impl Value for BranchInst with getName(_) {
  None
}

///|
pub impl Value for BranchInst with setName(_, _) {
  let msg = "Calling always failed function `ReturnInst::setName`. " +
    "Set name for ReturnInst is not allowed."
  raise LLVMValueError(msg)
}

///|
pub impl Value for BranchInst with removeName(_) {
  ()
}

///|
pub impl Value for BranchInst with getNameOrSlot(_) {
  None
}

///|
pub impl User for BranchInst with asUserEnum(self) {
  BranchInst(self)
}

///|
pub impl User for BranchInst with getUserBase(self) {
  let operands : Array[&Value] = []
  match self.condition {
    Some(cond) => operands.push(cond)
    None => ()
  }
  match self.trueBlock {
    Some(block) => operands.push(block)
    None => ()
  }
  match self.falseBlock {
    Some(block) => operands.push(block)
    None => ()
  }
  UserBase::{ operands, }
}

///|
pub impl Instruction for BranchInst with getInstBase(self) {
  InstBase::{ bb: self.bb, prev: self.prev, next: self.next }
}

///|
pub impl Instruction for BranchInst with asInstEnum(self) {
  InstEnum::BranchInst(self)
}

///|
pub impl Instruction for BranchInst with getParent(self) {
  self.parent
}

///|
pub impl Show for BranchInst with output(self, logger) {
  if self.isConditional() {
    let condition_repr = self.condition.unwrap().getValueRepr()
    let true_block_repr = self.trueBlock.unwrap().getValueRepr()
    let false_block_repr = self.falseBlock.unwrap().getValueRepr()
    logger.write_string(
      "  br \{self.condition.unwrap().getType()} \{condition_repr}, label \{true_block_repr}, label \{false_block_repr}",
    )
  } else {
    let target_block_repr = self.trueBlock.unwrap().getValueRepr()
    logger.write_string("  br label \{target_block_repr}")
  }
}