#pragma once

#include <initializer_list>
#include <iosfwd>
#include <memory>
#include <string>
#include <vector>

namespace ir {
class Module;
}

namespace mir {

class MIRContext {
 public:
  MIRContext() = default;
};

MIRContext& DefaultContext();

enum class PhysReg {
  W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15,
  W19, W20, W21, W22, W23, W24, W25, W26, W27, W28,
  X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15,
  X19, X20, X21, X22, X23, X24, X25, X26, X27, X28,
  S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15,
  X29, X30, SP
};

const char* PhysRegName(PhysReg reg);

enum class Opcode {
  Prologue,
  Epilogue,
  MovImm,
  LoadStack,
  StoreStack,
  AddRR,
  SubRR,
  MulRR,
  SDivRR,
  MSubRRRR,
  FAddRRR,
  FSubRRR,
  FMulRRR,
  FDivRRR,
  CmpRR,
  FCmpRR,
  Cset,
  B,
  BCond,
  Call,
  Ret,
  MovReg,
  Adrp,
  AddRegImm,
  LslImm,
  LdrRegReg,
  StrRegReg,
  SIToFP,
  FPToSI,
  ZExt
};

class Operand {
 public:
  enum class Kind { Reg, Imm, FrameIndex, Global, Label, Cond };

  static Operand Reg(PhysReg reg);
  static Operand Imm(int value);
  static Operand FrameIndex(int index);
  static Operand Global(std::string name);
  static Operand Label(std::string name);
  static Operand Cond(std::string cond);

  Kind GetKind() const { return kind_; }
  PhysReg GetReg() const { return reg_; }
  int GetImm() const { return imm_; }
  int GetFrameIndex() const { return imm_; }
  const std::string& GetGlobalName() const { return str_; }
  const std::string& GetLabelName() const { return str_; }
  const std::string& GetCondCode() const { return str_; }

 private:
  Operand(Kind kind, PhysReg reg, int imm, std::string str = "");

  Kind kind_;
  PhysReg reg_;
  int imm_;
  std::string str_;
};

class MachineInstr {
 public:
  MachineInstr(Opcode opcode, std::vector<Operand> operands = {});

  Opcode GetOpcode() const { return opcode_; }
  const std::vector<Operand>& GetOperands() const { return operands_; }

 private:
  Opcode opcode_;
  std::vector<Operand> operands_;
};

struct FrameSlot {
  int index = 0;
  int size = 4;
  int offset = 0;
};

class MachineBasicBlock {
 public:
  explicit MachineBasicBlock(std::string name);

  const std::string& GetName() const { return name_; }
  std::vector<MachineInstr>& GetInstructions() { return instructions_; }
  const std::vector<MachineInstr>& GetInstructions() const { return instructions_; }

  MachineInstr& Append(Opcode opcode,
                       std::initializer_list<Operand> operands = {});

 private:
  std::string name_;
  std::vector<MachineInstr> instructions_;
};

class MachineFunction {
 public:
  explicit MachineFunction(std::string name);

  const std::string& GetName() const { return name_; }
  
  MachineBasicBlock& CreateBlock(std::string name);
  std::vector<MachineBasicBlock>& GetBlocks() { return blocks_; }
  const std::vector<MachineBasicBlock>& GetBlocks() const { return blocks_; }

  // Stack/Frame management
  int CreateFrameIndex(int size = 4);
  FrameSlot& GetFrameSlot(int index);
  const FrameSlot& GetFrameSlot(int index) const;
  const std::vector<FrameSlot>& GetFrameSlots() const { return frame_slots_; }

  int GetFrameSize() const { return frame_size_; }
  void SetFrameSize(int size) { frame_size_ = size; }

 private:
  std::string name_;
  std::vector<MachineBasicBlock> blocks_;
  std::vector<FrameSlot> frame_slots_;
  int frame_size_ = 0;
};

std::vector<std::unique_ptr<MachineFunction>> LowerToMIR(const ir::Module& module);
void RunRegAlloc(MachineFunction& function);
void RunFrameLowering(MachineFunction& function);
void RunPeephole(MachineFunction& function);
void PrintAsm(const MachineFunction& function, std::ostream& os);
void PrintGlobals(const ir::Module& module, std::ostream& os);

}  // namespace mir
