// =======================================================
// GetElementPtrInst
// =======================================================

///|
/// GetElementPtrInst represents a getelementptr instruction that calculates the address of a sub-element of an aggregate object.
///
/// **Note**:
///
/// Use `IRBuilder::createGEP` to create a `GetElementPtrInst`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let array_ty = ctx.getArrayType(i32_ty, 10)
///   let ptr_ty = ctx.getPtrTy()
///   let fty = ctx.getFunctionType(ptr_ty, [ptr_ty])
///   let fval = mod.addFunction(fty, "gep_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   let zero = ctx.getConstInt32(0)
///   let two = ctx.getConstInt32(2)
///   builder.setInsertPoint(bb)
///   let gep = builder.createGEP(
///     arg,
///     array_ty,
///     [zero, two],
///     name="elem_ptr",
///     inbounds=true,
///   )
///   inspect(
///     gep,
///     content="  %elem_ptr = getelementptr inbounds [10 x i32], ptr %0, i32 0, i32 2",
///   )
///   assert_true(gep.asValueEnum() is GetElementPtrInst(_))
/// }
/// ```
pub struct GetElementPtrInst {
  uid : UInt64
  vty : PointerType
  users : Array[&User]
  ptr : &Value
  indices : Array[&Value]
  mut name : String?
  parent : Function

  // --- InstBase ---
  bb : Ref[BasicBlock?]
  prev : Ref[&Instruction?]
  next : Ref[&Instruction?]
  isInbounds : Bool
  pointeeType : &Type
}

///|
fn GetElementPtrInst::new(
  ptr : &Value,
  pointeeType : &Type,
  indices : Array[&Value],
  isInbounds : Bool,
  parent : Function,
  name~ : String?,
) -> GetElementPtrInst {
  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().getPtrTy()
  let inst = GetElementPtrInst::{
    uid,
    vty,
    users: [],
    ptr,
    indices,
    name,
    parent,
    bb,
    prev,
    next,
    isInbounds,
    pointeeType,
  }
  ptr.addUser(inst)
  indices.each(idx => idx.addUser(inst))
  inst
}

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

///|
/// Get simple representation of the value.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let array_ty = ctx.getArrayType(i32_ty, 10)
///   let ptr_ty = ctx.getPtrTy()
///   let fty = ctx.getFunctionType(ptr_ty, [ptr_ty])
///   let fval = mod.addFunction(fty, "gep_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   let zero = ctx.getConstInt32(0)
///   let two = ctx.getConstInt32(2)
///   builder.setInsertPoint(bb)
///   let gep = builder.createGEP(arg, array_ty, [zero, two])
///   inspect(gep.getValueRepr(), content="%1")
///   gep.setName("elem_ptr")
///   inspect(gep.getValueRepr(), content="%elem_ptr")
/// }
/// ```
pub impl Value for GetElementPtrInst with getValueRepr(self) {
  match self.getNameOrSlot() {
    Some(Left(name)) => "%\{name}"
    Some(Right(slot)) => "%\{slot}"
    None => ""
  }
}

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

///|
/// Get the name of the instruction.
///
/// **Note**:
///
/// If the instruction has no name, return `None`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let array_ty = ctx.getArrayType(i32_ty, 10)
///   let ptr_ty = ctx.getPtrTy()
///   let fty = ctx.getFunctionType(ptr_ty, [ptr_ty])
///   let fval = mod.addFunction(fty, "gep_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   let zero = ctx.getConstInt32(0)
///   let two = ctx.getConstInt32(2)
///   builder.setInsertPoint(bb)
///   let gep = builder.createGEP(arg, array_ty, [zero, two])
///   inspect(gep.getName(), content="None")
///   gep.setName("elem_ptr")
///   inspect(gep.getName(), content="Some(\"elem_ptr\")")
/// }
/// ```
pub impl Value for GetElementPtrInst with getName(self) {
  self.name
}

///|
/// Set the name of the instruction.
///
/// **Note**:
///
/// If the name has already been used in the parent function,
/// it will raise Error.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let array_ty = ctx.getArrayType(i32_ty, 10)
///   let ptr_ty = ctx.getPtrTy()
///   let fty = ctx.getFunctionType(ptr_ty, [ptr_ty])
///   let fval = mod.addFunction(fty, "gep_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   let zero = ctx.getConstInt32(0)
///   let two = ctx.getConstInt32(2)
///   builder.setInsertPoint(bb)
///   let gep = builder.createGEP(arg, array_ty, [zero, two])
///   inspect(gep.getName(), content="None")
///   gep.setName("elem_ptr")
///   inspect(gep.getName(), content="Some(\"elem_ptr\")")
/// }
/// ```
pub impl Value for GetElementPtrInst with setName(self, name) {
  match self.getParent().setSymbol(name, self) {
    EmptyName => {
      let msg = "Misuse `GetElementPtrInst::setName`: name cannot be empty."
      raise LLVMValueError(msg)
    }
    InvalidName => {
      let msg =
        $|Misuse `GetElementPtrInst::setName`:
        $|name '\{name}' contains illegal characters,
        $|only alphanumeric characters and underscores are allowed
      raise LLVMValueError(msg)
    }
    DuplicateName(existed) => {
      let msg =
        $|Misuse `GetElementPtrInst::setName`:
        $|name '\{name}' already exists in the parent function,
        $|it is used by:
        $|\{existed}"
      raise LLVMValueError(msg)
    }
    Success => self.name = Some(name)
  }
}

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

///|
pub impl Value for GetElementPtrInst 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 User for GetElementPtrInst with asUserEnum(self) {
  GetElementPtrInst(self)
}

///|
pub impl User for GetElementPtrInst with getUserBase(self) {
  UserBase::{ operands: [self.ptr] + self.indices }
}

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

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

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

///|
pub impl Show for GetElementPtrInst with output(self, logger) {
  let repr = self.getValueRepr()
  let ptr_repr = self.ptr.getValueRepr()
  let indices_reprs = []
  self.indices.each(index => indices_reprs.push(
    "\{index.getType()} \{index.getValueRepr()}",
  ))
  let indices_repr = if indices_reprs.length() > 0 {
    ", " + indices_reprs.join(", ")
  } else {
    ""
  }
  let inbounds_str = if self.isInbounds { " inbounds" } else { "" }
  logger.write_string(
    "  \{repr} = getelementptr\{inbounds_str} \{self.pointeeType}, ptr \{ptr_repr}\{indices_repr}",
  )
}