#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, W8, W9, X29, X30, SP };

const char* PhysRegName(PhysReg reg);

enum class Opcode {
  Prologue,
  Epilogue,
  MovImm,
  LoadStack,
  StoreStack,
  AddRR,
  Ret,
};

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

  static Operand Reg(PhysReg reg);
  static Operand Imm(int value);
  static Operand FrameIndex(int index);

  Kind GetKind() const { return kind_; }
  PhysReg GetReg() const { return reg_; }
  int GetImm() const { return imm_; }
  int GetFrameIndex() const { return imm_; }

 private:
  Operand(Kind kind, PhysReg reg, int imm);

  Kind kind_;
  PhysReg reg_;
  int imm_;
};

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& GetEntry() { return entry_; }
  const MachineBasicBlock& GetEntry() const { return entry_; }

  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_;
  MachineBasicBlock entry_;
  std::vector<FrameSlot> frame_slots_;
  int frame_size_ = 0;
};

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

}  // namespace mir
