
// arbitrary precision helper class for 64 bit numbers
const NumberBaseConverter = {
    // Adds two arrays for the given base (10 or 16), returning the result.
    add(x, y, base) {
        const z = [], n = Math.max(x.length, y.length);
        let carry = 0;
        for (let i = 0; i < n || carry; i++) {
            const xi = i < x.length ? x[i] : 0;
            const yi = i < y.length ? y[i] : 0;
            const zi = carry + xi + yi;
            z.push(zi % base);
            carry = Math.floor(zi / base);
        }
        return z;
    },
    // Returns a*x, where x is an array of decimal digits and a is an ordinary
    // JavaScript number. base is the number base of the array x.
    multiplyByNumber(num, x, base) {
        if (num < 0) {
            return null;
        }
        if (num == 0) {
            return [];
        }
        let result = [], power = x;
        for(;;) {
            if (num & 1) {
                result = this.add(result, power, base);
            }
            num = num >> 1;
            if (num === 0) {
                return result;
            }
            power = this.add(power, power, base);
        }
    },
    twosComplement(str, base) {
        const invdigits = str.split('').map(c => base - 1 - parseInt(c,base)).reverse();
        const negdigits = this.add(invdigits, [1], base).slice(0,str.length);
        return negdigits.reverse().map(d => d.toString(base)).join('');
    },
    convertBase(str, fromBase, toBase) {
        if (fromBase === 10 && /[eE]/.test(str)) {
            // convert exponents to a string of zeros
            const s = str.split(/[eE]/);
            str = s[0] + '0'.repeat(parseInt(s[1],10)); // works for 0/+ve exponent,-ve throws
        }
        const digits = str.split('').map(d => parseInt(d,fromBase)).reverse();
        let outArray = [], power = [1];
        for (let i = 0; i < digits.length; i++) {
            if (digits[i]) {
                outArray = this.add(outArray, this.multiplyByNumber(digits[i], power, toBase), toBase);
            }
            power = this.multiplyByNumber(fromBase, power, toBase);
        }
        return outArray.reverse().map(d => d.toString(toBase)).join('');
    },
    decToHex(decstr, minlen) {
        let res;
        const isneg = decstr[0] === '-';
        if (isneg) {
            decstr = decstr.slice(1);
        }
        decstr = decstr.match(/^0*(.+)$/)[1];   // strip leading zeros
        if (decstr.length < 16 && !/[eE]/.test(decstr)) {  // 16 = Math.pow(2,52).toString(10).length
            // less than 52 bits - just use parseInt
            res = parseInt(decstr, 10).toString(16);
        } else {
            res = NumberBaseConverter.convertBase(decstr, 10, 16);
        }
        if (isneg) {
            res = NumberBaseConverter.twosComplement(res, 16);
            if (/^[0-7]/.test(res)) res = 'f'+res;  //msb must be set for -ve numbers
        } else if (/^[^0-7]/.test(res)) {
            res = '0' + res;    // msb must not be set for +ve numbers
        }
        if (minlen && res.length < minlen) {
            res = (isneg?'f':'0').repeat(minlen - res.length) + res;
        }
        return res;
    },
    hexToDec(hexstr, signed) {
        const isneg = /^[^0-7]/.test(hexstr);
        if (hexstr.match(/^0*(.+)$/)[1].length*4 < 52) {
            // less than 52 bits - just use parseInt
            let res = parseInt(hexstr, 16);
            if (signed && isneg) {
                res = -res;
            }
            return res.toString(10);
        }
        if (isneg) {
            hexstr = NumberBaseConverter.twosComplement(hexstr, 16);
        }
        const res = (isneg ? '-' : '') + NumberBaseConverter.convertBase(hexstr, 16, 10);
        return res;
    },
};

module.exports = {
    NumberBaseConverter,
}
