/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8                                :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney                              │
│                                                                              │
│ Permission to use, copy, modify, and/or distribute this software for         │
│ any purpose with or without fee is hereby granted, provided that the         │
│ above copyright notice and this permission notice appear in all copies.      │
│                                                                              │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL                │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED                │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE             │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL         │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR        │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER               │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
│ PERFORMANCE OF THIS SOFTWARE.                                                │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/weaken.h"
#include "libc/fmt/fmts.h"
#include "libc/fmt/internal.h"
#include "libc/nexgen32e/tinystrlen.internal.h"
#include "libc/str/str.h"
#include "libc/str/thompike.h"
#include "libc/str/tpenc.h"
#include "libc/str/utf16.h"
#include "libc/unicode/unicode.h"

typedef int (*emit_f)(int (*)(long, void *), void *, wint_t);

static noinstrument int __fmt_stoa_byte(int f(long, void *), void *a,
                                        wint_t c) {
  return f(c, a);
}

static noinstrument int __fmt_stoa_word(int f(long, void *), void *a,
                                        uint64_t w) {
  do {
    if (f(w & 0xff, a) == -1) {
      return -1;
    }
  } while ((w >>= 8));
  return 0;
}

static noinstrument int __fmt_stoa_wide(int f(long, void *), void *a,
                                        wint_t c) {
  if (isascii(c)) {
    return f(c, a);
  } else {
    return __fmt_stoa_word(f, a, tpenc(c));
  }
}

static noinstrument int __fmt_stoa_bing(int f(long, void *), void *a,
                                        wint_t c) {
  return __fmt_stoa_wide(f, a, (*weaken(kCp437))[c]);
}

static noinstrument int __fmt_stoa_quoted(int f(long, void *), void *a,
                                          wint_t c) {
  if (isascii(c)) {
    return __fmt_stoa_word(f, a, cescapec(c));
  } else {
    return __fmt_stoa_word(f, a, tpenc(c));
  }
}

static noinstrument int __fmt_stoa_quote(int out(long, void *), void *arg,
                                         unsigned flags, char ch,
                                         unsigned char signbit) {
  if (flags & FLAGS_REPR) {
    if (signbit == 63) {
      if (out('L', arg) == -1) return -1;
    } else if (signbit == 15) {
      if (out('u', arg) == -1) return -1;
    }
    if (out(ch, arg) == -1) return -1;
  }
  return 0;
}

/**
 * Converts string to array.
 *
 * This is used by __fmt() to implement the %s and %c directives. The
 * content outputted to the array is always UTF-8, but the input may be
 * UTF-16 or UTF-32.
 *
 * @see __fmt()
 */
int __fmt_stoa(int out(long, void *), void *arg, void *data,
               unsigned long flags, unsigned long precision,
               unsigned long width, unsigned char signbit,
               unsigned char qchar) {
  char *p;
  wint_t wc;
  unsigned n;
  emit_f emit;
  unsigned w, c, pad;
  bool justdobytes, ignorenul;

  p = data;
  if (!p) {
    p = ((flags & FLAGS_REPR) ? "NULL" : "(null)");
    flags &= ~FLAGS_PRECISION;
    flags |= FLAGS_NOQUOTE;
    signbit = 0;
  } else {
    if (__fmt_stoa_quote(out, arg, flags, qchar, signbit) == -1) return -1;
  }

  ignorenul = false;
  justdobytes = false;
  if (signbit == 15 || signbit == 63) {
    if (flags & FLAGS_QUOTE) {
      emit = __fmt_stoa_quoted;
      ignorenul = flags & FLAGS_PRECISION;
    } else {
      emit = __fmt_stoa_wide;
    }
  } else if ((flags & FLAGS_HASH) && weaken(kCp437)) {
    justdobytes = true;
    emit = __fmt_stoa_bing;
    ignorenul = flags & FLAGS_PRECISION;
  } else if (flags & FLAGS_QUOTE) {
    emit = __fmt_stoa_quoted;
    ignorenul = flags & FLAGS_PRECISION;
  } else {
    justdobytes = true;
    emit = __fmt_stoa_byte;
  }

  if (!(flags & FLAGS_PRECISION)) precision = -1;
  if (!(flags & FLAGS_PRECISION) || !ignorenul) {
    if (signbit == 63) {
      precision = tinywcsnlen((const wchar_t *)p, precision);
    } else if (signbit == 15) {
      precision = tinystrnlen16((const char16_t *)p, precision);
    } else {
      precision = strnlen(p, precision);
    }
  }

  pad = 0;
  if (width) {
    w = precision;
    if (signbit == 63) {
      if (weaken(wcsnwidth)) {
        w = weaken(wcsnwidth)((const wchar_t *)p, precision, 0);
      }
    } else if (signbit == 15) {
      if (weaken(strnwidth16)) {
        w = weaken(strnwidth16)((const char16_t *)p, precision, 0);
      }
    } else if (weaken(strnwidth)) {
      w = weaken(strnwidth)(p, precision, 0);
    }
    if (w < width) {
      pad = width - w;
    }
  }

  if (pad && !(flags & FLAGS_LEFT)) {
    if (__fmt_pad(out, arg, pad) == -1) return -1;
  }

  if (justdobytes) {
    while (precision--) {
      wc = *p++ & 0xff;
      if (!wc && !ignorenul) break;
      if (emit(out, arg, wc) == -1) return -1;
    }
  } else {
    while (precision--) {
      if (signbit == 15) {
        wc = *(const char16_t *)p;
        if (!wc && !ignorenul) break;
        if (IsUcs2(wc)) {
          p += sizeof(char16_t);
        } else if (IsUtf16Cont(wc)) {
          p += sizeof(char16_t);
          continue;
        } else if (!precision) {
          break;
        } else {
          --precision;
          wc = MergeUtf16(wc, *(const char16_t *)p);
        }
      } else if (signbit == 63) {
        wc = *(const wint_t *)p;
        if (!wc && !ignorenul) break;
        p += sizeof(wint_t);
        if (!wc) break;
      } else {
        wc = *p++ & 0xff;
        if (!wc && !ignorenul) break;
        if (!isascii(wc)) {
          if (ThomPikeCont(wc)) continue;
          n = ThomPikeLen(wc) - 1;
          wc = ThomPikeByte(wc);
          if (n > precision) break;
          precision -= n;
          while (n--) {
            wc = ThomPikeMerge(wc, *p++);
          }
        }
      }
      if (emit(out, arg, wc) == -1) return -1;
    }
  }

  if (pad && (flags & FLAGS_LEFT)) {
    if (__fmt_pad(out, arg, pad) == -1) return -1;
  }

  if (!(flags & FLAGS_NOQUOTE) && (flags & FLAGS_REPR)) {
    if (out(qchar, arg) == -1) return -1;
  }

  return 0;
}
