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

#include "ir/IR.h"

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

#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::Int1:
      return "i1";
    case Type::Kind::Int32:
      return "i32";
    case Type::Kind::PtrInt32:
      return "i32*";
    case Type::Kind::Float:
      return "float";
    case Type::Kind::PtrFloat:
      return "float*";
    case Type::Kind::Array: {
      static thread_local std::string buf;
      buf = "[" + std::to_string(ty.GetNumElements()) + " x " + TypeToString(*ty.GetElementType()) + "]";
      return buf.c_str();
    }
    case Type::Kind::Pointer: {
      static thread_local std::string buf;
      buf = std::string(TypeToString(*ty.GetPointedType())) + "*";
      return buf.c_str();
    }
  }
  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::Neg:
      return "neg";
    case Opcode::Alloca:
      return "alloca";
    case Opcode::Load:
      return "load";
    case Opcode::Store:
      return "store";
    case Opcode::Ret:
      return "ret";
    case Opcode::Cmp:
      return "icmp";
    case Opcode::FCmp:
      return "fcmp";
    case Opcode::Zext:
      return "zext";
    case Opcode::Br:
    case Opcode::CondBr:
      return "br";
    case Opcode::Call:
      return "call";
    case Opcode::GEP:
      return "getelementptr";
    case Opcode::SIToFP:
      return "sitofp";
    case Opcode::FPToSI:
      return "fptosi";
  }
  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::Gt:
      return "sgt";
    case CmpOp::Le:
      return "sle";
    case CmpOp::Ge:
      return "sge";
  }
  return "?";
}

static const char* GetElementTypeName(const Type& ty) {
  if (ty.IsPointer()) {
    return TypeToString(*ty.GetPointedType());
  }
  switch (ty.GetKind()) {
    case Type::Kind::Array:
      return TypeToString(*ty.GetElementType());
    default:
      return TypeToString(ty);
  }
}

static const char* FCmpOpToString(CmpOp op) {
  switch (op) {
    case CmpOp::Eq:
      return "oeq";
    case CmpOp::Ne:
      return "one";
    case CmpOp::Lt:
      return "olt";
    case CmpOp::Gt:
      return "ogt";
    case CmpOp::Le:
      return "ole";
    case CmpOp::Ge:
      return "oge";
  }
  return "?";
}

static std::string ValueToString(const Value* v) {
  if (auto* ci = dynamic_cast<const ConstantInt*>(v)) {
    return std::to_string(ci->GetValue());
  }
  if (auto* cf = dynamic_cast<const ConstantFloat*>(v)) {
      double d = (double)cf->GetValue();
      uint64_t val;
      static_assert(sizeof(double) == sizeof(uint64_t));
      std::memcpy(&val, &d, sizeof(double));
      char buf[64];
      snprintf(buf, sizeof(buf), "0x%lX", val);
      return std::string(buf);
    }
  if (dynamic_cast<const GlobalValue*>(v)) {
      return "@" + v->GetName();
    }
    if (auto* ca = dynamic_cast<const ConstantArray*>(v)) {
      std::string s = "[";
      const auto& elems = ca->GetElements();
      for (size_t i = 0; i < elems.size(); ++i) {
        if (i > 0) s += ", ";
        s += TypeToString(*elems[i]->GetType());
        s += " ";
        s += ValueToString(elems[i]);
      }
      s += "]";
      return s;
    }
    if (dynamic_cast<const ConstantZero*>(v)) {
      return "zeroinitializer";
    }
    if (v) {
    std::string name = v->GetName();
    if (!name.empty() && name[0] != '%' && name[0] != '@') {
      return "%" + name;
    }
    return name;
  }
  return "<null>";
}

static std::string PrintLabel(const Value* bb) {
  if (!bb) return "<null>";
  std::string name = bb->GetName();
  if (name.empty()) return "<empty>";
  if (name[0] == '%') return name;
  return "%" + name;
}

static std::string PrintLabelDef(const Value* bb) {
  if (!bb) return "<null>";
  std::string name = bb->GetName();
  if (!name.empty() && name[0] == '%') return name.substr(1);
  return name;
}

void IRPrinter::Print(const Module& module, std::ostream& os) {
  for (const auto& gv : module.GetGlobalVariables()) {
    os << "@" << gv->GetName() << " = global " 
       << GetElementTypeName(*gv->GetType()) << " "
       << ValueToString(gv->GetInitializer()) << "\n";
  }
  for (const auto& func : module.GetFunctions()) {
    if (func->GetBlocks().empty()) {
      os << "declare " << TypeToString(*func->GetType()) << " @" << func->GetName() << "(";
      const auto& args = func->GetArgs();
      for (size_t i = 0; i < args.size(); ++i) {
        if (i > 0) os << ", ";
        os << TypeToString(*args[i]->GetType());
      }
      os << ")\n";
      continue;
    }
    os << "define " << TypeToString(*func->GetType()) << " @" << func->GetName() << "(";
    const auto& args = func->GetArgs();
    for (size_t i = 0; i < args.size(); ++i) {
      if (i > 0) os << ", ";
      os << TypeToString(*args[i]->GetType()) << " " << args[i]->GetName();
    }
    os << ") {\n";
    for (const auto& bb : func->GetBlocks()) {
      if (!bb) {
        continue;
      }
      os << PrintLabelDef(bb.get()) << ":\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);
            bool is_float = bin->GetType()->IsFloat();
            std::string op_name = OpcodeToString(bin->GetOpcode());
            if (is_float) {
              if (op_name == "add") op_name = "fadd";
              else if (op_name == "sub") op_name = "fsub";
              else if (op_name == "mul") op_name = "fmul";
              else if (op_name == "sdiv") op_name = "fdiv";
            }
            os << "  " << ValueToString(bin) << " = "
               << op_name << " "
               << TypeToString(*bin->GetLhs()->GetType()) << " "
               << ValueToString(bin->GetLhs()) << ", "
               << ValueToString(bin->GetRhs()) << "\n";
            break;
          }
          case Opcode::Neg: {
            auto* unary = static_cast<const UnaryInst*>(inst);
            bool is_float = unary->GetType()->IsFloat();
            os << "  " << ValueToString(unary) << " = "
               << (is_float ? "fneg" : "sub") << " "
               << TypeToString(*unary->GetUnaryOperand()->GetType()) << " "
               << (is_float ? "" : "0, ")
               << ValueToString(unary->GetUnaryOperand()) << "\n";
            break;
          }
          case Opcode::Alloca: {
            auto* alloca = static_cast<const AllocaInst*>(inst);
            os << "  " << ValueToString(alloca) << " = alloca "
               << GetElementTypeName(*alloca->GetType()) << "\n";
            break;
          }
          case Opcode::Load: {
            auto* load = static_cast<const LoadInst*>(inst);
            os << "  " << ValueToString(load) << " = load "
               << TypeToString(*load->GetType()) << ", "
               << TypeToString(*load->GetPtr()->GetType()) << " "
               << ValueToString(load->GetPtr()) << "\n";
            break;
          }
          case Opcode::Store: {
            auto* store = static_cast<const StoreInst*>(inst);
            os << "  store " << TypeToString(*store->GetValue()->GetType()) << " "
               << ValueToString(store->GetValue()) << ", "
               << TypeToString(*store->GetPtr()->GetType()) << " "
               << ValueToString(store->GetPtr()) << "\n";
            break;
          }
          case Opcode::Ret: {
            auto* ret = static_cast<const ReturnInst*>(inst);
            if (auto* val = ret->GetValue()) {
              os << "  ret " << TypeToString(*val->GetType()) << " "
                 << ValueToString(val) << "\n";
            } else {
              os << "  ret void\n";
            }
            break;
          }
          case Opcode::Cmp: {
            auto* cmp = static_cast<const CmpInst*>(inst);
            os << "  " << ValueToString(cmp) << " = icmp "
               << CmpOpToString(cmp->GetCmpOp()) << " "
               << TypeToString(*cmp->GetLhs()->GetType()) << " "
               << ValueToString(cmp->GetLhs()) << ", "
               << ValueToString(cmp->GetRhs()) << "\n";
            break;
          }
          case Opcode::FCmp: {
            auto* cmp = static_cast<const FCmpInst*>(inst);
            os << "  " << ValueToString(cmp) << " = fcmp "
               << FCmpOpToString(cmp->GetCmpOp()) << " "
               << TypeToString(*cmp->GetLhs()->GetType()) << " "
               << ValueToString(cmp->GetLhs()) << ", "
               << ValueToString(cmp->GetRhs()) << "\n";
            break;
          }
          case Opcode::Zext: {
            auto* zext = static_cast<const ZextInst*>(inst);
            os << "  " << ValueToString(zext) << " = zext "
               << TypeToString(*zext->GetOperand(0)->GetType()) << " "
               << ValueToString(zext->GetOperand(0)) << " to "
               << TypeToString(*zext->GetType()) << "\n";
            break;
          }
          case Opcode::Br: {
            auto* br = static_cast<const BranchInst*>(inst);
            os << "  br label " << PrintLabel(br->GetDest()) << "\n";
            break;
          }
          case Opcode::CondBr: {
            auto* cbr = static_cast<const CondBranchInst*>(inst);
            os << "  br i1 " << ValueToString(cbr->GetCond())
               << ", label " << PrintLabel(cbr->GetTrueBlock())
               << ", label " << PrintLabel(cbr->GetFalseBlock()) << "\n";
            break;
          }
          case Opcode::Call: {
            auto* call = static_cast<const CallInst*>(inst);
            if (call->GetType()->IsVoid()) {
              os << "  call void @" << call->GetFunc()->GetName() << "(";
            } else {
              os << "  " << ValueToString(call) << " = call " << TypeToString(*call->GetType())
                 << " @" << call->GetFunc()->GetName() << "(";
            }
            for (size_t i = 0; i < call->GetArgs().size(); ++i) {
              if (i > 0) os << ", ";
              auto* arg = call->GetArgs()[i];
              os << TypeToString(*arg->GetType()) << " " << ValueToString(arg);
            }
            os << ")\n";
            break;
          }
          case Opcode::GEP: {
            auto* gep = static_cast<const GEPInst*>(inst);
            os << "  " << ValueToString(gep) << " = getelementptr "
               << GetElementTypeName(*gep->GetPtr()->GetType()) << ", "
               << TypeToString(*gep->GetPtr()->GetType()) << " "
               << ValueToString(gep->GetPtr());
            for (auto* idx : gep->GetIndices()) {
              os << ", " << TypeToString(*idx->GetType()) << " " << ValueToString(idx);
            }
            os << "\n";
            break;
          }
          case Opcode::SIToFP: {
            auto* conv = static_cast<const SIToFPInst*>(inst);
            os << "  " << ValueToString(conv) << " = sitofp "
               << TypeToString(*conv->GetOperand(0)->GetType()) << " "
               << ValueToString(conv->GetOperand(0)) << " to "
               << TypeToString(*conv->GetType()) << "\n";
            break;
          }
          case Opcode::FPToSI: {
            auto* conv = static_cast<const FPToSIInst*>(inst);
            os << "  " << ValueToString(conv) << " = fptosi "
               << TypeToString(*conv->GetOperand(0)->GetType()) << " "
               << ValueToString(conv->GetOperand(0)) << " to "
               << TypeToString(*conv->GetType()) << "\n";
            break;
          }
        }
      }
    }
    os << "}\n";
  }
}

}  // namespace ir
