/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Mikkel F. Jørgensen, dvide.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *
 * Fast printing of (u)int8/16/32/64_t, (u)int, (u)long.
 *
 * Functions take for the
 *
 *   int print_<type>(type value, char *buf);
 *
 * and returns number of characters printed, excluding trailing '\0'
 * which is also printed. Prints at most 21 characters including zero-
 * termination.
 *
 * The function `print_bool` is a bit different - it simply prints "true\0" for
 * non-zero integers, and "false\0" otherwise.
 *
 * The general algorithm is in-place formatting using binary search log10
 * followed by duff device loop unrolling div / 100 stages.
 *
 * The simpler post copy algorithm also provided for fmt_(u)int uses a
 * temp buffer and loops over div/100 and post copy to target buffer.
 *
 *
 * Benchmarks on core-i7, 2.2GHz, 64-bit clang/OS-X -O2:
 *
 * print_int64: avg 15ns for values between INT64_MIN + (10^7/2 .. 10^7/2)
 * print_int64: avg 11ns for values between 10^9 + (0..10,000,000).
 * print_int32: avg 7ns for values cast from INT64_MIN + (10^7/2 .. 10^7/2)
 * print_int32: avg 7ns for values between 10^9 + (0..10,000,000).
 * print_int64: avg 13ns for values between 10^16 + (0..10,000,000).
 * print_int64: avg 5ns for values between 0 and 10,000,000.
 * print_int32: avg 5ns for values between 0 and 10,000,000.
 * print_int16: avg 10ns for values cast from 0 and 10,000,000.
 * print_int8:  avg 4ns for values cast from 0 and 10,000,000.
 *
 * Post copy algorithm:
 * print_int: avg 12ns for values between INT64_MIN + (10^7/2 .. 10^7/2)
 * print_int: avg 14ns for values between 10^9 + (0..10,000,000).
 * print_long: avg 29ns for values between INT64_MIN + (10^7/2 .. 10^7/2)
 *
 * The post copy algorithm is nearly half as fast as the in-place
 * algorithm, but can also be faster occasionally - possibly because the
 * optimizer being able to skip the copy step.
 */

#ifndef PPRINTINT_H
#define PPRINTINT_H

#ifdef __cplusplus
extern "C" {
#endif

#ifndef UINT8_MAX
#include <stdint.h>
#endif

#define PDIAGNOSTIC_IGNORE_UNUSED_FUNCTION
#include "pdiagnostic_push.h"

static int print_bool(int n, char *p);

static int print_uint8(uint8_t n, char *p);
static int print_uint16(uint16_t n, char *p);
static int print_uint32(uint32_t n, char *p);
static int print_uint64(uint64_t n, char *p);
static int print_int8(int8_t n, char *p);
static int print_int16(int16_t n, char *p);
static int print_int32(int32_t n, char *p);
static int print_int64(int64_t n, char *p);

/*
 * Uses slightly slower, but more compact alogrithm
 * that is not hardcoded to implementation size.
 * Other types may be defined using macros below.
 */
static int print_ulong(unsigned long n, char *p);
static int print_uint(unsigned int n, char *p);
static int print_int(int n, char *p);
static int print_long(long n, char *p);


#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64)
#define __print_unaligned_copy_16(p, q) (*(uint16_t*)(p) = *(uint16_t*)(q))
#else
#define __print_unaligned_copy_16(p, q)                                     \
    ((((uint8_t*)(p))[0] = ((uint8_t*)(q))[0]),                             \
     (((uint8_t*)(p))[1] = ((uint8_t*)(q))[1]))
#endif

static const char __print_digit_pairs[] =
    "0001020304050607080910111213141516171819"
    "2021222324252627282930313233343536373839"
    "4041424344454647484950515253545556575859"
    "6061626364656667686970717273747576777879"
    "8081828384858687888990919293949596979899";

#define __print_stage()                                                     \
        p -= 2;                                                             \
        dp = __print_digit_pairs + (n % 100) * 2;                           \
        n /= 100;                                                           \
        __print_unaligned_copy_16(p, dp);

#define __print_long_stage()                                                \
        __print_stage()                                                     \
        __print_stage()

#define __print_short_stage()                                               \
        *--p = (n % 10) + '0';                                              \
        n /= 10;

static int print_bool(int n, char *buf)
{
    if (n) {
        memcpy(buf, "true\0", 5);
        return 4;
    } else {
        memcpy(buf, "false\0", 6);
        return 5;
    }
}

static int print_uint8(uint8_t n, char *p)
{
    const char *dp;

    if (n >= 100) {
        p += 3;
        *p = '\0';
        __print_stage();
        p[-1] = n + '0';
        return 3;
    }
    if (n >= 10) {
        p += 2;
        *p = '\0';
        __print_stage();
        return 2;
    }
    p[1] = '\0';
    p[0] = n + '0';
    return 1;
}

static int print_uint16(uint16_t n, char *p)
{
    int k = 0;
    const char *dp;

    if (n >= 1000) {
        if(n >= 10000) {
            k = 5;
        } else {
            k = 4;
        }
    } else {
        if(n >= 100) {
            k = 3;
        } else if(n >= 10) {
            k = 2;
        } else {
            k = 1;
        }
    }
    p += k;
    *p = '\0';
    if (k & 1) {
        /* Fall through comments needed to silence gcc 7 warnings. */
        switch (k) {
        case 5:
            __print_stage();
	    /* Fall through */
        case 3:
            __print_stage();
	    /* Fall through */
        case 1:
            p[-1] = n + '0';
        }
    } else {
        /* Fall through comments needed to silence gcc 7 warnings. */
        switch (k) {
        case 4:
            __print_stage();
	    /* Fall through */
        case 2:
            __print_stage();
        }
    }
    return k;
}

static int print_uint32(uint32_t n, char *p)
{
    int k = 0;
    const char *dp;

    if(n >= 10000UL) {
        if(n >= 10000000UL) {
            if(n >= 1000000000UL) {
                k = 10;
            } else if(n >= 100000000UL) {
                k = 9;
            } else {
               k = 8;
            }
        } else {
            if(n >= 1000000UL) {
                k = 7;
            } else if(n >= 100000UL) {
                k = 6;
            } else {
                k = 5;
            }
        }
    } else {
        if(n >= 100UL) {
            if(n >= 1000UL) {
                k = 4;
            } else {
                k = 3;
            }
        } else {
            if(n >= 10UL) {
                k = 2;
            } else {
                k = 1UL;
            }
        }
    }
    p += k;
    *p = '\0';
    if (k & 1) {
        /* Fall through comments needed to silence gcc 7 warnings. */
        switch (k) {
        case 9:
            __print_stage();
	    /* Fall through */
        case 7:
            __print_stage();
	    /* Fall through */
        case 5:
            __print_stage();
	    /* Fall through */
        case 3:
            __print_stage();
	    /* Fall through */
        case 1:
            p[-1] = n + '0';
        }
    } else {
        /* Fall through comments needed to silence gcc 7 warnings. */
        switch (k) {
        case 10:
            __print_stage();
	    /* Fall through */
        case 8:
            __print_stage();
	    /* Fall through */
        case 6:
            __print_stage();
	    /* Fall through */
        case 4:
            __print_stage();
	    /* Fall through */
        case 2:
            __print_stage();
	    /* Fall through */
        }
    }
    return k;
}

static int print_uint64(uint64_t n, char *p)
{
    int k = 0;
    const char *dp;
    const uint64_t x = 1000000000ULL;

    if (n < x) {
        return print_uint32((uint32_t)n, p);
    }
    if(n >= 10000ULL * x) {
        if(n >= 10000000ULL * x) {
            if(n >= 1000000000ULL * x) {
                if (n >= 10000000000ULL * x) {
                    k = 11 + 9;
                } else {
                    k = 10 + 9;
                }
            } else if(n >= 100000000ULL * x) {
                k = 9 + 9;
            } else {
               k = 8 + 9;
            }
        } else {
            if(n >= 1000000ULL * x) {
                k = 7 + 9;
            } else if(n >= 100000ULL * x) {
                k = 6 + 9;
            } else {
                k = 5 + 9;
            }
        }
    } else {
        if(n >= 100ULL * x) {
            if(n >= 1000ULL * x) {
                k = 4 + 9;
            } else {
                k = 3 + 9;
            }
        } else {
            if(n >= 10ULL * x) {
                k = 2 + 9;
            } else {
                k = 1 + 9;
            }
        }
    }
    p += k;
    *p = '\0';
    if (k & 1) {
        /* Fall through comments needed to silence gcc 7 warnings. */
        switch (k) {
        case 19:
            __print_stage();
	    /* Fall through */
        case 17:
            __print_stage();
	    /* Fall through */
        case 15:
            __print_stage();
	    /* Fall through */
        case 13:
            __print_stage();
	    /* Fall through */
        case 11:
            __print_stage()
            __print_short_stage();
        }
    } else {
        /* Fall through comments needed to silence gcc 7 warnings. */
        switch (k) {
        case 20:
            __print_stage();
	    /* Fall through */
        case 18:
            __print_stage();
	    /* Fall through */
        case 16:
            __print_stage();
	    /* Fall through */
        case 14:
            __print_stage();
	    /* Fall through */
        case 12:
            __print_stage();
	    /* Fall through */
        case 10:
            __print_stage();
        }
    }
    __print_long_stage()
    __print_long_stage()
    return k;
}

static int print_int8(int8_t n, char *p)
{
    int sign;

    if ((sign = n < 0)) {
        *p++ = '-';
        n = -n;
    }
    return print_uint8(n, p) + sign;
}

static int print_int16(int16_t n, char *p)
{
    int sign;

    if ((sign = n < 0)) {
        *p++ = '-';
        n = -n;
    }
    return print_uint16(n, p) + sign;
}

static int print_int32(int32_t n, char *p)
{
    int sign;

    if ((sign = n < 0)) {
        *p++ = '-';
        n = -n;
    }
    return print_uint32(n, p) + sign;
}

static int print_int64(int64_t n, char *p)
{
    int sign;

    if ((sign = n < 0)) {
        *p++ = '-';
        n = -n;
    }
    return print_uint64(n, p) + sign;
}

#define __define_print_int_simple(NAME, UNAME, T, UT)                       \
static int UNAME(UT n, char *buf)                                           \
{                                                                           \
    char tmp[20];                                                           \
    char* p = tmp + 20;                                                     \
    char* q = p;                                                            \
    unsigned int k, m;                                                      \
                                                                            \
    while (n >= 100) {                                                      \
        p -= 2;                                                             \
        m = (unsigned int)(n % 100) * 2;                                    \
        n /= 100;                                                           \
        __print_unaligned_copy_16(p, __print_digit_pairs + m);              \
    }                                                                       \
    p -= 2;                                                                 \
    m = (unsigned int)n * 2;                                                \
    __print_unaligned_copy_16(p, __print_digit_pairs + m);                  \
    if (n < 10) {                                                           \
        ++p;                                                                \
    }                                                                       \
    k = (unsigned int)(q - p);                                              \
    while (p != q) {                                                        \
        *buf++ = *p++;                                                      \
    }                                                                       \
    *buf = '\0';                                                            \
    return k;                                                               \
}                                                                           \
                                                                            \
static int NAME(T n, char *buf)                                             \
{                                                                           \
    int sign = n < 0;                                                       \
                                                                            \
    if (sign) {                                                             \
        *buf++ = '-';                                                       \
        n = -n;                                                             \
    }                                                                       \
    return UNAME((UT)n, buf) + sign;                                        \
}

__define_print_int_simple(print_int, print_uint, int, unsigned int)
__define_print_int_simple(print_long, print_ulong, long, unsigned long)

#ifdef PPRINTINT_BENCH
int main() {
    int64_t count = 10000000; /* 10^7 */
#if 0
    int64_t base = 0;
    int64_t base = 10000000000000000; /* 10^16 */
    int64_t base = 1000000000; /* 10^9 */
#endif
    int64_t base = INT64_MIN - count/2;
    char buf[100];
    int i, k = 0, n = 0;
    for (i = 0; i < count; i++) {
        k = print_int64(i + base, buf);
        n += buf[0] + buf[k - 1];
    }
    return n;
}
/* Call with time on executable, multiply time in seconds by 100 to get time unit in ns/number. */
#endif /* PPRINTINT_BENCH */

#ifdef PPRINTINT_TEST

#include <stdio.h>
#include <string.h>

int main()
{
    char buf[21];
    int failed = 0;
    int k;

    k = print_uint64(UINT64_MAX, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("18446744073709551615", buf)) {
        printf("UINT64_MAX didn't print correctly, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_int64(INT64_MAX, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("9223372036854775807", buf)) {
        printf("INT64_MAX didn't print correctly, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_int64(INT64_MIN, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("-9223372036854775808", buf)) {
        printf("INT64_MIN didn't print correctly, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_uint32(UINT32_MAX, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("4294967295", buf)) {
        printf("UINT32_MAX didn't print correctly, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_int32(INT32_MAX, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("2147483647", buf)) {
        printf("INT32_MAX didn't print correctly, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_int32(INT32_MIN, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("-2147483648", buf)) {
        printf("INT32_MIN didn't print correctly, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_uint16(UINT16_MAX, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("65535", buf)) {
        printf("UINT16_MAX didn't print correctly, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_int16(INT16_MAX, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("32767", buf)) {
        printf("INT16_MAX didn't print correctly, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_int16(INT16_MIN, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("-32768", buf)) {
        printf("INT16_MIN didn't print correctly, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_uint8(UINT8_MAX, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("255", buf)) {
        printf("INT8_MAX didn't print correctly, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_int8(INT8_MAX, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("127", buf)) {
        printf("INT8_MAX didn't print correctly, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_int8(INT8_MIN, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("-128", buf)) {
        printf("INT8_MIN didn't print correctly, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_int(INT32_MAX, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("2147483647", buf)) {
        printf("INT32_MAX didn't print correctly with k = print_int, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_int(INT32_MIN, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("-2147483648", buf)) {
        printf("INT32_MIN didn't print correctly k = print_int, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_long(INT32_MAX, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("2147483647", buf)) {
        printf("INT32_MAX didn't print correctly with fmt_long, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_long(INT32_MIN, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("-2147483648", buf)) {
        printf("INT32_MIN didn't print correctly fmt_long, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_bool(1, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("true", buf) {
        printf("1 didn't print 'true' as expected, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_bool(-1, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("true", buf) {
        printf("-1 didn't print 'true' as expected, got:\n'%s'\n", buf);
        ++failed;
    }
    k = print_bool(, buf);
    if (strlen(buf) != k) printf("length error\n");
    if (strcmp("false", buf) {
        printf("0 didn't print 'false' as expected, got:\n'%s'\n", buf);
        ++failed;
    }
    if (failed) {
        printf("FAILED\n");
        return -1;
    }
    printf("SUCCESS\n");
    return 0;
}
#endif /* PPRINTINT_TEST */

#include "pdiagnostic_pop.h"

#ifdef __cplusplus
}
#endif

#endif /* PPRINTINT_H */
