/* Copyright (c) 2026 Jakub Juszczakiewicz
 * All rights reserved.
 */

#include "int128acc.h"
#include <string.h>

#ifdef HAVE_INT128

size_t logA(uint128_t in)
{
  size_t r = 0;
  unsigned __int128 a = in.u128;
  do {
    a /= 10;
    r++;
  } while (a > 0);

  return r;
}

void uint128_export_dec_str(char * out, const uint128_t * in)
{
  unsigned __int128 a = in->u128;
  char * out2 = out + logA(*in);
  *out2 = 0;

  do {
    char ch = '0' + (a % 10);
    a /= 10;
    *--out2 = ch;
  } while (a);
}

void uint128_inc_u64(uint128_t * acc, uint64_t b)
{
  acc->u128 += b;
}

#else

static const uint32_t div10lo = 0xCCCCCCCD;
static const uint32_t div10hi = 0xC;
static const uint32_t shr10 = 3+4;

static uint32_t uint36_div_mod_10(uint32_t * out, uint64_t in)
{
  uint64_t a = (uint64_t)(in & 0xFFFFFFFF) * div10lo;
  uint64_t b = (uint64_t)(in & 0xFFFFFFFF) * div10hi;
  uint64_t c = (uint64_t)(in >> 32) * div10lo;
  uint64_t d = (uint32_t)(in >> 32) * div10hi;

  b += c;
  if (b < c)
    d++;

  b += a >> 32;
  if (b < c)
    d++;

  a = b + (d << 32);
  int carry = a < (d << 32);
  d >>= 32;
  d += carry;

  a >>= shr10;
  a |= d << (32 - shr10);

  *out = a;
  return in - a * 10;
}

static uint32_t uint128_div_mod_10(uint128_t * out, const uint128_t * in)
{
  uint64_t tmp = ((uint64_t)uint36_div_mod_10(&out->u32a, in->u32a)) << 32;
  tmp |= in->u32b;
  tmp = ((uint64_t)uint36_div_mod_10(&out->u32b, tmp)) << 32;
  tmp |= in->u32c;
  tmp = ((uint64_t)uint36_div_mod_10(&out->u32c, tmp)) << 32;
  tmp |= in->u32d;
  tmp = uint36_div_mod_10(&out->u32d, tmp);

  return tmp;
}

static int uint128_cmp_zero(const uint128_t * a)
{
  return (a->u64a == 0) && (a->u64b == 0);
}

size_t logA(uint128_t in)
{
  size_t r = 0;
  uint128_t a;
  memcpy(&a, &in, sizeof(a));

  do {
    uint128_div_mod_10(&a, &a);
    r++;
  } while (uint128_cmp_zero(&a) == 0);

  return r;
}

void uint128_export_dec_str(char * out, const uint128_t * in)
{
  uint128_t a;
  memcpy(&a, in, sizeof(a));
  char * out2 = out + logA(a);
  *out2 = 0;

  do {
    char ch = '0' + uint128_div_mod_10(&a, &a);
    *--out2 = ch;
  } while (uint128_cmp_zero(&a) == 0);
}

void uint128_inc_u64(uint128_t * acc, uint64_t b)
{
  acc->u64b += b;
  if (acc->u64b < b)
    acc->u64a++;
}

#endif
