// IR 文本输出：
// - 将 IR 打印为 .ll 风格的文本
// - 支撑调试与测试对比（diff）

#include "ir/IR.h"

#include <ostream>
#include <stdexcept>
#include <string>

#include "utils/Log.h"

namespace ir {

static const char* TypeToString(const Type& ty) {
  switch (ty.GetKind()) {
    case Type::Kind::Void:
      return "void";
    case Type::Kind::Int32:
      return "i32";
    case Type::Kind::PtrInt32:
      return "i32*";
  }
  throw std::runtime_error(FormatError("ir", "未知类型"));
}

static const char* OpcodeToString(Opcode op) {
  switch (op) {
    case Opcode::Add:
      return "add";
    case Opcode::Sub:
      return "sub";
    case Opcode::Mul:
      return "mul";
    case Opcode::Div:
      return "sdiv";
    case Opcode::Mod:
      return "srem";
    case Opcode::Cmp:
      return "icmp";
    case Opcode::Br:
      return "br";
    case Opcode::CondBr:
      return "br";
    case Opcode::Call:
      return "call";
    case Opcode::Alloca:
      return "alloca";
    case Opcode::Load:
      return "load";
    case Opcode::Store:
      return "store";
    case Opcode::Ret:
      return "ret";
    case Opcode::Gep:
      return "getelementptr";
  }
  return "?";
}

static const char* CmpOpToString(CmpOp op) {
  switch (op) {
    case CmpOp::Eq:
      return "eq";
    case CmpOp::Ne:
      return "ne";
    case CmpOp::Lt:
      return "slt";
    case CmpOp::Le:
      return "sle";
    case CmpOp::Gt:
      return "sgt";
    case CmpOp::Ge:
      return "sge";
  }
  return "?";
}

static std::string ValueToString(const Value* v) {
  if (auto* ci = dynamic_cast<const ConstantInt*>(v)) {
    return std::to_string(ci->GetValue());
  }
  if (auto* gv = dynamic_cast<const GlobalVariable*>(v)) {
    return "@" + gv->GetName();
  }
  if (auto* func = dynamic_cast<const Function*>(v)) {
    return "@" + func->GetName();
  }
  if (auto* arg = dynamic_cast<const Argument*>(v)) {
    return arg->GetName();
  }
  return v ? v->GetName() : "<null>";
}

void IRPrinter::Print(const Module& module, std::ostream& os) {
  // 先打印全局变量
  for (const auto& gv : module.GetGlobalVars()) {
    if (!gv) continue;
    if (gv->IsArray()) {
      os << "@" << gv->GetName() << " = global [" << gv->GetCount()
         << " x i32] zeroinitializer\n";
    } else {
      os << "@" << gv->GetName() << " = global i32 " << gv->GetInitValue() << "\n";
    }
  }
  if (!module.GetGlobalVars().empty()) os << "\n";

  for (const auto& func : module.GetFunctions()) {
    if (func->IsExternal()) {
      // 外部函数声明：declare rettype @name(paramtypes)
      os << "declare " << TypeToString(*func->GetType()) << " @" << func->GetName() << "(";
      const auto& ptypes = func->GetParamTypes();
      for (size_t i = 0; i < ptypes.size(); ++i) {
        if (i != 0) os << ", ";
        os << TypeToString(*ptypes[i]);
      }
      os << ")\n";
      continue;
    }

    std::string params;
    const auto& param_types = func->GetParamTypes();
    for (size_t i = 0; i < param_types.size(); ++i) {
      if (i != 0) {
        params += ", ";
      }
      params += TypeToString(*param_types[i]);
      params += " %arg" + std::to_string(i);
    }
    os << "define " << TypeToString(*func->GetType()) << " @" << func->GetName()
       << "(" << params << ") {\n";
    for (const auto& bb : func->GetBlocks()) {
      if (!bb) {
        continue;
      }
      os << bb->GetName() << ":\n";
      for (const auto& instPtr : bb->GetInstructions()) {
        const auto* inst = instPtr.get();
        switch (inst->GetOpcode()) {
          case Opcode::Add:
          case Opcode::Sub:
          case Opcode::Mul:
          case Opcode::Div:
          case Opcode::Mod: {
            auto* bin = static_cast<const BinaryInst*>(inst);
            os << "  " << bin->GetName() << " = "
               << OpcodeToString(bin->GetOpcode()) << " "
               << TypeToString(*bin->GetLhs()->GetType()) << " "
               << ValueToString(bin->GetLhs()) << ", "
               << ValueToString(bin->GetRhs()) << "\n";
            break;
          }
          case Opcode::Cmp: {
            auto* cmp = static_cast<const CmpInst*>(inst);
            os << "  " << cmp->GetName() << " = " << OpcodeToString(cmp->GetOpcode())
               << " " << CmpOpToString(cmp->GetCmpOp()) << " "
               << TypeToString(*cmp->GetLhs()->GetType()) << " "
               << ValueToString(cmp->GetLhs()) << ", "
               << ValueToString(cmp->GetRhs()) << "\n";
            break;
          }
          case Opcode::Alloca: {
            auto* alloca = static_cast<const AllocaInst*>(inst);
            if (alloca->IsArray()) {
              os << "  " << alloca->GetName() << " = alloca i32, i32 "
                 << alloca->GetCount() << "\n";
            } else {
              os << "  " << alloca->GetName() << " = alloca i32\n";
            }
            break;
          }
          case Opcode::Load: {
            auto* load = static_cast<const LoadInst*>(inst);
            os << "  " << load->GetName() << " = load i32, i32* "
               << ValueToString(load->GetPtr()) << "\n";
            break;
          }
          case Opcode::Store: {
            auto* store = static_cast<const StoreInst*>(inst);
            os << "  store i32 " << ValueToString(store->GetValue())
               << ", i32* " << ValueToString(store->GetPtr()) << "\n";
            break;
          }
          case Opcode::Br: {
            auto* br = static_cast<const BranchInst*>(inst);
            os << "  br label %" << br->GetTarget()->GetName() << "\n";
            break;
          }
          case Opcode::CondBr: {
            auto* cbr = static_cast<const CondBranchInst*>(inst);
            os << "  br i1 " << ValueToString(cbr->GetCond()) << ", label %"
               << cbr->GetTrueBlock()->GetName() << ", label %"
               << cbr->GetFalseBlock()->GetName() << "\n";
            break;
          }
          case Opcode::Call: {
            auto* call = static_cast<const CallInst*>(inst);
            if (!call->GetType()->IsVoid()) {
              os << "  " << call->GetName() << " = ";
            } else {
              os << "  ";
            }
            os << "call " << TypeToString(*call->GetType()) << " @"
               << call->GetCallee()->GetName() << "(";
            for (size_t i = 0; i < call->GetNumArgs(); ++i) {
              if (i != 0) {
                os << ", ";
              }
              auto* arg = call->GetArg(i);
              os << TypeToString(*arg->GetType()) << " " << ValueToString(arg);
            }
            os << ")\n";
            break;
          }
          case Opcode::Gep: {
            auto* gep = static_cast<const GepInst*>(inst);
            auto* base = gep->GetBase();
            // 全局数组用双下标 GEP，局部 alloca 用平坦 GEP。
            if (auto* gv = dynamic_cast<const GlobalVariable*>(base)) {
              if (gv->IsArray()) {
                os << "  " << gep->GetName()
                   << " = getelementptr [" << gv->GetCount() << " x i32], ["
                   << gv->GetCount() << " x i32]* @" << gv->GetName()
                   << ", i32 0, i32 " << ValueToString(gep->GetIndex()) << "\n";
                break;
              }
            }
            os << "  " << gep->GetName()
               << " = getelementptr i32, i32* " << ValueToString(base)
               << ", i32 " << ValueToString(gep->GetIndex()) << "\n";
            break;
          }
          case Opcode::Ret: {
            auto* ret = static_cast<const ReturnInst*>(inst);
            auto* retval = ret->GetValue();
            if (!retval) {
              os << "  ret void\n";
            } else {
              os << "  ret " << TypeToString(*retval->GetType()) << " "
                 << ValueToString(retval) << "\n";
            }
            break;
          }
        }
      }
    }
    os << "}\n";
  }
}

}  // namespace ir
