#include "mir/MIR.h"

#include <ostream>
#include <stdexcept>

namespace mir {
namespace {

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

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.name() << "\n";
  os << ".type " << function.name() << ", %function\n";
  os << function.name() << ":\n";

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

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

}  // namespace mir
