/* -*- Mode: C; c-file-style: "python" -*- */

#include <Python.h>
#include <locale.h>

/* ascii character tests (as opposed to locale tests) */
#define ISSPACE(c)  ((c) == ' ' || (c) == '\f' || (c) == '\n' || \
                    (c) == '\r' || (c) == '\t' || (c) == '\v')
#define ISDIGIT(c)  ((c) >= '0' && (c) <= '9')
#define ISXDIGIT(c) (ISDIGIT(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F'))


/**
* PyOS_ascii_strtod:
* @nptr:    the string to convert to a numeric value.
* @endptr:  if non-%NULL, it returns the character after
*           the last character used in the conversion.
*
* Converts a string to a #gdouble value.
* This function behaves like the standard strtod() function
* does in the C locale. It does this without actually
* changing the current locale, since that would not be
* thread-safe.
*
* This function is typically used when reading configuration
* files or other non-user input that should be locale independent.
* To handle input from the user you should normally use the
* locale-sensitive system strtod() function.
*
* If the correct value would cause overflow, plus or minus %HUGE_VAL
* is returned (according to the sign of the value), and %ERANGE is
* stored in %errno. If the correct value would cause underflow,
* zero is returned and %ERANGE is stored in %errno.
* If memory allocation fails, %ENOMEM is stored in %errno.
*
* This function resets %errno before calling strtod() so that
* you can reliably detect overflow and underflow.
*
* Return value: the #gdouble value.
**/
double
PyOS_ascii_strtod(const char *nptr, char **endptr)
{
       char *fail_pos;
       double val = -1.0;
       struct lconv *locale_data;
       const char *decimal_point;
       size_t decimal_point_len;
       const char *p, *decimal_point_pos;
       const char *end = NULL; /* Silence gcc */

       assert(nptr != NULL);

       fail_pos = NULL;

       locale_data = localeconv();
       decimal_point = locale_data->decimal_point;
       decimal_point_len = strlen(decimal_point);

       assert(decimal_point_len != 0);

       decimal_point_pos = NULL;
       if (decimal_point[0] != '.' ||
           decimal_point[1] != 0)
       {
               p = nptr;
                 /* Skip leading space */
               while (ISSPACE(*p))
                       p++;

                 /* Skip leading optional sign */
               if (*p == '+' || *p == '-')
                       p++;

               while (ISDIGIT(*p))
                       p++;

               if (*p == '.')
               {
                       decimal_point_pos = p++;

                       while (ISDIGIT(*p))
                               p++;

                       if (*p == 'e' || *p == 'E')
                               p++;
                       if (*p == '+' || *p == '-')
                               p++;
                       while (ISDIGIT(*p))
                               p++;
                       end = p;
               }
               else if (strncmp(p, decimal_point, decimal_point_len) == 0)
               {
                       /* Python bug #1417699 */
                       *endptr = (char*)nptr;
                       errno = EINVAL;
                       return val;
               }
               /* For the other cases, we need not convert the decimal point */
       }

       /* Set errno to zero, so that we can distinguish zero results
          and underflows */
       errno = 0;

       if (decimal_point_pos)
       {
               char *copy, *c;

               /* We need to convert the '.' to the locale specific decimal point */
               copy = (char *)PyMem_MALLOC(end - nptr + 1 + decimal_point_len);
               if (copy == NULL) {
                       if (endptr)
                               *endptr = (char *)nptr;
                       errno = ENOMEM;
                       return val;
               }

               c = copy;
               memcpy(c, nptr, decimal_point_pos - nptr);
               c += decimal_point_pos - nptr;
               memcpy(c, decimal_point, decimal_point_len);
               c += decimal_point_len;
               memcpy(c, decimal_point_pos + 1, end - (decimal_point_pos + 1));
               c += end - (decimal_point_pos + 1);
               *c = 0;

               val = strtod(copy, &fail_pos);

               if (fail_pos)
               {
                       if (fail_pos > decimal_point_pos)
                               fail_pos = (char *)nptr + (fail_pos - copy) - (decimal_point_len - 1);
                       else
                               fail_pos = (char *)nptr + (fail_pos - copy);
               }

               PyMem_FREE(copy);

       }
       else {
               unsigned i = 0;
               if (nptr[i] == '-')
                       i++;
               if (nptr[i] == '0' && (nptr[i+1] == 'x' || nptr[i+1] == 'X'))
                       fail_pos = (char*)nptr;
               else
                       val = strtod(nptr, &fail_pos);
       }

       if (endptr)
               *endptr = fail_pos;

       return val;
}


/**
* PyOS_ascii_formatd:
* @buffer: A buffer to place the resulting string in
* @buf_len: The length of the buffer.
* @format: The printf()-style format to use for the
*          code to use for converting.
* @d: The #gdouble to convert
*
* Converts a #gdouble to a string, using the '.' as
* decimal point. To format the number you pass in
* a printf()-style format string. Allowed conversion
* specifiers are 'e', 'E', 'f', 'F', 'g' and 'G'.
*
* Return value: The pointer to the buffer with the converted string.
**/
char *
PyOS_ascii_formatd(char       *buffer,
                  size_t      buf_len,
                  const char *format,
                  double      d)
{
       struct lconv *locale_data;
       const char *decimal_point;
       size_t decimal_point_len, rest_len;
       char *p;
       char format_char;

/*      g_return_val_if_fail (buffer != NULL, NULL); */
/*      g_return_val_if_fail (format[0] == '%', NULL); */
/*      g_return_val_if_fail (strpbrk (format + 1, "'l%") == NULL, NULL); */

       format_char = format[strlen(format) - 1];

/*      g_return_val_if_fail (format_char == 'e' || format_char == 'E' || */
/*                            format_char == 'f' || format_char == 'F' || */
/*                            format_char == 'g' || format_char == 'G', */
/*                            NULL); */

       if (format[0] != '%')
               return NULL;

       if (strpbrk(format + 1, "'l%"))
               return NULL;

       if (!(format_char == 'e' || format_char == 'E' ||
             format_char == 'f' || format_char == 'F' ||
             format_char == 'g' || format_char == 'G'))
               return NULL;


       PyOS_snprintf(buffer, buf_len, format, d);

       locale_data = localeconv();
       decimal_point = locale_data->decimal_point;
       decimal_point_len = strlen(decimal_point);

       assert(decimal_point_len != 0);

       if (decimal_point[0] != '.' ||
           decimal_point[1] != 0)
       {
               p = buffer;

               if (*p == '+' || *p == '-')
                       p++;

               while (isdigit((unsigned char)*p))
                       p++;

               if (strncmp(p, decimal_point, decimal_point_len) == 0)
               {
                       *p = '.';
                       p++;
                       if (decimal_point_len > 1) {
                               rest_len = strlen(p + (decimal_point_len - 1));
                               memmove(p, p + (decimal_point_len - 1),
                                       rest_len);
                               p[rest_len] = 0;
                       }
               }
       }

       return buffer;
}

double
PyOS_ascii_atof(const char *nptr)
{
       return PyOS_ascii_strtod(nptr, NULL);
}