// =======================================================
// Value
// =======================================================

///|
pub enum ValueEnum {

  // ======= Constant Values =======
  /// The following two is global values.
  Function(Function)
  GlobalVariable(GlobalVariable)
  GlobalConstant(GlobalConstant)
  //GlobalIFunc(GlobalIFunc)

  /// Base class for constants with no operands.
  ///
  /// These constants have no operands; they represent their data directly.
  /// Since they can be in use by unrelated modules (and are never based on
  /// GlobalValues), it never makes sense to RAUW them.
  ///
  /// These do not have use lists. It is illegal to inspect the uses. These behave
  /// as if they have no uses (i.e. use_empty() is always true).
  ConstantInt(ConstantInt)
  ConstantFP(ConstantFP)
  ConstantPointerNull(ConstantPointerNull)
  //ConstantAggregateZero(ConstantAggregateZero)
  //ConstantAggregate(ConstantAggregate)
  ConstantArray(ConstantArray)
  ConstantVector(ConstantVector)
  ConstantString(ConstantString)
  ConstantStruct(ConstantStruct)
  //ConstantTokenNone(ConstantTokenNone)
  //UndefValue(UndefValue)
  //PoisonValue(PoisonValue)
  //
  //BlockAddress(BlockAddress)

  // =========
  Argument(Argument)
  BasicBlock(BasicBlock)
  // MetadataAsValue(MetadataAsValue)

  // ======= Instruction Values =======
  // ------- Unary Instruction
  AllocaInst(AllocaInst)
  LoadInst(LoadInst)
  ExtractValueInst(ExtractValueInst)
  FNegInst(FNegInst)
  // FreezeInst(FreezeInst)
  CastInst(CastInst)
  BinaryInst(BinaryInst)
  ICmpInst(ICmpInst)
  FCmpInst(FCmpInst)
  StoreInst(StoreInst)
  GetElementPtrInst(GetElementPtrInst)
  SelectInst(SelectInst)
  //ExtractElementInst(ExtractElementInst)
  InsertValueInst(InsertValueInst)
  //InsertElementInst(InsertElementInst)
  //ShuffleVectorInst(ShuffleVectorInst)
  PHINode(PHINode)
  ReturnInst(ReturnInst)
  BranchInst(BranchInst)
  SwitchInst(SwitchInst)
  CallInst(CallInst)
  //FenceInst(FenceInst)
  //AtomicCmpXchgInst(AtomicCmpXchgInst)
  //AtomicRMWInst(AtomicRMWInst)

  // ======= Not Plan To Support =======
  //VAArgInst(VAArgInst)
  //InvokeInst(InvokeInst)
  //CallBrInst(CallBrInst)
  //IndirectBrInst(IndirectBrInst)
  //ResumeInst(ResumeInst)
  //CatchSwitchInst(CatchSwitchInst)
  //CatchReturnInst(CatchReturnInst)
  //CleanupReturnInst(CleanupReturnInst)
  //UnreachableInst(UnreachableInst)
  //LandingPadInst(LandingPadInst)
  //InlineAsm(InlineAsm)
}

///|
pub fn ValueEnum::tryAsConstantEnum(self : ValueEnum) -> ConstantEnum? {
  match self {
    ConstantInt(c) => ConstantInt(c) |> Some
    ConstantFP(c) => ConstantFP(c) |> Some
    ConstantPointerNull(c) => ConstantPointerNull(c) |> Some
    ConstantArray(c) => ConstantArray(c) |> Some
    ConstantString(c) => ConstantString(c) |> Some
    ConstantVector(c) => ConstantVector(c) |> Some
    ConstantStruct(c) => ConstantStruct(c) |> Some
    _ => None
  }
}

///|
pub fn ValueEnum::tryAsUserEnum(self : ValueEnum) -> UserEnum? {
  match self {
    LoadInst(c) => LoadInst(c) |> Some
    //VAArgInst(c) => VAArgInst(c) |> Some
    ExtractValueInst(c) => ExtractValueInst(c) |> Some
    //FreezeInst(c) => FreezeInst(c) |> Some
    CastInst(c) => CastInst(c) |> Some
    BinaryInst(b) => BinaryInst(b) |> Some
    ICmpInst(c) => ICmpInst(c) |> Some
    FCmpInst(c) => FCmpInst(c) |> Some
    StoreInst(s) => StoreInst(s) |> Some
    GetElementPtrInst(g) => GetElementPtrInst(g) |> Some
    SelectInst(s) => SelectInst(s) |> Some
    InsertValueInst(i) => InsertValueInst(i) |> Some
    PHINode(p) => PHINode(p) |> Some
    ReturnInst(r) => ReturnInst(r) |> Some
    BranchInst(b) => BranchInst(b) |> Some
    SwitchInst(s) => SwitchInst(s) |> Some
    CallInst(c) => CallInst(c) |> Some
    _ => None
  }
}

///|
pub fn ValueEnum::tryAsInstEnum(self : ValueEnum) -> InstEnum? {
  match self {
    AllocaInst(c) => AllocaInst(c) |> Some
    LoadInst(c) => LoadInst(c) |> Some
    //VAArgInst(c) => VAArgInst(c) |> Some
    ExtractValueInst(c) => ExtractValueInst(c) |> Some
    //FreezeInst(c) => FreezeInst(c) |> Some
    CastInst(c) => CastInst(c) |> Some
    BinaryInst(b) => BinaryInst(b) |> Some
    ICmpInst(c) => ICmpInst(c) |> Some
    FCmpInst(c) => FCmpInst(c) |> Some
    StoreInst(s) => StoreInst(s) |> Some
    GetElementPtrInst(g) => GetElementPtrInst(g) |> Some
    SelectInst(s) => SelectInst(s) |> Some
    InsertValueInst(i) => InsertValueInst(i) |> Some
    PHINode(p) => PHINode(p) |> Some
    ReturnInst(r) => ReturnInst(r) |> Some
    BranchInst(b) => BranchInst(b) |> Some
    SwitchInst(s) => SwitchInst(s) |> Some
    CallInst(c) => CallInst(c) |> Some
    _ => None
  }
}

///|
pub fn ValueEnum::asValueClass(self : ValueEnum) -> &Value {
  match self {
    Function(f) => f as &Value
    GlobalVariable(g) => g
    GlobalConstant(g) => g
    ConstantInt(c) => c
    ConstantFP(c) => c
    ConstantPointerNull(c) => c
    ConstantArray(c) => c
    ConstantVector(c) => c
    ConstantString(c) => c
    ConstantStruct(c) => c
    Argument(a) => a
    BasicBlock(b) => b
    AllocaInst(i) => i
    LoadInst(i) => i
    ExtractValueInst(i) => i
    FNegInst(i) => i
    CastInst(i) => i
    BinaryInst(b) => b
    ICmpInst(i) => i
    FCmpInst(i) => i
    StoreInst(s) => s
    GetElementPtrInst(g) => g
    SelectInst(s) => s
    InsertValueInst(i) => i
    PHINode(p) => p
    ReturnInst(r) => r
    BranchInst(b) => b
    SwitchInst(s) => s
    CallInst(c) => c
  }
}

///|
pub trait Value: Show {
  getValueBase(Self) -> ValueBase
  asValueEnum(Self) -> ValueEnum

  // All values are typed, get the type of this value.
  getType(Self) -> &Type = _

  // All values hold a context through their type.
  getContext(Self) -> Context = _
  //getValueName(Self) -> String? = _

  addUser(Self, user : &User) -> Unit = _

  // Get the string representation of the value.
  // If the value is named value, use name or its slot as the representation.
  // If the value is constant, use its value as the representation.
  getValueRepr(Self) -> String

  /// Change the name of the value.
  ///
  /// Choose a new unique name if the provided name is taken.
  ///
  /// ** Parameters **
  ///
  /// - `name`: The new name; if name is "", the value's name will be removed.
  //setName(Self, name: String) -> Unit raise LLVMValueError = _
  getName(Self) -> String?
  setName(Self, name : String) -> Unit raise LLVMValueError
  removeName(Self) -> Unit raise LLVMValueError
  getNameOrSlot(Self) -> Either[String, UInt64]?
  getNameOrSlotStr(Self) -> String = _

  /// Change all uses of this to point to a new Value.
  ///
  /// Go through the uses list for this definition and make each use point to
  /// "V" instead of "this".  After this completes, 'this's use list is
  /// guaranteed to be empty.
  ///
  /// It's equivalent to: LLVM: replaceAllUsesWith(this, V)
  /// Please note that in LLVM, this API is `replaceAllUsesWith`, while here
  /// is `replaceAllUsersWith`.
  replaceAllUsersWith(Self, other : &Value) -> Unit = _
  getUsers(Self) -> Array[&User]? = _

  // It's equivalent to: LLVM: use_empty
  // Please note that in LLVM, this API is `use_empty`, while here
  // is `user_empty`.
  user_empty(Self) -> Bool = _
  tryAsConstant(Self) -> &Constant? = _
  tryAsConstantEnum(Self) -> ConstantEnum? = _
  tryAsUser(Self) -> &User? = _
  tryAsUserEnum(Self) -> UserEnum? = _
  tryAsInst(Self) -> &Instruction? = _
  tryAsInstEnum(Self) -> InstEnum? = _
  tryAsGlobalValue(Self) -> &GlobalValue? = _
  tryAsGlobalValueEnum(Self) -> GlobalValueEnum? = _
}

///|
pub impl Eq for &Value with equal(self, other) {
  self.getValueBase() == other.getValueBase()
}

///|
impl Value with getType(self) {
  self.getValueBase().vty
}

///|
impl Value with getContext(self) {
  self.getValueBase().vty.getContext()
}

///|
impl Value with addUser(self, user) {
  self.getValueBase().users.push(user)
}

///|
impl Value with getNameOrSlotStr(self) {
  match self.getNameOrSlot() {
    Some(Left(name)) => name
    Some(Right(slot)) => slot.to_string()
    None => ""
  }
}

///|
impl Value with replaceAllUsersWith(self, other : &Value) {
  let users = match self.getUsers() {
    None => return
    Some(users) => users
  }
  for user in users {
    let operands = user.getOperands()
    for i in 0.. users.is_empty()
    None => false // if self is constant, it `users` is None
    // but we don't think constant's users is empty.
  }
}

///|
impl Value with tryAsConstant(self) {
  match self.asValueEnum() {
    ConstantInt(c) => Some((c : &Constant))
    ConstantFP(c) => Some(c)
    ConstantPointerNull(c) => Some(c)
    ConstantArray(c) => Some(c)
    ConstantVector(c) => Some(c)
    ConstantString(c) => Some(c)
    ConstantStruct(c) => Some(c)
    _ => None
  }
}

///|
impl Value with tryAsConstantEnum(self) {
  match self.asValueEnum() {
    ConstantInt(c) => Some(ConstantInt(c))
    ConstantFP(c) => Some(ConstantFP(c))
    ConstantPointerNull(c) => Some(ConstantPointerNull(c))
    ConstantArray(c) => Some(ConstantArray(c))
    ConstantVector(c) => Some(ConstantVector(c))
    ConstantString(c) => Some(ConstantString(c))
    ConstantStruct(c) => Some(ConstantStruct(c))
    _ => None
  }
}

///|
impl Value with tryAsUser(self) {
  match self.asValueEnum() {
    LoadInst(c) => Some((c : &User))
    //VAArgInst(c) => Some(VAArgInst(c))
    ExtractValueInst(c) => Some(c)
    //FreezeInst(c) => Some(FreezeInst(c))
    CastInst(c) => Some(c)
    BinaryInst(b) => Some(b)
    ICmpInst(c) => Some(c)
    FCmpInst(c) => Some(c)
    StoreInst(s) => Some(s)
    GetElementPtrInst(g) => Some(g)
    SelectInst(s) => Some(s)
    InsertValueInst(i) => Some(i)
    PHINode(p) => Some(p)
    ReturnInst(r) => Some(r)
    BranchInst(b) => Some(b)
    SwitchInst(s) => Some(s)
    CallInst(c) => Some(c)
    _ => None
  }
}

///|
impl Value with tryAsUserEnum(self) {
  match self.asValueEnum() {
    LoadInst(c) => Some(LoadInst(c))
    //VAArgInst(c) => Some(VAArgInst(c))
    ExtractValueInst(c) => Some(ExtractValueInst(c))
    //FreezeInst(c) => Some(FreezeInst(c))
    CastInst(c) => Some(CastInst(c))
    BinaryInst(b) => Some(BinaryInst(b))
    ICmpInst(c) => Some(ICmpInst(c))
    FCmpInst(c) => Some(FCmpInst(c))
    StoreInst(s) => Some(StoreInst(s))
    GetElementPtrInst(g) => Some(GetElementPtrInst(g))
    SelectInst(s) => Some(SelectInst(s))
    InsertValueInst(i) => Some(InsertValueInst(i))
    PHINode(p) => Some(PHINode(p))
    ReturnInst(r) => Some(ReturnInst(r))
    BranchInst(b) => Some(BranchInst(b))
    SwitchInst(s) => Some(SwitchInst(s))
    CallInst(c) => Some(CallInst(c))
    _ => None
  }
}

///|
impl Value with tryAsInst(self) {
  match self.asValueEnum() {
    AllocaInst(c) => Some((c : &Instruction))
    LoadInst(c) => Some(c)
    //VAArgInst(c) => Some(VAArgInst(c))
    ExtractValueInst(c) => Some(c)
    //FreezeInst(c) => Some(FreezeInst(c))
    CastInst(c) => Some(c)
    BinaryInst(b) => Some(b)
    ICmpInst(c) => Some(c)
    FCmpInst(c) => Some(c)
    StoreInst(s) => Some(s)
    GetElementPtrInst(g) => Some(g)
    SelectInst(s) => Some(s)
    InsertValueInst(i) => Some(i)
    PHINode(p) => Some(p)
    ReturnInst(r) => Some(r)
    BranchInst(b) => Some(b)
    SwitchInst(s) => Some(s)
    CallInst(c) => Some(c)
    _ => None
  }
}

///|
impl Value with tryAsInstEnum(self) {
  match self.asValueEnum() {
    AllocaInst(c) => Some(AllocaInst(c))
    LoadInst(c) => Some(LoadInst(c))
    //VAArgInst(c) => Some(VAArgInst(c))
    ExtractValueInst(c) => Some(ExtractValueInst(c))
    //FreezeInst(c) => Some(FreezeInst(c))
    CastInst(c) => Some(CastInst(c))
    BinaryInst(b) => Some(BinaryInst(b))
    ICmpInst(c) => Some(ICmpInst(c))
    FCmpInst(c) => Some(FCmpInst(c))
    StoreInst(s) => Some(StoreInst(s))
    GetElementPtrInst(g) => Some(GetElementPtrInst(g))
    SelectInst(s) => Some(SelectInst(s))
    InsertValueInst(i) => Some(InsertValueInst(i))
    PHINode(p) => Some(PHINode(p))
    ReturnInst(r) => Some(ReturnInst(r))
    BranchInst(b) => Some(BranchInst(b))
    SwitchInst(s) => Some(SwitchInst(s))
    CallInst(c) => Some(CallInst(c))
    _ => None
  }
}

///|
impl Value with tryAsGlobalValue(self) {
  match self.asValueEnum() {
    Function(f) => Some((f : &GlobalValue))
    GlobalVariable(g) => Some(g)
    GlobalConstant(g) => Some(g)
    //GlobalIFunc(g) => Some(g)
    _ => None
  }
}

///|
impl Value with tryAsGlobalValueEnum(self) {
  match self.asValueEnum() {
    Function(f) => Some(Function(f))
    GlobalVariable(g) => Some(GlobalVariable(g))
    GlobalConstant(g) => Some(GlobalConstant(g))
    //GlobalIFunc(g) => Some(GlobalIFunc(g))
    _ => None
  }
}

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

///|
/// 
/// - `users`: means the values which use this value, all values can get users list
/// from `getUsers()`
/// - `uses`: means the values used by this value, not all values have `uses` list.
/// for example, constant values, function arguments, have no `uses` list.
///
/// For example: %2 = add i32 %0, %1
///
/// users of %2 : []
/// uses of %2 : [%0, %1]
///
/// users of %0 : [%2]
/// uses of %0 : []
///
/// users of %1 : [%2]
/// uses of %1 : []
struct ValueBase {
  uid : UInt64
  vty : &Type
  users : Array[&User]
} derive(Eq)

///|
//fn ValueBase::new(vty : &Type, users~ : Array[&Value] = []) -> ValueBase {
//  let uid = valueUIDAssigner.assign()
//  ValueBase::{ vty, users, uid }
//}

// =======================================================
// NamedValue
// =======================================================

pub trait NamedValue: Value {}

// =======================================================
// User
// =======================================================

///|
struct UserBase {
  operands : Array[&Value]
} derive(Eq)

///|
//pub fn UserBase::new(operands~ : Array[&Value] = []) -> UserBase {
//  let name = match name {
//    "" => None
//    name => Some(name)
//  }
//  UserBase::{ name, operands }
//}

///|
pub trait User: Value {
  asUserEnum(Self) -> UserEnum
  getUserBase(Self) -> UserBase
  getOperands(Self) -> Array[&Value] = _
  getOperand(Self, index : Int) -> &Value? = _
  getNumOperands(Self) -> Int = _
}

///|
pub impl Eq for &User with equal(self, other) {
  self.getValueBase().uid == other.getValueBase().uid
}

///|
impl User with getOperands(self) {
  self.getUserBase().operands
}

///|
impl User with getOperand(self, index : Int) {
  let operands = self.getOperands()
  guard index >= 0 && index < operands.length() else { return None }
  Some(operands[index])
}

///|
impl User with getNumOperands(self) {
  self.getOperands().length()
}

///|
pub enum UserEnum {
  //Instruction(Instruction)
  LoadInst(LoadInst)
  //VAArgInst(VAArgInst)
  ExtractValueInst(ExtractValueInst)
  //FreezeInst(FreezeInst)
  FNegInst(FNegInst)
  CastInst(CastInst)
  BinaryInst(BinaryInst)
  ICmpInst(ICmpInst)
  FCmpInst(FCmpInst)
  StoreInst(StoreInst)
  GetElementPtrInst(GetElementPtrInst)
  SelectInst(SelectInst)
  InsertValueInst(InsertValueInst)
  PHINode(PHINode)
  ReturnInst(ReturnInst)
  BranchInst(BranchInst)
  SwitchInst(SwitchInst)
  CallInst(CallInst)
}

///|
pub fn UserEnum::asUserClass(self : Self) -> &User {
  match self {
    LoadInst(l) => l as &User
    ExtractValueInst(e) => e
    BinaryInst(b) => b
    ICmpInst(c) => c
    FCmpInst(c) => c
    StoreInst(s) => s
    CastInst(c) => c
    FNegInst(f) => f
    GetElementPtrInst(g) => g
    SelectInst(s) => s
    InsertValueInst(i) => i
    PHINode(p) => p
    ReturnInst(r) => r
    BranchInst(b) => b
    SwitchInst(s) => s
    CallInst(c) => c
  }
}

// =======================================================
// Instruction
// =======================================================

///|
pub struct InstBase {
  priv bb : Ref[BasicBlock?]
  priv next : Ref[&Instruction?]
  priv prev : Ref[&Instruction?]
}

///|
//pub fn InstBase::new(
//  bb~ : BasicBlock? = None,
//  next~ : &Instruction? = None,
//  prev~ : &Instruction? = None,
//) -> InstBase {
//  InstBase::{ bb, next, prev }
//}

///|
pub trait Instruction: Value {
  getInstBase(Self) -> InstBase
  asInstEnum(Self) -> InstEnum
  getParent(Self) -> Function
  getModule(Self) -> Module = _
  getBasicBlock(Self) -> BasicBlock? = _
  getInstName(Self) -> String? = _
  isIndependent(Self) -> Bool = _
  isTerminator(Self) -> Bool = _
  next(Self) -> &Instruction? = _
  prev(Self) -> &Instruction? = _
  insertAfter(Self, &Instruction) -> Unit raise LLVMValueError = _
  insertBefore(Self, &Instruction) -> Unit raise LLVMValueError = _
  moveBefore(Self, &Instruction) -> Unit = _
  moveAfter(Self, &Instruction) -> Unit = _
  removeFromParent(Self) -> Unit = _
  eraseFromParent(Self) -> Unit = _
}

///|
impl Instruction with getModule(self) {
  self.getParent().mod
}

///|
impl Instruction with getBasicBlock(self) {
  self.getInstBase().bb.val
}

///|
impl Instruction with getInstName(self) {
  match self.asInstEnum() {
    AllocaInst(i) => i.getName()
    LoadInst(i) => i.getName()
    ExtractValueInst(i) => i.getName()
    CastInst(i) => i.getName()
    FNegInst(i) => i.getName()
    BinaryInst(i) => i.getName()
    ICmpInst(i) => i.getName()
    FCmpInst(i) => i.getName()
    StoreInst(_) => None
    GetElementPtrInst(i) => i.getName()
    SelectInst(i) => i.getName()
    InsertValueInst(i) => i.getName()
    PHINode(i) => i.getName()
    ReturnInst(_) => None
    BranchInst(_) => None
    SwitchInst(_) => None
    CallInst(i) => i.getName()
  }
}

///|
impl Instruction with isIndependent(self) {
  let bb = self.getBasicBlock()
  let prev = self.prev()
  let next = self.next()
  bb is None && prev is None && next is None
}

///|
impl Instruction with isTerminator(self) {
  match self.asInstEnum() {
    ReturnInst(_) => true
    BranchInst(_) => true
    SwitchInst(_) => true
    _ => false
  }
}

///|
impl Instruction with next(self) {
  self.getInstBase().next.val
}

///|
impl Instruction with prev(self) {
  self.getInstBase().prev.val
}

///|
impl Instruction with insertAfter(self, insertPt : &Instruction) {
  guard self.isIndependent() else {
    raise LLVMValueError(
      "Misuse `Instruction::insertAfter`, Only independent instruction can be inserted",
    )
  }
  match insertPt.next() {
    None => {
      insertPt.getInstBase().next.val = Some(self)
      self.getInstBase().prev.val = Some(insertPt)
      self.getInstBase().bb.val = insertPt.getBasicBlock()
    }
    Some(ori_next) => {
      insertPt.getInstBase().next.val = Some(self)
      self.getInstBase().prev.val = Some(insertPt)
      self.getInstBase().next.val = Some(ori_next)
      ori_next.getInstBase().prev.val = Some(self)
      self.getInstBase().bb.val = insertPt.getBasicBlock()
    }
  }
}

///|
impl Instruction with insertBefore(self, insertPt : &Instruction) {
  guard self.isIndependent() else {
    raise LLVMValueError(
      "Misuse `Instruction::insertAfter`, Only independent instruction can be inserted",
    )
  }
  match insertPt.prev() {
    None => {
      insertPt.getInstBase().prev.val = Some(self)
      self.getInstBase().next.val = Some(insertPt)
      self.getInstBase().bb.val = insertPt.getBasicBlock()
    }
    Some(ori_prev) => {
      insertPt.getInstBase().prev.val = Some(self)
      self.getInstBase().next.val = Some(insertPt)
      self.getInstBase().prev.val = Some(ori_prev)
      ori_prev.getInstBase().next.val = Some(self)
      self.getInstBase().bb.val = insertPt.getBasicBlock()
    }
  }
}

///|
impl Instruction with moveBefore(self, insertPt : &Instruction) {
  self.removeFromParent()
  try! self.insertBefore(insertPt)
}

///|
impl Instruction with moveAfter(self, insertPt : &Instruction) {
  self.removeFromParent()
  try! self.insertAfter(insertPt)
}

///|
impl Instruction with removeFromParent(self) {
  let bb = match self.getBasicBlock() {
    Some(bb) => bb
    None => return
  }
  match (self.prev(), self.next()) {
    // independent instruction, do nothing.
    (None, None) => ()
    (Some(prev), None) => prev.getInstBase().next.val = None
    // It must be the head of the basic block.
    (None, Some(next)) => bb.head = Some(next)
    (Some(prev), Some(next)) => {
      prev.getInstBase().next.val = Some(next)
      next.getInstBase().prev.val = Some(prev)
    }
  }
  self.getInstBase().bb.val = None
  self.getInstBase().prev.val = None
  self.getInstBase().next.val = None
}

///|
impl Instruction with eraseFromParent(self) {
  self.removeFromParent()
  if self.getInstName() is Some(name) {
    self.getParent().symbols.remove(name)
  }
}

///|
pub enum InstEnum {
  AllocaInst(AllocaInst)
  LoadInst(LoadInst)
  //VAArgInst(VAArgInst)
  ExtractValueInst(ExtractValueInst)
  //FreezeInst(FreezeInst)
  FNegInst(FNegInst)
  CastInst(CastInst)
  BinaryInst(BinaryInst)
  ICmpInst(ICmpInst)
  FCmpInst(FCmpInst)
  StoreInst(StoreInst)
  GetElementPtrInst(GetElementPtrInst)
  SelectInst(SelectInst)
  InsertValueInst(InsertValueInst)
  PHINode(PHINode)
  ReturnInst(ReturnInst)
  BranchInst(BranchInst)
  SwitchInst(SwitchInst)
  CallInst(CallInst)
}

// =======================================================
// UnaryInst
// =======================================================

///|
pub trait UnaryInst: Instruction {
  asUnaryInstEnum(Self) -> UnaryInstEnum
}

///|
pub enum UnaryInstEnum {
  AllocaInst(AllocaInst)
  LoadInst(LoadInst)
  //VAArgInst(VAArgInst)
  ExtractValueInst(ExtractValueInst)
  //FreezeInst(FreezeInst)
  FNegInst(FNegInst)
  CastInst(CastInst)
}

// =======================================================
// Constant
// =======================================================

///|
pub enum ConstantEnum {
  ConstantInt(ConstantInt)
  ConstantFP(ConstantFP)
  ConstantPointerNull(ConstantPointerNull)
  ConstantArray(ConstantArray)
  ConstantVector(ConstantVector)
  ConstantString(ConstantString)
  ConstantStruct(ConstantStruct)
} derive(Eq)

///|
pub trait Constant: Value {
  asConstantEnum(Self) -> ConstantEnum
}

///|
impl Eq for &Constant with equal(self, other) {
  self.asConstantEnum() == other.asConstantEnum()
}

// =======================================================
// GlobalValue
// =======================================================

///|
pub trait GlobalValue: Value {
  getGlobalValueBase(Self) -> GlobalValueBase
  asGlobalValueEnum(Self) -> GlobalValueEnum
  getModule(Self) -> Module = _
  getLinkage(Self) -> Linkage = _
  setLinkage(Self, linkage : Linkage) -> Unit = _
  setUnnamedAddr(Self, unnamed_addr : UnnamedAddr) -> Unit = _
}

///|
impl GlobalValue with getModule(self) {
  match self.asGlobalValueEnum() {
    Function({ mod, .. }) => mod
    GlobalVariable({ mod, .. }) => mod
    GlobalConstant({ mod, .. }) => mod
  }
}

///|
impl GlobalValue with getLinkage(self) {
  self.getGlobalValueBase().linkage.val
}

///|
impl GlobalValue with setLinkage(self, linkage : Linkage) {
  self.getGlobalValueBase().linkage.val = linkage
}

///|
impl GlobalValue with setUnnamedAddr(self, unnamed_addr : UnnamedAddr) {
  self.getGlobalValueBase().unnamed_addr.val = unnamed_addr
}

///|
struct GlobalValueBase {
  linkage : Ref[Linkage]
  visibility : Ref[Visibility]
  unnamed_addr : Ref[UnnamedAddr]
  //dllStorageClass: DLLStorageClass
  //threadLocal: ThreadLocalTypes
}

///|
pub enum GlobalValueEnum {
  Function(Function)
  GlobalVariable(GlobalVariable)
  GlobalConstant(GlobalConstant)
  //GlobalIFunc(GlobalIFunc)
}

///|
pub(all) enum Linkage {
  /// Externally visible function
  External
  /// Available for inspection, not emission.
  AvailableExternally
  /// Keep one copy of function when linking (inline)
  LinkOnceAny
  /// Keep one copy of function when linking,\
  /// but only replaced by something equivalent.
  LinkOnceODR
  /// Keep one copy of named function when linking (weak)
  WeakAny
  /// Keep one copy of named function when linkin,
  /// but only replaced by something equivalent.
  WeakODR
  /// Special purpose, only applies to global arrays
  Appending
  /// Rename collisions when linking (static functions).
  Internal
  /// Like Internal, but omit from symbol table.
  Private
  /// ExternalWeak linkage description.
  ExternalWeak
  /// Tentative definitions.
  Common
}

///|
pub impl Show for Linkage with output(self, logger) {
  let s = match self {
    External => ""
    AvailableExternally => "available_externally"
    LinkOnceODR => "linkonce_odr"
    LinkOnceAny => "linkonce"
    WeakAny => "weak"
    WeakODR => "weak_odr"
    Appending => "appending"
    Internal => "internal"
    Private => "private"
    ExternalWeak => "external_weak"
    Common => "common"
  }
  logger.write_string(s)
}

///|
pub(all) enum Visibility {
  Default
  Hidden
  Protected
}

///|
pub impl Show for Visibility with output(self, logger) {
  let s = match self {
    Default => ""
    Hidden => "dso_local"
    Protected => "protected"
  }
  logger.write_string(s)
}

///|
pub(all) enum UnnamedAddr {
  NoUnnamedAddr
  Local
  Global
}

///|
pub impl Show for UnnamedAddr with output(self, logger) {
  let s = match self {
    NoUnnamedAddr => ""
    Local => "local_unnamed_addr"
    Global => "unnamed_addr"
  }
  logger.write_string(s)
}

///|
pub(all) enum DLLStorageClass {
  DefaultDLLStorageClass
  /// Function to be imported from DLL.
  DLLImportStorageClass
  /// Function to be accessoble from DLL.
  DLLExportStorageClass
}