void AMXStackFramePrinter::PrintCallerName(const AMXStackFrame &frame) {
  if (IsMain(frame.amx(), frame.caller_address())) {
    stream_ << "main";
    return;
  }

  if (debug_info_.IsLoaded()) {
    AMXDebugSymbol caller =
      debug_info_.GetExactFunction(frame.caller_address());
    if (caller) {
      if (IsPublicFunction(frame.amx(), caller.GetCodeStart())
          && !IsMain(frame.amx(), caller.GetCodeStart())) {
        stream_ << "public ";
      }
      PrintTag(caller);
      stream_ << caller.GetName();
      return;
    }
  }

  const char *name = 0;
  if (frame.caller_address() != 0) {
    name = frame.amx().FindPublic(frame.caller_address());
  }
  if (name != 0) {
    stream_ << "public " << name;
  } else {
    stream_ << "??";
  }
}
void AMXStackFramePrinter::PrintCallerName(const AMXStackFrame &frame,
                                           const AMXDebugSymbol &caller) {
  bool is_public = IsPublicFunction(frame.amx(),
                                    caller.GetCodeStart());
  bool is_main = IsMain(frame.amx(), caller.GetCodeStart());
  if (is_public && !is_main) {
    *stream_ << "public ";
  }
  PrintTag(caller);
  *stream_ << caller.GetName();
}
void AMXStackFramePrinter::PrintArgumentValue(const AMXStackFrame &frame,
                                              const AMXDebugSymbol &arg,
                                              int index) {
  std::string tag_name = debug_info_->GetTagName(arg.GetTag());
  cell value = GetArgumentValue(frame.amx(), frame.address(), index);

  if (arg.IsVariable()) {
    if (tag_name == "bool") {
      *stream_ << (value ? "true" : "false");
    } else if (tag_name == "Float") {
      *stream_ << std::fixed << std::setprecision(5) << amx_ctof(value);
    } else {
      *stream_ << value;
    }
  } else {
    std::vector<AMXDebugSymbolDim> dims = arg.GetDims();

    // For arrays/references we just output their AMX address.
    char old_fill = stream_->fill('0');
    *stream_ << "@0x" << std::hex << std::setw(kCellWidthChars)
             << value << std::dec;
    stream_->fill(old_fill);

    if ((arg.IsArray() || arg.IsArrayRef())
        && dims.size() == 1
        && tag_name == "_"
        && debug_info_->GetTagName(dims[0].GetTag()) == "_")
    {
      std::string string;
      bool packed;
      
      GetStringContents(frame.amx(), value, dims[0].GetSize(), string, packed);
      *stream_ << (packed ? " !" : " ");
      
      static const std::size_t kMaxString = 30;
      if (string.length() > kMaxString) {
        string.replace(kMaxString, string.length() - kMaxString, "...");
      }
      
      *stream_ << "\"" << string << "\"";
    }
  }
}
void AMXStackFramePrinter::PrintArgument(const AMXStackFrame &frame,
                                         const AMXDebugSymbol &arg,
                                         int index) {
  if (arg.IsReference()) {
    *stream_ << "&";
  }

  PrintTag(arg);
  *stream_ << arg.GetName();

  if (!arg.IsVariable()) {
    std::vector<AMXDebugSymbolDim> dims = arg.GetDims();

    if (arg.IsArray() || arg.IsArrayRef()) {
      for (std::size_t i = 0; i < dims.size(); ++i) {
        if (dims[i].GetSize() == 0) {
          *stream_ << "[]";
        } else {
          std::string tag = debug_info_->GetTagName(dims[i].GetTag()) + ":";
          if (tag == "_:") tag.clear();
          *stream_ << "[" << tag << dims[i].GetSize() << "]";
        }
      }
    }
  }

  *stream_ << "=";
  PrintArgumentValue(frame, arg, index);
}
void AMXStackFramePrinter::PrintArgumentValue(const AMXStackFrame &frame,
                                              const AMXDebugSymbol &arg,
                                              int index) {
  std::string tag_name = debug_info_.GetTagName(arg.GetTag());
  cell value = GetArgumentValue(frame, index);

  if (arg.IsVariable()) {
    PrintValue(tag_name, value);
    return;
  }

  stream_ << "@";
  PrintAddress(value);

  if (arg.IsReference()) {
    if (cell *ptr = GetDataPtr(frame.amx(), value)) {
      stream_ << " ";
      PrintValue(tag_name, *ptr);
    }
    return;
  }

  if (arg.IsArray() || arg.IsArrayRef()) {
    std::vector<AMXDebugSymbolDim> dims = arg.GetDims();

    // Try to filter out non-printable arrays (e.g. non-strings).
    // This doesn't work 100% of the time, but it's better than nothing.
    if (dims.size() == 1
        && tag_name == "_"
        && debug_info_.GetTagName(dims[0].GetTag()) == "_")
    {
      std::string string;
      bool packed;

      GetStringContents(frame.amx(), value, dims[0].GetSize(), string, packed);
      stream_ << (packed ? " !" : " ");

      static const std::size_t kMaxString = 80;
      if (string.length() > kMaxString) {
        string.replace(kMaxString, string.length() - kMaxString, "...");
      }

      stream_ << "\"" << string << "\"";
    }
  }
}
void AMXStackFramePrinter::PrintTag(const AMXDebugSymbol &symbol) {
  std::string tag_name = debug_info_->GetTagName(symbol.GetTag());
  if (!tag_name.empty() && tag_name != "_") {
    *stream_ << tag_name << ":";
  }
}