#include "mir/MIR.h"

#include <ostream>
#include <stdexcept>

#include "utils/Log.h"

namespace mir {
namespace {

const FrameSlot& GetFrameSlot(const MachineFunction& function,
                              const Operand& operand) {
  if (operand.GetKind() != Operand::Kind::FrameIndex) {
    throw std::runtime_error(FormatError("mir", "期望 FrameIndex 操作数"));
  }
  return function.GetFrameSlot(operand.GetFrameIndex());
}

void PrintStackAccess(std::ostream& os, const char* mnemonic, PhysReg reg,
                      int offset) {
  os << "  " << mnemonic << " " << PhysRegName(reg) << ", [x29, #" << offset
     << "]\n";
}

}  // namespace

void PrintAsm(const MachineFunction& function, std::ostream& os) {
  os << ".text\n";
  os << ".global " << function.GetName() << "\n";
  os << ".type " << function.GetName() << ", %function\n";
  os << function.GetName() << ":\n";

  for (const auto& inst : function.GetEntry().GetInstructions()) {
    const auto& ops = inst.GetOperands();
    switch (inst.GetOpcode()) {
      case Opcode::Prologue:
        os << "  stp x29, x30, [sp, #-16]!\n";
        os << "  mov x29, sp\n";
        if (function.GetFrameSize() > 0) {
          os << "  sub sp, sp, #" << function.GetFrameSize() << "\n";
        }
        break;
      case Opcode::Epilogue:
        if (function.GetFrameSize() > 0) {
          os << "  add sp, sp, #" << function.GetFrameSize() << "\n";
        }
        os << "  ldp x29, x30, [sp], #16\n";
        break;
      case Opcode::MovImm:
        os << "  mov " << PhysRegName(ops.at(0).GetReg()) << ", #"
           << ops.at(1).GetImm() << "\n";
        break;
      case Opcode::LoadStack: {
        const auto& slot = GetFrameSlot(function, ops.at(1));
        PrintStackAccess(os, "ldur", ops.at(0).GetReg(), slot.offset);
        break;
      }
      case Opcode::StoreStack: {
        const auto& slot = GetFrameSlot(function, ops.at(1));
        PrintStackAccess(os, "stur", ops.at(0).GetReg(), slot.offset);
        break;
      }
      case Opcode::AddRR:
        os << "  add " << PhysRegName(ops.at(0).GetReg()) << ", "
           << PhysRegName(ops.at(1).GetReg()) << ", "
           << PhysRegName(ops.at(2).GetReg()) << "\n";
        break;
      case Opcode::Ret:
        os << "  ret\n";
        break;
    }
  }

  os << ".size " << function.GetName() << ", .-" << function.GetName()
     << "\n";
}

}  // namespace mir
