import { assert } from "../../assert.js";
import { msb_32 } from "../msb_32.js";

// TODO this has some overlap with `frexp` function behavior, perhaps we can generalize

/*
 largely a port from https://github.com/sebbbi/OffsetAllocator
 */

const MANTISSA_BITS = 3;
const MANTISSA_VALUE = 1 << MANTISSA_BITS;
const MANTISSA_MASK = MANTISSA_VALUE - 1;

/**
 * Bin sizes follow floating point (exponent + mantissa) distribution (piecewise linear log approx)
 * This ensures that for each size class, the average overhead percentage stays the same
 * @param {number} size
 * @returns {number} uint32
 */
export function uintToFloatRoundUp(size) {
    let exp = 0;
    let mantissa = 0;

    if (size < MANTISSA_VALUE) {
        // Denorm: 0..(MANTISSA_VALUE-1)
        mantissa = size;
    } else {
        // Normalized: Hidden high bit always 1. Not stored. Just like float.
        const highestSetBit = msb_32(size);

        const mantissaStartBit = highestSetBit - MANTISSA_BITS;
        exp = mantissaStartBit + 1;
        mantissa = (size >>> mantissaStartBit) & MANTISSA_MASK;

        const lowBitsMask = (1 << mantissaStartBit) - 1;

        // Round up!
        if ((size & lowBitsMask) !== 0) {
            mantissa++;
        }
    }

    return (exp << MANTISSA_BITS) + mantissa; // + allows mantissa->exp overflow for round up
}

/**
 *
 * @param {number} size
 * @return {number}
 */
export function uintToFloatRoundDown(size) {
    let exp = 0;
    let mantissa = 0;

    if (size < MANTISSA_VALUE) {
        // Denorm: 0..(MANTISSA_VALUE-1)
        mantissa = size;
    } else {
        // Normalized: Hidden high bit always 1. Not stored. Just like float.
        const highestSetBit = msb_32(size);

        const mantissaStartBit = highestSetBit - MANTISSA_BITS;

        exp = mantissaStartBit + 1;
        mantissa = (size >>> mantissaStartBit) & MANTISSA_MASK;
    }

    return (exp << MANTISSA_BITS) | mantissa;
}

/**
 *
 * @param {number} floatValue
 * @return {number}
 */
export function floatToUint(floatValue) {
    assert.isNonNegativeInteger(floatValue, 'floatValue');

    let exponent = floatValue >>> MANTISSA_BITS;
    let mantissa = floatValue & MANTISSA_MASK;
    if (exponent === 0) {
        // Denorms
        return mantissa;
    } else {
        const x = (mantissa | MANTISSA_VALUE) << (exponent - 1);
        return x >>> 0; // convert to unsigned in case of sign overflow
    }
}