import { fromParts, truncate } from "./Decimal.js";
import { Exception, bigintHash } from "./Util.js";
const isBigEndian = false;
BigInt.prototype.toJSON = function () {
    return `${this.toString()}`;
};
const zero = 0n;
const one = 1n;
const two = 2n;
const minusOne = -1n;
export function isBigInt(x) {
    return typeof x === "bigint";
}
export function hash(x) {
    return bigintHash(x);
}
export function equals(x, y) {
    return x === y;
}
export function compare(x, y) {
    return x < y ? -1 : x > y ? 1 : 0;
}
export function abs(x) { return x < zero ? -x : x; }
export function sign(x) { return x < zero ? -1 : x > zero ? 1 : 0; }
export function max(x, y) { return x > y ? x : y; }
export function min(x, y) { return x < y ? x : y; }
export function maxMagnitude(x, y) { return abs(x) > abs(y) ? x : y; }
export function minMagnitude(x, y) { return abs(x) < abs(y) ? x : y; }
export function clamp(x, min, max) {
    return x < min ? min : x > max ? max : x;
}
export function add(x, y) { return x + y; }
export function subtract(x, y) { return x - y; }
export function multiply(x, y) { return x * y; }
export function divide(x, y) { return x / y; }
export function remainder(x, y) { return x % y; }
export function negate(x) { return -x; }
export function op_UnaryNegation(x) { return -x; }
export function op_LogicalNot(x) { return ~x; }
export function op_UnaryPlus(x) { return x; }
export function op_Addition(x, y) { return x + y; }
export function op_Subtraction(x, y) { return x - y; }
export function op_Multiply(x, y) { return x * y; }
export function op_Division(x, y) { return x / y; }
export function op_Modulus(x, y) { return x % y; }
export function op_RightShift(x, n) { return x >> BigInt(n); }
export function op_LeftShift(x, n) { return x << BigInt(n); }
export function op_BitwiseAnd(x, y) { return x & y; }
export function op_BitwiseOr(x, y) { return x | y; }
export function op_ExclusiveOr(x, y) { return x ^ y; }
export function op_LessThan(x, y) { return x < y; }
export function op_LessThanOrEqual(x, y) { return x <= y; }
export function op_GreaterThan(x, y) { return x > y; }
export function op_GreaterThanOrEqual(x, y) { return x >= y; }
export function op_Equality(x, y) { return x === y; }
export function op_Inequality(x, y) { return x !== y; }
export function get_Zero() { return zero; }
export function get_One() { return one; }
export function get_MinusOne() { return minusOne; }
export function get_IsZero(x) { return x === zero; }
export function get_IsOne(x) { return x === one; }
export function get_IsEven(x) { return isEvenInteger(x); }
export function get_IsPowerOfTwo(x) { return isPow2(x); }
export function get_Sign(x) { return sign(x); }
export function isNegative(x) { return x < zero; }
export function isPositive(x) { return x > zero; }
export function isEvenInteger(x) { return (x % two) === zero; }
export function isOddInteger(x) { return (x % two) !== zero; }
export function isPow2(x) { return (x & (x - one)) === zero; }
export function fromZero() { return zero; }
export function fromOne() { return one; }
export function fromInt8(n) { return BigInt(n); }
export function fromUInt8(n) { return BigInt(n); }
export function fromInt16(n) { return BigInt(n); }
export function fromUInt16(n) { return BigInt(n); }
export function fromInt32(n) { return BigInt(n); }
export function fromUInt32(n) { return BigInt(n); }
export function fromInt64(n) { return n; }
export function fromUInt64(n) { return n; }
export function fromInt128(n) { return n; }
export function fromUInt128(n) { return n; }
export function fromNativeInt(n) { return n; }
export function fromUNativeInt(n) { return n; }
export function fromFloat16(n) { return BigInt(Math.trunc(n)); }
export function fromFloat32(n) { return BigInt(Math.trunc(n)); }
export function fromFloat64(n) { return BigInt(Math.trunc(n)); }
export function fromDecimal(d) { return BigInt(truncate(d).toString()); }
export function fromBigInt(x) { return x; }
export function fromBoolean(b) { return BigInt(b); }
export function fromChar(c) { return BigInt(c.charCodeAt(0)); }
export function fromString(s) { return BigInt(s); }
export function fromByteArray(bytes) {
    return fromSignedBytes(bytes, isBigEndian);
}
export function toByteArray(value) {
    return toSignedBytes(value, isBigEndian);
}
export function toIntN_unchecked(bits, x, signed) {
    return signed ? BigInt.asIntN(bits, x) : BigInt.asUintN(bits, x);
}
export function toIntN(bits, x, signed) {
    let higher_bits = abs(x) >> BigInt(bits);
    if (higher_bits !== 0n) {
        const s = signed ? "a signed" : "an unsigned";
        throw new Exception(`Value was either too large or too small for ${s} ${bits}-bit integer.`);
    }
    return signed ? BigInt.asIntN(bits, x) : BigInt.asUintN(bits, x);
}
export function toInt8(x) { return Number(toIntN(8, x, true)); }
export function toUInt8(x) { return Number(toIntN(8, x, false)); }
export function toInt16(x) { return Number(toIntN(16, x, true)); }
export function toUInt16(x) { return Number(toIntN(16, x, false)); }
export function toInt32(x) { return Number(toIntN(32, x, true)); }
export function toUInt32(x) { return Number(toIntN(32, x, false)); }
export function toInt64(x) { return toIntN(64, x, true); }
export function toUInt64(x) { return toIntN(64, x, false); }
export function toInt128(x) { return toIntN(128, x, true); }
export function toUInt128(x) { return toIntN(128, x, false); }
export function toNativeInt(x) { return toIntN(64, x, true); }
export function toUNativeInt(x) { return toIntN(64, x, false); }
export function toInt8_unchecked(x) { return Number(toIntN_unchecked(8, x, true)); }
export function toUInt8_unchecked(x) { return Number(toIntN_unchecked(8, x, false)); }
export function toInt16_unchecked(x) { return Number(toIntN_unchecked(16, x, true)); }
export function toUInt16_unchecked(x) { return Number(toIntN_unchecked(16, x, false)); }
export function toInt32_unchecked(x) { return Number(toIntN_unchecked(32, x, true)); }
export function toUInt32_unchecked(x) { return Number(toIntN_unchecked(32, x, false)); }
export function toInt64_unchecked(x) { return toIntN_unchecked(64, x, true); }
export function toUInt64_unchecked(x) { return toIntN_unchecked(64, x, false); }
export function toInt128_unchecked(x) { return toIntN_unchecked(128, x, true); }
export function toUInt128_unchecked(x) { return toIntN_unchecked(128, x, false); }
export function toNativeInt_unchecked(x) { return toIntN_unchecked(64, x, true); }
export function toUNativeInt_unchecked(x) { return toIntN_unchecked(64, x, false); }
export function toFloat16(x) { return Number(x); }
export function toFloat32(x) { return Number(x); }
export function toFloat64(x) { return Number(x); }
export function toDecimal(x) {
    const isNegative = x < zero;
    const bits = abs(x);
    const low = Number(BigInt.asUintN(32, bits));
    const mid = Number(BigInt.asUintN(32, bits >> 32n));
    const high = Number(BigInt.asUintN(32, bits >> 64n));
    const scale = 0;
    return fromParts(low, mid, high, isNegative, scale);
}
export function toBigInt(x) { return x; }
export function toBoolean(x) { return x !== zero; }
export function toChar(x) {
    return String.fromCharCode(toUInt16(x));
}
export function toString(x) { return x.toString(); }
export function tryParse(s, res) {
    try {
        res.contents = BigInt(s);
        return true;
    }
    catch (err) {
        return false;
    }
}
export function parse(s) {
    return BigInt(s);
}
export function pow(x, n) {
    return x ** BigInt(n);
}
export function modPow(x, e, m) {
    return (x ** e) % m;
}
export function divRem(x, y, out) {
    const div = x / y;
    const rem = x % y;
    if (out === void 0) {
        return [div, rem];
    }
    else {
        out.contents = rem;
        return div;
    }
}
export function greatestCommonDivisor(x, y) {
    while (y > zero) {
        const q = x / y;
        const r = x - q * y;
        x = y;
        y = r;
    }
    return x;
}
export function getBitLength(x) {
    return fromFloat64(x === zero ? 1 : log2(abs(x)) + 1);
}
export function log2(x) {
    const n = Number(x);
    if (Number.isFinite(n))
        return Math.log2(n); // fast path
    if (x < zero)
        return Number.NaN;
    let shift = one;
    while (x >= (one << shift)) {
        shift = shift << one;
    }
    let log = zero;
    while (shift > one) {
        shift = shift >> one;
        if (x >= (one << shift)) {
            log = log + shift;
            x = x >> shift;
        }
    }
    return Number(log);
}
export function log10(x) {
    return log2(x) * Math.log10(2);
}
export function ln(x) {
    return log2(x) * Math.log(2);
}
export function log(x, base) {
    return log2(x) / Math.log2(base);
}
export function ilog2(x) {
    return BigInt(log2(x));
}
// export function copySign
// export function createChecked
// export function createSaturating
// export function createTruncating
// export function getByteCount
// export function leadingZeroCount
// export function popCount
// export function rotateLeft
// export function rotateRight
// export function trailingZeroCount
// export function tryFormat
// export function tryWriteBytes
// -------------------------------------------------
// Binary serialization
// -------------------------------------------------
const hexCodes = new Uint8Array([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102]);
function fromHexCode(code) {
    if (48 <= code && code <= 57)
        return code - 48;
    if (97 <= code && code <= 102)
        return code - 97 + 10;
    if (65 <= code && code <= 70)
        return code - 65 + 10;
    throw Error(`Invalid hex code: ${code}`);
}
function toSignedBytes(x, isBigEndian) {
    const isNeg = x < 0n;
    if (isNeg) {
        const len = log2(-x);
        const bits = len + (8 - len % 8);
        const pow2 = (1n << BigInt(bits));
        x = x + pow2; // two's complement
    }
    const hex = x.toString(16);
    const len = hex.length;
    const odd = len % 2;
    const first = hex.charCodeAt(0);
    const isLow = 48 <= first && first <= 55; // 0..7
    const start = (isNeg && isLow) || (!isNeg && !isLow) ? 1 : 0;
    const bytes = new Uint8Array(start + (len + odd) / 2);
    const inc = isBigEndian ? 1 : -1;
    let pos = isBigEndian ? 0 : bytes.length - 1;
    if (start > 0) {
        bytes[pos] = isNeg ? 255 : 0;
        pos += inc;
    }
    if (odd > 0) {
        bytes[pos] = fromHexCode(first);
        pos += inc;
    }
    for (let i = odd; i < len; i += 2, pos += inc) {
        const a = fromHexCode(hex.charCodeAt(i));
        const b = fromHexCode(hex.charCodeAt(i + 1));
        bytes[pos] = (a << 4) | b;
    }
    return bytes;
}
function fromSignedBytes(bytes, isBigEndian) {
    if (bytes == null) {
        throw new Exception("bytes is null");
    }
    const len = bytes.length;
    const first = isBigEndian ? 0 : len - 1;
    const isNeg = bytes[first] > 127;
    const codes = new Uint16Array(len * 2 + 2);
    codes[0] = 48; // 0
    codes[1] = 120; // x
    const inc = isBigEndian ? 1 : -1;
    let pos = isBigEndian ? 0 : len - 1;
    for (let i = 0; i < bytes.length; i++, pos += inc) {
        const byte = bytes[pos];
        codes[2 * i + 2] = hexCodes[byte >> 4];
        codes[2 * i + 3] = hexCodes[byte & 15];
    }
    const str = String.fromCharCode.apply(null, codes);
    let x = BigInt(str);
    if (isNeg) {
        const bits = len * 8;
        const pow2 = (1n << BigInt(bits));
        x = x - pow2; // two's complement
    }
    return x;
}
