/*
 * Copyright © 2026  Behdad Esfahbod
 *
 *  This is part of HarfBuzz, a text shaping library.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 *
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 * Author(s): Behdad Esfahbod
 */

#ifndef HB_VECTOR_BUF_HH
#define HB_VECTOR_BUF_HH

#include "hb.hh"
#include "hb-vector.hh"
#include <math.h>
#include <stdio.h>
#include <string.h>

HB_INTERNAL const char *
hb_vector_decimal_point_get (void);

static const double _hb_vector_buf_pow10[13] = {1., 1e1, 1e2, 1e3, 1e4, 1e5, 1e6,
						1e7, 1e8, 1e9, 1e10, 1e11, 1e12};

struct hb_vector_buf_t : hb_vector_t<char>
{
  unsigned precision = 2;

  unsigned scale_precision () const
  { return precision < 7 ? 7 : precision; }

  bool append_len (const char *s, unsigned l)
  {
    unsigned old_len = length;
    unsigned new_len;
    if (unlikely (hb_unsigned_add_overflows (old_len, l, &new_len) ||
		  !resize_dirty ((int) new_len)))
      return false;
    hb_memcpy (arrayZ + old_len, s, l);
    return true;
  }

  bool append_c (char ch)
  { return push_or_fail (ch); }

  bool append_str (const char *s)
  { return append_len (s, (unsigned) strlen (s)); }

  bool append_unsigned (unsigned v)
  {
    char tmp[16];
    snprintf (tmp, sizeof (tmp), "%u", v);
    return append_len (tmp, (unsigned) strlen (tmp));
  }

  bool append_hex_byte (unsigned v)
  {
    char tmp[2] = {"0123456789ABCDEF"[(v >> 4) & 15],
		   "0123456789ABCDEF"[v & 15]};
    return append_len (tmp, 2);
  }

  void append_num (float v)
  { append_num (v, precision); }

  void append_num (float v, unsigned p)
  {
    if (p > 12) p = 12;

    float rounded_zero_threshold = 0.5f;
    for (unsigned i = 0; i < p; i++)
      rounded_zero_threshold *= 0.1f;
    if (fabsf (v) < rounded_zero_threshold)
      v = 0.f;

    if (!(v == v) || !std::isfinite (v))
    {
      append_c ('0');
      return;
    }

    /* Fast path: fixed-point decimal via integer math.  @v has a
     * 24-bit significand and 5^12 has 28 bits, so v*10^p is exact in
     * a double for p <= 12; rounding it half-to-even therefore
     * matches snprintf "%.pf" output digit-for-digit, while skipping
     * the (much slower) printf floating-point formatting and any
     * locale decimal-point fix-up. */
    double scaled = (double) v * _hb_vector_buf_pow10[p];
    if (likely (fabs (scaled) < 9.0e18))
    {
      double fl = floor (scaled);
      double diff = scaled - fl;
      int64_t n = (int64_t) fl;
      if (diff > 0.5 || (diff == 0.5 && (n & 1)))
	n++;

      uint64_t u = (uint64_t) (n < 0 ? -n : n);
      unsigned frac = p;
      while (frac && !(u % 10))
      {
	u /= 10;
	frac--;
      }

      char tmp[24];
      char *end = tmp + sizeof (tmp);
      char *s = end;
      unsigned d = 0;
      do
      {
	*--s = (char) ('0' + (char) (u % 10));
	u /= 10;
	if (++d == frac)
	  *--s = '.';
      }
      while (u || d < frac + 1);
      if (v < 0.f)
	*--s = '-';

      append_len (s, (unsigned) (end - s));
      return;
    }

    char fmt[6];
    snprintf (fmt, sizeof (fmt), "%%.%uf", p);
    char out[128];
    snprintf (out, sizeof (out), fmt, (double) v);

    const char *decimal_point = hb_vector_decimal_point_get ();

    if (decimal_point[0] != '.' || decimal_point[1] != '\0')
    {
      char *dp = strstr (out, decimal_point);
      if (dp)
      {
	unsigned dp_len = (unsigned) strlen (decimal_point);
	unsigned tail_len = (unsigned) strlen (dp + dp_len);
	memmove (dp + 1, dp + dp_len, tail_len + 1);
	*dp = '.';
      }
    }

    char *dot = strchr (out, '.');
    if (dot)
    {
      char *end = out + strlen (out) - 1;
      while (end > dot && *end == '0')
	*end-- = '\0';
      if (end == dot)
	*end = '\0';
    }

    append_len (out, (unsigned) strlen (out));
  }

  void append_svg_color (hb_color_t color, bool with_alpha)
  {
    unsigned r = hb_color_get_red (color);
    unsigned g = hb_color_get_green (color);
    unsigned b = hb_color_get_blue (color);
    unsigned a = hb_color_get_alpha (color);
    append_c ('#');
    if (((r >> 4) == (r & 0xF)) &&
	((g >> 4) == (g & 0xF)) &&
	((b >> 4) == (b & 0xF)))
    {
      append_c ("0123456789ABCDEF"[r & 0xF]);
      append_c ("0123456789ABCDEF"[g & 0xF]);
      append_c ("0123456789ABCDEF"[b & 0xF]);
    }
    else
    {
      append_hex_byte (r);
      append_hex_byte (g);
      append_hex_byte (b);
    }
    if (with_alpha && a != 255)
    {
      append_str ("\" fill-opacity=\"");
      append_num (a / 255.f, 4);
    }
  }

  bool append_base64 (const uint8_t *data, unsigned len)
  {
    /* Output is 4 bytes per 3 input bytes; compute without overflowing. */
    unsigned ngroups = len / 3 + (len % 3 != 0);
    unsigned out_len;
    unsigned old_len = length;
    unsigned new_len;
    if (unlikely (hb_unsigned_mul_overflows (ngroups, 4, &out_len) ||
		  hb_unsigned_add_overflows (old_len, out_len, &new_len) ||
		  !resize_dirty ((int) new_len)))
      return false;

    char *dst = arrayZ + old_len;
    unsigned di = 0;
    unsigned i = 0;
    while (i + 2 < len)
    {
      unsigned v = ((unsigned) data[i] << 16) |
		   ((unsigned) data[i + 1] << 8) |
		   ((unsigned) data[i + 2]);
      dst[di++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(v >> 18) & 63];
      dst[di++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(v >> 12) & 63];
      dst[di++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(v >> 6) & 63];
      dst[di++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[v & 63];
      i += 3;
    }

    if (i < len)
    {
      unsigned v = (unsigned) data[i] << 16;
      if (i + 1 < len)
	v |= (unsigned) data[i + 1] << 8;
      dst[di++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(v >> 18) & 63];
      dst[di++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(v >> 12) & 63];
      dst[di++] = (i + 1 < len) ? "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(v >> 6) & 63] : '=';
      dst[di++] = '=';
    }

    return true;
  }
};

#endif /* HB_VECTOR_BUF_HH */
