void AMXStackFramePrinter::Print(const AMXStackFrame &frame) {
  PrintReturnAddress(frame);
  *stream_ << " in ";

  AMXDebugSymbol caller = GetCallerSymbol(frame);
  if (caller) {
    PrintCallerName(frame, caller);
  } else {
    PrintCallerName(frame);
  }

  *stream_ << " (";
  PrintArgumentList(frame);
  *stream_ << ")";

  if (HaveDebugInfo() && UsesAutomata(frame)) {
    *stream_ << " ";
    PrintState(frame);
  }

  if (HaveDebugInfo() && frame.return_address() != 0) {
    *stream_ << " at ";
    PrintSourceLocation(frame.return_address());
  }
}
void AMXStackFramePrinter::PrintArgumentValue(const AMXStackFrame &frame,
                                              int index) {
  cell value = GetArgumentValue(frame.amx(), frame.address(), index);
  char old_fill = stream_->fill('0');
  *stream_ << std::hex << "0x" << std::setw(kCellWidthChars)
           << value << std::dec;
  stream_->fill(old_fill);
}
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::PrintReturnAddress(const AMXStackFrame &frame) {
  if (frame.return_address() == 0) {
    *stream_ << "????????";
  } else {
    char old_fill = stream_->fill('0');
    *stream_ << std::hex << std::setw(kCellWidthChars)
             << frame.return_address()
             << std::dec;
    stream_->fill(old_fill);
  }
}
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) {
  if (IsMain(frame.amx(), frame.caller_address())) {
    *stream_ << "main";
  } else {
    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::Print(const AMXStackFrame &frame) {
  PrintReturnAddress(frame);
  stream_ << " in ";

  PrintCallerNameAndArguments(frame);

  if (debug_info_.IsLoaded() && UsesAutomata(frame)) {
    stream_ << " ";
    PrintState(frame);
  }

  if (debug_info_.IsLoaded() && frame.return_address() != 0) {
    stream_ << " at ";
    PrintSourceLocation(frame.return_address());
  }
}
AMXDebugSymbol AMXStackFramePrinter::GetCallerSymbol(
                                            const AMXStackFrame &frame) const {
  AMXDebugSymbol caller;
  if (HaveDebugInfo()) {
    caller = debug_info_->GetExactFunction(frame.caller_address());
  }
  return caller;
}
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::PrintArgumentList(const AMXStackFrame &frame) {
  AMXStackFrame prev_frame = frame.GetPrevious();

  if (prev_frame) {
    // Although the symbol's code start address points at the state
    // switch code block, function arguments actually use the real
    // function address for the code start because in different states
    // they may be not the same.
    cell arg_address = frame.caller_address();
    if (UsesAutomata(frame)) {
      arg_address = GetRealFunctionAddress(frame.amx(), frame.caller_address(),
                                                        frame.return_address());
    }

    std::vector<AMXDebugSymbol> args;
    int num_actual_args = 0;

    if (HaveDebugInfo()) {
      std::remove_copy_if(debug_info_->GetSymbols().begin(),
                          debug_info_->GetSymbols().end(),
                          std::back_inserter(args),
                          std::not1(IsArgumentOf(arg_address)));
      std::sort(args.begin(), args.end());
      num_actual_args = static_cast<int>(args.size());
    } else {
      static const int kMaxRawArgs = 10;
      num_actual_args = std::min(kMaxRawArgs,
                                 GetNumArgs(frame.amx(), prev_frame.address()));
    }

    // Print a comma-separated list of arguments and their values.
    // If debug info is not available argument names are omitted,
    // so only the values are printed.
    for (int i = 0; i < num_actual_args; i++) {
      if (i > 0) {
        *stream_ << ", ";
      }
      if (HaveDebugInfo()) {
        PrintArgument(prev_frame, args[i], i);
      } else {
        PrintArgument(prev_frame, i);
      }
    }

    // If the number of actual arguments passed to the function exceeds
    // that obtained via debug info the function may take a variable
    // number of arguments. In this case we don't evaluate them but just
    // just say that they are present as we can't say anything about
    // their names and types.
    int num_var_args = GetNumArgs(frame.amx(), prev_frame.address())
                     - num_actual_args;
    if (num_var_args > 0) {
      if (num_actual_args != 0) {
        *stream_ << ", ";
      }
      PrintVariableArguments(num_var_args);
    }
  }
}
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::PrintState(const AMXStackFrame &frame) {
  AMXDebugAutomaton automaton = debug_info_->GetAutomaton(
    GetStateVarAddress(frame.amx(), frame.caller_address()));
  if (automaton) {
    std::vector<cell> states = GetStateIDs(frame.amx(), frame.caller_address(),
                                                        frame.return_address());
    if (!states.empty()) {
      *stream_ << "<" << automaton.GetName() << ":";
      for (std::size_t i = 0; i < states.size(); i++ ) {
        if (i > 0) {
          *stream_ << ", ";
        }
        AMXDebugState state = debug_info_->GetState(automaton.GetID(), states[i]);
        if (state) {
          *stream_ << state.GetName();
        }
      }
      *stream_ << ">";
    }
  }
}
bool AMXStackFramePrinter::UsesAutomata(const AMXStackFrame &frame) const {
  return GetStateVarAddress(frame.amx(), frame.caller_address()) > 0;
}
void AMXStackFramePrinter::PrintReturnAddress(const AMXStackFrame &frame) {
  PrintAddress(frame.return_address());
}