// =======================================================
// StoreInst
// =======================================================

///|
/// StoreInst is an instruction that stores a value to a pointer.
///
/// **Note**:
///
/// Use `IRBuilder::createStore` to create a `StoreInst`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let ptr_ty = ctx.getPtrTy()
///   let i32_ty = ctx.getInt32Ty()
///   let void_ty = ctx.getVoidTy()
///   let fty = ctx.getFunctionType(void_ty, [ptr_ty, i32_ty])
///   let fval = mod.addFunction(fty, "store_an_integer")
///   let bb = fval.addBasicBlock(name="entry")
///   builder.setInsertPoint(bb)
///   let ptr = fval.getArg(0).unwrap()
///   ptr.setName("arg0")
///   let value = fval.getArg(1).unwrap()
///   value.setName("value")
///   let s = builder.createStore(value, ptr)
///   inspect(s, content="  store i32 %value, ptr %arg0, align 4")
/// }
/// ```
pub struct StoreInst {
  // --- ValueBase ---

  // Unique identifier
  uid : UInt64

  // Type of the value
  vty : &Type

  // --- UserBase ---
  value : &Value
  ptr : &Value
  parent : Function

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

  // --- StoreInst ---
  isVolatile : Bool
  atomicOrdering : AtomicOrdering
  align : Align
}

///|
fn StoreInst::new(
  value : &Value,
  ptr : &Value,
  isVolatile : Bool,
  atomicOrdering : AtomicOrdering,
  parent : Function,
) -> StoreInst {
  let ctx = value.getContext()
  let uid = valueUIDAssigner.assign()
  let value_ty = value.getType()
  let bb : Ref[BasicBlock?] = Ref::new(None)
  let prev : Ref[&Instruction?] = Ref::new(None)
  let next : Ref[&Instruction?] = Ref::new(None)
  let align = parent.getDataLayout().getAlignment(value_ty)
  let inst = StoreInst::{
    uid,
    vty: ctx.getVoidTy(),
    value,
    ptr,
    parent,
    bb,
    prev,
    next,
    isVolatile,
    atomicOrdering,
    align,
  }
  value.addUser(inst)
  ptr.addUser(inst)
  inst
}

///|
/// Get the value operand of the store instruction.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let ptr_ty = ctx.getPtrTy()
///   let i32_ty = ctx.getInt32Ty()
///   let void_ty = ctx.getVoidTy()
///   let fty = ctx.getFunctionType(void_ty, [ptr_ty, i32_ty])
///   let fval = mod.addFunction(fty, "store_an_integer")
///   let bb = fval.addBasicBlock(name="entry")
///   builder.setInsertPoint(bb)
///   let ptr = fval.getArg(0).unwrap()
///   ptr.setName("arg0")
///   let val1 = fval.getArg(1).unwrap()
///   let const_42 = ctx.getConstInt32(42)
///   let val2 = builder.createNSWAdd(val1, const_42, name="value")
///   let s = builder.createStore(val2, ptr)
///   inspect(s.getValueOperand(), content="  %value = add nsw i32 %0, 42")
/// }
/// ```
pub fn StoreInst::getValueOperand(self : StoreInst) -> &Value {
  self.value
}

///|
/// Get the pointer operand of the store instruction.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let void_ty = ctx.getVoidTy()
///   let fty = ctx.getFunctionType(void_ty, [])
///   let fval = mod.addFunction(fty, "store_an_integer")
///   let bb = fval.addBasicBlock(name="entry")
///   builder.setInsertPoint(bb)
///   let alloca = builder.createAlloca(ctx.getInt32Ty(), name="ptr")
///   let const42 = ctx.getConstInt32(42)
///   let s = builder.createStore(const42, alloca)
///   inspect(s.getPointerOperand(), content="  %ptr = alloca i32, align 4")
/// }
/// ```
pub fn StoreInst::getPointerOperand(self : StoreInst) -> &Value {
  self.ptr
}

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

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

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

///|
/// StoreInst has no name, calling `getName` will always return `None`.
pub impl Value for StoreInst with getName(self) {
  ignore(self)
  None
}

///|
/// StoreInst has no name, calling `setName` will always fail with an error.
pub impl Value for StoreInst with setName(_, _) {
  let msg = "Calling always failed function `StoreInst::setName`. " +
    "Set name for StoreInst is not allowed."
  raise LLVMValueError(msg)
}

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

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

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

///|
pub impl User for StoreInst with getUserBase(self) {
  UserBase::{ operands: [self.value, self.ptr] }
}

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

///|
pub impl Instruction for StoreInst with asInstEnum(self) {
  StoreInst(self)
}

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

///|
pub impl Show for StoreInst with output(self, logger) {
  let align = self.align
  let value = self.getValueOperand()
  let ptr = self.getPointerOperand()
  let ptrty = self.getParent().getContext().getPtrTy()
  let value_ty = match value.tryAsGlobalValue() {
    Some(_) => (ptrty : &Type)
    None => value.getType()
  }
  let value_repr = value.getValueRepr()
  let ptr_repr = ptr.getValueRepr()
  let str = match (self.isVolatile, self.atomicOrdering) {
    (true, _) =>
      "  store volatile \{value_ty} \{value_repr}, ptr \{ptr_repr}, \{align}"
    (false, NotAtomic) =>
      "  store \{value_ty} \{value_repr}, ptr \{ptr_repr}, \{align}"
    (false, _) =>
      "  store atomic \{value_ty} \{value_repr}, ptr \{ptr_repr} \{self.atomicOrdering}, \{align}"
  }
  logger.write_string(str)
}