import { MASKS, DEFAULT_PAGE_SIZE, BLOCK_SIZE, greatestMultiple, roundUpToMultipleOf32, normalizePageSize, } from "../decoding/fastPforShared";
const EXCEPTION_OVERHEAD_BITS = 8;
const MAX_BIT_WIDTH = 32;
const BIT_WIDTH_SLOTS = MAX_BIT_WIDTH + 1;
const PAGE_SIZE = normalizePageSize(DEFAULT_PAGE_SIZE);
const INITIAL_PACKED_BUFFER_SIZE_WORDS = (PAGE_SIZE / 32) * 4;
const BYTE_CONTAINER_SIZE = ((3 * PAGE_SIZE) / BLOCK_SIZE + PAGE_SIZE) | 0;
function requiredBits(value) {
    return 32 - Math.clz32(value >>> 0);
}
function ensureInt32Capacity(buffer, requiredLength) {
    if (requiredLength <= buffer.length)
        return buffer;
    let newLength = buffer.length === 0 ? 1 : buffer.length;
    while (newLength < requiredLength) {
        newLength *= 2;
    }
    const next = new Uint32Array(newLength);
    next.set(buffer);
    return next;
}
function ensureUint8Capacity(buffer, requiredLength) {
    if (requiredLength <= buffer.length)
        return buffer;
    let newLength = buffer.length === 0 ? 1 : buffer.length;
    while (newLength < requiredLength) {
        newLength *= 2;
    }
    const next = new Uint8Array(newLength);
    next.set(buffer);
    return next;
}
export function fastPack32(inValues, inPos, out, outPos, bitWidth) {
    if (bitWidth === 0)
        return;
    if (bitWidth === 32) {
        out.set(inValues.subarray(inPos, inPos + 32), outPos);
        return;
    }
    const mask = MASKS[bitWidth] >>> 0;
    let outputWordIndex = outPos;
    let bitOffset = 0;
    let currentWord = 0;
    for (let i = 0; i < 32; i++) {
        const value = (inValues[inPos + i] >>> 0) & mask;
        if (bitOffset + bitWidth <= 32) {
            currentWord |= value << bitOffset;
            bitOffset += bitWidth;
            if (bitOffset === 32) {
                out[outputWordIndex++] = currentWord | 0;
                bitOffset = 0;
                currentWord = 0;
            }
        }
        else {
            const lowBits = 32 - bitOffset;
            const lowMask = MASKS[lowBits] >>> 0;
            currentWord |= (value & lowMask) << bitOffset;
            out[outputWordIndex++] = currentWord | 0;
            currentWord = value >>> lowBits;
            bitOffset = bitWidth - lowBits;
        }
    }
}
export function createFastPforEncoderWorkspace() {
    const dataToBePacked = new Array(BIT_WIDTH_SLOTS);
    for (let k = 1; k < BIT_WIDTH_SLOTS; k++) {
        dataToBePacked[k] = new Uint32Array(INITIAL_PACKED_BUFFER_SIZE_WORDS);
    }
    return {
        dataToBePacked,
        dataPointers: new Int32Array(BIT_WIDTH_SLOTS),
        byteContainer: new Uint8Array(BYTE_CONTAINER_SIZE),
        bitWidthFrequencies: new Int32Array(BIT_WIDTH_SLOTS),
        bestBitWidthPlan: new Int32Array(3),
    };
}
function computeBestBitWidthPlan(inValues, pos, workspace) {
    const bitWidthFrequencies = workspace.bitWidthFrequencies;
    const bestBitWidthPlan = workspace.bestBitWidthPlan;
    bitWidthFrequencies.fill(0);
    for (let k = pos, kEnd = pos + BLOCK_SIZE; k < kEnd; k++) {
        bitWidthFrequencies[requiredBits(inValues[k])]++;
    }
    let maxBitWidth = MAX_BIT_WIDTH;
    while (bitWidthFrequencies[maxBitWidth] === 0)
        maxBitWidth--;
    let bestBitWidth = maxBitWidth;
    let bestCost = maxBitWidth * BLOCK_SIZE;
    let exceptionCount = 0;
    let bestExceptionCount = exceptionCount;
    for (let candidateBitWidth = maxBitWidth - 1; candidateBitWidth >= 0; candidateBitWidth--) {
        exceptionCount += bitWidthFrequencies[candidateBitWidth + 1];
        if (exceptionCount === BLOCK_SIZE)
            break;
        let candidateCost = exceptionCount * EXCEPTION_OVERHEAD_BITS +
            exceptionCount * (maxBitWidth - candidateBitWidth) +
            candidateBitWidth * BLOCK_SIZE +
            8;
        if (maxBitWidth - candidateBitWidth === 1)
            candidateCost -= exceptionCount;
        if (candidateCost < bestCost) {
            bestCost = candidateCost;
            bestBitWidth = candidateBitWidth;
            bestExceptionCount = exceptionCount;
        }
    }
    bestBitWidthPlan[0] = bestBitWidth;
    bestBitWidthPlan[1] = bestExceptionCount;
    bestBitWidthPlan[2] = maxBitWidth;
}
function writeByte(workspace, byteContainerPos, byteValue) {
    if (byteContainerPos >= workspace.byteContainer.length) {
        workspace.byteContainer = ensureUint8Capacity(workspace.byteContainer, byteContainerPos + 1);
    }
    workspace.byteContainer[byteContainerPos] = byteValue & 0xff;
    return byteContainerPos + 1;
}
function ensureExceptionValuesCapacity(dataToBePacked, dataPointers, exceptionBitWidth, exceptionCount) {
    if (exceptionBitWidth === 1)
        return;
    const needed = dataPointers[exceptionBitWidth] + exceptionCount;
    const currentExceptionValues = dataToBePacked[exceptionBitWidth];
    if (!currentExceptionValues || needed >= currentExceptionValues.length) {
        let newSize = 2 * needed;
        newSize = roundUpToMultipleOf32(newSize);
        const next = new Uint32Array(newSize);
        if (currentExceptionValues)
            next.set(currentExceptionValues);
        dataToBePacked[exceptionBitWidth] = next;
    }
}
function writeBlockHeader(workspace, byteContainerPos, bitWidth, exceptionCount, maxBitWidth) {
    byteContainerPos = writeByte(workspace, byteContainerPos, bitWidth);
    byteContainerPos = writeByte(workspace, byteContainerPos, exceptionCount);
    if (exceptionCount > 0) {
        byteContainerPos = writeByte(workspace, byteContainerPos, maxBitWidth);
    }
    return byteContainerPos;
}
function recordBlockExceptions(workspace, inValues, blockPos, bitWidth, exceptionCount, exceptionBitWidth, byteContainerPos) {
    if (exceptionCount === 0)
        return byteContainerPos;
    const dataToBePacked = workspace.dataToBePacked;
    const dataPointers = workspace.dataPointers;
    ensureExceptionValuesCapacity(dataToBePacked, dataPointers, exceptionBitWidth, exceptionCount);
    for (let k = 0; k < BLOCK_SIZE; k++) {
        const value = inValues[blockPos + k] >>> 0;
        if (value >>> bitWidth !== 0) {
            byteContainerPos = writeByte(workspace, byteContainerPos, k);
            if (exceptionBitWidth !== 1) {
                const exceptionValues = dataToBePacked[exceptionBitWidth];
                exceptionValues[dataPointers[exceptionBitWidth]++] = (value >>> bitWidth) | 0;
            }
        }
    }
    return byteContainerPos;
}
function packBlock(inValues, blockPos, bitWidth, state) {
    for (let k = 0; k < BLOCK_SIZE; k += 32) {
        state.out = ensureInt32Capacity(state.out, state.outPos + bitWidth);
        fastPack32(inValues, blockPos + k, state.out, state.outPos, bitWidth);
        state.outPos += bitWidth;
    }
}
function padByteContainerToInt32(workspace, byteContainerPos) {
    while ((byteContainerPos & 3) !== 0) {
        byteContainerPos = writeByte(workspace, byteContainerPos, 0);
    }
    return byteContainerPos;
}
function writeByteContainerInts(workspace, state, byteContainerPos) {
    const howManyInts = byteContainerPos / 4;
    state.out = ensureInt32Capacity(state.out, state.outPos + howManyInts);
    const byteContainer = workspace.byteContainer;
    for (let i = 0; i < howManyInts; i++) {
        const base = i * 4;
        const packedWord = byteContainer[base] |
            (byteContainer[base + 1] << 8) |
            (byteContainer[base + 2] << 16) |
            (byteContainer[base + 3] << 24) |
            0;
        state.out[state.outPos + i] = packedWord;
    }
    state.outPos += howManyInts;
}
function computeExceptionBitmap(dataPointers) {
    let bitmap = 0;
    for (let k = 2; k <= MAX_BIT_WIDTH; k++) {
        if (dataPointers[k] !== 0) {
            bitmap |= k === MAX_BIT_WIDTH ? 0x80000000 : 1 << (k - 1);
        }
    }
    return bitmap;
}
function writeExceptionStreams(workspace, state) {
    const dataPointers = workspace.dataPointers;
    const dataToBePacked = workspace.dataToBePacked;
    const bitmap = computeExceptionBitmap(dataPointers);
    state.out = ensureInt32Capacity(state.out, state.outPos + 1);
    state.out[state.outPos++] = bitmap;
    for (let k = 2; k <= MAX_BIT_WIDTH; k++) {
        const size = dataPointers[k];
        if (size !== 0) {
            state.out = ensureInt32Capacity(state.out, state.outPos + 1);
            state.out[state.outPos++] = size | 0;
            let j = 0;
            for (; j < size; j += 32) {
                const exceptionValues = dataToBePacked[k];
                state.out = ensureInt32Capacity(state.out, state.outPos + k);
                fastPack32(exceptionValues, j, state.out, state.outPos, k);
                state.outPos += k;
            }
            const overflow = j - size;
            state.outPos -= (overflow * k) >>> 5;
        }
    }
}
function encodePage(inValues, thisSize, state, workspace) {
    const headerPos = state.outPos;
    state.out = ensureInt32Capacity(state.out, headerPos + 1);
    state.outPos = (state.outPos + 1) | 0;
    const dataPointers = workspace.dataPointers;
    dataPointers.fill(0);
    let byteContainerPos = 0;
    let tmpInPos = state.inPos;
    const finalInPos = tmpInPos + thisSize - BLOCK_SIZE;
    for (; tmpInPos <= finalInPos; tmpInPos += BLOCK_SIZE) {
        computeBestBitWidthPlan(inValues, tmpInPos, workspace);
        const bestBitWidthPlan = workspace.bestBitWidthPlan;
        const bitWidth = bestBitWidthPlan[0];
        const exceptionCount = bestBitWidthPlan[1];
        const maxBitWidth = bestBitWidthPlan[2];
        const exceptionBitWidth = exceptionCount > 0 ? maxBitWidth - bitWidth : 0;
        byteContainerPos = writeBlockHeader(workspace, byteContainerPos, bitWidth, exceptionCount, maxBitWidth);
        byteContainerPos = recordBlockExceptions(workspace, inValues, tmpInPos, bitWidth, exceptionCount, exceptionBitWidth, byteContainerPos);
        packBlock(inValues, tmpInPos, bitWidth, state);
    }
    const pageEndOutPos = state.outPos;
    state.inPos = tmpInPos;
    state.out[headerPos] = (pageEndOutPos - headerPos) | 0;
    const byteSize = byteContainerPos;
    byteContainerPos = padByteContainerToInt32(workspace, byteContainerPos);
    state.out = ensureInt32Capacity(state.out, state.outPos + 1);
    state.out[state.outPos++] = byteSize | 0;
    writeByteContainerInts(workspace, state, byteContainerPos);
    writeExceptionStreams(workspace, state);
}
function encodeAlignedPages(inValues, inLength, state, workspace) {
    const alignedLength = greatestMultiple(inLength, BLOCK_SIZE);
    const finalInPos = state.inPos + alignedLength;
    while (state.inPos !== finalInPos) {
        const thisSize = Math.min(PAGE_SIZE, finalInPos - state.inPos);
        encodePage(inValues, thisSize, state, workspace);
    }
}
function encode(inValues, inLength, state, workspace) {
    const alignedLength = greatestMultiple(inLength, BLOCK_SIZE);
    state.out = ensureInt32Capacity(state.out, state.outPos + 1);
    state.out[state.outPos++] = alignedLength;
    if (alignedLength === 0)
        return;
    encodeAlignedPages(inValues, alignedLength, state, workspace);
}
/**
 * VByte encoding for FastPFOR tail values (MSB=1 terminator).
 * Note: Inverts standard Protobuf Varint (MSB=0 terminator), so we cannot reuse generic methods.
 */
function encodeVByte(inValues, inLength, state, workspace) {
    if (inLength === 0)
        return;
    const requiredBytes = inLength * 5 + 3;
    workspace.byteContainer = ensureUint8Capacity(workspace.byteContainer, requiredBytes);
    const start = state.inPos;
    let bytePos = 0;
    for (let k = start; k < start + inLength; k++) {
        let value = inValues[k] >>> 0;
        while (value >= 0x80) {
            workspace.byteContainer[bytePos++] = value & 0x7f;
            value >>>= 7;
        }
        workspace.byteContainer[bytePos++] = (value | 0x80) & 0xff;
    }
    while ((bytePos & 3) !== 0)
        workspace.byteContainer[bytePos++] = 0;
    const intsToWrite = bytePos / 4;
    state.out = ensureInt32Capacity(state.out, state.outPos + intsToWrite);
    let outIdx = state.outPos;
    for (let i = 0; i < bytePos; i += 4) {
        const packedWord = workspace.byteContainer[i] |
            (workspace.byteContainer[i + 1] << 8) |
            (workspace.byteContainer[i + 2] << 16) |
            (workspace.byteContainer[i + 3] << 24) |
            0;
        state.out[outIdx++] = packedWord;
    }
    state.outPos = outIdx;
    state.inPos = (state.inPos + inLength) | 0;
}
/**
 * Encodes an int32 stream using the FastPFOR wire format (pages + VByte tail).
 */
export function encodeFastPforInt32WithWorkspace(values, workspace) {
    const state = { inPos: 0, outPos: 0, out: new Uint32Array(values.length + 1024) };
    encode(values, values.length, state, workspace);
    const remaining = values.length - state.inPos;
    encodeVByte(values, remaining, state, workspace);
    return state.out.subarray(0, state.outPos);
}
//# sourceMappingURL=fastPforEncoder.js.map