/*
Copyright (c) 2016, PyData Development Team
All rights reserved.

Distributed under the terms of the BSD Simplified License.

The full license is in the LICENSE file, distributed with this software.
*/

#ifndef PANDAS__LIBS_SRC_PARSE_HELPER_H_
#define PANDAS__LIBS_SRC_PARSE_HELPER_H_

#include <errno.h>
#include <float.h>
#include "headers/portable.h"

static double xstrtod(const char *p, char **q, char decimal, char sci,
                      int skip_trailing, int *maybe_int);

int to_double(char *item, double *p_value, char sci, char decimal,
              int *maybe_int) {
    char *p_end = NULL;

    *p_value = xstrtod(item, &p_end, decimal, sci, 1, maybe_int);

    return (errno == 0) && (!*p_end);
}

#if PY_VERSION_HEX < 0x02060000
#define PyBytes_Check PyString_Check
#define PyBytes_AS_STRING PyString_AS_STRING
#endif

int floatify(PyObject *str, double *result, int *maybe_int) {
    int status;
    char *data;
    PyObject *tmp = NULL;
    const char sci = 'E';
    const char dec = '.';

    if (PyBytes_Check(str)) {
        data = PyBytes_AS_STRING(str);
    } else if (PyUnicode_Check(str)) {
        tmp = PyUnicode_AsUTF8String(str);
        data = PyBytes_AS_STRING(tmp);
    } else {
        PyErr_SetString(PyExc_TypeError, "Invalid object type");
        return -1;
    }

    status = to_double(data, result, sci, dec, maybe_int);

    if (!status) {
        /* handle inf/-inf */
        if (strlen(data) == 3) {
            if (0 == strcasecmp(data, "inf")) {
                *result = HUGE_VAL;
                *maybe_int = 0;
            } else {
                goto parsingerror;
            }
        } else if (strlen(data) == 4) {
            if (0 == strcasecmp(data, "-inf")) {
                *result = -HUGE_VAL;
                *maybe_int = 0;
            } else if (0 == strcasecmp(data, "+inf")) {
                *result = HUGE_VAL;
                *maybe_int = 0;
            } else {
                goto parsingerror;
            }
        } else {
            goto parsingerror;
        }
    }

    Py_XDECREF(tmp);
    return 0;

parsingerror:
    PyErr_Format(PyExc_ValueError, "Unable to parse string \"%s\"", data);
    Py_XDECREF(tmp);
    return -1;

    /*
    #if PY_VERSION_HEX >= 0x03000000
      return PyFloat_FromString(str);
    #else
      return PyFloat_FromString(str, NULL);
    #endif
    */
}

// ---------------------------------------------------------------------------
// Implementation of xstrtod

//
// strtod.c
//
// Convert string to double
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// -----------------------------------------------------------------------
// Modifications by Warren Weckesser, March 2011:
// * Rename strtod() to xstrtod().
// * Added decimal and sci arguments.
// * Skip trailing spaces.
// * Commented out the other functions.
//

PANDAS_INLINE void lowercase(char *p) {
    for (; *p; ++p) *p = tolower(*p);
}

PANDAS_INLINE void uppercase(char *p) {
    for (; *p; ++p) *p = toupper(*p);
}

static double xstrtod(const char *str, char **endptr, char decimal, char sci,
                      int skip_trailing, int *maybe_int) {
    double number;
    int exponent;
    int negative;
    char *p = (char *)str;
    double p10;
    int n;
    int num_digits;
    int num_decimals;

    errno = 0;
    *maybe_int = 1;

    // Skip leading whitespace
    while (isspace(*p)) p++;

    // Handle optional sign
    negative = 0;
    switch (*p) {
        case '-':
            negative = 1;  // Fall through to increment position
        case '+':
            p++;
    }

    number = 0.;
    exponent = 0;
    num_digits = 0;
    num_decimals = 0;

    // Process string of digits
    while (isdigit(*p)) {
        number = number * 10. + (*p - '0');
        p++;
        num_digits++;
    }

    // Process decimal part
    if (*p == decimal) {
        *maybe_int = 0;
        p++;

        while (isdigit(*p)) {
            number = number * 10. + (*p - '0');
            p++;
            num_digits++;
            num_decimals++;
        }

        exponent -= num_decimals;
    }

    if (num_digits == 0) {
        errno = ERANGE;
        return 0.0;
    }

    // Correct for sign
    if (negative) number = -number;

    // Process an exponent string
    if (toupper(*p) == toupper(sci)) {
        *maybe_int = 0;

        // Handle optional sign
        negative = 0;
        switch (*++p) {
            case '-':
                negative = 1;  // Fall through to increment pos
            case '+':
                p++;
        }

        // Process string of digits
        num_digits = 0;
        n = 0;
        while (isdigit(*p)) {
            n = n * 10 + (*p - '0');
            num_digits++;
            p++;
        }

        if (negative)
            exponent -= n;
        else
            exponent += n;

        // If no digits, after the 'e'/'E', un-consume it
        if (num_digits == 0) p--;
    }

    if (exponent < DBL_MIN_EXP || exponent > DBL_MAX_EXP) {
        errno = ERANGE;
        return HUGE_VAL;
    }

    // Scale the result
    p10 = 10.;
    n = exponent;
    if (n < 0) n = -n;
    while (n) {
        if (n & 1) {
            if (exponent < 0)
                number /= p10;
            else
                number *= p10;
        }
        n >>= 1;
        p10 *= p10;
    }

    if (number == HUGE_VAL) {
        errno = ERANGE;
    }

    if (skip_trailing) {
        // Skip trailing whitespace
        while (isspace(*p)) p++;
    }

    if (endptr) *endptr = p;

    return number;
}

#endif  // PANDAS__LIBS_SRC_PARSE_HELPER_H_
