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

#include "ir/IR.h"

#include <cstdio>
#include <iomanip>
#include <sstream>
#include <ostream>
#include <cstring>
#include <stdexcept>
#include <string>

#include "utils/Log.h"

namespace ir {

static std::string 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*";
    case Type::Kind::Float:
      return "float";
    case Type::Kind::PtrFloat:
      return "float*";
    case Type::Kind::Label:
      return "label";
    case Type::Kind::Array: {
      const auto* arr_ty = static_cast<const ArrayType*>(&ty);
      return "[" + std::to_string(arr_ty->GetNumElements()) + " x " +
             TypeToString(*arr_ty->GetElementType()) + "]";
    }
  }
  return "unknown";
}

static std::string 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::FAdd:
      return "fadd";
    case Opcode::FSub:
      return "fsub";
    case Opcode::FMul:
      return "fmul";
    case Opcode::FDiv:
      return "fdiv";
    case Opcode::ICmpEQ:
      return "icmp eq";
    case Opcode::ICmpNE:
      return "icmp ne";
    case Opcode::ICmpLT:
      return "icmp slt";
    case Opcode::ICmpGT:
      return "icmp sgt";
    case Opcode::ICmpLE:
      return "icmp sle";
    case Opcode::ICmpGE:
      return "icmp sge";
    case Opcode::FCmpEQ:
      return "fcmp oeq";
    case Opcode::FCmpNE:
      return "fcmp une";
    case Opcode::FCmpLT:
      return "fcmp olt";
    case Opcode::FCmpGT:
      return "fcmp ogt";
    case Opcode::FCmpLE:
      return "fcmp ole";
    case Opcode::FCmpGE:
      return "fcmp oge";
    case Opcode::Alloca:
      return "alloca";
    case Opcode::Load:
      return "load";
    case Opcode::Store:
      return "store";
    case Opcode::Ret:
      return "ret";
    case Opcode::Br:
      return "br";
    case Opcode::Call:
      return "call";
    case Opcode::GEP:
      return "getelementptr";
    case Opcode::ZExt:
      return "zext";
    case Opcode::SIToFP:
      return "sitofp";
    case Opcode::FPToSI:
      return "fptosi";
    case Opcode::Phi:
      return "phi";
  }
  return "?";
}

static std::string ValueToString(const Value* v) {
  if (!v) return "<null>";
  if (auto* ci = dynamic_cast<const ConstantInt*>(v)) {
    return std::to_string(ci->GetValue());
  }
  if (auto* cf = dynamic_cast<const ConstantFloat*>(v)) {
    const double as_double = static_cast<double>(cf->GetValue());
    uint64_t bits = 0;
    std::memcpy(&bits, &as_double, sizeof(bits));
    std::ostringstream oss;
    oss << "0x" << std::hex << std::uppercase << std::setw(16)
        << std::setfill('0') << bits;
    return oss.str();
  }
  if (v->IsGlobalValue() || v->IsFunction()) {
    return "@" + v->GetName();
  }
  if (v->IsInstruction() || v->IsArgument() || v->GetType()->IsLabel()) {
    return "%" + v->GetName();
  }
  return v->GetName();
}

static bool IsBoolLikeValue(const Value* v) {
  if (auto* inst = dynamic_cast<const Instruction*>(v)) {
    switch (inst->GetOpcode()) {
      case Opcode::ICmpEQ:
      case Opcode::ICmpNE:
      case Opcode::ICmpLT:
      case Opcode::ICmpGT:
      case Opcode::ICmpLE:
      case Opcode::ICmpGE:
      case Opcode::FCmpEQ:
      case Opcode::FCmpNE:
      case Opcode::FCmpLT:
      case Opcode::FCmpGT:
      case Opcode::FCmpLE:
      case Opcode::FCmpGE:
        return true;
      default:
        break;
    }
  }
  return false;
}

static std::string PrintedValueType(const Value* v) {
  if (IsBoolLikeValue(v)) return "i1";
  return TypeToString(*v->GetType());
}

void IRPrinter::Print(const Module& module, std::ostream& os) {
  // Print global variables
  for (const auto& gv : module.GetGlobalValues()) {
    os << "@" << gv->GetName() << " = global ";
    if (gv->GetType()->IsPtrInt32()) {
      os << "i32";
    } else if (gv->GetType()->IsPtrFloat()) {
      os << "float";
    } else {
      os << TypeToString(*gv->GetType());
    }
    if (gv->GetInitializer()) {
      os << " " << ValueToString(gv->GetInitializer());
    } else {
      os << " zeroinitializer";
    }
    os << "\n";
  }
  if (!module.GetGlobalValues().empty()) os << "\n";

  for (const auto& func : module.GetFunctions()) {
    if (func->GetBlocks().empty()) {
      os << "declare " << TypeToString(*func->GetType()) << " @" << func->GetName()
         << "(";
      // For declarations, we just need types. But Argument objects might exist.
      const auto& args = func->GetArguments();
      for (size_t i = 0; i < args.size(); ++i) {
        os << TypeToString(*args[i]->GetType());
        if (i + 1 < args.size()) os << ", ";
      }
      os << ")\n\n";
      continue;
    }
    os << "define " << TypeToString(*func->GetType()) << " @" << func->GetName()
       << "(";
    const auto& args = func->GetArguments();
    for (size_t i = 0; i < args.size(); ++i) {
      os << TypeToString(*args[i]->GetType()) << " %" << args[i]->GetName();
      if (i + 1 < args.size()) os << ", ";
    }
    os << ") {\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:
          case Opcode::FAdd:
          case Opcode::FSub:
          case Opcode::FMul:
          case Opcode::FDiv: {
            auto* bin = static_cast<const BinaryInst*>(inst);
            os << "  %" << bin->GetName() << " = "
               << OpcodeToString(bin->GetOpcode()) << " "
               << PrintedValueType(bin->GetLhs()) << " "
               << ValueToString(bin->GetLhs()) << ", "
               << ValueToString(bin->GetRhs()) << "\n";
            break;
          }
          case Opcode::ICmpEQ:
          case Opcode::ICmpNE:
          case Opcode::ICmpLT:
          case Opcode::ICmpGT:
          case Opcode::ICmpLE:
          case Opcode::ICmpGE:
          case Opcode::FCmpEQ:
          case Opcode::FCmpNE:
          case Opcode::FCmpLT:
          case Opcode::FCmpGT:
          case Opcode::FCmpLE:
          case Opcode::FCmpGE: {
            auto* bin = static_cast<const BinaryInst*>(inst);
            os << "  %" << bin->GetName() << " = "
               << OpcodeToString(bin->GetOpcode()) << " "
               << PrintedValueType(bin->GetLhs()) << " "
               << ValueToString(bin->GetLhs()) << ", "
               << ValueToString(bin->GetRhs()) << "\n";
            break;
          }
          case Opcode::Alloca: {
            auto* alloca = static_cast<const AllocaInst*>(inst);
            os << "  %" << alloca->GetName() << " = alloca ";
            if (alloca->GetType()->IsPtrInt32())
              os << "i32";
            else if (alloca->GetType()->IsPtrFloat())
              os << "float";
            else
              os << TypeToString(*alloca->GetType());
            os << "\n";
            break;
          }
          case Opcode::Load: {
            auto* load = static_cast<const LoadInst*>(inst);
            os << "  %" << load->GetName() << " = 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);
            os << "  ret ";
            if (ret->GetValue()) {
              os << TypeToString(*ret->GetValue()->GetType()) << " "
                 << ValueToString(ret->GetValue());
            } else {
              os << "void";
            }
            os << "\n";
            break;
          }
          case Opcode::Br: {
            auto* br = static_cast<const BranchInst*>(inst);
            if (br->IsConditional()) {
              os << "  br i1 " << ValueToString(br->GetCondition())
                 << ", label " << ValueToString(br->GetIfTrue()) << ", label "
                 << ValueToString(br->GetIfFalse()) << "\n";
            } else {
              os << "  br label " << ValueToString(br->GetDest()) << "\n";
            }
            break;
          }
          case Opcode::Call: {
            auto* call = static_cast<const CallInst*>(inst);
            auto* func = call->GetFunction();
            if (!call->GetType()->IsVoid()) {
              os << "  %" << call->GetName() << " = ";
            } else {
              os << "  ";
            }
            os << "call " << TypeToString(*call->GetType()) << " "
               << ValueToString(func) << "(";
            for (size_t i = 1; i < call->GetNumOperands(); ++i) {
              auto* arg = call->GetOperand(i);
              os << PrintedValueType(arg) << " " << ValueToString(arg);
              if (i + 1 < call->GetNumOperands()) os << ", ";
            }
            os << ")\n";
            break;
          }
          case Opcode::GEP: {
            auto* gep = static_cast<const GetElementPtrInst*>(inst);
            os << "  %" << gep->GetName() << " = getelementptr ";
            if (gep->GetPtr()->GetType()->IsPtrInt32())
              os << "i32";
            else if (gep->GetPtr()->GetType()->IsPtrFloat())
              os << "float";
            else
              os << TypeToString(*gep->GetPtr()->GetType());
            os << ", ";
            if (gep->GetPtr()->GetType()->IsArray()) {
              os << TypeToString(*gep->GetPtr()->GetType()) << "* ";
            } else {
              os << TypeToString(*gep->GetPtr()->GetType()) << " ";
            }
            os << ValueToString(gep->GetPtr());
            for (size_t i = 1; i < gep->GetNumOperands(); ++i) {
              os << ", " << TypeToString(*gep->GetOperand(i)->GetType()) << " "
                 << ValueToString(gep->GetOperand(i));
            }
            os << "\n";
            break;
          }
          case Opcode::ZExt:
          case Opcode::SIToFP:
          case Opcode::FPToSI: {
            auto* cast = static_cast<const CastInst*>(inst);
            os << "  %" << cast->GetName() << " = "
               << OpcodeToString(cast->GetOpcode()) << " "
               << PrintedValueType(cast->GetValue()) << " "
               << ValueToString(cast->GetValue()) << " to "
               << TypeToString(*cast->GetType()) << "\n";
            break;
          }
          case Opcode::Phi: {
            auto* phi = static_cast<const PhiInst*>(inst);
            os << "  %" << phi->GetName() << " = phi " << TypeToString(*phi->GetType()) << " ";
            for (size_t i = 0; i < phi->GetNumIncoming(); ++i) {
              if (i > 0) os << ", ";
              os << "[ " << ValueToString(phi->GetIncomingValue(i)) << ", %" << phi->GetIncomingBlock(i)->GetName() << " ]";
            }
            os << "\n";
            break;
          }
        }
      }
    }
    os << "}\n";
  }
}

}  // namespace ir
