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

/** @file

    Formatted output for BufferWriter.
 */

#include <array>
#include <cctype>
#include <chrono>
#include <cmath>
#include <ctime>
#include <cmath>
#include <sys/param.h>
#include <unistd.h>

#include "swoc/BufferWriter.h"
#include "swoc/bwf_base.h"
#include "swoc/bwf_ex.h"
#include "swoc/swoc_meta.h"
#include "swoc/DiscreteRange.h"

using namespace std::literals;
using namespace swoc::literals;


using swoc::svto_radix;

namespace swoc { inline namespace SWOC_VERSION_NS {

namespace bwf {
ExternalNames& Global_Names() {
  static swoc::bwf::ExternalNames Global_Names;
  return Global_Names;
}

const Spec Spec::DEFAULT;

#pragma GCC diagnostic ignored "-Wchar-subscripts"

Spec::Property::Property() {
  memset(_data, 0, sizeof(_data));
  _data['b'] = TYPE_CHAR | NUMERIC_TYPE_CHAR;
  _data['B'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR;
  _data['d'] = TYPE_CHAR | NUMERIC_TYPE_CHAR;
  _data['g'] = TYPE_CHAR;
  _data['o'] = TYPE_CHAR | NUMERIC_TYPE_CHAR;
  _data['p'] = TYPE_CHAR;
  _data['P'] = TYPE_CHAR | UPPER_TYPE_CHAR;
  _data['s'] = TYPE_CHAR;
  _data['S'] = TYPE_CHAR | UPPER_TYPE_CHAR;
  _data['x'] = TYPE_CHAR | NUMERIC_TYPE_CHAR;
  _data['X'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR;

  _data[SIGN_NEVER]  = SIGN_CHAR;
  _data[SIGN_NEG]    = SIGN_CHAR;
  _data[SIGN_ALWAYS] = SIGN_CHAR;

  _data['<'] = static_cast<uint8_t>(Spec::Align::LEFT);
  _data['>'] = static_cast<uint8_t>(Spec::Align::RIGHT);
  _data['^'] = static_cast<uint8_t>(Spec::Align::CENTER);
  _data['='] = static_cast<uint8_t>(Spec::Align::SIGN);
}

Spec::Spec(const TextView &fmt) {
  this->parse(fmt);
}

/// Parse a format specification.
bool
Spec::parse(TextView fmt) {
  TextView num; // temporary for number parsing.

  _name = fmt.take_prefix_at(':');
  // if it's parsable as a number, treat it as an index.
  num    = _name;
  auto n = svto_radix<10>(num);
  if (num.empty()) {
    _idx = static_cast<decltype(_idx)>(n);
  }

  if (fmt.size()) {
    TextView sz = fmt.take_prefix_at(':'); // the format specifier.
    _ext        = fmt;                     // anything past the second ':' is the extension.
    if (sz.size()) {
      // fill and alignment
      if ('%' == *sz) { // enable URI encoding of the fill character so
        // metasyntactic chars can be used if needed.
        if (sz.size() < 4) {
          throw std::invalid_argument("Fill URI encoding without 2 hex characters and align mark");
        }
        if (Align::NONE == (_align = align_of(sz[3]))) {
          throw std::invalid_argument("Fill URI without alignment mark");
        }
        char d1 = sz[1], d0 = sz[2];
        if (!isxdigit(d0) || !isxdigit(d1)) {
          throw std::invalid_argument("URI encoding with non-hex characters");
        }
        _fill  = isdigit(d0) ? d0 - '0' : tolower(d0) - 'a' + 10;
        _fill += (isdigit(d1) ? d1 - '0' : tolower(d1) - 'a' + 10) << 4;
        sz    += 4;
      } else if (sz.size() > 1 && Align::NONE != (_align = align_of(sz[1]))) {
        _fill  = *sz;
        sz    += 2;
      } else if (Align::NONE != (_align = align_of(*sz))) {
        ++sz;
      }
      if (!sz.size()) {
        return true;
      }
      // sign
      if (is_sign(*sz)) {
        _sign = *sz;
        if (!(++sz).size()) {
          return true;
        }
      }
      // radix prefix
      if ('#' == *sz) {
        _radix_lead_p = true;
        if (!(++sz).size()) {
          return true;
        }
      }
      // 0 fill for integers
      if ('0' == *sz) {
        if (Align::NONE == _align) {
          _align = Align::SIGN;
        }
        _fill = '0';
        ++sz;
      }
      num = sz;
      n   = svto_radix<10>(num);
      if (num.size() < sz.size()) {
        _min = static_cast<decltype(_min)>(n);
        sz   = num;
        if (!sz.size()) {
          return true;
        }
      }
      // precision
      if ('.' == *sz) {
        num = ++sz;
        n   = svto_radix<10>(num);
        if (num.size() < sz.size()) {
          _prec = static_cast<decltype(_prec)>(n);
          sz    = num;
          if (!sz.size()) {
            return true;
          }
        } else {
          throw std::invalid_argument("Precision mark without precision");
        }
      }
      // style (type). Hex, octal, etc.
      if (is_type(*sz)) {
        _type = *sz;
        if (!(++sz).size()) {
          return true;
        }
      }
      // maximum width
      if (',' == *sz) {
        num = ++sz;
        n   = svto_radix<10>(num);
        if (num.size() < sz.size()) {
          _max = static_cast<decltype(_max)>(n);
          sz   = num;
          if (!sz.size()) {
            return true;
          }
        } else {
          throw std::invalid_argument("Maximum width mark without width");
        }
        // Can only have a type indicator here if there was a max width.
        if (is_type(*sz)) {
          _type = *sz;
          if (!(++sz).size()) {
            return true;
          }
        }
      }
    }
  }
  return true;
}

/// Parse out the next literal and/or format specifier from the format string.
/// Pass the results back in @a literal and @a specifier as appropriate.
/// Update @a fmt to strip the parsed text.
/// @return @c true if a specifier was parsed, @c false if not.
bool
Format::TextViewExtractor::parse(TextView &fmt, std::string_view &literal, std::string_view &specifier) {
  TextView::size_type off = 0;

  // Check for brace delimiters.
  off = fmt.find_if([](char c) { return '{' == c || '}' == c; });
  if (off == TextView::npos) {
    // not found, it's a literal, ship it.
    literal = fmt;
    fmt.remove_prefix(literal.size());
    return false;
  }

  // Processing for braces that don't enclose specifiers.
  if (fmt.size() > off + 1) {
    char c1 = fmt[off];
    char c2 = fmt[off + 1];
    if (c1 == c2) {
      // double braces count as literals, but must tweak to output only 1 brace.
      literal = fmt.take_prefix(off + 1);
      return false;
    } else if ('}' == c1) {
      throw std::invalid_argument("Unopened } in format string.");
    } else {
      literal = std::string_view{fmt.data(), off};
      fmt.remove_prefix(off + 1);
    }
  } else {
    throw std::invalid_argument("Invalid trailing character in format string.");
  }

  if (fmt.size()) {
    // Need to be careful, because an empty format is OK and it's hard to tell
    // if take_prefix_at failed to find the delimiter or found it as the first
    // byte.
    off = fmt.find('}');
    if (off == TextView::npos) {
      throw std::invalid_argument("BWFormat: Unclosed { in format string");
    }
    specifier = fmt.take_prefix(off);
    return true;
  }
  return false;
}

bool
Format::TextViewExtractor::operator()(std::string_view &literal_v, Spec &spec) {
  if (!_fmt.empty()) {
    std::string_view spec_v;
    if (parse(_fmt, literal_v, spec_v)) {
      return spec.parse(spec_v);
    }
  }
  return false;
}

bool
Format::FormatExtractor::operator()(std::string_view &literal_v, swoc::bwf::Spec &spec) {
  literal_v = {};
  if (_idx < int(_fmt.size()) && _fmt[_idx]._type == Spec::LITERAL_TYPE) {
    literal_v = _fmt[_idx++]._ext;
  }
  if (_idx < int(_fmt.size()) && _fmt[_idx]._type != Spec::LITERAL_TYPE) {
    spec = _fmt[_idx++];
    return true;
  }
  return false;
}

void
Err_Bad_Arg_Index(BufferWriter &w, int i, size_t n) {
  static const Format fmt{"{{BAD_ARG_INDEX:{} of {}}}"sv};
  w.print(fmt, i, n);
}

/** This performs generic alignment operations.

   If a formatter specialization performs this operation instead, that should
   result in output that is at least @a spec._min characters wide, which will
   cause this function to make no further adjustments.
 */
void
Adjust_Alignment(BufferWriter &aux, Spec const &spec) {
  size_t extent = aux.extent();
  size_t min    = spec._min;
  if (extent < min) {
    size_t delta      = min - extent;
    size_t left_delta = 0, right_delta = delta; // left justify values
    if (Spec::Align::RIGHT == spec._align) {
      left_delta  = delta;
      right_delta = 0;
    } else if (Spec::Align::CENTER == spec._align) {
      left_delta  = delta / 2;
      right_delta = (delta + 1) / 2;
    }
    if (left_delta > 0) {
      size_t work_area = extent + left_delta;
      aux.commit(left_delta);          // cover work area.
      aux.copy(left_delta, 0, extent); // move to create space for left fill.
      aux.discard(work_area);          // roll back to write the left fill.
      for (int i = left_delta; i > 0; --i) {
        aux.write(spec._fill);
      }
      aux.commit(extent);
    }
    for (int i = right_delta; i > 0; --i) {
      aux.write(spec._fill);
    }

  } else {
    size_t max = spec._max;
    if (max < extent) {
      aux.discard(extent - max);
    }
  }
}

// Conversions from remainder to character, in upper and lower case versions.
// Really only useful for hexadecimal currently.
namespace {
char UPPER_DIGITS[]                                 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char LOWER_DIGITS[]                                 = "0123456789abcdefghijklmnopqrstuvwxyz";
static const std::array<uint64_t, 11> POWERS_OF_TEN = {
  {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000}
};
} // namespace

/// Templated radix based conversions. Only a small number of radix are
/// supported and providing a template minimizes cut and paste code while also
/// enabling compiler optimizations (e.g. for power of 2 radix the modulo /
/// divide become bit operations).
template <size_t RADIX>
size_t
To_Radix(uintmax_t n, char *buff, size_t width, char *digits) {
  static_assert(1 < RADIX && RADIX <= 36, "RADIX must be in the range 2..36");
  char *out = buff + width;
  if (n) {
    while (n) {
      *--out  = digits[n % RADIX];
      n      /= RADIX;
    }
  } else {
    *--out = '0';
  }
  return (buff + width) - out;
}

/** Output a string with a specified alignment.
 *
 * @tparam F Output functor.
 * @param w Output buffer.
 * @param f Functor that generates output.
 * @param align Alignment.
 * @param width Field width.
 * @param fill Fill character.
 * @param neg Character to use for negative (null char means do not output).
 */
template <typename F>
void
Write_Aligned(BufferWriter &w, F const &f, Spec::Align align, int width, char fill, char neg) {
  switch (align) {
  case Spec::Align::LEFT:
    if (neg) {
      w.write(neg);
    }
    f();
    while (width-- > 0) {
      w.write(fill);
    }
    break;
  case Spec::Align::RIGHT:
    while (width-- > 0) {
      w.write(fill);
    }
    if (neg) {
      w.write(neg);
    }
    f();
    break;
  case Spec::Align::CENTER:
    for (int i = width / 2; i > 0; --i) {
      w.write(fill);
    }
    if (neg) {
      w.write(neg);
    }
    f();
    for (int i = (width + 1) / 2; i > 0; --i) {
      w.write(fill);
    }
    break;
  case Spec::Align::SIGN:
    if (neg) {
      w.write(neg);
    }
    while (width-- > 0) {
      w.write(fill);
    }
    f();
    break;
  default:
    if (neg) {
      w.write(neg);
    }
    f();
    break;
  }
}

BufferWriter &
Format_Integer(BufferWriter &w, Spec const &spec, uintmax_t i, bool neg_p) {
  size_t n     = 0;
  int width    = static_cast<int>(spec._min); // amount left to fill.
  char neg     = 0;
  char prefix1 = spec._radix_lead_p ? '0' : 0;
  char prefix2 = 0;
  char buff[std::numeric_limits<uintmax_t>::digits + 1];

  if (spec._sign != Spec::SIGN_NEVER) {
    if (neg_p) {
      neg = '-';
    } else if (spec._sign == Spec::SIGN_ALWAYS) {
      neg = spec._sign;
    }
  }

  switch (spec._type) {
  case 'x':
    prefix2 = 'x';
    n       = bwf::To_Radix<16>(i, buff, sizeof(buff), bwf::LOWER_DIGITS);
    break;
  case 'X':
    prefix2 = 'X';
    n       = bwf::To_Radix<16>(i, buff, sizeof(buff), bwf::UPPER_DIGITS);
    break;
  case 'b':
    prefix2 = 'b';
    n       = bwf::To_Radix<2>(i, buff, sizeof(buff), bwf::LOWER_DIGITS);
    break;
  case 'B':
    prefix2 = 'B';
    n       = bwf::To_Radix<2>(i, buff, sizeof(buff), bwf::UPPER_DIGITS);
    break;
  case 'o':
    n = bwf::To_Radix<8>(i, buff, sizeof(buff), bwf::LOWER_DIGITS);
    break;
  default:
    prefix1 = 0;
    n       = bwf::To_Radix<10>(i, buff, sizeof(buff), bwf::LOWER_DIGITS);
    break;
  }
  // Clip fill width by stuff that's already committed to be written.
  if (neg) {
    --width;
  }
  if (prefix1) {
    --width;
    if (prefix2) {
      --width;
    }
  }
  width -= static_cast<int>(n);
  std::string_view digits{buff + sizeof(buff) - n, n};

  if (spec._align == Spec::Align::SIGN) { // custom for signed case because
    // prefix and digits are seperated.
    if (neg) {
      w.write(neg);
    }
    if (prefix1) {
      w.write(prefix1);
      if (prefix2) {
        w.write(prefix2);
      }
    }
    while (width-- > 0) {
      w.write(spec._fill);
    }
    w.write(digits);
  } else { // use generic Write_Aligned
    Write_Aligned(
      w,
      [&]() {
        if (prefix1) {
          w.write(prefix1);
          if (prefix2) {
            w.write(prefix2);
          }
        }
        w.write(digits);
      },
      spec._align, width, spec._fill, neg);
  }
  return w;
}

/// Format for floating point values. Seperates floating point into a whole
/// number and a fraction. The fraction is converted into an unsigned integer
/// based on the specified precision, spec._prec. ie. 3.1415 with precision two
/// is seperated into two unsigned integers 3 and 14. The different pieces are
/// assembled and placed into the BufferWriter. The default is two decimal
/// places. ie. X.XX. The value is always written in base 10.
///
/// format: whole.fraction
///     or: left.right
BufferWriter &
Format_Float(BufferWriter &w, Spec const &spec, double f, bool negative_p) {
  static const std::string_view infinity_bwf{"Inf"};
  static const std::string_view nan_bwf{"NaN"};
  static const std::string_view zero_bwf{"0"};
  static const std::string_view subnormal_bwf{"subnormal"};
  static const std::string_view unknown_bwf{"unknown float"};

  // Handle floating values that are not normal
  if (!std::isnormal(f)) {
    std::string_view unnormal;
    switch (std::fpclassify(f)) {
    case FP_INFINITE:
      unnormal = infinity_bwf;
      break;
    case FP_NAN:
      unnormal = nan_bwf;
      break;
    case FP_ZERO:
      unnormal = zero_bwf;
      break;
    case FP_SUBNORMAL:
      unnormal = subnormal_bwf;
      break;
    default:
      unnormal = unknown_bwf;
    }

    w.write(unnormal);
    return w;
  }

  auto whole_part = static_cast<uint64_t>(f);
  if (whole_part == f || spec._prec == 0) { // integral
    return Format_Integer(w, spec, whole_part, negative_p);
  }

  static constexpr char dec = '.';
  double frac               = NAN;
  size_t l                  = 0;
  size_t r                  = 0;
  char whole[std::numeric_limits<double>::digits10 + 1];
  char fraction[std::numeric_limits<double>::digits10 + 1];
  char neg               = 0;
  int width              = static_cast<int>(spec._min);                          // amount left to fill.
  unsigned int precision = (spec._prec == Spec::DEFAULT._prec) ? 2 : spec._prec; // default precision 2

  frac = f - whole_part; // split the number

  if (negative_p) {
    neg = '-';
  } else if (spec._sign != '-') {
    neg = spec._sign;
  }

  // Shift the floating point based on the precision. Used to convert
  //  trailing fraction into an integer value.
  uint64_t shift = 0;
  if (precision < POWERS_OF_TEN.size()) {
    shift = POWERS_OF_TEN[precision];
  } else { // not precomputed.
    shift = POWERS_OF_TEN.back();
    for (precision -= (POWERS_OF_TEN.size() - 1); precision > 0; --precision) {
      shift *= 10;
    }
  }

  auto frac_part = static_cast<uint64_t>(frac * shift + 0.5 /* rounding */);

  l = bwf::To_Radix<10>(whole_part, whole, sizeof(whole), bwf::LOWER_DIGITS);
  r = bwf::To_Radix<10>(frac_part, fraction, sizeof(fraction), bwf::LOWER_DIGITS);

  // Clip fill width
  if (neg) {
    --width;
  }
  width -= static_cast<int>(l);
  --width; // '.'
  width -= static_cast<int>(r);

  std::string_view whole_digits{whole + sizeof(whole) - l, l};
  std::string_view frac_digits{fraction + sizeof(fraction) - r, r};

  Write_Aligned(
    w,
    [&]() {
      w.write(whole_digits);
      w.write(dec);
      w.write(frac_digits);
    },
    spec._align, width, spec._fill, neg);

  return w;
}

void
Format_As_Hex(BufferWriter &w, std::string_view view, const char *digits) {
  const char *ptr = view.data();
  for (auto n = view.size(); n > 0; --n) {
    char c = *ptr++;
    w.write(digits[(c >> 4) & 0xF]);
    w.write(digits[c & 0xf]);
  }
}

/// Preparse format string for later use.
Format::Format(TextView fmt) {
  Spec lit_spec;
  int arg_idx = 0;
  auto ex{bind(fmt)};
  std::string_view literal_v;

  lit_spec._type = Spec::LITERAL_TYPE;

  while (ex) {
    Spec spec;
    bool spec_p = ex(literal_v, spec);

    if (literal_v.size()) {
      lit_spec._ext = literal_v;
      _items.emplace_back(lit_spec);
    }

    if (spec_p) {
      if (spec._name.size() == 0) { // no name provided, use implicit index.
        spec._idx = arg_idx++;
      }
      if (spec._idx >= 0) {
        ++arg_idx;
      }
      _items.emplace_back(spec);
    }
  }
}

bool
Format::is_literal() const {
  for (auto const &spec : _items) {
    if (Spec::LITERAL_TYPE != spec._type) {
      return false;
    }
  }
  return true;
}

NameBinding::~NameBinding() = default;

} // namespace bwf

BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, std::string_view sv) {
  auto width = int(spec._min); // amount to fill.
  if (spec._prec > 0) {
    sv = sv.substr(0, spec._prec);
  }
  if ('x' == spec._type || 'X' == spec._type) {
    return bwformat(w, spec, bwf::HexDump(sv.data(), sv.size()));
  } else if ('s' == spec._type) {
    bwformat(w, spec, transform_view_of(&tolower, sv));
  } else if ('S' == spec._type) {
    bwformat(w, spec, transform_view_of(&toupper, sv));
  } else {
    width -= sv.size();
    bwf::Write_Aligned(
      w, [&w, &sv]() { w.write(sv); }, spec._align, width, spec._fill, 0);
  }
  return w;
}

// Generic pointer formatting
BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, const void *ptr) {
  using namespace swoc::literals;
  bwf::Spec ptr_spec{spec};
  ptr_spec._radix_lead_p = true;

  if (ptr == nullptr) {
    if (spec._type == 's' || spec._type == 'S') {
      ptr_spec._type = bwf::Spec::DEFAULT_TYPE;
      ptr_spec._ext  = ""_sv; // clear any extension.
      return bwformat(w, spec, spec._type == 's' ? "null"_sv : "NULL"_sv);
    } else if (spec._type == bwf::Spec::DEFAULT_TYPE) {
      return w; // print nothing if there is no format character override.
    }
  }

  if (ptr_spec._type == bwf::Spec::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 bwf::Format_Integer(w, ptr_spec, reinterpret_cast<intptr_t>(ptr), false);
}
// doc end

BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::HexDump const &hex) {
  char fmt_type      = spec._type;
  const char *digits = bwf::UPPER_DIGITS;

  if ('X' != fmt_type) {
    fmt_type = 'x';
    digits   = bwf::LOWER_DIGITS;
  }

  int width = int(spec._min) - hex._view.size() * 2; // amount left to fill.
  if (spec._radix_lead_p) {
    w.write('0');
    w.write(fmt_type);
    width -= 2;
  }
  bwf::Write_Aligned(
    w, [&w, &hex, digits]() { bwf::Format_As_Hex(w, hex._view, digits); }, spec._align, width, spec._fill, 0);
  return w;
}

BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, MemSpan<void const> const &span) {
  if ('x' == spec._type || 'X' == spec._type) {
    const char *digits = ('X' == spec._type) ? bwf::UPPER_DIGITS : bwf::LOWER_DIGITS;
    size_t block       = spec._prec > 0 ? spec._prec : span.size();
    TextView view{span.rebind<char const>()};
    bool space_p = false;
    while (view) {
      if (space_p) {
        w.write(' ');
      }
      space_p = true;
      if (spec._radix_lead_p) {
        w.write('0').write(digits[33]);
      }
      bwf::Format_As_Hex(w, view.prefix(block), digits);
      view.remove_prefix(block);
    }
  } else {
    static const bwf::Format default_fmt{"{:#x}@{:p}"};
    w.print(default_fmt, span.size(), span.data());
  }
  return w;
}

std::ostream &
FixedBufferWriter::operator>>(std::ostream &s) const {
  return s << this->view();
}

namespace {
// errno value -> symbolic name, indexed directly by the errno number (O(1)).
// The numbering differs between Linux and the BSD-derived platforms, and even
// macOS and FreeBSD disagree on a few of their own high-numbered codes, so each
// platform carries a table generated from its real <errno.h>. Gaps in the
// numbering are empty and fall through to "Unknown"; a platform with no table
// is a compile error. The long form (the 'l' spec, below) still uses strerror().
// clang-format off
#if defined(__linux__)
static constexpr std::array<std::string_view, 134> ERRNO_NAMES = {{
  /*   0 */ "SUCCESS", "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG",
  /*   8 */ "ENOEXEC", "EBADF", "ECHILD", "EAGAIN", "ENOMEM", "EACCES", "EFAULT", "ENOTBLK",
  /*  16 */ "EBUSY", "EEXIST", "EXDEV", "ENODEV", "ENOTDIR", "EISDIR", "EINVAL", "ENFILE",
  /*  24 */ "EMFILE", "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", "EROFS", "EMLINK",
  /*  32 */ "EPIPE", "EDOM", "ERANGE", "EDEADLK", "ENAMETOOLONG", "ENOLCK", "ENOSYS", "ENOTEMPTY",
  /*  40 */ "ELOOP", "", "ENOMSG", "EIDRM", "ECHRNG", "EL2NSYNC", "EL3HLT", "EL3RST",
  /*  48 */ "ELNRNG", "EUNATCH", "ENOCSI", "EL2HLT", "EBADE", "EBADR", "EXFULL", "ENOANO",
  /*  56 */ "EBADRQC", "EBADSLT", "", "EBFONT", "ENOSTR", "ENODATA", "ETIME", "ENOSR",
  /*  64 */ "ENONET", "ENOPKG", "EREMOTE", "ENOLINK", "EADV", "ESRMNT", "ECOMM", "EPROTO",
  /*  72 */ "EMULTIHOP", "EDOTDOT", "EBADMSG", "EOVERFLOW", "ENOTUNIQ", "EBADFD", "EREMCHG", "ELIBACC",
  /*  80 */ "ELIBBAD", "ELIBSCN", "ELIBMAX", "ELIBEXEC", "EILSEQ", "ERESTART", "ESTRPIPE", "EUSERS",
  /*  88 */ "ENOTSOCK", "EDESTADDRREQ", "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", "EOPNOTSUPP",
  /*  96 */ "EPFNOSUPPORT", "EAFNOSUPPORT", "EADDRINUSE", "EADDRNOTAVAIL", "ENETDOWN", "ENETUNREACH", "ENETRESET", "ECONNABORTED",
  /* 104 */ "ECONNRESET", "ENOBUFS", "EISCONN", "ENOTCONN", "ESHUTDOWN", "ETOOMANYREFS", "ETIMEDOUT", "ECONNREFUSED",
  /* 112 */ "EHOSTDOWN", "EHOSTUNREACH", "EALREADY", "EINPROGRESS", "ESTALE", "EUCLEAN", "ENOTNAM", "ENAVAIL",
  /* 120 */ "EISNAM", "EREMOTEIO", "EDQUOT", "ENOMEDIUM", "EMEDIUMTYPE", "ECANCELED", "ENOKEY", "EKEYEXPIRED",
  /* 128 */ "EKEYREVOKED", "EKEYREJECTED", "EOWNERDEAD", "ENOTRECOVERABLE", "ERFKILL", "EHWPOISON",
}};
#elif defined(__APPLE__)
static constexpr std::array<std::string_view, 108> ERRNO_NAMES = {{
  /*   0 */ "SUCCESS", "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG",
  /*   8 */ "ENOEXEC", "EBADF", "ECHILD", "EDEADLK", "ENOMEM", "EACCES", "EFAULT", "ENOTBLK",
  /*  16 */ "EBUSY", "EEXIST", "EXDEV", "ENODEV", "ENOTDIR", "EISDIR", "EINVAL", "ENFILE",
  /*  24 */ "EMFILE", "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", "EROFS", "EMLINK",
  /*  32 */ "EPIPE", "EDOM", "ERANGE", "EAGAIN", "EINPROGRESS", "EALREADY", "ENOTSOCK", "EDESTADDRREQ",
  /*  40 */ "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", "ENOTSUP", "EPFNOSUPPORT", "EAFNOSUPPORT",
  /*  48 */ "EADDRINUSE", "EADDRNOTAVAIL", "ENETDOWN", "ENETUNREACH", "ENETRESET", "ECONNABORTED", "ECONNRESET", "ENOBUFS",
  /*  56 */ "EISCONN", "ENOTCONN", "ESHUTDOWN", "ETOOMANYREFS", "ETIMEDOUT", "ECONNREFUSED", "ELOOP", "ENAMETOOLONG",
  /*  64 */ "EHOSTDOWN", "EHOSTUNREACH", "ENOTEMPTY", "EPROCLIM", "EUSERS", "EDQUOT", "ESTALE", "EREMOTE",
  /*  72 */ "EBADRPC", "ERPCMISMATCH", "EPROGUNAVAIL", "EPROGMISMATCH", "EPROCUNAVAIL", "ENOLCK", "ENOSYS", "EFTYPE",
  /*  80 */ "EAUTH", "ENEEDAUTH", "EPWROFF", "EDEVERR", "EOVERFLOW", "EBADEXEC", "EBADARCH", "ESHLIBVERS",
  /*  88 */ "EBADMACHO", "ECANCELED", "EIDRM", "ENOMSG", "EILSEQ", "ENOATTR", "EBADMSG", "EMULTIHOP",
  /*  96 */ "ENODATA", "ENOLINK", "ENOSR", "ENOSTR", "EPROTO", "ETIME", "EOPNOTSUPP", "ENOPOLICY",
  /* 104 */ "ENOTRECOVERABLE", "EOWNERDEAD", "EQFULL", "ENOTCAPABLE",
}};
#elif defined(__FreeBSD__)
static constexpr std::array<std::string_view, 98> ERRNO_NAMES = {{
  /*   0 */ "SUCCESS", "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG",
  /*   8 */ "ENOEXEC", "EBADF", "ECHILD", "EDEADLK", "ENOMEM", "EACCES", "EFAULT", "ENOTBLK",
  /*  16 */ "EBUSY", "EEXIST", "EXDEV", "ENODEV", "ENOTDIR", "EISDIR", "EINVAL", "ENFILE",
  /*  24 */ "EMFILE", "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", "EROFS", "EMLINK",
  /*  32 */ "EPIPE", "EDOM", "ERANGE", "EAGAIN", "EINPROGRESS", "EALREADY", "ENOTSOCK", "EDESTADDRREQ",
  /*  40 */ "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", "EOPNOTSUPP", "EPFNOSUPPORT", "EAFNOSUPPORT",
  /*  48 */ "EADDRINUSE", "EADDRNOTAVAIL", "ENETDOWN", "ENETUNREACH", "ENETRESET", "ECONNABORTED", "ECONNRESET", "ENOBUFS",
  /*  56 */ "EISCONN", "ENOTCONN", "ESHUTDOWN", "ETOOMANYREFS", "ETIMEDOUT", "ECONNREFUSED", "ELOOP", "ENAMETOOLONG",
  /*  64 */ "EHOSTDOWN", "EHOSTUNREACH", "ENOTEMPTY", "EPROCLIM", "EUSERS", "EDQUOT", "ESTALE", "EREMOTE",
  /*  72 */ "EBADRPC", "ERPCMISMATCH", "EPROGUNAVAIL", "EPROGMISMATCH", "EPROCUNAVAIL", "ENOLCK", "ENOSYS", "EFTYPE",
  /*  80 */ "EAUTH", "ENEEDAUTH", "EIDRM", "ENOMSG", "EOVERFLOW", "ECANCELED", "EILSEQ", "ENOATTR",
  /*  88 */ "EDOOFUS", "EBADMSG", "EMULTIHOP", "ENOLINK", "EPROTO", "ENOTCAPABLE", "ECAPMODE", "ENOTRECOVERABLE",
  /*  96 */ "EOWNERDEAD", "EINTEGRITY",
}};
#else
#error "errno name table not defined for this platform"
#endif
// clang-format on

std::string_view
errno_short_name(int e)
{
  if (e >= 0 && e < static_cast<int>(ERRNO_NAMES.size()) && !ERRNO_NAMES[e].empty()) {
    return ERRNO_NAMES[e];
  }
  return "Unknown"sv;
}
} // namespace

BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Errno const &e) {
  static const bwf::Format number_fmt{"[{}]"sv}; // numeric value format.

  if (spec.has_numeric_type()) { // if numeric type, print just the numeric part
    w.print(number_fmt, e._e);
  } else {
    TextView ext{spec._ext};
    bool short_p = false;
    if (ext.empty() || ext.npos != ext.find('s')) {
      w.write(errno_short_name(e._e));
      short_p = true;
    }
    if (ext.empty() || ext.npos != ext.find('l')) {
      if (short_p) {
        w.write(": ");
      }
      w.write(TextView(strerror(e._e), TextView::npos));
    }
    if (spec._type != 's' && spec._type != 'S') {
      w.write(' ');
      w.print(number_fmt, e._e);
    }
  }
  return w;
}

bwf::Date::Date(std::string_view fmt) : _epoch(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())), _fmt(fmt) {}

BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Date const &date) {
  if (spec.has_numeric_type()) {
    bwformat(w, spec, date._epoch);
  } else {
    struct tm t {};
    auto r = w.remaining();
    size_t n{0};
    // Verify @a fmt is null terminated, even outside the bounds of the view.
    if (date._fmt.data()[date._fmt.size() - 1] != 0 && date._fmt.data()[date._fmt.size()] != 0) {
      throw(std::invalid_argument{"BWF Date String is not null terminated."});
    }
    // Get the time, GMT or local if specified.
    if (spec._ext == "local"sv) {
      localtime_r(&date._epoch, &t);
    } else {
      gmtime_r(&date._epoch, &t);
    }
    // Try a direct write, faster if it works.
    if (r > 0) {
      n = strftime(w.aux_data(), r, date._fmt.data(), &t);
    }
    if (n > 0) {
      w.commit(n);
    } else {
      // Direct write didn't work. Unfortunately need to write to a temporary
      // buffer or the sizing isn't correct if @a w is clipped because @c
      // strftime returns 0 if the buffer isn't large enough.
      char buff[256]; // hope for the best - no real way to resize appropriately on failure.
      n = strftime(buff, sizeof(buff), date._fmt.data(), &t);
      w.write(buff, n);
    }
  }
  return w;
}

BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Pattern const &pattern) {
  if (!pattern._text.empty()) { // If there's no text, no point in looping.
    auto limit        = std::min<size_t>(spec._max, pattern._text.size() * pattern._n);
    decltype(limit) n = 0;
    while (n < limit) {
      w.write(pattern._text);
      n += pattern._text.size();
    }
  }
  return w;
}

BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, std::error_code const &ec) {
  static const auto G_CAT = &std::generic_category();
  static const auto S_CAT = &std::system_category();

  // This provides convenient safe access to the errno short name array.
  static const swoc::bwf::Format number_fmt{"[{}]"_sv}; // numeric value format.
  if (spec.has_numeric_type()) {                        // if numeric type, print just the numeric part.
    bwformat(w, spec, ec.value());
  } else {
    auto short_name = (&ec.category() == G_CAT || &ec.category() == S_CAT) ? errno_short_name(ec.value()) : "Unknown"sv;
    if (short_name != "Unknown"sv) {
      bwformat(w, spec, short_name);
    } else {
      w.write(ec.message());
    }
    if (spec._type != 's' && spec._type != 'S') {
      w.write(' ').write('[').format(spec, ec.value()).write(']');
    }
  }
  return w;
}

BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::UnHex const &obj) {
  auto span{obj._span};
  size_t limit = spec._max;
  while (span.size() >= 2 && limit--) {
    auto b = svto_radix<16>(span.clip_prefix(2).rebind<char const>());
    w.write(b);
  }
  return w;
}

}} // namespace swoc::SWOC_VERSION_NS

namespace std {
ostream &
operator<<(ostream &s, swoc::FixedBufferWriter &w) {
  return s << w.view();
}

} // namespace std
