/*
 * numconv.c
 *
 * Copyright (C) 2017 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define DEFINE_XTOA(t, p, s)                                            \
    char *p##toa(t value, char *str, int base)                          \
    {                                                                   \
        char *ptr = str;                                                \
        if (base < 2 || base > 36) return NULL;                         \
                                                                        \
        if (value == 0##s)                                              \
        {                                                               \
            *ptr++ = '0';                                               \
            *ptr++ = '\0';                                              \
            return str;                                                 \
        }                                                               \
                                                                        \
        if (value < 0##s)                                               \
        {                                                               \
            *ptr++ = '-';                                               \
            value = -value;                                             \
        }                                                               \
                                                                        \
        t temp;                                                         \
        for (temp = value; temp > 0##s; temp /= (t)base) ptr++;         \
        *ptr = '\0';                                                    \
                                                                        \
        while (value > 0##s)                                            \
        {                                                               \
            *--ptr = all_digits[value % (t)base];                       \
            value /= (t)base;                                           \
        }                                                               \
                                                                        \
        return str;                                                     \
    }                                                                   \
                                                                        \
    char *u##p##toa(unsigned t value, char *str, int base)              \
    {                                                                   \
        char *ptr = str;                                                \
        if (base < 2 || base > 36) return NULL;                         \
                                                                        \
        if (value == 0U##s)                                             \
        {                                                               \
            *ptr++ = '0';                                               \
            *ptr++ = '\0';                                              \
            return str;                                                 \
        }                                                               \
                                                                        \
        unsigned t temp;                                                \
        for (temp = value; temp > 0U##s; temp /= (unsigned t)base) ptr++; \
        *ptr = '\0';                                                    \
                                                                        \
        while (value > 0U##s)                                           \
        {                                                               \
            *--ptr = all_digits[value % (unsigned t)base];              \
            value /= (unsigned t)base;                                  \
        }                                                               \
                                                                        \
        return str;                                                     \
    }

#define DEFINE_ATOX(t, n, p, s)                                         \
    t ato##p(const char *str)                                           \
    {                                                                   \
        return strto##p(str, NULL, 10);                                 \
    }                                                                   \
                                                                        \
    t strto##p(const char *str, char **endptr, int base)                \
    {                                                                   \
        const char *ptr;                                                \
        t result = 0##s;                                                \
        int overflow = 0, negative = 0;                                 \
                                                                        \
        if (base < 2 || base > 36) return 0;                            \
                                                                        \
        switch (*str)                                                   \
        {                                                               \
        case '-':                                                       \
            negative = 1;                                               \
        case '+':                                                       \
            str++;                                                      \
            break;                                                      \
        }                                                               \
                                                                        \
        for (ptr = str; *ptr; ptr++)                                    \
        {                                                               \
            char *digit_ptr = strchr(all_digits, toupper(*ptr));        \
            if (digit_ptr == NULL) break;                               \
                                                                        \
            t digit = (t)(digit_ptr - all_digits);                      \
            if (digit >= base) break;                                   \
                                                                        \
            t new_result = result * (t)base + digit;                    \
            if (new_result < result) overflow = 1;                      \
            result = new_result;                                        \
        }                                                               \
                                                                        \
        if (overflow)                                                   \
        {                                                               \
            errno = ERANGE;                                             \
            return negative ? n##_MIN : n##_MAX;                        \
        }                                                               \
                                                                        \
        if (negative) result = -result;                                 \
        if (endptr) *endptr = (char*)ptr;                               \
        return result;                                                  \
    }                                                                   \
                                                                        \
    unsigned t strtou##p(const char *str, char **endptr, int base)      \
    {                                                                   \
        const char *ptr;                                                \
        unsigned t result = 0UL;                                        \
        int overflow = 0;                                               \
                                                                        \
        if (base < 2 || base > 36) return 0;                            \
                                                                        \
        for (ptr = str; *ptr; ptr++)                                    \
        {                                                               \
            char *digit_ptr = strchr(all_digits, toupper(*ptr));        \
            if (digit_ptr == NULL) break;                               \
                                                                        \
            unsigned t digit = (unsigned t)(digit_ptr - all_digits);    \
            if (digit >= base) break;                                   \
                                                                        \
            unsigned t new_result = result * (unsigned t)base + digit;  \
            if (new_result < result) overflow = 1;                      \
            result = new_result;                                        \
        }                                                               \
                                                                        \
        if (overflow)                                                   \
        {                                                               \
            errno = ERANGE;                                             \
            return U##n##_MAX;                                          \
        }                                                               \
                                                                        \
        if (endptr) *endptr = (char*)ptr;                               \
        return result;                                                  \
    }

static const char all_digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

DEFINE_XTOA(int, i, );
DEFINE_XTOA(long, l, L);
DEFINE_XTOA(long long, ll, LL);

DEFINE_ATOX(long, LONG, l, L);
DEFINE_ATOX(long long, LLONG, ll, LL);

int atoi(const char *str)
{
    return (int)strtol(str, NULL, 10);
}
