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

#include "ir/IR.h"

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

#include "utils/Log.h"

namespace ir {

static std::string PredicateToString(CmpInst::Predicate pred, bool is_float) {
  if (is_float) {
    switch (pred) {
      case CmpInst::EQ: return "oeq";
      case CmpInst::NE: return "one";
      case CmpInst::LT: return "olt";
      case CmpInst::LE: return "ole";
      case CmpInst::GT: return "ogt";
      case CmpInst::GE: return "oge";
    }
  } else {
    switch (pred) {
      case CmpInst::EQ: return "eq";
      case CmpInst::NE: return "ne";
      case CmpInst::LT: return "slt";
      case CmpInst::LE: return "sle";
      case CmpInst::GT: return "sgt";
      case CmpInst::GE: return "sge";
    }
  }
  return "unknown";
}

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::Float32:
      return "float";
    case Type::Kind::Pointer: {
      const PointerType* p = static_cast<const PointerType*>(&ty);
      return TypeToString(*p->GetPointeeType()) + "*";
    }
    case Type::Kind::Array: {
      const ArrayType* a = static_cast<const ArrayType*>(&ty);
      return std::string("[") + std::to_string(a->GetSize()) + " x " + TypeToString(*a->GetElementType()) + "]";
    }
    case Type::Kind::Function:
      return "[function]";
    case Type::Kind::Label:
      return "label";
  }
  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::And: return "and";
    case Opcode::Or: return "or";
    case Opcode::Xor: return "xor";
    case Opcode::Shl: return "shl";
    case Opcode::LShr: return "lshr";
    case Opcode::AShr: return "ashr";
    case Opcode::ICmp: return "icmp";
    case Opcode::FCmp: return "fcmp";
    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::CondBr: return "br";
    case Opcode::Call: return "call";
    case Opcode::GEP: return "getelementptr";
    case Opcode::Phi: return "phi";
  }
  return "?";
}

static std::string ConstantValueToString(const ConstantValue* cv);

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)) {
    // simple float literal
    return std::to_string(cf->GetValue());
  }
  if (auto* ca = dynamic_cast<const ConstantArray*>(v)) {
    return ConstantValueToString(ca);
  }
  // fallback to name for instructions/alloca/vars — prefix with '%'
  return std::string("%") + v->GetName();
}

static std::string ConstantValueToString(const ConstantValue* cv) {
  if (!cv) return "<null-const>";
  if (auto* ci = dynamic_cast<const ConstantInt*>(cv)) return std::to_string(ci->GetValue());
  if (auto* cf = dynamic_cast<const ConstantFloat*>(cv)) {
    std::string s = std::to_string(cf->GetValue());
    size_t dot = s.find('.');
    if (dot != std::string::npos) {
      size_t e = s.find('e');
      if (e == std::string::npos) e = s.size();
      while (e > dot + 1 && s[e-1] == '0') e--;
      if (e == dot + 1) s = s.substr(0, dot + 1) + "0";
      else s = s.substr(0, e);
    }
    return s;
  }
  if (auto* ca = dynamic_cast<const ConstantArray*>(cv)) {
    // format: [ <elem_ty> <elem>, <elem_ty> <elem>, ... ]
    const auto& elems = ca->GetElements();
    std::string out = "[";
    for (size_t i = 0; i < elems.size(); ++i) {
      if (i) out += ", ";
      // each element should be printed with its type and value
      auto* e = elems[i];
      std::string etype = TypeToString(*e->GetType());
      out += etype + " " + ConstantValueToString(e);
    }
    out += "]";
    return out;
  }
  return "<const-unk>";
}

void IRPrinter::Print(const Module& module, std::ostream& os) {
  for (const auto& func : module.GetFunctions()) {
    os << "define " << TypeToString(*func->GetType()) << " @" << func->GetName() << "(";
    const auto& params = func->GetParams();
    for (size_t i = 0; i < params.size(); ++i) {
      if (i) os << ", ";
      os << TypeToString(*params[i]->GetType()) << " " << ValueToString(params[i]);
    }
    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::And:
          case Opcode::Or:
          case Opcode::Xor:
          case Opcode::Shl:
          case Opcode::LShr:
          case Opcode::AShr: {
            auto* bin = static_cast<const BinaryInst*>(inst);
            // choose opcode name: integer ops use e.g. 'add','sub', float ops use 'fadd','fsub', etc.
            std::string op_name = OpcodeToString(bin->GetOpcode());
            bool is_float = bin->GetLhs()->GetType()->IsFloat32();
            if (is_float) {
              switch (bin->GetOpcode()) {
                case Opcode::Add: op_name = "fadd"; break;
                case Opcode::Sub: op_name = "fsub"; break;
                case Opcode::Mul: op_name = "fmul"; break;
                case Opcode::Div: op_name = "fdiv"; break;
                case Opcode::Mod: op_name = "frem"; break;
                default: break;
              }
            }
            os << "  %" << bin->GetName() << " = "
              << op_name << " "
              << TypeToString(*bin->GetLhs()->GetType()) << " "
              << ValueToString(bin->GetLhs()) << ", "
              << ValueToString(bin->GetRhs()) << "\n";
            break;
          }
          case Opcode::ICmp:
          case Opcode::FCmp: {
            auto* cmp = static_cast<const CmpInst*>(inst);
            os << "  %" << cmp->GetName() << " = "
               << OpcodeToString(cmp->GetOpcode()) << " " << PredicateToString(cmp->GetPredicate(), cmp->GetOpcode() == Opcode::FCmp) << " "
               << TypeToString(*cmp->GetLhs()->GetType()) << " "
               << ValueToString(cmp->GetLhs()) << ", "
               << ValueToString(cmp->GetRhs()) << "\n";
            break;
          }
          case Opcode::Alloca: {
            auto* alloca = static_cast<const AllocaInst*>(inst);
            os << "  %" << alloca->GetName() << " = alloca "
              << TypeToString(*static_cast<const PointerType*>(alloca->GetType().get())->GetPointeeType()) << "\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 " << TypeToString(*ret->GetValue()->GetType()) << " "
               << ValueToString(ret->GetValue()) << "\n";
            break;
          }
          case Opcode::Br: {
            auto* br = static_cast<const BranchInst*>(inst);
            os << "  br label %" << br->GetTarget()->GetName() << "\n";
            break;
          }
          case Opcode::CondBr: {
            auto* condbr = static_cast<const CondBranchInst*>(inst);
            os << "  br i1 " << ValueToString(condbr->GetCond())
               << ", label %" << condbr->GetTrueBlock()->GetName()
               << ", label %" << condbr->GetFalseBlock()->GetName() << "\n";
            break;
          }
          case Opcode::Call: {
            auto* call = static_cast<const CallInst*>(inst);
            os << "  ";
            if (!call->GetName().empty()) {
              os << "%" << call->GetName() << " = ";
            }
            os << "call " << TypeToString(*call->GetCallee()->GetType()) << " @"
               << call->GetCallee()->GetName() << "(";
            for (size_t i = 0; i < call->GetArgs().size(); ++i) {
              if (i > 0) os << ", ";
              os << TypeToString(*call->GetArgs()[i]->GetType()) << " "
                 << ValueToString(call->GetArgs()[i]);
            }
            os << ")\n";
            break;
          }
          case Opcode::GEP: {
            auto* gep = static_cast<const GetElementPtrInst*>(inst);
            os << "  %" << gep->GetName() << " = getelementptr ";
            // Print element type first, then the pointer type and pointer value
            const auto ptrType = gep->GetPtr()->GetType();
            const PointerType* pty = static_cast<const PointerType*>(ptrType.get());
            os << TypeToString(*pty->GetPointeeType()) << ", "
               << TypeToString(*ptrType) << " " << ValueToString(gep->GetPtr());
            for (auto* idx : gep->GetIndices()) {
              os << ", " << TypeToString(*idx->GetType()) << " " << ValueToString(idx);
            }
            os << "\n";
            break;
          }
          case Opcode::Phi: {
            auto* phi = static_cast<const PhiInst*>(inst);
            os << "  %" << phi->GetName() << " = phi "
              << TypeToString(*phi->GetType());
            for (const auto& incoming : phi->GetIncomings()) {
              os << " [ " << ValueToString(incoming.first) << ", %"
                 << incoming.second->GetName() << " ]";
            }
            os << "\n";
            break;
          }
        }
      }
    }
    os << "}\n";
  }
}

}  // namespace ir
