/**
 * Instruction ir
 * Create by Zhang Junbin at 2024/6/1
 */

#ifndef _INSTRUCTION_H_
#define _INSTRUCTION_H_

#include "Value.hh"

class BasicBlock;
class Function;

enum OpTag {
  ADD,
  FADD,
  SUB,
  FSUB,
  MUL,
  FMUL,
  SDIV,
  FDIV,
  SREM,
  FREM,
  AND,
  OR,
  XOR,
  EQ,
  NE,
  SLE,
  SLT,
  SGE,
  SGT,
  OEQ,
  ONE,
  OGT,
  OGE,
  OLE,
  OLT,
};

class Instruction : public Value {
 private:
  unique_ptr<vector<Use*>> useList;
  unique_ptr<vector<Value*>> valueList;

 public:
  Instruction(ValueTag vtag);
  Instruction(string name, ValueTag vTag);
  Instruction(Type* t, string name, ValueTag vTag);
  string getOpName(OpTag op) const;
  void pushValue(Value* v);
  Value* getRValue(int idx) const;
  int getRValueSize() const { return valueList->size(); }
};

class AllocaInst : public Instruction {
 private:
  Type* elemType;

 public:
  AllocaInst(Type* type, string name);
  void printIR(ostream& stream) const override;
};

class BinaryOpInst : public Instruction {
 private:
  OpTag bOpType;

 public:
  BinaryOpInst(OpTag opType, Value* op1, Value* op2, string name);
  void printIR(ostream& stream) const override;
};

class BranchInst : public Instruction {
 public:
  BranchInst(Value* cond, BasicBlock* trueBlock, BasicBlock* falseBlock);
  void printIR(ostream& stream) const override;
};

class CallInst : public Instruction {
 private:
  Function* function;

 public:
  CallInst(Function* func, string name);
  CallInst(Function* func, vector<Value*>& params, string name);
  void pushArgument(Value* value);
  void printIR(ostream& stream) const override;
};

class IcmpInst : public Instruction {
 private:
  OpTag icmpType;

 public:
  IcmpInst(OpTag opType, Value* op1, Value* op2, string name);
  void printIR(ostream& stream) const override;
};

class FcmpInst : public Instruction {
 private:
  OpTag fcmpType;

 public:
  FcmpInst(OpTag opType, Value* op1, Value* op2, string name);
  void printIR(ostream& stream) const override;
};

class FptosiInst : public Instruction {
 public:
  FptosiInst(Value* src, string name);
  void printIR(ostream& stream) const override;
};

class GetElemPtrInst : public Instruction {
 private:
  Type* ptrType;

 public:
  GetElemPtrInst(Value* ptr, Value* idx1, Value* idx2,
                 string name);
  GetElemPtrInst(Value* ptr, Value* idx1, string name);
  void printIR(ostream& stream) const override;
};

class JumpInst : public Instruction {
 public:
  JumpInst(BasicBlock* block);
  void printIR(ostream& stream) const override;
};

class LoadInst : public Instruction {
 public:
  LoadInst(Value* addr, string name);
  void printIR(ostream& stream) const override;
};

class PhiInst : public Instruction {
 public:
  PhiInst(string name);
  void printIR(ostream& stream) const override;
  void pushIncoming(Value* v, BasicBlock* bb);
};

class ReturnInst : public Instruction {
 public:
  ReturnInst(Value* retValue);
  ReturnInst();
  void printIR(ostream& stream) const override;
};

class SitofpInst : public Instruction {
 public:
  SitofpInst(Value* src, string name);
  void printIR(ostream& stream) const override;
};

class StoreInst : public Instruction {
 public:
  StoreInst(Value* value, Value* addr);
  void printIR(ostream& stream) const override;
};

// class UnaryOpInst : public Instruction {
//  private:
//   OpTag uOpType;

//  public:
//   UnaryOpInst(OpTag opType, Value* op1, string name);
//   void printIR(ostream& stream) const override;
// };

class ZextInst : public Instruction {
 public:
  ZextInst(Value* src, Type* dstType, string name);
  void printIR(ostream& stream) const override;
};

#endif