///|
let contextKey : Ref[UInt] = Ref::new(0)

///|
pub struct Context {
  // used for identify different contexts, or hash
  key : UInt
  priv mut pimpl : LLVMContextImpl?
}

///|
struct LLVMContextImpl {
  /// OwnedModules - The set of modules instantiated in this context, and which
  /// will be automatically deleted if this context is deleted.
  ownedPrograms : Array[Module]

  // Types
  half_ty : HalfType
  bfloat_ty : BFloatType
  float_ty : FloatType
  double_ty : DoubleType
  fp128_ty : FP128Type
  void_ty : VoidType
  label_ty : LabelType
  metadata_ty : MetadataType
  token_ty : TokenType
  i1ty : Int1Type
  i8ty : Int8Type
  i16ty : Int16Type
  i32ty : Int32Type
  i64ty : Int64Type
  namedStructTypes : Map[String, StructType]
  pointerTypes : Map[AddressSpace, PointerType]

  // Metadata
  mdstrings : Map[String, MDString]
  //valueMetadata : Map[&Value, Array[(String, MDNode)]]
}

///|
pub fn Context::new() -> Context {
  let ctx = Context::{ key: contextKey.val, pimpl: None }
  contextKey.val += 1
  let addr0 = AddressSpace::default()
  let pointerTypes = Map::new()
  pointerTypes.set(addr0, PointerType::new(ctx, addressSpace=addr0))
  // Initialize the LLVMContextImpl.
  let pimpl = LLVMContextImpl::{
    ownedPrograms: [],
    half_ty: HalfType::new(ctx),
    bfloat_ty: BFloatType::new(ctx),
    float_ty: FloatType::new(ctx),
    double_ty: DoubleType::new(ctx),
    fp128_ty: FP128Type::new(ctx),
    void_ty: VoidType::new(ctx),
    label_ty: LabelType::new(ctx),
    metadata_ty: MetadataType::new(ctx),
    token_ty: TokenType::new(ctx),
    i1ty: Int1Type::new(ctx),
    i8ty: Int8Type::new(ctx),
    i16ty: Int16Type::new(ctx),
    i32ty: Int32Type::new(ctx),
    i64ty: Int64Type::new(ctx),
    namedStructTypes: Map::new(),
    pointerTypes,
    mdstrings: Map::new(),
    //valueMetadata: Map::new(),
  }
  ctx.pimpl = Some(pimpl)
  ctx
}

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

///|
pub impl Hash for Context with hash_combine(self, hasher) {
  hasher.combine_uint(self.key)
}

///|
pub fn Context::addModule(
  self : Context,
  moduleID : String,
  source_file? : String? = None,
) -> Module {
  let source_file = match source_file {
    Some(f) => f
    None => moduleID
  }
  let prog = Module::new(moduleID, source_file, self)
  self.pimpl.unwrap().ownedPrograms.push(prog)
  prog
}

///|
pub fn Context::createBuilder(self : Context) -> IRBuilder {
  IRBuilder::new(self)
}

///|
/// Get the half type from context.
///
/// - See LLVM: `Type::getHalfTy`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let half_ty = ctx.getHalfTy()
///   inspect(half_ty, content="half")
/// }
/// ```
pub fn Context::getHalfTy(self : Context) -> HalfType {
  self.pimpl.unwrap().half_ty
}

///|
/// Get the bfloat type from context.
///
/// - See LLVM: `Type::getBFloatTy`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let bfloat_ty = ctx.getBFloatTy()
///   inspect(bfloat_ty, content="bfloat")
/// }
/// ```
pub fn Context::getBFloatTy(self : Context) -> BFloatType {
  self.pimpl.unwrap().bfloat_ty
}

///|
/// Get the float type from context.
///
/// - See LLVM: `Type::getFloatTy`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let f32ty = ctx.getFloatTy()
///   inspect(f32ty, content="float")
/// }
/// ```
pub fn Context::getFloatTy(self : Context) -> FloatType {
  self.pimpl.unwrap().float_ty
}

///|
/// Get the double type from context.
///
/// - See LLVM: `Type::getDoubleTy`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let doubletype = ctx.getDoubleTy()
///   inspect(doubletype, content="double")
/// }
/// ```
pub fn Context::getDoubleTy(self : Context) -> DoubleType {
  self.pimpl.unwrap().double_ty
}

///|
/// Get the fp128 type from context.
///
/// - See LLVM: `Type::getFP128Ty`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let fp128ty = ctx.getFP128Ty()
///   inspect(fp128ty, content="fp128")
/// }
/// ```
pub fn Context::getFP128Ty(self : Context) -> FP128Type {
  self.pimpl.unwrap().fp128_ty
}

///|
/// Get the void type from context.
///
/// - See LLVM: `Type::getVoidTy`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let voidty = ctx.getVoidTy()
///   inspect(voidty, content="void")
/// }
/// ```
pub fn Context::getVoidTy(self : Context) -> VoidType {
  self.pimpl.unwrap().void_ty
}

///|
/// Get the label type from context.
pub fn Context::getLabelTy(self : Context) -> LabelType {
  self.pimpl.unwrap().label_ty
}

///|
/// Get the metadata type from context.
pub fn Context::getMetadataTy(self : Context) -> MetadataType {
  self.pimpl.unwrap().metadata_ty
}

///|
/// Get the token type from context.
///
/// - See LLVM: `Type::getTokenTy`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let tokenty = ctx.getTokenTy()
///   inspect(tokenty, content="token")
/// }
/// ```
pub fn Context::getTokenTy(self : Context) -> TokenType {
  self.pimpl.unwrap().token_ty
}

///|
/// Get the integer type with 1 bit from context.
pub fn Context::getInt1Ty(self : Context) -> Int1Type {
  self.pimpl.unwrap().i1ty
}

///|
/// Get the integer type with 8 bits from context.
pub fn Context::getInt8Ty(self : Context) -> Int8Type {
  self.pimpl.unwrap().i8ty
}

///|
/// Get the integer type with 16 bits from context.
pub fn Context::getInt16Ty(self : Context) -> Int16Type {
  self.pimpl.unwrap().i16ty
}

///|
/// Get the integer type with 32 bits from context.
pub fn Context::getInt32Ty(self : Context) -> Int32Type {
  self.pimpl.unwrap().i32ty
}

///|
/// Get the integer type with 64 bits from context.
pub fn Context::getInt64Ty(self : Context) -> Int64Type {
  self.pimpl.unwrap().i64ty
}

///|
/// Get the Pointer type from context.
///
/// - See LLVM: `PointerType::get`.
///
/// **Note**:
/// 
///   After LLVM17, typed pointer has been deprecated. Therefore in 
///   Moonbit Aether framework, all pointer type is opaque.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   inspect(ctx.getPtrTy(), content="ptr")
///   let addressSpace = AddressSpace::new(1)
///   inspect(ctx.getPtrTy(addressSpace~), content="ptr")
/// }
/// ```
pub fn Context::getPtrTy(
  self : Context,
  addressSpace? : AddressSpace = AddressSpace::default(),
) -> PointerType {
  match self.pimpl.unwrap().pointerTypes.get(addressSpace) {
    Some(ty) => ty
    None => {
      let ty = PointerType::new(self, addressSpace~)
      self.pimpl.unwrap().pointerTypes.set(addressSpace, ty)
      ty
    }
  }
}

///|
/// Create a function type.
///
/// This is different with the method of creating function type in LLVM.
/// In LLVM, usually use `FunctionType::get` to create a function type.
///
/// In Moonbit Aether framework, we use `Context::getFunctionType` to create a function type.
///
/// - See LLVM: `FunctionType::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let voidty = ctx.getVoidTy()
///   let i32ty = ctx.getInt32Ty()
///   let f64ty = ctx.getDoubleTy()
///   let fty = ctx.getFunctionType(voidty, [i32ty, f64ty])
///   inspect(fty, content="void (i32, double)")
/// }
/// ```
pub fn Context::getFunctionType(
  self : Context,
  returnType : &Type,
  paramTypes : Array[&Type],
  isVarArg? : Bool = false,
) -> FunctionType raise LLVMTypeError {
  ignore(self)
  FunctionType::new(returnType, paramTypes, isVarArg~)
}

///|
/// Create a struct type in the context.
///
/// This is different with the method of creating struct type in LLVM.
/// In LLVM, usually use `StructType::create` to create a struct type.
///
/// In Moonbit Aether framework, we use `Context::getStructType` to create a struct type.
///
/// - See LLVM: `StructType::create`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let i32ty = ctx.getInt32Ty()
///   let f32ty = ctx.getFloatTy()
///   let f64ty = ctx.getDoubleTy()
///   let sty = ctx.getStructType([i32ty, f32ty, f64ty], name="foo")
///   inspect(sty.full_info(), content="%foo = type { i32, float, double }")
///
///   // Cannot create a ananymous struct type with empty elements.
///   assert_true((try? ctx.getStructType([])) is Err(_))
///
///   // Cannot create a struct which has same name with other struct.
///   // the `foo` struct is already created.
///   assert_true((try? ctx.getStructType([], name="foo")) is Err(_))
/// }
/// ```
pub fn Context::getStructType(
  self : Context,
  elements : Array[&Type],
  name? : String = "",
  isPacked? : Bool = false,
) -> StructType raise LLVMTypeError {
  let name = unless(name.is_empty(), fn() { name })
  StructType::new(self, elements, name~, isPacked~)
}

///|
/// Search a named struct type in the context.
///
/// LLVM Cpp version has no this method, since llvm-cpp allow duplicated struct type names.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let i32ty = ctx.getInt32Ty()
///   let f32ty = ctx.getFloatTy()
///   let _ = ctx.getStructType([i32ty, f32ty], name="foo")
///   let sty = ctx.getStructTypeByName("foo").unwrap()
///   inspect(sty.full_info(), content="%foo = type { i32, float }")
/// }
/// ```
pub fn Context::getStructTypeByName(self : Self, name : String) -> StructType? {
  self.pimpl.unwrap().namedStructTypes.get(name)
}

///|
/// Create an array type in the context.
///
/// This is different with the method of creating array type in LLVM.
/// In LLVM, usually use `ArrayType::get` to create an array type.
///
/// In Moonbit Aether framework, we use `Context::getArrayType` to create an array type.
///
/// - See LLVM: `ArrayType::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let i32ty = ctx.getInt32Ty()
///   let arrty = ctx.getArrayType(i32ty, 16)
///   inspect(arrty, content="[16 x i32]")
///   inspect(arrty.getElementType(), content="i32")
///   assert_eq(arrty.getElementCount(), 16)
/// }
/// ```
pub fn Context::getArrayType(
  self : Context,
  elementType : &Type,
  numElements : Int,
) -> ArrayType raise LLVMTypeError {
  if numElements < 0 {
    raise InValidTypeSize(numElements)
  }
  ArrayType::new(self, elementType, numElements)
}

///|
/// Create a fixed length vector type in the context.
///
/// - See LLVM: `VectorType::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let i32ty = ctx.getInt32Ty()
///   let fixedVecTy = ctx.getFixedVectorType(i32ty, 32)
///   inspect(fixedVecTy, content="<32 x i32>")
/// }
/// ```
pub fn Context::getFixedVectorType(
  self : Context,
  elementType : &Type,
  elementQuantity : Int,
) -> VectorType raise LLVMTypeError {
  if elementQuantity < 0 {
    raise InValidTypeSize(elementQuantity)
  }
  VectorType::new(self, elementType, elementQuantity)
}

///|
/// Create a scalable vector type in the context.
///
/// - See LLVM: `ScalableVectorType::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let i32ty = ctx.getInt32Ty()
///   let scalableVecTy = ctx.getScalableVectorType(i32ty, 16)
///   inspect(scalableVecTy, content="")
/// }
/// ```
pub fn Context::getScalableVectorType(
  self : Context,
  elementType : &Type,
  elementQuantity : Int,
) -> ScalableVectorType raise LLVMTypeError {
  ScalableVectorType::new(self, elementType, elementQuantity)
}

///|
/// Create a constant with int8 type.
///
/// - See LLVM: `ConstantInt::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   inspect(ctx.getConstInt8(0), content="i8 0")
///   inspect(ctx.getConstInt8(1), content="i8 1")
///   inspect(ctx.getConstInt8(-1), content="i8 -1")
/// }
/// ```
pub fn Context::getConstInt8(self : Context, value : Int) -> ConstantInt {
  let i8val = Int8::from(value)
  ConstantInt::new(self.getInt8Ty(), i8val.to_int64())
}

///|
/// Create a constant with int16 type.
///
/// - See LLVM: `ConstantInt::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   inspect(ctx.getConstInt16(0), content="i16 0")
///   inspect(ctx.getConstInt16(1), content="i16 1")
///   inspect(ctx.getConstInt16(-1), content="i16 -1")
/// }
/// ```
pub fn Context::getConstInt16(self : Context, value : Int16) -> ConstantInt {
  ConstantInt::new(self.getInt16Ty(), value.to_int64())
}

///|
/// Create a constant with int32 type.
///
/// - See LLVM: `ConstantInt::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   inspect(ctx.getConstInt32(0), content="i32 0")
///   inspect(ctx.getConstInt32(1), content="i32 1")
///   inspect(ctx.getConstInt32(-1), content="i32 -1")
/// }
/// ```
pub fn Context::getConstInt32(self : Context, value : Int) -> ConstantInt {
  ConstantInt::new(self.getInt32Ty(), value.to_int64())
}

///|
/// Create a constant with int64 type.
///
/// - See LLVM: `ConstantInt::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   inspect(ctx.getConstInt64(0), content="i64 0")
///   inspect(ctx.getConstInt64(1), content="i64 1")
///   inspect(ctx.getConstInt64(-1), content="i64 -1")
/// }
/// ```
pub fn Context::getConstInt64(self : Context, value : Int64) -> ConstantInt {
  ConstantInt::new(self.getInt64Ty(), value)
}

///|
/// Create a constant with bool type, true value.
///
/// - See LLVM: `ConstantInt::getTrue`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   inspect(ctx.getConstTrue(), content="i1 true")
/// }
/// ```
pub fn Context::getConstTrue(self : Context) -> ConstantInt {
  ConstantInt::new(self.getInt1Ty(), 1)
}

///|
/// Create a constant with bool type, false value.
///
/// - See LLVM: `ConstantInt::getFalse`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   inspect(ctx.getConstFalse(), content="i1 false")
/// }
/// ```
pub fn Context::getConstFalse(self : Context) -> ConstantInt {
  ConstantInt::new(self.getInt1Ty(), 0)
}

///|
/// Create a constant with bool type.
///
/// - See LLVM: `ConstantInt::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   inspect(ctx.getConstBool(true), content="i1 true")
///   inspect(ctx.getConstBool(false), content="i1 false")
/// }
/// ```
pub fn Context::getConstBool(self : Context, b : Bool) -> ConstantInt {
  ConstantInt::new(self.getInt1Ty(), b.to_int64())
}

///|
/// Create a constant with float type.
///
/// - See LLVM: `ConstantFP::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let one = ctx.getConstFloat(1.0)
///   let two = ctx.getConstFloat(2.0)
///   inspect(one, content="float 0x3FF0000000000000")
///   inspect(two, content="float 0x4000000000000000")
/// }
/// ```
pub fn Context::getConstFloat(self : Context, value : Float) -> ConstantFP {
  ConstantFP::new(self.getFloatTy(), value.to_double())
}

///|
/// Create a constant with double type.
///
/// - See LLVM: `ConstantFP::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let one = ctx.getConstDouble(1.0)
///   let two = ctx.getConstDouble(2.0)
///   inspect(one, content="double 0x3FF0000000000000")
///   inspect(two, content="double 0x4000000000000000")
/// }
/// ```
pub fn Context::getConstDouble(self : Context, value : Double) -> ConstantFP {
  ConstantFP::new(self.getDoubleTy(), value)
}

///|
/// Create a constant zero with float type.
///
/// - See LLVM: `ConstantFP::getZero`.
///
/// **Parameter**:
///
///  - `isNegative`: If true, create a negative zero constant.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let zero = ctx.getConstZeroFloat()
///   let neg_zero = ctx.getConstZeroFloat(isNegative=true)
///   inspect(zero, content="float 0x0")
///   inspect(neg_zero, content="float 0x8000000000000000")
/// }
/// ```
pub fn Context::getConstZeroFloat(
  self : Context,
  isNegative? : Bool = false,
) -> ConstantFP {
  let uv = match isNegative {
    false => 0UL
    true => 0x8000_0000_0000_0000UL
  }
  ConstantFP::new(self.getFloatTy(), uv.reinterpret_as_double())
}

///|
/// Create a constant zero with double type.
///
/// - See LLVM: `ConstantFP::getZero`.
///
/// **Parameter**:
///
/// - `isNegative`: If true, create a negative zero constant.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let zero = ctx.getConstZeroDouble()
///   let neg_zero = ctx.getConstZeroDouble(isNegative=true)
///   inspect(zero, content="double 0x0")
///   inspect(neg_zero, content="double 0x8000000000000000")
/// }
/// ```
pub fn Context::getConstZeroDouble(
  self : Context,
  isNegative? : Bool = false,
) -> ConstantFP {
  let uv = match isNegative {
    false => 0UL
    true => 0x8000_0000_0000_0000UL
  }
  ConstantFP::new(self.getDoubleTy(), uv.reinterpret_as_double())
}

///|
/// Create a constant NaN with double type.
///
/// - See LLVM: `ConstantFP::getZero`.
///
/// **Parameter**:
///
/// - `isNegative`: If true, create a negative zero constant.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let nan = ctx.getConstNaNDouble()
///   let neg_nan = ctx.getConstNaNDouble(isNegative=true)
///   inspect(nan, content="double 0x7FF8000000000000")
///   inspect(neg_nan, content="double 0xFFF8000000000000")
/// }
/// ```
pub fn Context::getConstNaNFloat(
  self : Context,
  isNegative? : Bool = false,
) -> ConstantFP {
  let uv = match isNegative {
    false => 0x7FF8_0000_0000_0000UL
    true => 0xFFF8_0000_0000_0000UL
  }
  ConstantFP::new(self.getFloatTy(), uv.reinterpret_as_double())
}

///|
/// Create a constant Quiet NaN with float type.
///
/// - See LLVM: `ConstantFP::getQNaN`.
///
/// **Parameter**:
///
/// - `isNegative`: If true, create a negative NaN constant.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let nan = ctx.getConstQNaNFloat()
///   let neg_nan = ctx.getConstQNaNFloat(isNegative=true)
///   inspect(nan, content="float 0x7FF8000000000000")
///   inspect(neg_nan, content="float 0xFFF8000000000000")
/// }
/// ```
pub fn Context::getConstQNaNFloat(
  self : Context,
  isNegative? : Bool = false,
) -> ConstantFP {
  let uv = match isNegative {
    false => 0x7FF8_0000_0000_0000UL
    true => 0xFFF8_0000_0000_0000UL
  }
  ConstantFP::new(self.getFloatTy(), uv.reinterpret_as_double())
}

///|
/// Create a constant Signaling NaN with float type.
///
/// - See LLVM: `ConstantFP::getSNaN`.
///
/// **Parameter**:
///
/// - `isNegative`: If true, create a negative NaN constant.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let nan = ctx.getConstSNaNFloat()
///   let neg_nan = ctx.getConstSNaNFloat(isNegative=true)
///   inspect(nan, content="float 0x7FF4000000000000")
///   inspect(neg_nan, content="float 0xFFF4000000000000")
/// }
/// ```
pub fn Context::getConstSNaNFloat(
  self : Context,
  isNegative? : Bool = false,
) -> ConstantFP {
  let uv = match isNegative {
    false => 0x7FF4_0000_0000_0000UL
    true => 0xFFF4_0000_0000_0000UL
  }
  ConstantFP::new(self.getFloatTy(), uv.reinterpret_as_double())
}

///|
/// Create a constant Infinity with float type.
///
/// - See LLVM: `ConstantFP::getInfinity`.
///
/// **Parameter**:
///
/// - `isNegative`: If true, create a negative Infinity constant.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let inf = ctx.getConstInfFloat()
///   let neg_inf = ctx.getConstInfFloat(isNegative=true)
///   inspect(inf, content="float 0x7FF0000000000000")
///   inspect(neg_inf, content="float 0xFFF0000000000000")
/// }
/// ```
pub fn Context::getConstInfFloat(
  self : Context,
  isNegative? : Bool = false,
) -> ConstantFP {
  let uv = match isNegative {
    false => 0x7FF0_0000_0000_0000UL
    true => 0xFFF0_0000_0000_0000UL
  }
  ConstantFP::new(self.getFloatTy(), uv.reinterpret_as_double())
}

///|
/// Create a constant NaN with double type.
///
/// - See LLVM: `ConstantFP::getNaN`.
///
/// **Parameter**:
///
/// - `isNegative`: If true, create a negative NaN constant.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let nan = ctx.getConstNaNDouble()
///   let neg_nan = ctx.getConstNaNDouble(isNegative=true)
///   inspect(nan, content="double 0x7FF8000000000000")
///   inspect(neg_nan, content="double 0xFFF8000000000000")
/// }
/// ```
pub fn Context::getConstNaNDouble(
  self : Context,
  isNegative? : Bool = false,
) -> ConstantFP {
  let uv = match isNegative {
    false => 0x7FF8_0000_0000_0000UL
    true => 0xFFF8_0000_0000_0000UL
  }
  ConstantFP::new(self.getDoubleTy(), uv.reinterpret_as_double())
}

///|
/// Create a constant Quiet NaN with double type.
///
/// - See LLVM: `ConstantFP::getQNaN`.
///
/// **Parameter**:
///
/// - `isNegative`: If true, create a negative NaN constant.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let nan = ctx.getConstQNaNDouble()
///   let neg_nan = ctx.getConstQNaNDouble(isNegative=true)
///   inspect(nan, content="double 0x7FF8000000000000")
///   inspect(neg_nan, content="double 0xFFF8000000000000")
/// }
/// ```
pub fn Context::getConstQNaNDouble(
  self : Context,
  isNegative? : Bool = false,
) -> ConstantFP {
  let uv = match isNegative {
    false => 0x7FF8_0000_0000_0000UL
    true => 0xFFF8_0000_0000_0000UL
  }
  ConstantFP::new(self.getDoubleTy(), uv.reinterpret_as_double())
}

///|
/// Create a constant Signaling NaN with double type.
///
/// - See LLVM: `ConstantFP::getSNaN`.
///
/// **Parameter**:
///
/// - `isNegative`: If true, create a negative NaN constant.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let nan = ctx.getConstSNaNDouble()
///   let neg_nan = ctx.getConstSNaNDouble(isNegative=true)
///   inspect(nan, content="double 0x7FF4000000000000")
///   inspect(neg_nan, content="double 0xFFF4000000000000")
/// }
/// ```
pub fn Context::getConstSNaNDouble(
  self : Context,
  isNegative? : Bool = false,
) -> ConstantFP {
  let uv = match isNegative {
    false => 0x7FF4_0000_0000_0000UL
    true => 0xFFF4_0000_0000_0000UL
  }
  ConstantFP::new(self.getDoubleTy(), uv.reinterpret_as_double())
}

///|
/// Create a constant Infinity with double type.
///
/// - See LLVM: `ConstantFP::getInfinity`.
///
/// **Parameter**:
///
/// - `isNegative`: If true, create a negative Infinity constant.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let inf = ctx.getConstInfDouble()
///   let neg_inf = ctx.getConstInfDouble(isNegative=true)
///   inspect(inf, content="double 0x7FF0000000000000")
///   inspect(neg_inf, content="double 0xFFF0000000000000")
/// }
/// ```
pub fn Context::getConstInfDouble(
  self : Context,
  isNegative? : Bool = false,
) -> ConstantFP {
  let uv = match isNegative {
    false => 0x7FF0_0000_0000_0000UL
    true => 0xFFF0_0000_0000_0000UL
  }
  ConstantFP::new(self.getDoubleTy(), uv.reinterpret_as_double())
}

///|
/// Create a constant array with int8 type.
///
/// -  See LLVM: `ConstantDataArray::get`.
///
/// **Note**:
///
/// - Core does not support `int8` currently, hence the type
/// of parameter `data` is `Array[Int]`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[Int] = [42, 63, 77, 89]
///   let arr = ctx.getConstInt8Array(data)
///   inspect(arr, content="[4 x i8] [i8 42, i8 63, i8 77, i8 89]")
/// }
/// ```
pub fn Context::getConstInt8Array(
  self : Context,
  data : Array[Int],
) -> ConstantArray {
  let ty = try! self.getArrayType(self.getInt8Ty(), data.length())
  ConstantArray::newInt8Array(ty, data)
}

///|
/// Create a constant array with int16 type.
///
/// -  See LLVM: `ConstantDataArray::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[Int16] = [42, 63, 77, 89]
///   let arr = ctx.getConstInt16Array(data)
///   inspect(arr, content="[4 x i16] [i16 42, i16 63, i16 77, i16 89]")
/// }
/// ```
pub fn Context::getConstInt16Array(
  self : Context,
  data : Array[Int16],
) -> ConstantArray {
  let ty = try! self.getArrayType(self.getInt16Ty(), data.length())
  ConstantArray::newInt16Array(ty, data)
}

///|
/// Create a constant array with int32 type.
///
/// -  See LLVM: `ConstantDataArray::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[Int] = [42, 63, 77, 89]
///   let arr = ctx.getConstInt32Array(data)
///   inspect(arr, content="[4 x i32] [i32 42, i32 63, i32 77, i32 89]")
/// }
/// ```
pub fn Context::getConstInt32Array(
  self : Context,
  data : Array[Int],
) -> ConstantArray {
  let ty = try! self.getArrayType(self.getInt32Ty(), data.length())
  ConstantArray::newInt32Array(ty, data)
}

///|
/// Create a constant array with int32 type.
///
/// -  See LLVM: `ConstantDataArray::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[Int64] = [42, 63, 77, 89]
///   let arr = ctx.getConstInt64Array(data)
///   inspect(arr, content="[4 x i64] [i64 42, i64 63, i64 77, i64 89]")
/// }
/// ```
pub fn Context::getConstInt64Array(
  self : Context,
  data : Array[Int64],
) -> ConstantArray {
  let ty = try! self.getArrayType(self.getInt64Ty(), data.length())
  ConstantArray::newInt64Array(ty, data)
}

///|
/// Create a constant array with uint8 type.
///
/// -  See LLVM: `ConstantDataArray::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[Byte] = [42, 63, 77, 89]
///   let arr = ctx.getConstUInt8Array(data)
///   inspect(arr, content="[4 x i8] [i8 42, i8 63, i8 77, i8 89]")
/// }
/// ```
pub fn Context::getConstUInt8Array(
  self : Context,
  data : Array[Byte],
) -> ConstantArray {
  let len = data.length()
  let ty = try! self.getArrayType(self.getInt8Ty(), len)
  ConstantArray::newUInt8Array(ty, data)
}

///|
/// Create a constant array with uint16 type.
///
/// -  See LLVM: `ConstantDataArray::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[UInt16] = [42, 63, 77, 89]
///   let arr = ctx.getConstUInt16Array(data)
///   inspect(arr, content="[4 x i16] [i16 42, i16 63, i16 77, i16 89]")
/// }
/// ```
pub fn Context::getConstUInt16Array(
  self : Context,
  data : Array[UInt16],
) -> ConstantArray {
  let len = data.length()
  let ty = try! self.getArrayType(self.getInt16Ty(), len)
  ConstantArray::newUInt16Array(ty, data)
}

///|
/// Create a constant array with uint32 type.
///
/// -  See LLVM: `ConstantDataArray::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[UInt] = [42, 63, 77, 89]
///   let arr = ctx.getConstUInt32Array(data)
///   inspect(arr, content="[4 x i32] [i32 42, i32 63, i32 77, i32 89]")
/// }
/// ```
pub fn Context::getConstUInt32Array(
  self : Context,
  data : Array[UInt],
) -> ConstantArray {
  let len = data.length()
  let ty = try! self.getArrayType(self.getInt32Ty(), len)
  ConstantArray::newUInt32Array(ty, data)
}

///|
/// Create a constant array with uint64 type.
///
/// -  See LLVM: `ConstantDataArray::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[UInt64] = [42, 63, 77, 89]
///   let arr = ctx.getConstUInt64Array(data)
///   inspect(arr, content="[4 x i64] [i64 42, i64 63, i64 77, i64 89]")
/// }
/// ```
pub fn Context::getConstUInt64Array(
  self : Context,
  data : Array[UInt64],
) -> ConstantArray {
  let len = data.length()
  let ty = try! self.getArrayType(self.getInt64Ty(), len)
  ConstantArray::newUInt64Array(ty, data)
}

///|
/// Create a constant array with float type.
///
/// -  See LLVM: `ConstantDataArray::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[Float] = [1.0, 2.0]
///   let arr = ctx.getConstFloatArray(data)
///   inspect(
///     arr,
///     content="[2 x float] [float 0x3FF0000000000000, float 0x4000000000000000]",
///   )
/// }
/// ```
pub fn Context::getConstFloatArray(
  self : Context,
  data : Array[Float],
) -> ConstantArray {
  let len = data.length()
  let ty = try! self.getArrayType(self.getFloatTy(), len)
  ConstantArray::newFloatArray(ty, data)
}

///|
/// Create a constant array with double type.
///
/// -  See LLVM: `ConstantDataArray::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[Double] = [1.0, 2.0]
///   let arr = ctx.getConstDoubleArray(data)
///   inspect(
///     arr,
///     content="[2 x double] [double 0x3FF0000000000000, double 0x4000000000000000]",
///   )
/// }
/// ```
pub fn Context::getConstDoubleArray(
  self : Context,
  data : Array[Double],
) -> ConstantArray {
  let len = data.length()
  let ty = try! self.getArrayType(self.getDoubleTy(), len)
  ConstantArray::newDoubleArray(ty, data)
}

///|
pub fn Context::getConstArray(
  self : Context,
  elementType : &Type,
  data : Array[&Constant],
) -> ConstantArray {
  let len = data.length()
  let ty = try! self.getArrayType(elementType, len)
  ConstantArray::newConstantArray(ty, data)
}

// ---------------------------

///|
/// Create a constant vector with int8 type.
///
/// -  See LLVM: `ConstantDataVector::get`.
///
/// **Note**:
///
/// - Core does not support `int8` currently, hence the type
/// of parameter `data` is `Array[Int]`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[Int] = [42, 63, 77, 89]
///   let arr = ctx.getConstInt8Vector(data)
///   inspect(arr, content="<4 x i8> ")
/// }
/// ```
pub fn Context::getConstInt8Vector(
  self : Context,
  data : Array[Int],
) -> ConstantVector {
  let len = data.length()
  let ty = try! self.getFixedVectorType(self.getInt8Ty(), len)
  ConstantVector::newInt8Vector(ty, data)
}

///|
/// Create a constant vector with int16 type.
///
/// -  See LLVM: `ConstantDataVector::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[Int16] = [42, 63, 77, 89]
///   let arr = ctx.getConstInt16Vector(data)
///   inspect(arr, content="<4 x i16> ")
/// }
/// ```
pub fn Context::getConstInt16Vector(
  self : Context,
  data : Array[Int16],
) -> ConstantVector {
  let len = data.length()
  let ty = try! self.getFixedVectorType(self.getInt16Ty(), len)
  ConstantVector::newInt16Vector(ty, data)
}

///|
/// Create a constant vector with int32 type.
///
/// -  See LLVM: `ConstantDataVector::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[Int] = [42, 63, 77, 89]
///   let arr = ctx.getConstInt32Vector(data)
///   inspect(arr, content="<4 x i32> ")
/// }
/// ```
pub fn Context::getConstInt32Vector(
  self : Context,
  data : Array[Int],
) -> ConstantVector {
  let len = data.length()
  let ty = try! self.getFixedVectorType(self.getInt32Ty(), len)
  ConstantVector::newInt32Vector(ty, data)
}

///|
/// Create a constant array with int32 type.
///
/// -  See LLVM: `ConstantDataVector::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[Int64] = [42, 63, 77, 89]
///   let arr = ctx.getConstInt64Vector(data)
///   inspect(arr, content="<4 x i64> ")
/// }
/// ```
pub fn Context::getConstInt64Vector(
  self : Context,
  data : Array[Int64],
) -> ConstantVector {
  let len = data.length()
  let ty = try! self.getFixedVectorType(self.getInt64Ty(), len)
  ConstantVector::newInt64Vector(ty, data)
}

///|
/// Create a constant array with uint8 type.
///
/// -  See LLVM: `ConstantDataVector::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[Byte] = [42, 63, 77, 89]
///   let arr = ctx.getConstUInt8Vector(data)
///   inspect(arr, content="<4 x i8> ")
/// }
/// ```
pub fn Context::getConstUInt8Vector(
  self : Context,
  data : Array[Byte],
) -> ConstantVector {
  let ty = try! self.getFixedVectorType(self.getInt8Ty(), data.length())
  ConstantVector::newUInt8Vector(ty, data)
}

///|
/// Create a constant array with uint16 type.
///
/// -  See LLVM: `ConstantDataVector::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[UInt16] = [42, 63, 77, 89]
///   let arr = ctx.getConstUInt16Vector(data)
///   inspect(arr, content="<4 x i16> ")
/// }
/// ```
pub fn Context::getConstUInt16Vector(
  self : Context,
  data : Array[UInt16],
) -> ConstantVector {
  let ty = try! self.getFixedVectorType(self.getInt16Ty(), data.length())
  ConstantVector::newUInt16Vector(ty, data)
}

///|
/// Create a constant array with uint32 type.
///
/// -  See LLVM: `ConstantDataVector::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[UInt] = [42, 63, 77, 89]
///   let arr = ctx.getConstUInt32Vector(data)
///   inspect(arr, content="<4 x i32> ")
/// }
/// ```
pub fn Context::getConstUInt32Vector(
  self : Context,
  data : Array[UInt],
) -> ConstantVector {
  let ty = try! self.getFixedVectorType(self.getInt32Ty(), data.length())
  ConstantVector::newUInt32Vector(ty, data)
}

///|
/// Create a constant array with uint64 type.
///
/// -  See LLVM: `ConstantDataVector::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[UInt64] = [42, 63, 77, 89]
///   let arr = ctx.getConstUInt64Vector(data)
///   inspect(arr, content="<4 x i64> ")
/// }
/// ```
pub fn Context::getConstUInt64Vector(
  self : Context,
  data : Array[UInt64],
) -> ConstantVector {
  let ty = try! self.getFixedVectorType(self.getInt64Ty(), data.length())
  ConstantVector::newUInt64Vector(ty, data)
}

///|
/// Create a constant array with float type.
///
/// -  See LLVM: `ConstantDataVector::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[Float] = [1.0, 2.0]
///   let arr = ctx.getConstFloatVector(data)
///   inspect(
///     arr,
///     content="<2 x float> ",
///   )
/// }
/// ```
pub fn Context::getConstFloatVector(
  self : Context,
  data : Array[Float],
) -> ConstantVector {
  let ty = try! self.getFixedVectorType(self.getFloatTy(), data.length())
  ConstantVector::newFloatVector(ty, data)
}

///|
/// Create a constant array with double type.
///
/// -  See LLVM: `ConstantDataVector::get`.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let data : Array[Double] = [1.0, 2.0]
///   let arr = ctx.getConstDoubleVector(data)
///   inspect(
///     arr,
///     content="<2 x double> ",
///   )
/// }
/// ```
pub fn Context::getConstDoubleVector(
  self : Context,
  data : Array[Double],
) -> ConstantVector {
  let ty = try! self.getFixedVectorType(self.getDoubleTy(), data.length())
  ConstantVector::newDoubleVector(ty, data)
}

///|
/// Create a constant zero value (null value) of a given type.
///
/// TODO: wait compiler bug fixed.
///
/// ```mbt test-no-run
/// let ctx = Context::new()
/// let i32ty = ctx.getInt32Ty()
/// let zero_i32 = ctx.getConstZero(i32ty)
/// inspect(zero_i32, content="i32 0")
/// ```
pub fn Context::getConstZero(
  self : Context,
  ty : &Type,
) -> &Constant raise LLVMValueError {
  match ty.asTypeEnum() {
    // Integer Types
    Int1Type(_) => self.getConstFalse() as &Constant
    Int8Type(_) => self.getConstInt8(0)
    Int16Type(_) => self.getConstInt16(0)
    Int32Type(_) => self.getConstInt32(0)
    Int64Type(_) => self.getConstInt64(0L)

    // Floating Point Types
    HalfType(t) => ConstantFP::new(t, 0.0)
    BFloatType(t) => ConstantFP::new(t, 0.0)
    FloatType(_) => self.getConstZeroFloat()
    DoubleType(_) => self.getConstZeroDouble()
    FP128Type(t) => ConstantFP::new(t, 0.0)

    // Pointer Type
    PointerType(_) => ConstantPointerNull::new(self)

    // Aggregate and Other Types where zero value is complex or not applicable
    //ArrayType(t) => llvm_unreachable("getConstZero: Unimplemented for ArrayType \{t.to_string()}")
    ArrayType({ elementType: t, elementCount: cnt, .. }) =>
      match t.asTypeEnum() {
        Int8Type(_) => self.getConstInt8Array(Array::make(cnt, 0))
        Int16Type(_) => self.getConstInt16Array(Array::make(cnt, 0))
        Int32Type(_) => self.getConstInt32Array(Array::make(cnt, 0))
        Int64Type(_) => self.getConstInt64Array(Array::make(cnt, 0L))
        FloatType(_) => self.getConstFloatArray(Array::make(cnt, 0.0))
        DoubleType(_) => self.getConstDoubleArray(Array::make(cnt, 0.0))
        _ =>
          llvm_unreachable(
            "getConstZero: Unimplemented for ArrayType with element type \{t.to_string()}",
          )
      }
    StructType(t) =>
      llvm_unreachable(
        "getConstZero: Unimplemented for StructType \{t.to_string()}",
      )
    VectorType(t) =>
      llvm_unreachable(
        "getConstZero: Unimplemented for VectorType \{t.to_string()}",
      )
    _ => {
      let msg = "Misuse `Context::getConstZero`: Cannot create zero value for type \{ty.to_string()}"
      raise LLVMValueError(msg)
    }
  }
}

///|
pub fn Context::getConstOne(
  self : Context,
  ty : &Type,
) -> &Constant raise LLVMValueError {
  match ty.asTypeEnum() {
    // Integer Types
    Int1Type(_) => self.getConstTrue() as &Constant
    Int8Type(_) => self.getConstInt8(1)
    Int16Type(_) => self.getConstInt16(1)
    Int32Type(_) => self.getConstInt32(1)
    Int64Type(_) => self.getConstInt64(1L)

    // Floating Point Types
    HalfType(t) => ConstantFP::new(t, 1.0)
    BFloatType(t) => ConstantFP::new(t, 1.0)
    FloatType(_) => self.getConstFloat(1.0)
    DoubleType(_) => self.getConstDouble(1.0)
    FP128Type(t) => ConstantFP::new(t, 1.0)
    _ => {
      let msg = "Misuse `Context::getConstOne`: Cannot create one value for type \{ty.to_string()}"
      raise LLVMValueError(msg)
    }
  }
}

///|
pub fn Context::getConstPointerNull(
  self : Context,
  ty : &Type,
  addressSpace? : UInt = 0,
) -> ConstantPointerNull {
  ignore(ty) // TODO: remove this in the future.
  ConstantPointerNull::new(self, addressSpace~)
}

///|
pub fn Context::getMDString(self : Context, str : String) -> MDString {
  let mdstrings = self.pimpl.unwrap().mdstrings
  match mdstrings.get(str) {
    Some(s) => s
    None => {
      let mds = MDString::{ str, }
      mdstrings.set(str, mds)
      mds
    }
  }
}