/*	CFBigNumber.c
	Copyright (c) 2012-2019, Apple Inc. and the Swift project authors
 
	Portions Copyright (c) 2014-2019, Apple Inc. and the Swift project authors
	Licensed under Apache License v2.0 with Runtime Library Exception
	See http://swift.org/LICENSE.txt for license information
	See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
	Responsibility: Philippe Hausler
	Original author: Zhi Feng Huang
*/

#include <CoreFoundation/CFBase.h>
#include <CoreFoundation/CFBigNumber.h>
#include <CoreFoundation/CFNumber_Private.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "CFInternal.h"


#if TARGET_RT_64_BIT

#ifndef _INT128_T
#define _INT128_T
typedef __int128_t  int128_t;
#endif

#ifndef _UINT128_T
#define _UINT128_T
typedef __uint128_t uint128_t;
#endif

#ifndef INT128_MIN
#define INT128_MIN  ((__int128_t)0 - ((__int128_t)1 << 126) - ((__int128_t)1 << 126))
#endif

#ifndef INT128_MAX
#define INT128_MAX  ((__int128_t)-1 + ((__int128_t)1 << 126) + ((__int128_t)1 << 126))
#endif

#ifndef UINT128_MAX
#define UINT128_MAX (((__uint128_t)1 << 127) - (__uint128_t)1 + ((__uint128_t)1 << 127))
#endif

#endif


#define BIG_DIGITS_LIMIT    1000000000L
#define BIG_DIGITS_LIMIT_2  ((uint64_t)BIG_DIGITS_LIMIT * (uint64_t)BIG_DIGITS_LIMIT)
#define BIG_DIGITS_LIMIT_3  ((__uint128_t)BIG_DIGITS_LIMIT_2 * (__uint128_t)BIG_DIGITS_LIMIT)
#define BIG_DIGITS_LIMIT_4  ((__uint128_t)BIG_DIGITS_LIMIT_3 * (__uint128_t)BIG_DIGITS_LIMIT)

#define GET_FIFTH_DIGIT(A)  (A / BIG_DIGITS_LIMIT_4)
#define GET_FOURTH_DIGIT(A) (A / BIG_DIGITS_LIMIT_3)
#define GET_THIRD_DIGIT(A)  (A / BIG_DIGITS_LIMIT_2)
#define GET_SECOND_DIGIT(A) (A / BIG_DIGITS_LIMIT)

#define GET_REMAINDER_FIFTH_DIGIT(A,B)  (A - B * BIG_DIGITS_LIMIT_4)
#define GET_REMAINDER_FOURTH_DIGIT(A,B) (A - B * BIG_DIGITS_LIMIT_3)
#define GET_REMAINDER_THIRD_DIGIT(A,B)  (A - B * BIG_DIGITS_LIMIT_2)
#define GET_REMAINDER_SECOND_DIGIT(A,B) (A - B * BIG_DIGITS_LIMIT)


void _CFBigNumInitWithInt8(_CFBigNum *r, int8_t inNum) {
    memset(r, 0, sizeof(*r));
    uint8_t unsignInNum = inNum;
    if (inNum < 0) {
        r->sign = -1;
        unsignInNum = -1 * inNum;
    }
    r->digits[0] = unsignInNum;
}

void _CFBigNumInitWithInt16(_CFBigNum *r, int16_t inNum) {
    memset(r, 0, sizeof(*r));
    uint16_t unsignInNum = inNum;
    if (inNum < 0) {
        r->sign = -1;
        unsignInNum = -1 * inNum;
    }
    r->digits[0] = unsignInNum;
}

void _CFBigNumInitWithInt32(_CFBigNum *r, int32_t inNum) {
    memset(r, 0, sizeof(*r));
    uint32_t unsignInNum = inNum;
    if (inNum < 0) {
        r->sign = -1;
        unsignInNum = -1 * inNum;
    }
    uint32_t dig0 = GET_SECOND_DIGIT(unsignInNum);
    r->digits[0] = GET_REMAINDER_SECOND_DIGIT(unsignInNum, dig0);
    r->digits[1] = dig0;
}

void _CFBigNumInitWithInt64(_CFBigNum *r, int64_t inNum) {
    memset(r, 0, sizeof(*r));
    uint64_t unsignInNum = inNum;
    if (inNum < 0) {
        r->sign = -1;
        unsignInNum = -1 * inNum;
    }
    uint32_t dig2 = GET_THIRD_DIGIT(unsignInNum);
    unsignInNum = GET_REMAINDER_THIRD_DIGIT(unsignInNum, (uint64_t)dig2);
    uint32_t dig1 = GET_SECOND_DIGIT(unsignInNum);
    r->digits[0] = GET_REMAINDER_SECOND_DIGIT(unsignInNum, (uint64_t)dig1);
    r->digits[1] = dig1;
    r->digits[2] = dig2;
}

#if TARGET_RT_64_BIT
void _CFBigNumInitWithInt128(_CFBigNum *r, __int128_t inNum) {
    memset(r, 0, sizeof(*r));
    __uint128_t unsignInNum = inNum;
    if (inNum < 0) {
        r->sign = -1;
        unsignInNum = -1 * inNum;
    }
    uint32_t dig4 = GET_FIFTH_DIGIT(unsignInNum);
    unsignInNum = GET_REMAINDER_FIFTH_DIGIT(unsignInNum, (__uint128_t)dig4);
    uint32_t dig3 = GET_FOURTH_DIGIT(unsignInNum);
    unsignInNum = GET_REMAINDER_FOURTH_DIGIT(unsignInNum, (__uint128_t)dig3);
    uint32_t dig2 = GET_THIRD_DIGIT(unsignInNum);
    unsignInNum = GET_REMAINDER_THIRD_DIGIT(unsignInNum, (__uint128_t)dig2);
    uint32_t dig1 = GET_SECOND_DIGIT(unsignInNum);
    r->digits[0] = GET_REMAINDER_SECOND_DIGIT(unsignInNum, (__uint128_t)dig1);
    r->digits[1] = dig1;
    r->digits[2] = dig2;
    r->digits[3] = dig3;
    r->digits[4] = dig4;
}
#endif

void _CFBigNumInitWithUInt8(_CFBigNum *r, uint8_t inNum) {
    memset(r, 0, sizeof(*r));
    uint8_t unsignInNum = inNum;
    r->digits[0] = unsignInNum;
}

void _CFBigNumInitWithUInt16(_CFBigNum *r, uint16_t inNum) {
    memset(r, 0, sizeof(*r));
    uint16_t unsignInNum = inNum;
    r->digits[0] = unsignInNum;
}

void _CFBigNumInitWithUInt32(_CFBigNum *r, uint32_t inNum) {
    memset(r, 0, sizeof(*r));
    uint32_t unsignInNum = inNum;
    uint32_t dig0 = GET_SECOND_DIGIT(unsignInNum);
    r->digits[0] = GET_REMAINDER_SECOND_DIGIT(unsignInNum, dig0);
    r->digits[1] = dig0;
}

void _CFBigNumInitWithUInt64(_CFBigNum *r, uint64_t inNum) {
    memset(r, 0, sizeof(*r));
    uint64_t unsignInNum = inNum;
    uint32_t dig2 = GET_THIRD_DIGIT(unsignInNum);
    unsignInNum = GET_REMAINDER_THIRD_DIGIT(unsignInNum, (uint64_t)dig2);
    uint32_t dig1 = GET_SECOND_DIGIT(unsignInNum);
    r->digits[0] = GET_REMAINDER_SECOND_DIGIT(unsignInNum, (uint64_t)dig1);
    r->digits[1] = dig1;
    r->digits[2] = dig2;
}

#if TARGET_RT_64_BIT
void _CFBigNumInitWithUInt128(_CFBigNum *r, __uint128_t inNum) {
    memset(r, 0, sizeof(*r));
    __uint128_t unsignInNum = inNum;
    uint32_t dig4 = GET_FIFTH_DIGIT(unsignInNum);
    unsignInNum = GET_REMAINDER_FIFTH_DIGIT(unsignInNum, (__uint128_t)dig4);
    uint32_t dig3 = GET_FOURTH_DIGIT(unsignInNum);
    unsignInNum = GET_REMAINDER_FOURTH_DIGIT(unsignInNum, (__uint128_t)dig3);
    uint32_t dig2 = GET_THIRD_DIGIT(unsignInNum);
    unsignInNum = GET_REMAINDER_THIRD_DIGIT(unsignInNum, (__uint128_t)dig2);
    uint32_t dig1 = GET_SECOND_DIGIT(unsignInNum);
    r->digits[0] = GET_REMAINDER_SECOND_DIGIT(unsignInNum, (__uint128_t)dig1);
    r->digits[1] = dig1;
    r->digits[2] = dig2;
    r->digits[3] = dig3;
    r->digits[4] = dig4;
}
#endif


int8_t _CFBigNumGetInt8(const _CFBigNum *num) {
    int8_t result = num->digits[0];
    if (num->sign < 0) {
        result = -1 * result;
    }
    return result;
}

int16_t _CFBigNumGetInt16(const _CFBigNum *num) {
    int16_t result = num->digits[0];
    if (num->sign < 0) {
        result = -1 * result;
    }
    return result;
}

int32_t _CFBigNumGetInt32(const _CFBigNum *num) {
    int32_t result = num->digits[0];
    result += num->digits[1] * BIG_DIGITS_LIMIT;
    if (num->sign < 0) {
        result = -1 * result;
    }
    return result;
}

int64_t _CFBigNumGetInt64(const _CFBigNum *num) {
    int64_t result = num->digits[0];
    result += (int64_t)num->digits[1] * BIG_DIGITS_LIMIT;
    result += (int64_t)num->digits[2] * BIG_DIGITS_LIMIT_2;
    if (num->sign < 0) {
        result = -1 * result;
    }
    return result;
}

#if TARGET_RT_64_BIT
__int128_t _CFBigNumGetInt128(const _CFBigNum *num) {
    __int128_t result = num->digits[0];
    result += (__int128_t)num->digits[1] * BIG_DIGITS_LIMIT;
    result += (__int128_t)num->digits[2] * BIG_DIGITS_LIMIT_2;
    result += (__int128_t)num->digits[3] * BIG_DIGITS_LIMIT_3;
    result += (__int128_t)num->digits[4] * BIG_DIGITS_LIMIT_4;
    if (num->sign < 0) {
        result = -1 * result;
    }
    return result;
}
#endif

uint8_t _CFBigNumGetUInt8(const _CFBigNum *num) {
    uint8_t result = num->digits[0];
    return result;
}

uint16_t _CFBigNumGetUInt16(const _CFBigNum *num) {
    uint16_t result = num->digits[0];
    return result;
}

uint32_t _CFBigNumGetUInt32(const _CFBigNum *num) {
    uint32_t result = num->digits[0];
    result += num->digits[1] * BIG_DIGITS_LIMIT;
    return result;
}

uint64_t _CFBigNumGetUInt64(const _CFBigNum *num) {
    uint64_t result = num->digits[0];
    result += (uint64_t)num->digits[1] * BIG_DIGITS_LIMIT;
    result += (uint64_t)num->digits[2] * BIG_DIGITS_LIMIT_2;
    return result;
}

#if TARGET_RT_64_BIT
__uint128_t _CFBigNumGetUInt128(const _CFBigNum *num) {
    __uint128_t result = num->digits[0];
    result += (__uint128_t)num->digits[1] * BIG_DIGITS_LIMIT;
    result += (__uint128_t)num->digits[2] * BIG_DIGITS_LIMIT_2;
    result += (__uint128_t)num->digits[3] * BIG_DIGITS_LIMIT_3;
    result += (__uint128_t)num->digits[4] * BIG_DIGITS_LIMIT_4;
    return result;
}
#endif


void _CFBigNumInitWithCFNumber(_CFBigNum *r, CFNumberRef inNum) {
    uint8_t bytes[128 + 16];
    memset(bytes, 0, sizeof(bytes));
    // round bytes up to next multiple of 16; compiler attributes won't always guarantee big alignment
    void *bytesa = (uint8_t *)(((uintptr_t)bytes / 16) * 16 + 16);
    CFNumberType type = _CFNumberGetType2(inNum);
    CFNumberGetValue(inNum, type, bytesa);
    _CFBigNumInitWithBytes(r, bytesa, type);
}

void _CFBigNumInitWithBytes(_CFBigNum *r, const void *bytes, CFNumberType type) {
    switch (type) {
    case kCFNumberSInt8Type:
        _CFBigNumInitWithInt8(r, *(int8_t *)bytes);
        return;
    case kCFNumberSInt16Type:
        _CFBigNumInitWithInt16(r, *(int16_t *)bytes);
        return;
    case kCFNumberSInt32Type:
        _CFBigNumInitWithInt32(r, *(int32_t *)bytes);
        return;
    case kCFNumberSInt64Type:
        _CFBigNumInitWithInt64(r, *(int64_t *)bytes);
        return;
    case kCFNumberCharType:
        _CFBigNumInitWithInt8(r, *(int8_t *)bytes);
        return;
    case kCFNumberShortType:
        _CFBigNumInitWithInt16(r, *(int16_t *)bytes);
        return;
    case kCFNumberIntType:
        _CFBigNumInitWithInt32(r, *(int32_t *)bytes);
        return;
    case kCFNumberLongType:
        if (sizeof(long) == 8) {
            _CFBigNumInitWithInt64(r, *(int64_t *)bytes);
        } else {
            _CFBigNumInitWithInt32(r, *(int32_t *)bytes);
        }
        return;
    case kCFNumberLongLongType:
        _CFBigNumInitWithInt64(r, *(int64_t *)bytes);
        return;
    case kCFNumberCFIndexType:
        if (sizeof(CFIndex) == 8) {
            _CFBigNumInitWithInt64(r, *(int64_t *)bytes);
        } else {
            _CFBigNumInitWithInt32(r, *(int32_t *)bytes);
        }
        return;
    case kCFNumberNSIntegerType:
        if (sizeof(long) == 8) { // NSInteger follows long
            _CFBigNumInitWithInt64(r, *(int64_t *)bytes);
        } else {
            _CFBigNumInitWithInt32(r, *(int32_t *)bytes);
        }
        return;
#if TARGET_RT_64_BIT
    case kCFNumberSInt128Type: {
        CFSInt128Struct s;
        memmove(&s, bytes, sizeof(CFSInt128Struct)); // the hard way because bytes might not be aligned
        __int128_t val = (__int128_t)s.low + ((__int128_t)s.high << 64);
        _CFBigNumInitWithInt128(r, val);
        return;
    }
#endif
    default:
        return;
    }
}

CFNumberRef _CFNumberCreateWithBigNum(const _CFBigNum *input) {
    if (0 == input->digits[4] && 0 == input->digits[3] && 0 == input->digits[2] && 0 == input->digits[1]) {
        // This bumps up the size of the most negative n-bit value to the next larger size; oh well
        if (input->digits[0] <= INT8_MAX) {
            int8_t num = _CFBigNumGetInt8(input);
            CFNumberRef result = CFNumberCreate(kCFAllocatorSystemDefault, kCFNumberSInt8Type, (const void *)&num);
            return result;
        } else if (input->digits[0] <= INT16_MAX) {
            int16_t num = _CFBigNumGetInt16(input);
            CFNumberRef result = CFNumberCreate(kCFAllocatorSystemDefault, kCFNumberSInt16Type, (const void *)&num);
            return result;
        }
    }    
    _CFBigNum maxlimit, minlimit;
    if (0 == input->digits[4] && 0 == input->digits[3] && 0 == input->digits[2]) {
        _CFBigNumInitWithInt32(&maxlimit, INT32_MAX);
        _CFBigNumInitWithInt32(&minlimit, INT32_MIN);
        CFComparisonResult cr = _CFBigNumCompare(input, &maxlimit);
        CFComparisonResult crn = _CFBigNumCompare(input, &minlimit);
        if ((kCFCompareLessThan == cr || kCFCompareEqualTo == cr) && (kCFCompareLessThan != crn)) {
            int32_t num = _CFBigNumGetInt32(input);
            CFNumberRef result = CFNumberCreate(kCFAllocatorSystemDefault, kCFNumberSInt32Type, (const void *)&num);
            return result;
        }
    }
    if (0 == input->digits[4] && 0 == input->digits[3]) {
        _CFBigNumInitWithInt64(&maxlimit, INT64_MAX);
        _CFBigNumInitWithInt64(&minlimit, INT64_MIN);
        CFComparisonResult cr = _CFBigNumCompare(input, &maxlimit);
        CFComparisonResult crn = _CFBigNumCompare(input, &minlimit);
        if ((kCFCompareLessThan == cr || kCFCompareEqualTo == cr) && (kCFCompareLessThan != crn)) {
            int64_t num = _CFBigNumGetInt64(input);
            CFNumberRef result = CFNumberCreate(kCFAllocatorSystemDefault, kCFNumberSInt64Type, (const void *)&num);
            return result;
        }
    }
#if TARGET_RT_64_BIT
    _CFBigNumInitWithInt128(&maxlimit, INT128_MAX);
    _CFBigNumInitWithInt128(&minlimit, INT128_MIN);
    CFComparisonResult cr = _CFBigNumCompare(input, &maxlimit);
    CFComparisonResult crn = _CFBigNumCompare(input, &minlimit);
    if ((kCFCompareLessThan == cr || kCFCompareEqualTo == cr) && (kCFCompareLessThan != crn)) {
        __int128_t num = _CFBigNumGetInt128(input);
        CFNumberRef result = CFNumberCreate(kCFAllocatorSystemDefault, kCFNumberSInt128Type, (const void *)&num);
        return result;
    }
#endif
    return NULL;
}

CFComparisonResult _CFBigNumCompare(const _CFBigNum *a, const _CFBigNum *b) {
    Boolean sameSign = a->sign == b->sign;
    if (sameSign) {
        Boolean negative = a->sign < 0;
        for (CFIndex i = sizeof(a->digits) / sizeof(a->digits[0]); i--;) {
            if (a->digits[i] < b->digits[i]) {
                return !negative ? kCFCompareLessThan : kCFCompareGreaterThan;
            }
            if (a->digits[i] > b->digits[i]) {
                return negative ? kCFCompareLessThan : kCFCompareGreaterThan;
            }
        }
        return kCFCompareEqualTo;
    }
    return (a->sign < b->sign) ? kCFCompareLessThan : kCFCompareGreaterThan;
}

// abs(a) < abs(b)
// do not care about the sign
static Boolean _CFBigNumAbsoluteLessThan(const _CFBigNum *a, const _CFBigNum *b) {
    for (CFIndex i = sizeof(a->digits) / sizeof(a->digits[0]); i--;) {
        if (a->digits[i] < b->digits[i]) {
            return true;
        }
        if (a->digits[i] > b->digits[i]) {
            return false;
        }
    }
    return false;
}

// r = a * -1
void _CFBigNumNeg(_CFBigNum *r, const _CFBigNum *a) {
    memmove(r, a, sizeof(*a));
    Boolean aIsZero = true;
    for (CFIndex i = 0; i < sizeof(a->digits) / sizeof(a->digits[0]); i++) {
        if (a->digits[i] != 0) {
            aIsZero = false;
            break;
        }
    }
    // if a is zero, we do not flip the sign
    if (!aIsZero) {
        // 0  -> -1
        // -1 -> 0
        r->sign = r->sign * r->sign - 1;
    }
}

uint8_t _CFBigNumAdd(_CFBigNum *r, const _CFBigNum *a, const _CFBigNum *b) {
    uint8_t carry = 0;
    Boolean sameSign = a->sign == b->sign;
    if (sameSign) {
        for (CFIndex i = 0; i < sizeof(a->digits) / sizeof(a->digits[0]); i++) {
            uint32_t result = a->digits[i] + b->digits[i] + carry;
            if (result > BIG_DIGITS_LIMIT) {
                carry = 1;
                result -= BIG_DIGITS_LIMIT;
            } else {
                carry = 0;
            }
            r->digits[i] = result;
        }
        r->sign = a->sign;
        return carry;
    } else {
        // the algorithm here is to find the larger one and do the subtraction and then neg the result if necessary
        const _CFBigNum *bigNum = NULL;
        const _CFBigNum *smallNum = NULL;
        if (_CFBigNumAbsoluteLessThan(a, b)) {
            bigNum = b;
            smallNum = a;
        } else {
            bigNum = a;
            smallNum = b;
        }
        for (int i = 0; i < sizeof(a->digits) / sizeof(a->digits[0]); i++) {
            int64_t result = (int64_t)bigNum->digits[i] - (int64_t)smallNum->digits[i] - carry;
            if (result < 0) {
                carry = 1;
                result += BIG_DIGITS_LIMIT;
            } else {
                carry = 0;
            }
            r->digits[i] = result;
        }
        if (bigNum->sign < 0) {
            r->sign = -1;
        } else {
            r->sign = 0;
        }
        return carry;
    }
}

uint8_t _CFBigNumSub(_CFBigNum *r, const _CFBigNum *a, const _CFBigNum *b) {
    _CFBigNum nb;
    _CFBigNumNeg(&nb, b);
    return _CFBigNumAdd(r, a, &nb);
}


void _CFBigNumToCString(const _CFBigNum *vp, Boolean leading_zeros, Boolean leading_plus, char *buffer, size_t buflen) {
    if (vp->sign < 0) {
        *buffer++ = '-';
        buflen--;
    } else if (leading_plus) {
        *buffer++ = '+';
        buflen--;
    }
    char tmp[46];
    snprintf(tmp, sizeof(tmp), "%09u%09u%09u%09u%09u", vp->digits[4], vp->digits[3], vp->digits[2], vp->digits[1], vp->digits[0]);
    if (leading_zeros) {
        memset(buffer, '0', buflen);
        uint32_t tocopy = __CFMin(sizeof(tmp), buflen);
        memmove(buffer + buflen - tocopy, tmp + sizeof(tmp) - tocopy, tocopy); // copies trailing nul from tmp to nul-terminate
    } else {
        char *s = tmp;
        while (*s == '0') s++;
        if (*s == 0) s--; // if tmp is all zeros, copy out at least one zero
        strlcpy(buffer, s, buflen);
    }
}

void _CFBigNumFromCString(_CFBigNum *r, const char *string) {
    memset(r, 0, sizeof(*r));
    char *copy = (char *)calloc(strlen(string)+1, sizeof(char));
    memcpy(copy, string, strlen(string)+1);
    char *working = copy;
    if (*working == '-') {
        r->sign = -1;
        working++;
    } else if (*working == '+') {
        working++;
    }
    while (*working == '0') {
        working++;
    }

    size_t length = strlen(working);
    if (length == 0) { // the number is zero
        free(copy);
        return;
    }
    int curDigit = 0;
    while (curDigit + 1 < sizeof(r->digits) / sizeof(r->digits[0]) && 9 < length) {
        uint32_t digit = atol(working+length-9);
        r->digits[curDigit] = digit;
        *(working+length-9) = 0;
        length -= 9;
        curDigit++;
    }
    uint32_t digit = atol(working);
    r->digits[curDigit] = digit;
    free(copy);
}

char *_CFBigNumCopyDescription(const _CFBigNum *num) {
    char *result = (char *)calloc(1024, sizeof(char));
    sprintf(result, "sign:%s 1st:%u 2nd:%u 3rd:%u 4th:%u 5th:%u", num->sign < 0 ? "-" : "+", num->digits[0], num->digits[1], num->digits[2], num->digits[3], num->digits[4]);
    return result;
}

