/*
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/

 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.

 * The Original Code is Rhino code, released
 * May 6, 1999.

 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1997-1999 Netscape Communications Corporation. All
 * Rights Reserved.

 * Contributor(s): 

 * Alternatively, the contents of this file may be used under the
 * terms of the GNU Public License (the "GPL"), in which case the
 * provisions of the GPL are applicable instead of those above.
 * If you wish to allow use of your version of this file only
 * under the terms of the GPL and not to allow others to use your
 * version of this file under the NPL, indicate your decision by
 * deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL.  If you do not delete
 * the provisions above, a recipient may use your version of this
 * file under either the NPL or the GPL.
 */
#ifdef DTOA

/* Headers */
#include "js-exceptionhandler.h"
#include "js-arithmeticexception.h"
#include "js-stringbuffer.h"
#include "js-biginteger.h"

/* Procedures */
int
 JSBigInteger::magnitudeCmp(Magnitude * arg1, Magnitude * arg2)
{
    int size1 = arg1->size();
    int size2 = arg2->size();

    if (size1 < size2)
        return -1;
    if (size1 > size2)
        return 1;

    /* Argument lengths are equal; compare the values */
    for (int i = size1; --i >= 0;) {
        Digit b1 = arg1->elementAt(i);
        Digit b2 = arg2->elementAt(i);

        if (b1 < b2)
            return -1;
        if (b1 > b2)
            return 1;
    }
    return 0;
}

jsChar_t JSBigInteger::forDigit(int digit, int radix)
{
    if ((digit >= radix) || (digit < 0)) {
        return '\0';
    }
    if ((radix < MIN_RADIX) || (radix > MAX_RADIX)) {
        return '\0';
    }
    if (digit < 10) {
        return (char)('0' + digit);
    }
    return (jsChar_t) ('a' - 10 + digit);
}

jsString_t JSBigInteger::int64ToString(JSContext * cx, int64_t i, int radix)
{
    JSStringBuffer buf(cx);

    if (radix < MIN_RADIX || radix > MAX_RADIX)
        radix = 10;
    jsBool_t negative = (i < 0);

    if (!negative)
        i = -i;
    while (i <= -radix) {
        buf.append(forDigit((int)(-(i % radix)), radix));
        i = i / radix;
    }
    buf.append(forDigit((int)(-i), radix));
    if (negative) {
        buf.append('-');
    }
    return buf.reverse()->offString();
}

void JSBigInteger::divideAndRemainder(JSContext * cx, JSBigInteger & x, JSBigInteger & y, JSBigInteger & div, JSBigInteger & rem)
{
    int sizex = x.magnitude.size();
    int sizey = y.magnitude.size();
    JSBigInteger *yp;
    DigitDBL digitdbl;
    Digit digit;
    int i;

    if (sizey == 1) {
        digit = y.magnitude.elementAt(0);
        digitdbl = 0;
        div = x;
        for (i = div.magnitude.size(); --i >= 0;) {
            digitdbl = (digitdbl << BITS_PER_DIGIT)
                    + div.magnitude.elementAt(i);
            div.magnitude.setElementAt(digitdbl / digit, i);
            digitdbl %= digit;
        }
        div.signum = (x.signum == y.signum) ? 1 : -1;
        div.normalize();

        rem = valueOf(cx, digitdbl);
        rem.signum = x.signum;
        rem.normalize();
        return;
    }
    JSBigInteger yTemp(cx);
    Digit hi0bits;

    div.signum = (x.signum == y.signum) ? 1 : -1;
    div.magnitude.setSize(sizey == sizex ? sizex + 2 : sizex + 1);
    if (sizey == sizex)
        div.magnitude.setElementAt(0, sizex + 1);

    hi0bits = 0;
    digit = y.magnitude.elementAt(sizey - 1);
    while ((digit & (1 << (BITS_PER_DIGIT - 1))) == 0) {
        digit <<= 1;
        hi0bits += 1;
    }
    if (hi0bits != 0) { // shifts x and y
        digitdbl = 0;
        yTemp = y;
        for (i = 0; i < sizey; ++i) {
            digitdbl += (DigitDBL) y.magnitude.elementAt(i) << hi0bits;
            yTemp.magnitude.setElementAt(digitdbl, i);
            digitdbl >>= BITS_PER_DIGIT;
        }
        yp = &yTemp;
        digitdbl = 0;
        for (i = 0; i < sizex; ++i) {
            digitdbl += (DigitDBL) x.magnitude.elementAt(i) << hi0bits;
            div.magnitude.setElementAt(digitdbl, i);
            digitdbl >>= BITS_PER_DIGIT;
        }
        div.magnitude.setElementAt(digitdbl, sizex);
    }
    else {
        div.magnitude.setElementAt(0, sizex);
        for (i = sizex; --i >= 0;) {
            div.magnitude.setElementAt(x.magnitude.elementAt(i), i);
        }
        yp = &y;
    }
    int divIndex = sizex == sizey ? sizex + 1 : sizex;

    do {
        if (div.magnitude.elementAt(divIndex)
                == yp->magnitude.elementAt(sizey - 1)) {
            digit = ((DigitDBL) 1 << BITS_PER_DIGIT) - 1;
        }
        else {
            digit = ((((DigitDBL) div.magnitude.elementAt(divIndex))
                            << BITS_PER_DIGIT)
                    + div.magnitude.elementAt(divIndex - 1))
                    / yp->magnitude.elementAt(sizey - 1);
        }
        if (digit != 0) {
            SDigitDBL num;

            i = 0;
            num = 0;
            digitdbl = 0;
            do {    /* multiply and subtract */
                DigitDBL ee;

                digitdbl += (DigitDBL) yp->magnitude.elementAt(i) * digit;
                ee = num - (digitdbl & DIGIT_MASK);
                num = (DigitDBL) div.magnitude.elementAt(divIndex - sizey + i)
                        + ee;
                if (ee != 0) {
                    div.magnitude.setElementAt(num & DIGIT_MASK, divIndex - sizey + i);
                }
                num >>= BITS_PER_DIGIT;
                digitdbl >>= BITS_PER_DIGIT;
            } while (++i < sizey);
            /* borrow from high digit; don't update */
            num += div.magnitude.elementAt(divIndex) - digitdbl;

            while (num != 0) {  /* "add back" required */
                i = 0;
                num = 0;
                digit -= 1;
                do {
                    DigitDBL ee = num + yp->magnitude.elementAt(i);

                    num = (DigitDBL) div.magnitude.elementAt(divIndex - sizey + i)
                            + ee;
                    if (ee != 0) {
                        div.magnitude.setElementAt(num & DIGIT_MASK, divIndex - sizey + i);
                    }
                    num >>= BITS_PER_DIGIT;
                } while (++i < sizey);
                num -= 1;
            }
        }
        div.magnitude.setElementAt(digit, divIndex);
    } while (--divIndex >= sizey);

    int sizeDiv;

    rem = div;  // assign before modifying div

    sizeDiv = div.magnitude.size() - sizey;
    for (i = 0; i < sizeDiv; ++i) {
        div.magnitude.setElementAt(div.magnitude.elementAt(i + sizey), i);
    }
    div.magnitude.setSize(sizeDiv);
    div.normalize();

    int sizeRem = sizey;

    sizeRem -= 1;
    while (sizeRem >= 0 && rem.magnitude.elementAt(sizeRem) == 0) {
        sizeRem -= 1;
    }
    sizeRem += 1;
    if (hi0bits != 0) {
        digitdbl = 0;
        for (int i = sizeRem; --i >= 0;) {
            digitdbl = (digitdbl | rem.magnitude.elementAt(i)) >> hi0bits;
            digit = rem.magnitude.elementAt(i);
            rem.magnitude.setElementAt(digitdbl & DIGIT_MASK, i);
            digitdbl = (DigitDBL) digit << BITS_PER_DIGIT;
        }
    }
    rem.magnitude.setSize(sizeRem);
    rem.signum = x.signum;
    rem.normalize();
}

JSBigInteger JSBigInteger::multiply(JSContext * cx, JSBigInteger & x, JSBigInteger & y)
{
    int sizex = x.magnitude.size();
    int sizey = y.magnitude.size();
    int size = sizex + sizey + 1;
    JSBigInteger result(cx);
    int index;

    result.signum = x.signum * y.signum;
    result.magnitude.setSize(size);
    index = size - 1;
    while (--index >= 0)
        result.magnitude.setElementAt(0, index);
    for (int i = 0; i < sizex; ++i) {
        DigitDBL digitdbl = x.magnitude.elementAt(i);

        if (digitdbl == 0)
            continue;

        DigitDBL n = 0;

        for (int j = 0; j < sizey; j++) {
            DigitDBL ee = n + (DigitDBL) digitdbl * y.magnitude.elementAt(j);

            n = result.magnitude.elementAt(i + j) + ee;
            if (ee != 0) {
                result.magnitude.setElementAt(n, i + j);
            }
            n >>= BITS_PER_DIGIT;
        }
        if (n != 0) {
            result.magnitude.setElementAt(n, i + sizey);
        }
    }
    result.normalize();

    return result;
}

JSBigInteger JSBigInteger::subtract(JSContext * cx, JSBigInteger & x, JSBigInteger & y)
{
    JSBigInteger *xp = &x;
    JSBigInteger *yp = &y;
    int sizex = xp->magnitude.size();
    int sizey = yp->magnitude.size();
    JSBigInteger z(cx);
    JSBigInteger *zp = NULL;
    SDigitDBL num;
    int i;

    if (yp->signum == 0) {
        z = *xp;
        return z;
    }
    /* if x is larger than y, swap */
    if (sizex < sizey) {
        zp = xp;
        xp = yp;
        yp = zp;    /* swap x y */
    }
    else if (sizex == sizey) {
        i = sizex;
        while (i > 0) {
            i--;
            if (xp->magnitude.elementAt(i) > yp->magnitude.elementAt(i)) {
                break;
            }
            if (xp->magnitude.elementAt(i) < yp->magnitude.elementAt(i)) {
                zp = xp;
                xp = yp;
                yp = zp;    /* swap x y */
                break;
            }
        }
    }
    sizex = xp->magnitude.size();
    sizey = yp->magnitude.size();

    z.magnitude.setSize(sizex);
    z.signum = zp == NULL ? 1 : -1;
    for (i = 0, num = 0; i < sizey; i++) {
        num += (SDigitDBL) xp->magnitude.elementAt(i)
                - (SDigitDBL) yp->magnitude.elementAt(i);
        z.magnitude.setElementAt(num & DIGIT_MASK, i);
        num >>= BITS_PER_DIGIT;
    }
    while (num != 0 && i < sizex) {
        num += xp->magnitude.elementAt(i);
        z.magnitude.setElementAt(num & DIGIT_MASK, i);
        ++i;
        num >>= BITS_PER_DIGIT;
    }
    while (i < sizex) {
        z.magnitude.setElementAt(xp->magnitude.elementAt(i), i);
        ++i;
    }

    return z;
}
void JSBigInteger::makePositive(Digit * val, int valLength)
{
    int keep, i, j;

    for (keep = 0; keep < valLength && val[keep] == (Digit) - 1; ++keep);
    for (j = keep; j < valLength && val[j] == 0; ++j);

    int extraByte = (j == valLength ? 1 : 0);

    magnitude.setSize(valLength - keep + extraByte);
    for (i = valLength; --i >= keep;) {
        /* reverse endian */
        magnitude.setElementAt(~val[i], valLength - i - 1);
    }
    if (extraByte != 0)
        magnitude.setElementAt(0, valLength - keep);

    /* Add one to one's complement to generate two's complement */
    int size = magnitude.size();

    for (i = 0; i < size; ++i) {
        Digit digit = magnitude.elementAt(i) + 1;

        magnitude.setElementAt(digit, i);
        if (digit == 0)
            break;
    }
}
void JSBigInteger::stripLeadingZeroBytes(Digit * val, int valLength)
{
    int keep;

    /* Find first nonzero byte */
    for (keep = 0; keep < valLength && val[keep] == 0; ++keep);
    magnitude.setSize(valLength - keep);
    for (int i = keep; i < valLength; ++i) {
        magnitude.setElementAt(val[i], valLength - i - 1);  /* reverse endian */
    }
}

JSBigInteger::Digit JSBigInteger::signDigit()
{
    return (signum < 0 ? -1 : 0);
}

JSBigInteger::Digit JSBigInteger::getDigit(int n)
{
    int size = magnitude.size();

    if (n >= size)
        return signDigit();

    Digit magDigit = magnitude.elementAt(n);

    return (signum >= 0 ? magDigit : ((n == size - 1) ? ~magDigit : -magDigit)
            );
}

void JSBigInteger::normalize()
{
    for (int i = magnitude.size(); --i >= 0;) {
        Digit digit = magnitude.elementAt(i);

        if (digit != 0) {
            magnitude.setSize(i + 1);
            return;
        }
    }
    signum = 0;
    magnitude.setSize(0);
}

JSBigInteger JSBigInteger::valueOf(JSContext * cx, int64_t val)
{
    JSBigInteger result(cx, (Digit *) & val, sizeof(val) / sizeof(Digit));

    return result;
}

void JSBigInteger::divideAndRemainder(JSContext * cx, JSBigInteger ** result, JSBigInteger & val)
{
    JSBigInteger ZERO(cx);

    if (val.signum == 0) {
        cx->exceptionHandler->throwEx(new(cx) JSArithmeticException("BigInteger divide by zero"));
    }
    else if (this->signum == 0) {
        *result[0] = ZERO;
        *result[1] = ZERO;
    }
    else if (this->magnitude.size() < val.magnitude.size()) {
        *result[0] = ZERO;
        *result[1] = *this;
    }
    else {
        divideAndRemainder(cx, *this, val, *result[0], *result[1]);
    }
}
int JSBigInteger::compareTo(JSBigInteger & val)
{
    return (signum == val.signum ? signum * magnitudeCmp(&magnitude, &val.magnitude)
            : (signum > val.signum ? 1 : -1)
            );
}

JSBigInteger JSBigInteger::shiftLeft(JSContext * cx, int n)
{
    if (n == 0) {
        JSBigInteger result(cx);

        result = *this;
        return result;
    }
    if (n < 0) {
        return shiftRight(cx, -n);
    }

    Magnitude val(cx);

    int nDigits = n / BITS_PER_DIGIT;
    int nBits = n % BITS_PER_DIGIT;
    int size = magnitude.size();

    val.addElement(signDigit());
    if (nBits == 0) {
        int i;

        for (i = size; --i >= 0;) {
            val.addElement(getDigit(i));
        }
        for (i = 0; i < nDigits; ++i) {
            val.addElement(0);
        }
    }
    else {
        Digit bits = 0;
        int i;

        for (i = 0; i < size; i++) {
            Digit digit;

            digit = bits << nBits;
            bits = getDigit(size - i - 1);
            digit |= bits >> (BITS_PER_DIGIT - nBits);
            val.addElement(digit);
        }
        val.addElement(bits << nBits);
        for (i = 0; i < nDigits; ++i) {
            val.addElement(0);
        }
    }
    JSBigInteger result(cx, val.getElementData(), val.size());

    return result;
}

JSBigInteger JSBigInteger::shiftRight(JSContext * cx, int n)
{
    if (n == 0) {
        JSBigInteger result(cx);

        result = *this;
        return result;
    }
    if (n < 0) {
        return shiftLeft(cx, -n);
    }

    Magnitude val(cx);

    int nDigits = n / BITS_PER_DIGIT;
    int nBits = n % BITS_PER_DIGIT;
    int size = magnitude.size();

    val.addElement(signDigit());
    if (nBits == 0) {
        for (int i = size; --i >= nDigits;) {
            val.addElement(getDigit(i));
        }
    }
    else {
        Digit bits = getDigit(size);

        for (int i = size; --i >= nDigits;) {
            Digit digit;

            digit = bits << (BITS_PER_DIGIT - nBits);
            bits = getDigit(i);
            digit |= bits >> nBits;
            val.addElement(digit);
        }
    }
    JSBigInteger result(cx, val.getElementData(), val.size());

    return result;
}

JSBigInteger JSBigInteger::multiply(JSContext * cx, JSBigInteger & val)
{
    return multiply(cx, *this, val);
}

JSBigInteger JSBigInteger::pow(JSContext * cx, int exponent)
{
    if (exponent < 0) {
        cx->exceptionHandler->throwEx(new(cx) JSArithmeticException("Negative exponent"));
    }
    if (signum == 0) {
        if (exponent == 0) {
            return valueOf(cx, 1);
        }
        JSBigInteger result(cx);

        result = *this;
        return result;
    }
    JSBigInteger z(cx);
    JSBigInteger valX(cx);

    valX = *this;
    z = valX;
    while (TRUE) {
        exponent -= 1;
        if (exponent == 0)
            break;

        while (exponent % 2 == 0) {
            exponent = exponent / 2;
            valX = valX.multiply(cx, valX);
        }
        z = z.multiply(cx, valX);
    }
    z.normalize();
    return z;
}

JSBigInteger JSBigInteger::subtract(JSContext * cx, JSBigInteger & val)
{
    return subtract(cx, *this, val);
}

JSBigInteger JSBigInteger::abs(JSContext * cx)
{
    if (signum >= 0) {
        JSBigInteger result(cx);

        result = *this;
        return result;
    }
    return negate(cx);
}

JSBigInteger JSBigInteger::negate(JSContext * cx)
{
    JSBigInteger result(cx);

    result = *this;
    result.signum = -result.signum;
    return result;
}

jsBool_t JSBigInteger::equals(JSBigInteger & val)
{
    if (val.signum != signum || val.magnitude.size() != magnitude.size()) {
        return FALSE;
    }

    /* This test is just an optimization, which may or may not help */
    if (&val == this)
        return TRUE;

    int size = magnitude.size();

    for (int i = 0; i < size; i++) {
        if (val.magnitude.elementAt(i) != magnitude.elementAt(i)) {
            return FALSE;
        }
    }
    return TRUE;
}

jsString_t JSBigInteger::toString(JSContext * cx, int radix)
{
    static struct {
        int digitsPerLong;
        int64_t longRadix;
    } longDivides[] = {
        {
        62, 0x4000000000000000LL},  /* 2 */
        {
        39, 0x383d9170b85ff80bLL},  /* 3 */
        {
        31, 0x4000000000000000LL},  /* 4 */
        {
        27, 0x6765c793fa10079dLL},  /* 5 */
        {
        24, 0x41c21cb8e1000000LL},  /* 6 */
        {
        22, 0x3642798750226111LL},  /* 7 */
        {
        20, 0x1000000000000000LL},  /* 8 */
        {
        19, 0x12bf307ae81ffd59LL},  /* 9 */
        {
        18, 0xde0b6b3a7640000LL},   /* 10 */
        {
        18, 0x4d28cb56c33fa539LL},  /* 11 */
        {
        17, 0x1eca170c00000000LL},  /* 12 */
        {
        17, 0x1eca170c00000000LL},  /* 13 */
        {
        16, 0x1e39a5057d810000LL},  /* 14 */
        {
        16, 0x5b27ac993df97701LL},  /* 15 */
        {
        15, 0x1000000000000000LL},  /* 16 */
        {
        15, 0x27b95e997e21d9f1LL},  /* 17 */
        {
        15, 0x5da0e1e53c5c8000LL},  /* 18 */
        {
        14, 0xb16a458ef403f19LL},   /* 19 */
        {
        14, 0x16bcc41e90000000LL},  /* 20 */
        {
        14, 0x2d04b7fdd9c0ef49LL},  /* 21 */
        {
        14, 0x5658597bcaa24000LL},  /* 22 */
        {
        13, 0x6feb266931a75b7LL},   /* 23 */
        {
        13, 0xc29e98000000000LL},   /* 24 */
        {
        13, 0x14adf4b7320334b9LL},  /* 25 */
        {
        13, 0x226ed36478bfa000LL},  /* 26 */
        {
        13, 0x383d9170b85ff80bLL},  /* 27 */
        {
        13, 0x5a3c23e39c000000LL},  /* 28 */
        {
        12, 0x4e900abb53e6b71LL},   /* 29 */
        {
        12, 0x7600ec618141000LL},   /* 30 */
        {
        12, 0xaee5720ee830681LL},   /* 31 */
        {
        12, 0x1000000000000000LL},  /* 32 */
        {
        12, 0x172588ad4f5f0981LL},  /* 33 */
        {
        12, 0x211e44f7d02c1000LL},  /* 34 */
        {
        12, 0x2ee56725f06e5c71LL},  /* 35 */
        {
        12, 0x41c21cb8e1000000LL},  /* 36 */
    };

    if (radix < MIN_RADIX || radix > MAX_RADIX)
        radix = 10;

    JSBigInteger x(cx);
    JSBigInteger y = valueOf(cx, longDivides[radix - 2].longRadix);
    JSBigInteger div(cx), rem(cx);
    JSBigInteger *divResult[] = { &div, &rem };
    jsString_t *digitGroup;
    int maxNumDigitGroups = (magnitude.size() + 6) / 7;
    int numGroups;

    digitGroup = (jsString_t *) JS_NewZoneObj(cx, sizeof(*digitGroup) * maxNumDigitGroups);
    numGroups = 0;

    x = *this;

    JSBigInteger val = this->abs(cx);

    do {
        x.divideAndRemainder(cx, divResult, y);

        digitGroup[numGroups++] = int64ToString(cx, rem.int64Value(), radix);
        val = div;
    } while (val.signum != 0);

    JSStringBuffer buf(cx);

    if (signum < 0) {
        buf.append('-');
    }
    buf.append(digitGroup[numGroups - 1]);
    JS_DeleteZoneObj(cx, digitGroup[numGroups - 1]);

    /* Append remaining digit groups padded with leading zeros */
    for (int i = numGroups - 2; i >= 0; i--) {
        static jsChar_t zeros[]
                = "000000000000000000000000000000000000000000000000000000000000000";
        /* Prepend (any) leading zeros for this digit group */
        int length = strlen(digitGroup[i]);
        int numLeadingZeros = longDivides[radix - 2].digitsPerLong - length;

        if (numLeadingZeros != 0) {
            buf.append(zeros[numLeadingZeros]);
        }
        buf.append(digitGroup[i]);
        JS_DeleteZoneObj(cx, digitGroup[i]);
    }
    JS_DeleteZoneObj(cx, digitGroup);

    return buf.offString();
}

JSBigInteger::Digit * JSBigInteger::toDigitArray(JSContext * cx, int *length)
{
    int size = magnitude.size();

    if (size == 0) {
        *length = 0;
        return NULL;
    }
    Digit *result = (Digit *) JS_NewZoneObj(cx, sizeof(*result) * size);

    for (int i = 0; i < size; ++i) {
        result[i] = getDigit(size - i - 1);
    }
    *length = size;
    return result;
}

int64_t JSBigInteger::int64Value()
{
    int64_t result = 0;
    int size = magnitude.size();

    for (int i = size; --i >= 0;) {
        result <<= BITS_PER_DIGIT;
        result |= magnitude.elementAt(i);
    }
    return (result * signum);
}

JSBigInteger & JSBigInteger::operator=(const JSBigInteger & src)
{
    JSBigInteger *srcp = (JSBigInteger *) & src;

    signum = srcp->signum;

    int size = srcp->magnitude.size();

    magnitude.setSize(0);
    for (int i = 0; i < size; ++i) {
        magnitude.addElement(srcp->magnitude.elementAt(i));
    }
    return (*this);
}

JSBigInteger::JSBigInteger(JSContext * cx, Digit * val, int valLength)
:      magnitude(cx)
{
    SDigit v = val[0];

    this->cx = cx;
    if (v < 0) {
        makePositive(val, valLength);
        signum = -1;
    }
    else {
        stripLeadingZeroBytes(val, valLength);
        signum = magnitude.size() == 0 ? 0 : 1;
    }
}
JSBigInteger::JSBigInteger(const JSBigInteger & src)
:magnitude(src.cx)
{
    JSBigInteger *srcp = (JSBigInteger *) & src;

    this->cx = src.cx;
    signum = srcp->signum;

    int size = srcp->magnitude.size();

    for (int i = 0; i < size; ++i) {
        magnitude.addElement(srcp->magnitude.elementAt(i));
    }
}

JSBigInteger::JSBigInteger(JSContext * cx)
:      magnitude(cx)
{
    this->cx = cx;
    signum = 0;
}
#endif /* DTOA */
