/**
 * Infinite Precision Javascript Library
 *
 * Author: Kyle W T Sherman
 */

/**
 * Infinite Precision Number Class
 */
var IpNum = function() {

    // right of decimal point
    this.base = new Array();
    // left of decimal point
    this.radix = new Array();
    // sign is 1 for positive or -1 for negative
    this.sign = 1;

    // remove any leading or trailing zeros
    this.removeZeros = function() {
        while (this.base.length > 1 && this.base[0] == 0) this.base.shift();
        while (this.radix.length > 0 && this.radix[this.radix.length - 1] == 0) this.radix.pop();
    }

    // get this ip number as an array of base, radix, sign
    this.getArray = function() {
        var r = new Array();
        r[0] = new Array();
        r[1] = new Array();
        r[2] = new Array();
        for (var i = 0; i < this.base.length; i++) {
            r[0][i] = this.base[i];
        }
        for (var i = 0; i < this.radix.length; i++) {
            r[1][i] = this.radix[i];
        }
        r[2] = this.sign;
        return r;
    }

    // get this ip number from an array of base, radix, sign
    this.setArray = function(parts) {
        this.base = parts[0];
        this.radix = parts[1];
        this.sign = parts[2];
        this.removeZeros();
    }

    // get this ip number as string
    this.getString = function() {
        var r = '';
        if (this.sign == -1) r += '-';
        if (this.base.length > 0) {
            for (var i = 0; i < this.base.length; i++) {
                r += this.base[i];
            }
        }
        if (this.radix.length > 0) {
            r += '.';
            for (var i = 0; i < this.radix.length; i++) {
                r += this.radix[i];
            }
        }
        return r;
    }

    // set this ip number from string
    this.setString = function (str) {
        this.base = new Array();
        this.radix = new Array();
        this.sign = 1;
        var loc = 0;
        var len = str.length;
        if (len > 0 && str.charAt(loc) == '-') {
            this.sign = -1;
            loc++;
        }
        while (loc < len && str.charAt(loc) !== '.') {
            this.base.push(parseInt(str.charAt(loc)));
            loc++;
        }
        if (loc < len) {
            loc++;
            while (loc < len) {
                this.radix.push(parseInt(str.charAt(loc)));
                loc++;
            }
        }
        this.removeZeros();
    }

    // return true if this is equal to NUM
    this.equals = function (num) {
        if (this.sign !== num.sign) return false;
        if (this.base.length !== num.base.length) return false;
        if (this.radix.length !== num.radix.length) return false;
        for (var i = 0; i < this.base.length; i++) {
            if (this.base[i] !== num.base[i]) return false;
        }
        for (var i = 0; i < this.radix.length; i++) {
            if (this.radix[i] !== num.radix[i]) return false;
        }
        return true;
    }

    // return absolute value of this
    this.abs = function () {
        var r = new IpNum();
        r.base = this.base;
        r.radix = this.radix;
        r.sign = 1;
        return r;
    }

    // return true if this is greater than NUM
    this.gt = function (num) {
        if (this.sign !== num.sign) {
            if (this.sign == 1) return true;
            else return false;
        } else if (this.sign == -1) {
            if (this.equals(num)) return false;
            else if (this.abs().gt(num.abs())) return false;
            else return true;
        } else {
            if (this.base.length > num.base.length) return true;
            if (this.base.length < num.base.length) return false;
            for (var i = 0; i < this.base.length; i++) {
                if (this.base[i] > num.base[i]) return true;
                if (this.base[i] < num.base[i]) return false;
            }
            for (var i = 0; i < this.radix.length; i++) {
                if ((this.radix[i] || 0) > (num.radix[i] || 0)) return true;
                if ((this.radix[i] || 0) < (num.radix[i] || 0)) return false;
            }
            return false;
        }
    }

    // return true if this is less than NUM
    this.lt = function (num) {
        return num.gt(this);
    }

    // return true if this is greater than or equal to NUM
    this.gte = function (num) {
        if (this.gt(num)) return true;
        if (this.equals(num)) return true;
        return false;
    }

    // return true if this is less than or equal to NUM
    this.lte = function (num) {
        if (this.lt(num)) return true;
        if (this.equals(num)) return true;
        return false;
    }

    // add this to NUM
    this.add = function (num) {
        var result = new IpNum();
        var r = new Array(3);
        r[0] = new Array();
        r[1] = new Array();
        var n1;
        var n2;
        var c = 0;
        var len = 0;
        var sign = 1;

        if (this.sign !== num.sign) {
            // one number is negative and one is positive
            // subtract the larger from the smaller and keep the sign of the larger
            if (this.abs().gt(num.abs())) {
                sign = this.sign;
                n1 = this.getArray();
                n2 = num.getArray();
            } else {
                sign = num.sign;
                n1 = num.getArray();
                n2 = this.getArray();
            }

            // subtract radix part
            while (n1[1].length > n2[1].length) {
                n2[1].push(0);
            }
            while (n2[1].length > n1[1].length) {
                n1[1].push(0);
            }
            len = n1[1].length;
            for (var i = len - 1; i >= 0; i--) {
                var d = n1[1][i] - n2[1][i] - c;
                if (d < 0) {
                    d += 10;
                    c = 1;
                } else {
                    c = 0;
                }
                r[1].unshift(d);
            }

            // add base part
            while (n1[0].length > n2[0].length) {
                n2[0].unshift(0);
            }
            while (n2[0].length > n1[0].length) {
                n1[0].unshift(0);
            }
            len = n1[0].length;
            for (var i = len - 1; i >= 0; i--) {
                var d = n1[0][i] - n2[0][i] - c;
                if (d < 0) {
                    d = d + 10;
                    c = 1;
                } else {
                    c = 0;
                }
                r[0].unshift(d);
            }
        } else {
            // both numbers have the same sign
            // add them and set the result sign appropriately
            if (this.sign == 1) sign = 1;
            else sign = -1;

            n1 = this.getArray();
            n2 = num.getArray();

            // add radix part
            while (n1[1].length > n2[1].length) {
                n2[1].push(0);
            }
            while (n2[1].length > n1[1].length) {
                n1[1].push(0);
            }
            len = n1[1].length;
            for (var i = len - 1; i >= 0; i--) {
                var d = n1[1][i] + n2[1][i] + c;
                if (d < 10) {
                    c = 0;
                } else {
                    d = d - 10;
                    c = 1;
                }
                r[1].unshift(d);
            }

            // add base part
            while (n1[0].length > n2[0].length) {
                n2[0].unshift(0);
            }
            while (n2[0].length > n1[0].length) {
                n1[0].unshift(0);
            }
            len = n1[0].length;
            for (var i = len - 1; i >= 0; i--) {
                var d = n1[0][i] + n2[0][i] + c;
                if (d < 10) {
                    c = 0;
                } else {
                    d = d - 10;
                    c = 1;
                }
                r[0].unshift(d);
            }
            if (c !== 0) r[0].unshift(c);
        }

        r[2] = sign;

        // return ip number
        result.setArray(r);
        return result;
    }

    // subtract NUM from this
    this.sub = function (num) {
        // set negate NUM then add
        var n = new IpNum();
        n.setArray(num.getArray());
        if (n.sign == 1) n.sign = -1;
        else n.sign = 1;
        return this.add(n);
    }

    // add two arrays together
    var mulAdd = function (num1, num2) {
        var r = new Array();
        var c = 0;
        while (num1.length > num2.length) {
            num2.unshift(0);
        }
        while (num2.length > num1.length) {
            num1.unshift(0);
        }
        for (var i=num1.length - 1; i >= 0; i--) {
            var d = num1[i] + num2[i] + c;
            if (d < 10) {
                c = 0;
            } else {
                d = d - 10;
                c = 1;
            }
            r.unshift(d);
        }
        return r;
    }

    // multiply this and NUM
    this.mul = function (num) {
        var result = new IpNum();
        var r = new Array(3);
        r[0] = new Array();
        r[1] = new Array();
        var n1 = this.getArray();
        var n2 = num.getArray();
        var sign = 1;
        // if the numbers have different signs, then result is negative, otherwise it is positive
        if (n1[2] !== n2[2]) sign = -1;
        var m1 = n1[0];
        m1 = m1.concat(n1[1]);
        var m2 = n2[0];
        m2 = m2.concat(n2[1]);
        var d = n1[1].length + n2[1].length;
        var m3 = new Array();
        for (var i = m1.length - 1; i >= 0; i--) {
            var c = 0;
            // offset (used to line up numbers)
            var os = m1.length - 1 - i;
            var m = new Array();
            for (var j = m2.length - 1; j >= 0; j--) {
                var n = m1[i] * m2[j] + c;
                c = (n - (n % 10)) / 10;
                n = n - (c * 10);
                m.unshift(n);
            }
            if (c !== 0) m.unshift(c);
            for (var j = 0; j < os; j++) {
                m.push(0);
            }
            m3 = mulAdd(m3, m);
        }
        for (i = 0; i < d; i++) r[1].unshift(m3.pop());
        var len = m3.length;
        for (i = 0; i < len; i++) r[0].unshift(m3.pop());
        r[2] = sign;

        // return ip number
        result.setArray(r);
        return result;
    }

    // return factorial of this
    this.fact = function () {
        var num = new IpNum();
        num.setArray(this.getArray());
        var n = num.getArray();
        var zero = new IpNum();
        zero.setString('0');
        var one = new IpNum();
        one.setString('1');
        var r;
        if (n[2] == 1 && n[1].length == 0) {
            r = new IpNum();
            r.setString('1');
            while (num.gt(zero)) {
                r = r.mul(num);
                num = num.sub(one);
            }
        }
        return r;
    }

    // return this with radix cleared
    this.floor = function () {
        var r = new IpNum();
        r.setArray(this.getArray());
        r.radix = new Array();
        return r;
    }

    // return this with radix cleared and base incremented if radix was not 0
    this.ceil = function () {
        var r = new IpNum();
        r.setArray(this.getArray());
        if (r.radix.length > 0) {
            var one = new IpNum();
            if (r.sign == 1) {
                one.setString('1');
            } else {
                one.setString('-1');
            }
            r = r.add(one);
        }
        r.radix = new Array();
        return r;
    }

    // return this rounded up if radix >= .5 or down otherwise
    this.round = function() {
        if (this.radix.length > 0 && this.radix[0] >= 5) return this.ceil();
        else return this.floor();
    }

}

/*
 * End of File
 */