/** @file

    Utilities for generating character sequences in buffers.

    @section license License

    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
 */

#pragma once

#include <cstdlib>
#include <utility>
#include <cstring>
#include <vector>
#include <string>
#include <iosfwd>
#include <string_view>

#include "tscpp/util/TextView.h"
#include "tscore/MemSpan.h"
#include "tscore/BufferWriterForward.h"

namespace ts
{
/** Base (abstract) class for concrete buffer writers.
 */
class BufferWriter
{
public:
  /** Add the character @a c to the buffer.

      @a c is added only if there is room in the buffer. If not, the instance is put in to an error
      state. In either case the value for @c extent is incremented.

      @internal If any variant of @c write discards any characters, the instance must be put in an
      error state (indicated by the override of @c error).  Derived classes must not assume the
      write() functions will not be called when the instance is in an error state.

      @return @c *this
  */
  virtual BufferWriter &write(char c) = 0;

  /** Add @a data to the buffer, up to @a length bytes.

      Data is added only up to the remaining room in the buffer. If the remaining capacity is
      exceeded (i.e. data is not written to the output), the instance is put in to an error
      state. In either case the value for @c extent is incremented by @a length.

      @internal This uses the single character write to output the data. It is presumed concrete
      subclasses will override this method to use more efficient mechanisms, dependent on the type
      of output buffer.

      @return @c *this
  */
  virtual BufferWriter &
  write(const void *data, size_t length)
  {
    const char *d = static_cast<const char *>(data);

    while (length--) {
      write(*(d++));
    }
    return *this;
  }

  /** Add the contents of @a sv to the buffer, up to the size of the view.

      Data is added only up to the remaining room in the buffer. If the remaining capacity is
      exceeded (i.e. data is not written to the output), the instance is put in to an error
      state. In either case the value for @c extent is incremented by the size of @a sv.

      @return @c *this
  */
  BufferWriter &
  write(const std::string_view &sv)
  {
    return write(sv.data(), sv.size());
  }

  /// Get the address of the first byte in the output buffer.
  virtual const char *data() const = 0;

  /// Get the error state.
  /// @return @c true if in an error state, @c false if not.
  virtual bool error() const = 0;

  /** Get the address of the next output byte in the buffer.

      Succeeding calls to non-const member functions, other than this method, must be presumed to
      invalidate the current auxiliary buffer (contents and address).

      Care must be taken to not write to data beyond this plus @c remaining bytes. Usually the
      safest mechanism is to create a @c FixedBufferWriter on the auxillary buffer and write to that.

      @code
      ts::FixedBufferWriter subw(w.auxBuffer(), w.remaining());
      write_some_stuff(subw); // generate output into the buffer.
      w.fill(subw.extent()); // update main buffer writer.
      @endcode

      @return Address of the next output byte, or @c nullptr if there is no remaining capacity.
   */
  virtual char *
  auxBuffer()
  {
    return nullptr;
  }

  /** Advance the buffer position @a n bytes.

      This treats the next @a n bytes as being written without changing the content. This is useful
      only in conjuction with @a auxBuffer to indicate that @a n bytes of the auxillary buffer has
      been written by some other mechanism.

      @internal Concrete subclasses @b must override this to advance in a way consistent with the
      specific buffer type.

      @return @c *this
  */
  virtual BufferWriter &
  fill(size_t n)
  {
    return *this;
  }

  /// Get the total capacity.
  /// @return The total number of bytes that can be written without causing an error condition.
  virtual size_t capacity() const = 0;

  /// Get the extent.
  /// @return Total number of characters that have been written, including those discarded due to an error condition.
  virtual size_t extent() const = 0;

  /// Get the output size.
  /// @return Total number of characters that are in the buffer (successfully written and not discarded)
  size_t
  size() const
  {
    return std::min(this->extent(), this->capacity());
  }

  /// Get the remaining buffer space.
  /// @return Number of additional characters that can be written without causing an error condidtion.
  size_t
  remaining() const
  {
    return capacity() - size();
  }

  /// Reduce the capacity by @a n bytes
  /// If the capacity is reduced below the current @c size the instance goes in to an error state.
  /// @return @c *this
  virtual BufferWriter &clip(size_t n) = 0;

  /// Increase the capacity by @a n bytes.
  /// If there is an error condition, this function clears it and sets the extent to the size.  It
  /// then increases the capacity by n characters.
  virtual BufferWriter &extend(size_t n) = 0;

  // Force virtual destructor.
  virtual ~BufferWriter() {}

  /** BufferWriter print.

      This prints its arguments to the @c BufferWriter @a w according to the format @a fmt. The format
      string is based on Python style formating, each argument substitution marked by braces, {}. Each
      specification has three parts, a @a name, a @a specifier, and an @a extention. These are
      separated by colons. The name should be either omitted or a number, the index of the argument to
      use. If omitted the place in the format string is used as the argument index. E.g. "{} {} {}",
      "{} {1} {}", and "{0} {1} {2}" are equivalent. Using an explicit index does not reset the
      position of subsequent substiations, therefore "{} {0} {}" is equivalent to "{0} {0} {2}".
  */
  template <typename... Rest> BufferWriter &print(TextView fmt, Rest &&... rest);
  /** Print overload to take arguments as a tuple instead of explicitly.
      This is useful for forwarding variable arguments from other functions / methods.
  */
  template <typename... Args> BufferWriter &printv(TextView fmt, std::tuple<Args...> const &args);

  /// Print using a preparsed @a fmt.
  template <typename... Args> BufferWriter &print(BWFormat const &fmt, Args &&... args);
  /** Print overload to take arguments as a tuple instead of explicitly.
      This is useful for forwarding variable arguments from other functions / methods.
  */
  template <typename... Args> BufferWriter &printv(BWFormat const &fmt, std::tuple<Args...> const &args);

  /// Output the buffer contents to the @a stream.
  /// @return The destination stream.
  virtual std::ostream &operator>>(std::ostream &stream) const = 0;
  /// Output the buffer contents to the file for file descriptor @a fd.
  /// @return The number of bytes written.
  virtual ssize_t operator>>(int fd) const = 0;
};

/** A @c BufferWrite concrete subclass to write to a fixed size buffer.
 *
 * Copies and moves are forbidden because that leaves the original in a potentially bad state. An
 * instance is cheap to construct and should be done explicitly when needed.
 */
class FixedBufferWriter : public BufferWriter
{
  using super_type = BufferWriter;
  using self_type  = FixedBufferWriter;

public:
  /** Construct a buffer writer on a fixed @a buffer of size @a capacity.

      If writing goes past the end of the buffer, the excess is dropped.

      @note If you create a instance of this class with capacity == 0 (and a nullptr buffer), you
      can use it to measure the number of characters a series of writes would result it (from the
      extent() value) without actually writing.
   */
  FixedBufferWriter(char *buffer, size_t capacity);

  /** Construct empty buffer.
   * This is useful for doing sizing before allocating a buffer.
   */
  FixedBufferWriter(std::nullptr_t);

  FixedBufferWriter(const FixedBufferWriter &) = delete;
  FixedBufferWriter &operator=(const FixedBufferWriter &) = delete;
  FixedBufferWriter(FixedBufferWriter &&)                 = delete;
  FixedBufferWriter &operator=(FixedBufferWriter &&) = delete;

  FixedBufferWriter(MemSpan &span) : _buf(span.begin()), _capacity(static_cast<size_t>(span.size())) {}

  /// Write a single character @a c to the buffer.
  FixedBufferWriter &
  write(char c) override
  {
    if (_attempted < _capacity) {
      _buf[_attempted] = c;
    }
    ++_attempted;

    return *this;
  }

  /// Write @a data to the buffer, up to @a length bytes.
  FixedBufferWriter &
  write(const void *data, size_t length) override
  {
    const size_t newSize = _attempted + length;

    if (_buf) {
      if (newSize <= _capacity) {
        std::memcpy(_buf + _attempted, data, length);
      } else if (_attempted < _capacity) {
        std::memcpy(_buf + _attempted, data, _capacity - _attempted);
      }
    }
    _attempted = newSize;

    return *this;
  }

  // Bring in non-overridden methods.
  using super_type::write;

  /// Return the output buffer.
  const char *
  data() const override
  {
    return _buf;
  }

  /// Return whether there has been an error.
  bool
  error() const override
  {
    return _attempted > _capacity;
  }

  /// Get the start of the unused output buffer.
  char *
  auxBuffer() override
  {
    return error() ? nullptr : _buf + _attempted;
  }

  /// Advance the used part of the output buffer.
  FixedBufferWriter &
  fill(size_t n) override
  {
    _attempted += n;

    return *this;
  }

  /// Get the total capacity of the output buffer.
  size_t
  capacity() const override
  {
    return _capacity;
  }

  /// Get the total output sent to the writer.
  size_t
  extent() const override
  {
    return _attempted;
  }

  /// Reduce the capacity by @a n.
  FixedBufferWriter &
  clip(size_t n) override
  {
    ink_assert(n <= _capacity);

    _capacity -= n;

    return *this;
  }

  /// Extend the capacity by @a n.
  FixedBufferWriter &
  extend(size_t n) override
  {
    if (error()) {
      _attempted = _capacity;
    }

    _capacity += n;

    return *this;
  }

  /// Reduce extent to @a n.
  /// If @a n is less than the capacity the error condition, if any, is cleared.
  /// This can be used to clear the output by calling @c reduce(0). In contrast
  /// to @c clip this reduces the data in the buffer, rather than the capacity.
  self_type &
  reduce(size_t n)
  {
    ink_assert(n <= _attempted);

    _attempted = n;
    return *this;
  }

  /// Clear the buffer, reset to empty (no data).
  /// This is a convenience for reusing a buffer. For instance
  /// @code
  ///   bw.reset().print("....."); // clear old data and print new data.
  /// @endcode
  /// This is equivalent to @c reduce(0) but clearer for that case.
  self_type &
  reset()
  {
    _attempted = 0;
    return *this;
  }

  /// Provide a string_view of all successfully written characters.
  std::string_view
  view() const
  {
    return std::string_view(_buf, size());
  }

  /// Provide a @c string_view of all successfully written characters as a user conversion.
  operator std::string_view() const { return view(); }

  /** Get a @c FixedBufferWriter for the unused output buffer.

      If @a reserve is non-zero then the buffer size for the auxillary writer will be @a reserve bytes
      smaller than the remaining buffer. This "reserves" space for additional output after writing
      to the auxillary buffer, in a manner similar to @c clip / @c extend.
   */
  FixedBufferWriter
  auxWriter(size_t reserve = 0)
  {
    return {this->auxBuffer(), reserve < this->remaining() ? this->remaining() - reserve : 0};
  }

  /// Output the buffer contents to the @a stream.
  std::ostream &operator>>(std::ostream &stream) const override;
  /// Output the buffer contents to the file for file descriptor @a fd.
  ssize_t operator>>(int fd) const override;

protected:
  char *const _buf;      ///< Output buffer.
  size_t _capacity;      ///< Size of output buffer.
  size_t _attempted = 0; ///< Number of characters written, including those discarded due error condition.
private:
  // INTERNAL - Overload removed, make sure it's not used.
  BufferWriter &write(size_t n);
};

/** A buffer writer that writes to an array of char (of fixed size N) that is internal to the writer instance.

    It's called 'local' because instances are typically declared as stack-allocated, local function
    variables.
*/
template <size_t N> class LocalBufferWriter : public FixedBufferWriter
{
public:
  /// Construct an empty writer.
  LocalBufferWriter() : FixedBufferWriter(_arr, N) {}

  /// Copy another writer.
  /// Any data in @a that is copied over.
  LocalBufferWriter(const LocalBufferWriter &that) : FixedBufferWriter(_arr, N)
  {
    std::memcpy(_arr, that._arr, that.size());
    _attempted = that._attempted;
  }

  /// Copy another writer.
  /// Any data in @a that is copied over.
  template <size_t K> LocalBufferWriter(const LocalBufferWriter<K> &that) : FixedBufferWriter(_arr, N)
  {
    size_t n = std::min(N, that.size());
    std::memcpy(_arr, that.data(), n);
    // if a bigger space here, don't leave a gap between size and attempted.
    _attempted = N > K ? n : that.extent();
  }

  /// Copy another writer.
  /// Any data in @a that is copied over.
  LocalBufferWriter &
  operator=(const LocalBufferWriter &that)
  {
    if (this != &that) {
      _attempted = that.extent();
      std::memcpy(_buf, that._buf, that.size());
    }

    return *this;
  }

  /// Copy another writer.
  /// Any data in @a that is copied over.
  template <size_t K>
  LocalBufferWriter &
  operator=(const LocalBufferWriter<K> &that)
  {
    size_t n = std::min(N, that.size());
    // if a bigger space here, don't leave a gap between size and attempted.
    _attempted = N > K ? n : that.extent();
    std::memcpy(_arr, that.data(), n);
    return *this;
  }

  /// Increase capacity by @a n.
  LocalBufferWriter &
  extend(size_t n) override
  {
    if (error()) {
      _attempted = _capacity;
    }

    _capacity += n;

    ink_assert(_capacity <= N);

    return *this;
  }

protected:
  char _arr[N]; ///< output buffer.
};

// --------------- Implementation --------------------
/** Overridable formatting for type @a V.

    This is the output generator for data to a @c BufferWriter. Default stream operators call this with
    the default format specification (although those can be overloaded specifically for performance).
    User types should overload this function to format output for that type.

    @code
      BufferWriter &
      bwformat(BufferWriter &w, BWFSpec  &, V const &v)
      {
        // generate output on @a w
      }
    @endcode

    The argument can be passed by value if that would be more efficient.
  */

namespace bw_fmt
{
  /// 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, BWFSpec 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, BWFSpec 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 langauge limitations it cannot be done directly. The formatters can be
  /// accessed via standard array access in constrast 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)] = {&bw_fmt::Arg_Formatter<TUPLE, N>...};
    return fa;
  }

  /// Perform alignment adjustments / fill on @a w of the content in @a lw.
  /// This is the normal mechanism, but a number of the builtin types handle this internally
  /// for performance reasons.
  void Do_Alignment(BWFSpec const &spec, BufferWriter &w, BufferWriter &lw);

  /// Global named argument table.
  using GlobalSignature = void (*)(BufferWriter &, BWFSpec const &);
  using GlobalTable     = std::map<std::string_view, GlobalSignature>;
  extern GlobalTable BWF_GLOBAL_TABLE;
  extern GlobalSignature Global_Table_Find(std::string_view name);

  /// Generic integral conversion.
  BufferWriter &Format_Integer(BufferWriter &w, BWFSpec const &spec, uintmax_t n, bool negative_p);

  /// Generic floating point conversion.
  BufferWriter &Format_Floating(BufferWriter &w, BWFSpec const &spec, double n, bool negative_p);

} // namespace bw_fmt

using BWGlobalNameSignature = bw_fmt::GlobalSignature;
/// Add a global @a name to BufferWriter formatting, output generated by @a formatter.
/// @return @c true if the name was register, @c false if not (name already in use).
bool bwf_register_global(std::string_view name, BWGlobalNameSignature formatter);

/** Compiled BufferWriter format.

    @note This is not as useful as hoped, the performance is not much better using this than parsing
    on the fly (about 30% better, which is fine for tight loops but not for general use).
 */
class BWFormat
{
public:
  /// Construct from a format string @a fmt.
  BWFormat(TextView fmt);
  ~BWFormat();

  /** Parse elements of a format string.

      @param fmt The format string [in|out]
      @param literal A literal if found
      @param spec 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 &spec);

  /** Parsed items from the format string.

      Literals are handled by putting the literal text in the extension field and setting the
      global formatter @a _gf to @c LiteralFormatter, which writes out the extension as a literal.
   */
  struct Item {
    BWFSpec _spec; ///< Specification.
    /// If the spec has a global formatter name, cache it here.
    mutable bw_fmt::GlobalSignature _gf = nullptr;

    Item() {}
    Item(BWFSpec const &spec, bw_fmt::GlobalSignature gf) : _spec(spec), _gf(gf) {}
  };

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

protected:
  /// Handles literals by writing the contents of the extension directly to @a w.
  static void Format_Literal(BufferWriter &w, BWFSpec const &spec);
};

template <typename... Args>
BufferWriter &
BufferWriter::print(TextView fmt, Args &&... args)
{
  return this->printv(fmt, std::forward_as_tuple(args...));
}

template <typename... Args>
BufferWriter &
BufferWriter::printv(TextView fmt, std::tuple<Args...> const &args)
{
  using namespace std::literals;
  static constexpr int N = sizeof...(Args); // used as loop limit
  static const auto fa   = bw_fmt::Get_Arg_Formatter_Array<decltype(args)>(std::index_sequence_for<Args...>{});
  int arg_idx            = 0; // the next argument index to be processed.

  while (fmt.size()) {
    // Next string piece of interest is an (optional) literal and then an (optinal) format specifier.
    // There will always be a specifier except for the possible trailing literal.
    std::string_view lit_v;
    std::string_view spec_v;
    bool spec_p = BWFormat::parse(fmt, lit_v, spec_v);

    if (lit_v.size()) {
      this->write(lit_v);
    }

    if (spec_p) {
      BWFSpec spec{spec_v}; // parse the specifier.
      size_t width = this->remaining();
      if (spec._max < width) {
        width = spec._max;
      }
      FixedBufferWriter lw{this->auxBuffer(), width};

      if (spec._name.size() == 0) {
        spec._idx = arg_idx;
      }
      if (0 <= spec._idx) {
        if (spec._idx < N) {
          fa[spec._idx](lw, spec, args);
        } else {
          bw_fmt::Err_Bad_Arg_Index(lw, spec._idx, N);
        }
        ++arg_idx;
      } else if (spec._name.size()) {
        auto gf = bw_fmt::Global_Table_Find(spec._name);
        if (gf) {
          gf(lw, spec);
        } else {
          lw.write("{~"sv).write(spec._name).write("~}"sv);
        }
      }
      if (lw.extent()) {
        bw_fmt::Do_Alignment(spec, *this, lw);
      }
    }
  }
  return *this;
}

template <typename... Args>
BufferWriter &
BufferWriter::print(BWFormat const &fmt, Args &&... args)
{
  return this->printv(fmt, std::forward_as_tuple(args...));
}

template <typename... Args>
BufferWriter &
BufferWriter::printv(BWFormat const &fmt, std::tuple<Args...> const &args)
{
  using namespace std::literals;
  static constexpr int N = sizeof...(Args);
  static const auto fa   = bw_fmt::Get_Arg_Formatter_Array<decltype(args)>(std::index_sequence_for<Args...>{});

  for (BWFormat::Item const &item : fmt._items) {
    size_t width = this->remaining();
    if (item._spec._max < width) {
      width = item._spec._max;
    }
    FixedBufferWriter lw{this->auxBuffer(), width};
    if (item._gf) {
      item._gf(lw, item._spec);
    } else {
      auto idx = item._spec._idx;
      if (0 <= idx && idx < N) {
        fa[idx](lw, item._spec, args);
      } else if (item._spec._name.size()) {
        lw.write("{~"sv).write(item._spec._name).write("~}"sv);
      }
    }
    bw_fmt::Do_Alignment(item._spec, *this, lw);
  }
  return *this;
}

// Pointers that are not specialized.
inline BufferWriter &
bwformat(BufferWriter &w, BWFSpec const &spec, const void *ptr)
{
  BWFSpec ptr_spec{spec};
  ptr_spec._radix_lead_p = true;
  if (ptr_spec._type == BWFSpec::DEFAULT_TYPE || ptr_spec._type == 'p') {
    ptr_spec._type = 'x'; // if default or 'p;, switch to lower hex.
  } else if (ptr_spec._type == 'P') {
    ptr_spec._type = 'X'; // P means upper hex, overriding other specializations.
  }
  return bw_fmt::Format_Integer(w, ptr_spec, reinterpret_cast<intptr_t>(ptr), false);
}

// MemSpan
BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, MemSpan const &span);

// -- Common formatters --

BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, std::string_view sv);

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

inline BufferWriter &
bwformat(BufferWriter &w, BWFSpec const &spec, const char *v)
{
  if (spec._type == 'x' || spec._type == 'X') {
    bwformat(w, spec, static_cast<const void *>(v));
  } else {
    bwformat(w, spec, std::string_view(v));
  }
  return w;
}

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

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

template <typename F>
auto
bwformat(BufferWriter &w, BWFSpec const &spec, F &&f) ->
  typename std::enable_if<std::is_floating_point<typename std::remove_reference<F>::type>::value, BufferWriter &>::type
{
  return f < 0 ? bw_fmt::Format_Floating(w, spec, -f, true) : bw_fmt::Format_Floating(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, BWFSpec 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 bw_fmt::Format_Integer(w, spec, i, false);
}

template <typename I>
auto
bwformat(BufferWriter &w, BWFSpec 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
{
  return i < 0 ? bw_fmt::Format_Integer(w, spec, -i, true) : bw_fmt::Format_Integer(w, spec, i, false);
}

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

inline BufferWriter &
bwformat(BufferWriter &w, BWFSpec 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 {
    bw_fmt::Format_Integer(w, spec, static_cast<uintmax_t>(f), false);
  }
  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, BWFSpec::DEFAULT, std::forward<V>(v));
}

// std::string support
/** Print to a @c std::string

    Print to the string @a s. If there is overflow then resize the string sufficiently to hold the output
    and print again. The effect is the string is resized only as needed to hold the output.
 */
template <typename... Args>
std::string &
bwprintv(std::string &s, ts::TextView fmt, std::tuple<Args...> const &args)
{
  auto len = s.size(); // remember initial size
  size_t n = ts::FixedBufferWriter(const_cast<char *>(s.data()), s.size()).printv(fmt, std::move(args)).extent();
  s.resize(n);   // always need to resize - if shorter, must clip pre-existing text.
  if (n > len) { // dropped data, try again.
    ts::FixedBufferWriter(const_cast<char *>(s.data()), s.size()).printv(fmt, std::move(args));
  }
  return s;
}

template <typename... Args>
std::string &
bwprint(std::string &s, ts::TextView fmt, Args &&... args)
{
  return bwprintv(s, fmt, std::forward_as_tuple(args...));
}

// -- FixedBufferWriter --
inline FixedBufferWriter::FixedBufferWriter(char *buffer, size_t capacity) : _buf(buffer), _capacity(capacity)
{
  ink_assert(_capacity == 0 || buffer != nullptr);
}

inline FixedBufferWriter::FixedBufferWriter(std::nullptr_t) : _buf(nullptr), _capacity(0) {}

} // end namespace ts

namespace std
{
inline ostream &
operator<<(ostream &s, ts::BufferWriter const &w)
{
  return w >> s;
}
} // end namespace std
