// SPDX-License-Identifier: Apache-2.0
// Copyright Apache Software Foundation 2019
/** @file

    Basic formatting support for @c BufferWriter.
 */

#pragma once

#include <cstdlib>
#include <utility>
#include <cstring>
#include <vector>
#include <unordered_map>
#include <string>
#include <iosfwd>
#include <string_view>
#include <functional>
#include <tuple>
#include <any>
#include <array>

#include "swoc/swoc_version.h"
#include "swoc/TextView.h"
#include "swoc/MemSpan.h"
#include "swoc/MemArena.h"
#include "swoc/BufferWriter.h"
#include "swoc/swoc_meta.h"

namespace swoc { inline namespace SWOC_VERSION_NS {
namespace bwf {
/** Parsed version of a format specifier.
 *
 * Literals are represented as an instance of this class, with the type set to
 * @c LITERAL_TYPE and the literal text in the @a _ext field.
 */
struct Spec {
  using self_type = Spec; ///< Self reference type.

  static constexpr char DEFAULT_TYPE = 'g'; ///< Default format type.
  static constexpr char INVALID_TYPE = 0;   ///< Type for missing or invalid specifier.
  static constexpr char LITERAL_TYPE = '"'; ///< Internal type to mark a literal.
  static constexpr char CAPTURE_TYPE = 1;   ///< Internal type to mark a capture.

  static constexpr char SIGN_ALWAYS = '+'; ///< Always print a sign character.
  static constexpr char SIGN_NEVER  = ' '; ///< Never print a sign character.
  static constexpr char SIGN_NEG    = '-'; ///< Print a sign character only for negative values (default).

  /// Constructor a default instance.
  constexpr Spec() {}

  /// Construct by parsing @a fmt.
  Spec(const TextView &fmt);

  /// Parse a specifier.
  /// State is not reset, @a this should be default constructed before calling.
  bool parse(TextView fmt);

  char _fill = ' ';      ///< Fill character.
  char _sign = SIGN_NEG; ///< Numeric sign style.
  /// Flag for how to align the output inside a limited width field.
  enum class Align : char {
    NONE,                            ///< No alignment.
    LEFT,                            ///< Left alignment '<'.
    RIGHT,                           ///< Right alignment '>'.
    CENTER,                          ///< Center alignment '^'.
    SIGN                             ///< Align plus/minus sign before numeric fill. '='
  } _align           = Align::NONE;  ///< Output field alignment.
  char _type         = DEFAULT_TYPE; ///< Type / radix indicator.
  bool _radix_lead_p = false;        ///< Print leading radix indication.
  // @a _min is unsigned because there's no point in an invalid default, 0 works fine.
  unsigned int _min = 0;                                        ///< Minimum width.
  int _prec         = -1;                                       ///< Precision
  unsigned int _max = std::numeric_limits<unsigned int>::max(); ///< Maximum width
  int _idx          = -1;                                       ///< Positional "name" of the specification.
  std::string_view _name;                                       ///< Name of the specification.
  std::string_view _ext;                                        ///< Extension if provided.

  /// Global default instance for use in situations where a format specifier isn't available.
  static const self_type DEFAULT;

  /// Validate @a c is a specifier type indicator.
  static bool is_type(char c);

  /// Check if the type flag is numeric.
  static bool is_numeric_type(char c);

  /// Check if the type is an upper case variant.
  static bool is_upper_case_type(char c);

  /// Check if the type @a in @a this is numeric.
  bool has_numeric_type() const;

  /// Check if the type in @a this is an upper case variant.
  bool has_upper_case_type() const;

  /// Check if the type is a raw pointer.
  bool has_pointer_type() const;

  /// Check if the type is valid.
  bool has_valid_type() const;

protected:
  /// Validate character is alignment character and return the appropriate enum value.
  Align align_of(char c);

  /// Validate is sign indicator.
  bool is_sign(char c);

  /// Handrolled initialization the character syntactic property data.
  static const struct Property {
    Property(); ///< Default constructor, creates initialized flag set.
    /// Flag storage, indexed by character value.
    uint8_t _data[0x100]{};
    /// Flag mask values.
    static constexpr uint8_t ALIGN_MASK        = 0x0F; ///< Alignment type.
    static constexpr uint8_t TYPE_CHAR         = 0x10; ///< A valid type character.
    static constexpr uint8_t UPPER_TYPE_CHAR   = 0x20; ///< Upper case flag.
    static constexpr uint8_t NUMERIC_TYPE_CHAR = 0x40; ///< Numeric output.
    static constexpr uint8_t SIGN_CHAR         = 0x80; ///< Is sign character.
  } _prop;                                             ///< Character property map.
};

/** Format string support.
 *
 * This contains the parsing logic for format strings and also serves as the type for pre-compiled
 * format string.
 *
 * When used by the print formatting logic, there is an abstraction layer, "extraction", which
 * performs the equivalent of the @c parse method. This allows the formatting to treat
 * pre-compiled or immediately parsed format strings the same. It also enables formatted print
 * support for any parser that can deliver literals and @c Spec instances.
 */
struct Format {
  using self_type = Format; ///< Self reference type.

  /// Empty format.
  Format() = default;

  /// Construct from a format string @a fmt.
  Format(TextView fmt);

  /// Move constructor.
  Format(self_type &&that) = default;
  /// No copy
  Format(self_type const &) = delete;

  /// Extraction support for TextView.
  struct TextViewExtractor {
    TextView _fmt; ///< Format string.

    /// @return @c true if more format string, @c false if none.
    explicit operator bool() const;

    /** Extract next formatting elements.
     *
     * @param literal_v [out] The next literal.
     * @param spec [out] The next format specifier.
     * @return @c true if @a spec was filled out, @c false if no specifier was found.
     */
    bool operator()(std::string_view &literal_v, Spec &spec);

    /** Parse elements of a format string.

        @param fmt The format string [in|out]
        @param literal A literal if found
        @param specifier A specifier if found (less enclosing braces)
        @return @c true if a specifier was found, @c false if not.

        Pull off the next literal and/or specifier from @a fmt. The return value distinguishes
        the case of no specifier found (@c false) or an empty specifier (@c true).

     */
    static bool parse(TextView &fmt, std::string_view &literal, std::string_view &specifier);
  };

  /// Wrap the format string in an extractor.
  static TextViewExtractor bind(TextView fmt);

  /// Extraction support for pre-parsed format strings.
  struct FormatExtractor {
    MemSpan<Spec const> _fmt; ///< Parsed format string.
    int _idx = 0;             ///< Element index.
    /// @return @c true if more format string, @c false if none.
    explicit operator bool() const;

    /** Extract next formatting elements.
     *
     * @param literal_v [out] The next literal.
     * @param spec [out] The next format specifier.
     * @return @c true if @a spec was filled out, @c false if no specifier was found.
     */
    bool operator()(std::string_view &literal_v, Spec &spec);
  };

  /// Wrap the format instance in an extractor.
  FormatExtractor bind() const;

  /// @return @c true if all specifiers are literal.
  bool is_literal() const;

  using Container = std::vector<Spec>;
  Container _items; ///< Items from format string.

  using iterator       = Container::iterator;
  using const_iterator = Container::const_iterator;

  iterator
  begin() {
    return _items.begin();
  }
  iterator
  end() {
    return _items.end();
  }
  const_iterator
  begin() const {
    return _items.begin();
  }
  const_iterator
  end() const {
    return _items.end();
  }
};

// Name binding - support for having format specifier names.

/** Signature for a functor bound to a name.
 *
 * @param w The output.
 * @param spec The format specifier.
 *
 * The functor is expected to write to @a w based on @a spec.
 */
using ExternalGeneratorSignature = BufferWriter &(BufferWriter &w, Spec const &spec);

/** Base class for implementing a name binding functor.
 *
 * This expected to be inherited by other classes that provide the name binding service.
 * It does a few small but handy things.
 *
 * - Force a virtual destructor.
 * - Force the implementation of the binding method by declaring it as a pure virtual.
 * - Provide a standard "missing name" method.
 */
class NameBinding {
public:
  virtual ~NameBinding(); ///< Force virtual destructor.

  /** Generate output text for @a name on the output @a w using the format specifier @a spec.
   * This must match the @c BoundNameSignature type.
   *
   * @param w Output stream.
   * @param spec Parsed format specifier.
   *
   * @note The tag name can be found in @c spec._name.
   *
   * @return @a w
   */
  virtual BufferWriter &operator()(BufferWriter &w, Spec const &spec) const = 0;

protected:
  /** Standardized missing name method.
   *
   * @param w The destination buffer.
   * @param spec Format specifier, used to determine the invalid name.
   * @return @a w
   */
  static BufferWriter &err_invalid_name(BufferWriter &w, Spec const &spec);
};

/** An explicitly empty set of bound names.
 *
 * To simplify the overall implementation, a name binding is always required to format output.
 * This class is used in situations where there is no available binding or such names would not be
 * useful. This class with @c throw on any attempt to use a name.
 */
class NilBinding : public NameBinding {
public:
  /// Do name based formatted output.
  /// This always throws an exception.
  BufferWriter &operator()(BufferWriter &, Spec const &) const override;
};

/** Associate generators with names.
 *
 *  @tparam F The function signature for generators in this container.
 *
 * This is a base class used by different types of name containers. It is not expected to be used
 * directly. A subclass should inherit from this by providing a function type @a F that is
 * suitable for the subclass generators.
 */
template <typename F> class NameMap {
public:
  /// Signature for generators.
  using Generator = std::function<F>;

protected:
  using Map = std::unordered_map<std::string_view, Generator>;

private:
  using self_type = NameMap; ///< self reference type.
public:
  /// Construct an empty container.
  NameMap();

  /// Construct and assign the names and generators in @a list
  NameMap(std::initializer_list<std::tuple<std::string_view, Generator const &>> list);

  /** Assign the @a generator to the @a name.
   *
   * @param name Name associated with the @a generator.
   * @param generator The generator function.
   */
  self_type &assign(std::string_view const &name, Generator const &generator);

  /** Check if a specific name is contained in this mapping.
   *
   * @param name Name to check.
   * @return @c true if present, @c false if not.
   */
  bool contains(std::string_view name);

protected:
  /// Copy @a name in to local storage and return a view of it.
  std::string_view localize(std::string_view const &name);

  /// Name to name generator.
  Map _map;              ///< Defined generators.
  MemArena _arena{1024}; ///< Local name storage.
};

/** A class to hold external / context-free name bindings.
 *
 * These names access external data and therefore have no context. An externally accessible
 * singleton instance of this is used as the default if no explicit name set is provided. This
 * enables the executable to establish a set of global names to be used.
 */
class ExternalNames : public NameMap<ExternalGeneratorSignature>, public NameBinding {
  using self_type  = ExternalNames;                       ///< Self reference type.
  using super_type = NameMap<ExternalGeneratorSignature>; ///< Super class.
  using Map        = super_type::Map;                     ///< Inherit from superclass.

public:
  using super_type::super_type; // import constructors.

  /// The bound accessor is this class.
  NameBinding const &bind() const;

  /// Bound name access.
  BufferWriter &operator()(BufferWriter &w, const Spec &spec) const override;

  /// @copydoc NameMap::assign(std::string_view const &name, Generator const &generator)
};

/** Associate names with context dependent generators.
 *
 * @tparam T The context type. This is used directly. If the context needs to be @c const
 * then this parameter should make that explicit, e.g. @c ContextNames<const Context>. This
 * parameter is accessible via the @c context_type alias.
 *
 * This provides a name binding that also has a local context, provided at the formatting call
 * site. The functors have access to this context and are presumed to use it to generate output.
 * This binding can also contain external generators which do not get access to the context to
 * make it convenient to add external generators as well as context generators.
 *
 * A context functor should have the signature
 * @code
 *   BufferWriter & generator(BufferWriter & w, const Spec & spec, T & context);
 * @endcode
 *
 * @a context will be the context for the binding passed to the formatter.
 *
 * This is used by the formatting logic by calling the @c bind method with a context object.
 */
template <typename T> class ContextNames : public NameMap<BufferWriter &(BufferWriter &, const Spec &, T &)> {
private:
  using self_type  = ContextNames; ///< self reference type.
  using super_type = NameMap<BufferWriter &(BufferWriter &, const Spec &, T &)>;
  using Map        = typename super_type::Map;

public:
  using context_type = T; ///< Export for external convenience.
  /// Functional type for a generator.
  using Generator = typename super_type::Generator;
  /// Signature for an external (context-free) generator.
  using ExternalGenerator = std::function<ExternalGeneratorSignature>;

  using super_type::super_type; // inherit @c super_type constructors.

  /// Specialized binding for names in an instance of @c ContextNames
  class Binding : public NameBinding {
  public:
    /** Override of virtual method to provide an implementation.
     *
     * @param w Output.
     * @param spec Format specifier for output.
     * @return @a w
     *
     * This is called from the formatting logic to generate output for a named specifier. Subclasses
     * that need to handle name dispatch differently need only override this method.
     */
    BufferWriter &
    operator()(BufferWriter &w, const Spec &spec) const override {
      return _names(w, spec, _ctx);
    }

  protected:
    /** Constructor.
     *
     * @param names Names to define for the binding.
     * @param ctx Binding context.
     */
    Binding(ContextNames const &names, context_type &ctx) : _ctx(ctx), _names(names) {}

    context_type &_ctx;         ///< Context for generators.
    ContextNames const &_names; ///< Base set of names.

    friend class ContextNames;
  };

  /** Assign the external generator @a bg to @a name.
   *
   * This is used for generators in the namespace that do not use the context.
   *
   * @param name Name associated with the generator.
   * @param bg An external generator that requires no context.
   * @return @c *this
   */
  self_type &assign(std::string_view const &name, const ExternalGenerator &bg);

  /** Assign the @a generator to the @a name.
   *
   * @param name Name associated with the @a generator.
   * @param generator The generator function.
   *
   * @internal This is a covarying override of the super type, added to covary and
   * provide documentation.
   */
  self_type &assign(std::string_view const &name, Generator const &generator);

  /** Bind the name map to a specific @a context.
   *
   * @param context The instance of @a T to use in the generators.
   * @return A reference to an internal instance of a subclass of the protocol class @c BoundNames.
   *
   * This is used when passing the context name map to the formatter.
   */
  Binding bind(context_type &context);

protected:
  /** Generate output based on the name in @a spec.
   *
   * @param w Output.
   * @param spec Format specifier for output.
   * @param ctx The context object.
   * @return @a w
   *
   * This is called from the formatting logic to generate output for a named specifier. Subclasses
   * that need to handle name dispatch differently should override this method. This method
   * performs a name lookup in the local nameset.
   */
  virtual BufferWriter &operator()(BufferWriter &w, const Spec &spec, context_type &ctx) const;
};

/** Default global names.
 * This nameset is used if no other is provided. Therefore bindings added to this nameset will be
 * available in the default formatting use.
 */
extern ExternalNames Global_Names;

// --------------- Implementation --------------------
/// --- Spec ---

inline Spec::Align
Spec::align_of(char c) {
  return static_cast<Align>(_prop._data[static_cast<unsigned>(c)] & Property::ALIGN_MASK);
}

inline bool
Spec::is_sign(char c) {
  return _prop._data[static_cast<unsigned>(c)] & Property::SIGN_CHAR;
}

inline bool
Spec::is_type(char c) {
  return _prop._data[static_cast<unsigned>(c)] & Property::TYPE_CHAR;
}

inline bool
Spec::is_upper_case_type(char c) {
  return _prop._data[static_cast<unsigned>(c)] & Property::UPPER_TYPE_CHAR;
}

inline bool
Spec::is_numeric_type(char c) {
  return _prop._data[static_cast<unsigned>(c)] & Property::NUMERIC_TYPE_CHAR;
}

inline bool
Spec::has_numeric_type() const {
  return _prop._data[static_cast<unsigned>(_type)] & Property::NUMERIC_TYPE_CHAR;
}

inline bool
Spec::has_upper_case_type() const {
  return _prop._data[static_cast<unsigned>(_type)] & Property::UPPER_TYPE_CHAR;
}

inline bool
Spec::has_pointer_type() const {
  return _type == 'p' || _type == 'P';
}

inline bool
Spec::has_valid_type() const {
  return _type != INVALID_TYPE;
}

inline auto
Format::bind(swoc::TextView fmt) -> TextViewExtractor {
  return {fmt};
}

inline auto
Format::bind() const -> FormatExtractor {
  return {_items};
}

inline Format::TextViewExtractor::operator bool() const {
  return !_fmt.empty();
}

inline Format::FormatExtractor::operator bool() const {
  return _idx < static_cast<int>(_fmt.size());
}

/// --- Names / Generators ---

inline BufferWriter &
NameBinding::err_invalid_name(BufferWriter &w, const Spec &spec) {
  return w.print("{{~{}~}}", spec._name);
}

inline BufferWriter &
NilBinding::operator()(BufferWriter &, bwf::Spec const &) const {
  throw std::runtime_error("Use of nil bound names in BW formatting");
}

template <typename T>
inline auto
ContextNames<T>::bind(context_type &ctx) -> Binding {
  return {*this, ctx};
}

template <typename T>
BufferWriter &
ContextNames<T>::operator()(BufferWriter &w, const Spec &spec, context_type &ctx) const {
  if (!spec._name.empty()) {
    if (auto spot = super_type::_map.find(spec._name); spot != super_type::_map.end()) {
      spot->second(w, spec, ctx);
    } else {
      Binding::err_invalid_name(w, spec);
    }
  }
  return w;
}

template <typename F> NameMap<F>::NameMap() {}

template <typename F> NameMap<F>::NameMap(std::initializer_list<std::tuple<std::string_view, const Generator &>> list) {
  for (auto &&[name, generator] : list) {
    this->assign(name, generator);
  }
}

template <typename F>
bool
NameMap<F>::contains(std::string_view name) {
  return _map.end() != _map.find(name);
}

template <typename F>
std::string_view
NameMap<F>::localize(std::string_view const &name) {
  auto span = _arena.alloc_span<char>(name.size());
  memcpy(span, name);
  return std::string_view(span.data(), span.size());
}

template <typename F>
auto
NameMap<F>::assign(std::string_view const &name, Generator const &generator) -> self_type & {
  _map[this->localize(name)] = generator;
  return *this;
}

inline BufferWriter &
ExternalNames::operator()(BufferWriter &w, const Spec &spec) const {
  if (!spec._name.empty()) {
    if (auto spot = _map.find(spec._name); spot != _map.end()) {
      spot->second(w, spec);
    } else {
      this->err_invalid_name(w, spec);
    }
  }
  return w;
}

inline NameBinding const &
ExternalNames::bind() const {
  return *this;
}

template <typename T>
auto
ContextNames<T>::assign(std::string_view const &name, ExternalGenerator const &bg) -> self_type & {
  // wrap @a bg in a shim that discards the context so it can be stored in the map.
  super_type::assign(name, [bg](BufferWriter &w, Spec const &spec, context_type &) -> BufferWriter & { return bg(w, spec); });
  return *this;
}

template <typename T>
auto
ContextNames<T>::assign(std::string_view const &name, Generator const &g) -> self_type & {
  super_type::assign(name, g);
  return *this;
}

/// --- Formatting ---

/// Internal signature for template generated formatting.
/// @a args is a forwarded tuple of arguments to be processed.
template <typename TUPLE> using ArgFormatterSignature = BufferWriter &(*)(BufferWriter &w, Spec const &, TUPLE const &args);

/// Internal error / reporting message generators
void Err_Bad_Arg_Index(BufferWriter &w, int i, size_t n);

// MSVC will expand the parameter pack inside a lambda but not gcc, so this indirection is required.

/// This selects the @a I th argument in the @a TUPLE arg pack and calls the formatter on it. This
/// (or the equivalent lambda) is needed because the array of formatters must have a homogenous
/// signature, not vary per argument. Effectively this indirection erases the type of the specific
/// argument being formatted. Instances of this have the signature @c ArgFormatterSignature.
template <typename TUPLE, size_t I>
BufferWriter &
Arg_Formatter(BufferWriter &w, Spec const &spec, TUPLE const &args) {
  return bwformat(w, spec, std::get<I>(args));
}

/// This exists only to expand the index sequence into an array of formatters for the tuple type
/// @a TUPLE.  Due to language limitations it cannot be done directly. The formatters can be
/// accessed via standard array access in contrast to templated tuple access. The actual array is
/// static and therefore at run time the only operation is loading the address of the array.
template <typename TUPLE, size_t... N>
ArgFormatterSignature<TUPLE> *
Get_Arg_Formatter_Array(std::index_sequence<N...>) {
  static ArgFormatterSignature<TUPLE> fa[sizeof...(N)] = {&bwf::Arg_Formatter<TUPLE, N>...};
  return fa;
}

/// Perform alignment adjustments / fill on @a w of the content in @a lw.
/// This is the normal mechanism, in cases where the length can be known or limited before
/// conversion, it can be more efficient to work in a temporary local buffer and copy out
/// as needed without moving data in the output buffer.
void Adjust_Alignment(BufferWriter &aux, Spec const &spec);

/** Format @a n as an integral value.
 *
 * @param w Output buffer.
 * @param spec Format specifier.
 * @param n Input value to format.
 * @param negative_p Input value should be treated as a negative value.
 * @return @a w
 *
 * A leading sign character will be output based on @a spec and @a negative_p.
 */
BufferWriter &Format_Integer(BufferWriter &w, Spec const &spec, uintmax_t n, bool negative_p);

/** Format @a f as a floating point value.
 *
 * @param w Output buffer.
 * @param spec Format specifier.
 * @param f Input value to format.
 * @param negative_p Input value shoudl be treated as a negative value.
 * @return @a w
 *
 * A leading sign character will be output based on @a spec and @a negative_p.
 */
BufferWriter &Format_Float(BufferWriter &w, Spec const &spec, double f, bool negative_p);

/** Format output as a hexadecimal dump.
 *
 * @param w Output buffer.
 * @param view Input view.
 * @param digits Digit array for hexadecimal digits.
 *
 * This dumps the memory in the @a view as a hexadecimal string.
 */
void Format_As_Hex(BufferWriter &w, std::string_view view, const char *digits);

/* Capture support, which allows format extractors to capture arguments and consume them.
 * This was built in order to support C style formatting, which needs to capture arguments
 * to set the minimum width and/or the precision of other arguments.
 *
 * The key component is the ability to dynamically access an element of a tuple using
 * @c std::any.
 *
 * Note: Much of this was originally in the meta support but it caused problems in use if
 * the tuple header wasn't also included. I was unable to determine why, so this code doesn't
 * depend on tuple explicitly.
 */
/// The signature for accessing an element of a tuple.
template <typename T> using TupleAccessorSignature = std::any (*)(T const &t);

/// Template access method.
template <size_t IDX, typename T>
std::any
TupleAccessor(T const &t) {
  return std::any(&std::get<IDX>(t));
}

/// Create and return an array of specialized accessors, indexed by tuple index.
template <typename T, size_t... N>
std::array<TupleAccessorSignature<T>, sizeof...(N)> &
Tuple_Accessor_Array(std::index_sequence<N...>) {
  static std::array<TupleAccessorSignature<T>, sizeof...(N)> accessors = {&TupleAccessor<N>...};
  return accessors;
}

/// Get the Nth element of the tuple as @c std::any.
template <typename T>
std::any
Tuple_Nth(T const &t, size_t idx) {
  return Tuple_Accessor_Array<T>(std::make_index_sequence<std::tuple_size<T>::value>())[idx](t);
}

/// If capture is used, the format extractor must provide a @c capture method. This isn't required
/// so make it compile time optional, but throw if the extractor sets up for capture and didn't
/// provide one.
template <typename F>
auto
arg_capture(F &&, BufferWriter &, Spec const &, std::any &&, swoc::meta::CaseTag<0>) -> void {
  throw std::runtime_error("Capture specification used in format extractor that does not support capture");
}

template <typename F>
auto
arg_capture(F &&f, BufferWriter &w, Spec const &spec, std::any &&value, swoc::meta::CaseTag<1>)
  -> decltype(f.capture(w, spec, value)) {
  return f.capture(w, spec, value);
}

/** Extract the specifier type from an Extractor.
 *
 * @tparam EXTRACTOR Format extractor functor type.
 * @tparam VIEW String view argument type.
 * @tparam SPEC Specifier argument type.
 * @return A value of type @a SPEC
 *
 * This is never called - it exists to extract @a SPEC from a format extractor functor to be used
 * to declare the specifier instance passed to the format extractor. When used in this fashion
 * with @c decltype the extracted type is a reference and that must be removed for the actual
 * declaration type. The purpose is to enable format extractors to subclass @c bwf::Spec to
 * pass additional information along, particularly to a name binding without interfering with
 * the base use case.
 */
template <typename EXTRACTOR, typename VIEW, typename SPEC>
auto
extractor_spec_type(bool (EXTRACTOR::*)(VIEW, SPEC)) -> SPEC {}

/** A pack of arguments for formatting.
 *
 * @internal After much consideration, I decided this was the correct choice, to enable type
 * erasure of the arguments to the base formatting logic. This costs a virtual function dispatch
 * but prevents the formatting logic from being duplicated for every permutation of arguments.
 * Overall, for any reasonably sized project, I think this is the better option.
 *
 * This also supports passing arguments in other than a tuple, which is necessary in order to
 * pass arguments in various containers such as a vector.
 */
class ArgPack {
public:
  virtual ~ArgPack() = default; /// Force virtual destructor for subclasses.

  /** Get argument at index @a idx.
   *
   * @param idx Argument index.
   * @return The argument value.
   *
   * In general the arguments will be stored by reference and so the returned @c std::any
   * instance will be a reference type.
   */
  virtual std::any capture(unsigned idx) const = 0;

  /** Generate formatted output for an argument.
   *
   * @param w Output.
   * @param spec Formatting specifier.
   * @param idx Argument index.
   *
   * @return @a w
   */
  virtual BufferWriter &print(BufferWriter &w, Spec const &spec, unsigned idx) const = 0;

  /// Number of arguments in the pack.
  virtual unsigned count() const = 0;
};

/** An argument pack based on a reference tuple.
 *
 * @tparam Args Type of arguments in the tuple.
 *
 * This contains a reference to the tuple, and so is only suitable for passing as a temporary.
 *
 */
template <typename... Args> class ArgTuple : public ArgPack {
public:
  /// Construct from a tuple.
  ArgTuple(std::tuple<Args...> const &tuple) : _tuple(tuple) {}

protected:
  /// Numnber of arguments in the tuple.
  unsigned count() const override;

  /// Generate formatted output on @a w for argument at @a idx.
  BufferWriter &print(BufferWriter &w, Spec const &spec, unsigned idx) const override;

  /// Capture the @a idx argument for later use.
  std::any capture(unsigned idx) const override;

  /// The source arguments.
  std::tuple<Args...> const &_tuple;
};

template <typename... Args>
unsigned
ArgTuple<Args...>::count() const {
  return sizeof...(Args);
}

template <typename... Args>
BufferWriter &
ArgTuple<Args...>::print(BufferWriter &w, Spec const &spec, unsigned idx) const {
  static const auto _fa{bwf::Get_Arg_Formatter_Array<std::tuple<Args...>>(std::index_sequence_for<Args...>{})};
  return _fa[idx](w, spec, _tuple);
}

template <typename... Args>
std::any
ArgTuple<Args...>::capture(unsigned idx) const {
  return {Tuple_Nth(_tuple, idx)};
}

} // namespace bwf

template <typename Binding, typename Extractor>
BufferWriter &
BufferWriter::print_nfv(Binding &&names, Extractor &&ex, bwf::ArgPack const &args) {
  using namespace std::literals;
  // This gets the actual specifier type from the Extractor - it must be a subclass of @c bwf::Spec
  // but this enables format extractors to use a subclass if additional data needs to be passed
  // via the specifier.
  using spec_type =
    typename std::remove_reference<decltype(bwf::extractor_spec_type(&std::remove_reference<Extractor>::type::operator()))>::type;
  int N       = args.count();
  int arg_idx = 0; // the next argument index to be processed.

  // Parser is required to return @c false if there's no more data, @c true if something was parsed.
  while (ex) {
    std::string_view lit_v;
    spec_type spec;
    bool spec_p = ex(lit_v, spec);

    // If there's a literal, just ship it.
    if (lit_v.size()) {
      this->write(lit_v);
    }

    if (spec_p) {
      if (spec._name.size() == 0) {
        spec._idx = arg_idx++;
      }

      while (true) {
        size_t width = this->remaining();
        if (spec._max < width) {
          width = spec._max;
        }

        FixedBufferWriter lw{this->aux_data(), width};

        if (0 <= spec._idx) {
          if (spec._idx < N) {
            if (spec._type == bwf::Spec::CAPTURE_TYPE) {
              bwf::arg_capture(ex, lw, spec, args.capture(spec._idx), swoc::meta::CaseArg);
            } else {
              args.print(lw, spec, spec._idx);
            }
          } else {
            bwf::Err_Bad_Arg_Index(lw, spec._idx, N);
          }
        } else if (spec._name.size()) {
          names(lw, spec);
        }
        if (lw.extent()) {
          bwf::Adjust_Alignment(lw, spec);
          if (!this->commit(lw.extent())) {
            continue;
          }
        }
        break;
      }
    }
  }
  return *this;
}

template <typename... Args>
BufferWriter &
BufferWriter::print(const TextView &fmt, Args &&...args) {
  return this->print_nfv(bwf::Global_Names.bind(), bwf::Format::bind(fmt), bwf::ArgTuple{std::forward_as_tuple(args...)});
}

template <typename... Args>
BufferWriter &
BufferWriter::print(bwf::Format const &fmt, Args &&...args) {
  return this->print_nfv(bwf::Global_Names.bind(), fmt.bind(), bwf::ArgTuple{std::forward_as_tuple(args...)});
}

template <typename... Args>
BufferWriter &
BufferWriter::print_v(TextView const &fmt, std::tuple<Args...> const &args) {
  return this->print_nfv(bwf::Global_Names.bind(), bwf::Format::bind(fmt), bwf::ArgTuple{args});
}

template <typename... Args>
BufferWriter &
BufferWriter::print_v(const bwf::Format &fmt, const std::tuple<Args...> &args) {
  return this->print_nfv(bwf::Global_Names.bind(), fmt.bind(), bwf::ArgTuple{args});
}

template <typename Binding, typename Extractor>
BufferWriter &
BufferWriter::print_nfv(Binding const &names, Extractor &&f) {
  return print_nfv(names, f, bwf::ArgTuple{std::make_tuple()});
}

template <typename Binding>
BufferWriter &
BufferWriter::print_n(Binding const &names, TextView const &fmt) {
  return print_nfv(names, bwf::Format::bind(fmt), bwf::ArgTuple{std::make_tuple()});
}

inline MemSpan<char>
BufferWriter::aux_span() {
  return {this->aux_data(), this->remaining()};
}

// ---- Formatting for specific types.

/** Output a @c string_view.
 *
 * @param w Output
 * @param spec Format specifier
 * @param sv View to format.
 * @return @a w
 *
 * @internal Must be first because it is used by other formatters, and is not inline.
 */
BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, std::string_view sv);

/** Format non-specialized pointers.
 *
 * @param w Output
 * @param spec Format specifier.
 * @param ptr Pointer to format.
 * @return @a w
 *
 * Non-specialized pointers are formatted simply as pointers, rather than the pointed to data.
 */
BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, const void *ptr);

/** Format a generic (void) memory span.
 *
 * @param w Output
 * @param spec Format specifier.
 * @param span Span to format.
 * @return @a w
 *
 * The format is by default "N:ptr" where N is the size and ptr is a hex formatter pointer. If the
 * format is "x" or "X" the span content is dumped as contiguous hex.
 */
BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, MemSpan<void const> const &span);

/** Format a generic (void) memory span.
 *
 * @param w Output
 * @param spec Format specifier.
 * @param span Span to format.
 * @return @a w
 *
 * The format is by default "N:ptr" where N is the size and ptr is a hex formatter pointer. If the
 * format is "x" or "X" the span content is dumped as contiguous hex.
 *
 * @internal Overload to avoid unfortunate ambiguities when constructing the span from other spans.
 * in particular @c MemSpan<char> vs. @c TextView.
 */
inline BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, MemSpan<void> const &span) {
  return bwformat(w, spec, span.rebind<void const>());
}

template <typename T>
BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, MemSpan<T> const &span) {
  bwf::Spec s{spec};
  // If the precision isn't already specified, make it the size of the objects in the span.
  // This will break the output into blocks of that size.
  if (spec._prec <= 0) {
    s._prec = sizeof(T);
  }
  return bwformat(w, s, span.template rebind<void const>());
}

template <size_t N>
BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, const char (&a)[N]) {
  return bwformat(w, spec, std::string_view(a, N - 1));
}

// Capture this explicitly so it doesn't go to any other pointer type.
inline BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, std::nullptr_t) {
  return bwformat(w, spec, static_cast<void *>(nullptr));
}

// Char pointer formatting
inline BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, const char *v) {
  if (spec._type == 'x' || spec._type == 'X' || spec._type == 'p' || spec._type == 'P') {
    bwformat(w, spec, static_cast<const void *>(v));
  } else if (v != nullptr) {
    bwformat(w, spec, std::string_view(v));
  } else {
    bwformat(w, spec, nullptr);
  }
  return w;
}
// doc end

inline BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, std::string const &s) {
  return bwformat(w, spec, std::string_view{s});
}

inline BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, TextView tv) {
  return bwformat(w, spec, static_cast<std::string_view>(tv));
}

template <typename X, typename V>
BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &, TransformView<X, V> &&view) {
  while (view)
    w.write(char(*(view++)));
  return w;
}

template <typename F>
auto
bwformat(BufferWriter &w, bwf::Spec const &spec, F &&f) ->
  typename std::enable_if<std::is_floating_point_v<typename std::remove_reference_t<F>>, BufferWriter &>::type {
  return f < 0 ? bwf::Format_Float(w, spec, -f, true) : bwf::Format_Float(w, spec, f, false);
}

/* Integer types.

   Due to some oddities for MacOS building, need a bit more template magic here. The underlying
   integer rendering is in @c Format_Integer which takes @c intmax_t or @c uintmax_t. For @c
   bwformat templates are defined, one for signed and one for unsigned. These forward their argument
   to the internal renderer. To avoid additional ambiguity the template argument is checked with @c
   std::enable_if to invalidate the overload if the argument type isn't a signed / unsigned
   integer. One exception to this is @c char which is handled by a previous overload in order to
   treat the value as a character and not an integer. The overall benefit is this works for any set
   of integer types, rather tuning and hoping to get just the right set of overloads.
 */

template <typename I>
auto
bwformat(BufferWriter &w, bwf::Spec const &spec, I &&i) ->
  typename std::enable_if<std::is_unsigned<typename std::remove_reference<I>::type>::value &&
                            std::is_integral<typename std::remove_reference<I>::type>::value,
                          BufferWriter &>::type {
  return bwf::Format_Integer(w, spec, i, false);
}

template <typename I>
auto
bwformat(BufferWriter &w, bwf::Spec const &spec, I &&i) ->
  typename std::enable_if<std::is_signed<typename std::remove_reference<I>::type>::value &&
                            std::is_integral<typename std::remove_reference<I>::type>::value,
                          BufferWriter &>::type {
  bool neg_p  = false;
  uintmax_t n = static_cast<uintmax_t>(i);
  if (i < 0) {
    n     = static_cast<uintmax_t>(-i);
    neg_p = true;
  }
  return bwf::Format_Integer(w, spec, n, neg_p);
}

inline BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &, char c) {
  return w.write(c);
}

inline BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, bool f) {
  using namespace std::literals;
  if ('s' == spec._type) {
    w.write(f ? "true"sv : "false"sv);
  } else if ('S' == spec._type) {
    w.write(f ? "TRUE"sv : "FALSE"sv);
  } else {
    bwf::Format_Integer(w, spec, static_cast<uintmax_t>(f), false);
  }
  return w;
}

// std::string support
/** Generate formatted output to a @c std::string @a s using format @a fmt with arguments @a args.
 *
 * @tparam Args Format argument types.
 * @param s Output string.
 * @param fmt Format string.
 * @param args A tuple of the format arguments.
 * @return @a s
 *
 * The output is generated to @a s as is. If @a s does not have sufficient space for the output
 * it is resized to be sufficient and the output formatted again. The result is that @a s will
 * containing exactly the formatted output.
 *
 * @note This function is intended for use by other formatting front ends, such as in classes that
 * need to generate formatted output. For direct use there is an overload that takes an argument
 * list.
 */
template <typename... Args>
std::string &
bwprint_v(std::string &s, TextView fmt, std::tuple<Args...> const &args) {
  auto const len = s.size(); // remember initial size
  auto printer   = [&]() { return FixedBufferWriter(s.data(), s.capacity()).print_v(fmt, args).extent(); };
  size_t n       = printer();
  s.resize(n);   // always need to resize - if shorter, must clip pre-existing text.
  if (n > len) { // dropped data, try again.
    printer();
  }
  return s;
}

/** Generate formatted output to a @c std::string @a s using format @a fmt with arguments @a args.
 *
 * @tparam Args Format argument types.
 * @param s Output string.
 * @param fmt Format string.
 * @param args Arguments for format string.
 * @return @a s
 *
 * The output is generated to @a s without resizing, completely replacing any existing text.
 * If @a s does not have sufficient space for the output
 * it is resized to be sufficient and the output formatted again. The result is that @a s will
 * contain exactly the formatted output.
 *
 * @note This is intended for direct use. For indirect use (as a backend for another class) see the
 * overload that takes an argument tuple.
 */
template <typename... Args>
std::string &
bwprint(std::string &s, TextView fmt, Args &&...args) {
  return bwprint_v(s, fmt, std::forward_as_tuple(args...));
}

/** Generate formatted output to a @c std::string @a s using format @a fmt with arguments @a args.
 *
 * @tparam Args Format argument types.
 * @param s Output string.
 * @param fmt Format string.
 * @param args Arguments for format string.
 * @return @a s
 *
 * The output is appended to @a s without resizing. If @a s does not have sufficient space for the output
 * it is resized to be sufficient and the output formatted again. The result is that @a s will
 * contain any previous text and the formatted output.
 */
template <typename... Args>
std::string &
bwappend(std::string &s, TextView fmt, Args &&...args) {
  auto const len      = s.length();   // Text to preserve.
  auto const capacity = s.capacity(); // Working space.
  auto printer        = [&]() { return FixedBufferWriter(s.data() + len, s.capacity() - len).print(fmt, args...).extent(); };
  // Resize first, otherwise capacity past @a len is cleared on @c resize.
  s.resize(capacity);
  auto n = printer() + len; // Get the final length.
  s.resize(n);              // Adjust to correct string length.
  if (n > capacity) {       // dropped data, write it again.
    printer();
  }
  return s;
}

/// @cond COVARY
template <typename... Args>
auto
FixedBufferWriter::print(TextView fmt, Args &&...args) -> self_type & {
  return static_cast<self_type &>(this->super_type::print_v(fmt, std::forward_as_tuple(args...)));
}

template <typename... Args>
auto
FixedBufferWriter::print_v(TextView fmt, std::tuple<Args...> const &args) -> self_type & {
  return static_cast<self_type &>(this->super_type::print_v(fmt, args));
}

template <typename... Args>
auto
FixedBufferWriter::print(bwf::Format const &fmt, Args &&...args) -> self_type & {
  return static_cast<self_type &>(this->super_type::print_v(fmt, std::forward_as_tuple(args...)));
}

template <typename... Args>
auto
FixedBufferWriter::print_v(bwf::Format const &fmt, std::tuple<Args...> const &args) -> self_type & {
  return static_cast<self_type &>(this->super_type::print_v(fmt, args));
}
/// @endcond

// Special case support for @c Scalar, because @c Scalar is a base utility for some other utilities
// there can be some unpleasant circularities if @c Scalar includes BufferWriter formatting. If the
// support is here then it's fine because anything using BWF for @c Scalar must include this header.
template <intmax_t N, typename C, typename T> class Scalar;
namespace detail {
template <typename T>
auto
tag_label(BufferWriter &, const bwf::Spec &, meta::CaseTag<0>) -> void {}

template <typename T>
auto
tag_label(BufferWriter &w, const bwf::Spec &, meta::CaseTag<1>) -> decltype(T::label, meta::TypeFunc<void>()) {
  w.print("{}", T::label);
}
} // namespace detail

template <intmax_t N, typename C, typename T>
BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, Scalar<N, C, T> const &x) {
  bwformat(w, spec, x.value());
  if (!spec.has_numeric_type()) {
    detail::tag_label<T>(w, spec, meta::CaseArg);
  }
  return w;
}

// Generically a stream operator is a formatter with the default specification.
template <typename V>
BufferWriter &
operator<<(BufferWriter &w, V &&v) {
  return bwformat(w, bwf::Spec::DEFAULT, std::forward<V>(v));
}

// Basic format wrappers - these are here because they're used internally.
namespace bwf {
/** Hex dump wrapper.
 *
 * This wrapper indicates the contained view should be dumped as raw memory in hexadecimal format.
 * This is intended primarily for internal use by other formatting logic.
 *
 * @see As_Hex
 */
struct HexDump {
  std::string_view _view; ///< A view of the memory to dump.

  /** Dump @a n bytes starting at @a mem as hex.
   *
   * @param mem First byte of memory to dump.
   * @param n Number of bytes.
   */
  HexDump(void const *mem, size_t n) : _view(static_cast<char const *>(mem), n) {}
};

/** Treat @a t as raw memory and dump the memory as hexadecimal.
 *
 * @tparam T Type of argument.
 * @param t Object to dump.
 * @return @a A wrapper to do a hex dump.
 *
 * This is the standard way to do a hexadecimal memory dump of an object.
 *
 * @internal This function exists so that other types can overload it for special processing,
 * which would not be possible with just @c HexDump.
 */
template <typename T>
HexDump
As_Hex(T const &t) {
  return HexDump(&t, sizeof(T));
}

} // namespace bwf

/** Format a hex dump.
 *
 * @param w The output.
 * @param spec Format specifier.
 * @param hex Hex dump wrapper.
 * @return @a w
 */
BufferWriter &bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::HexDump const &hex);

/** Format a buffer writer.
 *
 * @param w Output buffer,
 * @param spec Format specifier.
 * @param ww Input buffer
 * @return @a w
 *
 * This treats @a ww as a view and prints it as text.
 */
inline BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, BufferWriter const &ww) {
  return bwformat(w, spec, TextView(ww));
}

template <typename T>
BufferWriter &
BufferWriter::format(bwf::Spec const &spec, T const &t) {
  return bwformat(*this, spec, t);
}

template <typename T>
BufferWriter &
BufferWriter::format(bwf::Spec const &spec, T &&t) {
  return bwformat(*this, spec, t);
}

}} // namespace swoc::SWOC_VERSION_NS
