/*
  This file is part of TALER
  (C) 2025 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU Lesser General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.

  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file value_kinds.c
 * @brief Parsing quantities and other decimal fractions
 * @author Christian Grothoff
 * @author Bohdan
 */
#include "taler/platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_db_lib.h>
#include <taler/taler_json_lib.h>
#include "taler/taler_merchant_util.h"


enum GNUNET_GenericReturnValue
TALER_MERCHANT_vk_parse_fractional_string (
  const char *value,
  int64_t *integer_part,
  uint32_t *fractional_part)
{
  const char *ptr;
  uint64_t integer = 0;
  uint32_t frac = 0;
  unsigned int digits = 0;

  GNUNET_assert (NULL != integer_part);
  GNUNET_assert (NULL != fractional_part);

  if (NULL == value)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  ptr = value;
  if ('\0' == *ptr)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  if ('-' == *ptr)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  if (! isdigit ((unsigned char) *ptr))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  while (isdigit ((unsigned char) *ptr))
  {
    unsigned int digit = (unsigned int) (*ptr - '0');

    if (integer > (UINT64_MAX - digit) / 10)
    {
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
    integer = integer * 10 + digit;
    ptr++;
  }
  if ('.' == *ptr)
  {
    ptr++;
    if ('\0' == *ptr)
    {
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
    while (isdigit ((unsigned char) *ptr))
    {
      unsigned int digit = (unsigned int) (*ptr - '0');

      if (digits >= TALER_MERCHANT_UNIT_FRAC_MAX_DIGITS)
      {
        GNUNET_break_op (0);
        return GNUNET_SYSERR;
      }
      frac = (uint32_t) (frac * 10 + digit);
      digits++;
      ptr++;
    }
    while (digits < TALER_MERCHANT_UNIT_FRAC_MAX_DIGITS)
    {
      frac *= 10;
      digits++;
    }
  }
  if ('\0' != *ptr)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  if (integer > (uint64_t) INT64_MAX)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  *integer_part = integer;
  *fractional_part = frac;
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
TALER_MERCHANT_vk_process_quantity_inputs (enum TALER_MERCHANT_ValueKind kind,
                                           bool allow_fractional,
                                           bool int_missing,
                                           int64_t int_raw,
                                           bool str_missing,
                                           const char *str_raw,
                                           uint64_t *int_out,
                                           uint32_t *frac_out,
                                           const char **error_param)
{
  static char errbuf[128];
  int64_t parsed_int = 0;
  uint32_t parsed_frac = 0;
  const char *int_field = (TALER_MERCHANT_VK_STOCK == kind)
                          ? "total_stock"
                          : "quantity";
  const char *str_field = (TALER_MERCHANT_VK_STOCK == kind)
                          ? "unit_total_stock"
                          : "unit_quantity";

  *error_param = NULL;

  if (int_missing && str_missing)
  {
    GNUNET_snprintf (errbuf,
                     sizeof (errbuf),
                     "missing %s and %s",
                     int_field,
                     str_field);
    *error_param = errbuf;
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  if (! str_missing)
  {
    if ( (TALER_MERCHANT_VK_STOCK == kind) &&
         (0 == strcmp ("-1",
                       str_raw)) )
    {
      parsed_int = -1;
      parsed_frac = 0;
    }
    else
    {
      if (GNUNET_OK !=
          TALER_MERCHANT_vk_parse_fractional_string (str_raw,
                                                     &parsed_int,
                                                     &parsed_frac))
      {
        GNUNET_snprintf (errbuf,
                         sizeof (errbuf),
                         "malformed %s",
                         str_field);
        *error_param = errbuf;
        GNUNET_break_op (0);
        return GNUNET_SYSERR;
      }
    }
  }

  if ( (! int_missing) && (! str_missing) )
  {
    if ( (parsed_int != int_raw) || (0 != parsed_frac) )
    {
      GNUNET_snprintf (errbuf,
                       sizeof (errbuf),
                       "%s/%s mismatch",
                       int_field,
                       str_field);
      *error_param = errbuf;
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
  }
  else if (int_missing)
  {
    int_raw = parsed_int;
  }

  if ( (TALER_MERCHANT_VK_STOCK == kind) && (-1 == int_raw) )
  {
    if ( (! str_missing) && (0 != parsed_frac) )
    {
      GNUNET_snprintf (errbuf,
                       sizeof (errbuf),
                       "fractional part forbidden with %s='-1'",
                       str_field);
      *error_param = errbuf;
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
    *int_out = INT64_MAX;
    *frac_out = INT32_MAX;
    return GNUNET_OK;
  }

  if (int_raw < 0)
  {
    GNUNET_snprintf (errbuf,
                     sizeof (errbuf),
                     "%s must be non-negative",
                     int_field);
    *error_param = errbuf;
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  if (! allow_fractional)
  {
    if ( (! str_missing) && (0 != parsed_frac) )
    {
      GNUNET_snprintf (errbuf,
                       sizeof (errbuf),
                       "fractional part not allowed for %s",
                       str_field);
      *error_param = errbuf;
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
    parsed_frac = 0;
  }
  else if (! str_missing)
  {
    if (parsed_frac >= TALER_MERCHANT_UNIT_FRAC_BASE)
    {
      GNUNET_snprintf (errbuf,
                       sizeof (errbuf),
                       "%s fractional part exceeds base %u",
                       str_field,
                       TALER_MERCHANT_UNIT_FRAC_BASE);
      *error_param = errbuf;
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
  }

  *int_out = (uint64_t) int_raw;
  *frac_out = parsed_frac;
  return GNUNET_OK;
}


void
TALER_MERCHANT_vk_format_fractional_string (
  enum TALER_MERCHANT_ValueKind kind,
  uint64_t integer,
  uint32_t fractional,
  size_t buffer_length,
  char buffer[static buffer_length])
{
  GNUNET_assert (0 < buffer_length);

  if ( (TALER_MERCHANT_VK_STOCK == kind) &&
       (INT64_MAX == (int64_t) integer) &&
       (INT32_MAX == (int32_t) fractional) )
  {
    GNUNET_snprintf (buffer,
                     buffer_length,
                     "-1");
    return;
  }

  GNUNET_assert ( (TALER_MERCHANT_VK_QUANTITY != kind) ||
                  ((INT64_MAX != (int64_t) integer) &&
                   (INT32_MAX != (int32_t) fractional)) );
  GNUNET_assert (fractional < TALER_MERCHANT_UNIT_FRAC_BASE);

  if (0 == fractional)
  {
    GNUNET_snprintf (buffer,
                     buffer_length,
                     "%lu",
                     integer);
    return;
  }
  {
    char frac_buf[TALER_MERCHANT_UNIT_FRAC_MAX_DIGITS + 1];
    size_t idx;

    GNUNET_snprintf (frac_buf,
                     sizeof (frac_buf),
                     "%0*u",
                     TALER_MERCHANT_UNIT_FRAC_MAX_DIGITS,
                     (unsigned int) fractional);
    for (idx = strlen (frac_buf); idx > 0; idx--)
    {
      if ('0' != frac_buf[idx - 1])
        break;
      frac_buf[idx - 1] = '\0';
    }
    GNUNET_snprintf (buffer,
                     buffer_length,
                     "%lu.%s",
                     integer,
                     frac_buf);
  }
}
