import IntWrapper from "../decoding/intWrapper";
import { createFastPforEncoderWorkspace, encodeFastPforInt32WithWorkspace } from "./fastPforEncoder";
import { encodeBigEndianInt32s } from "./bigEndianEncode";
export function encodeVarintInt32Value(value, dst, offset) {
    let v = value;
    while (v > 0x7f) {
        dst[offset.get()] = (v & 0x7f) | 0x80;
        offset.increment();
        v >>>= 7;
    }
    dst[offset.get()] = v & 0x7f;
    offset.increment();
}
export function encodeVarintInt32(values) {
    const buffer = new Uint8Array(values.length * 5);
    const offset = new IntWrapper(0);
    for (const value of values) {
        encodeVarintInt32Value(value, buffer, offset);
    }
    return buffer.slice(0, offset.get());
}
export function encodeVarintInt64(values) {
    const buffer = new Uint8Array(values.length * 10);
    const offset = new IntWrapper(0);
    for (const value of values) {
        encodeVarintInt64Value(value, buffer, offset);
    }
    return buffer.slice(0, offset.get());
}
function encodeVarintInt64Value(value, dst, offset) {
    let v = value;
    while (v > 0x7fn) {
        dst[offset.get()] = Number(v & 0x7fn) | 0x80;
        offset.increment();
        v >>= 7n;
    }
    dst[offset.get()] = Number(v & 0x7fn);
    offset.increment();
}
export function encodeVarintFloat64(values) {
    // 1. Calculate the exact size required for the buffer
    let size = 0;
    for (let i = 0; i < values.length; i++) {
        let val = values[i];
        // Ensure we handle the value as a positive integer
        val = val < 0 ? 0 : Math.floor(val);
        // 0 always takes 1 byte
        if (val === 0) {
            size++;
            continue;
        }
        // Calculate bytes needed: ceil(log128(val + 1))
        while (val > 0) {
            size++;
            val = Math.floor(val / 128);
        }
    }
    const dst = new Uint8Array(size);
    const offset = new IntWrapper(0);
    for (let i = 0; i < values.length; i++) {
        encodeVarintFloat64Value(values[i], dst, offset);
    }
    return dst;
}
/**
 * Encodes a single number into the buffer at the given offset using Varint encoding.
 * Handles numbers up to 2^53 (MAX_SAFE_INTEGER) correctly.
 */
function encodeVarintFloat64Value(val, buf, offset) {
    // Ensure integer
    val = Math.floor(val);
    // Handle 0 explicitly or ensure loop runs once
    if (val === 0) {
        buf[offset.get()] = 0;
        offset.increment();
        return;
    }
    while (val >= 128) {
        // Write 7 bits of data | 0x80 (continuation bit)
        buf[offset.get()] = (val % 128) | 0x80;
        offset.increment();
        // Shift right by 7 bits
        val = Math.floor(val / 128);
    }
    // Write the last byte (no continuation bit)
    buf[offset.get()] = val;
    offset.increment();
}
export function encodeFastPfor(values) {
    const encoderWorkspace = createFastPforEncoderWorkspace();
    const encodedWords = encodeFastPforInt32WithWorkspace(values, encoderWorkspace);
    return encodeBigEndianInt32s(encodedWords);
}
export function encodeZigZagInt32Value(value) {
    return (value << 1) ^ (value >> 31);
}
export function encodeZigZagInt64Value(value) {
    return (value << 1n) ^ (value >> 63n);
}
export function encodeZigZagFloat64Value(n) {
    return n >= 0 ? n * 2 : n * -2 - 1;
}
export function encodeZigZagInt32(data) {
    const result = new Uint32Array(data.length);
    for (let i = 0; i < data.length; i++) {
        result[i] = encodeZigZagInt32Value(data[i]);
    }
    return result;
}
export function encodeZigZagInt64(data) {
    const result = new BigUint64Array(data.length);
    for (let i = 0; i < data.length; i++) {
        result[i] = encodeZigZagInt64Value(data[i]);
    }
    return result;
}
export function encodeZigZagFloat64(data) {
    for (let i = 0; i < data.length; i++) {
        data[i] = encodeZigZagFloat64Value(data[i]);
    }
}
export function encodeUnsignedRleInt32(input) {
    if (input.length === 0) {
        return { data: new Uint32Array(0), runs: 0 };
    }
    const runLengths = [];
    const runValues = [];
    let currentRunLength = 0;
    let currentValue = input[0];
    for (let i = 0; i < input.length; i++) {
        const nextValue = input[i];
        if (nextValue === currentValue) {
            currentRunLength++;
        }
        else {
            // End of the current run, record it
            runLengths.push(currentRunLength);
            runValues.push(currentValue);
            // Start a new run
            currentValue = nextValue;
            currentRunLength = 1;
        }
    }
    // Record the final run after the loop finishes
    runLengths.push(currentRunLength);
    runValues.push(currentValue);
    // Combine lengths and values into the final structured output array
    const numRuns = runLengths.length;
    const encodedData = new Uint32Array(numRuns * 2);
    // Populate the first half with lengths
    encodedData.set(runLengths, 0);
    // Populate the second half with values, offset by the total number of runs
    encodedData.set(runValues, numRuns);
    return { data: encodedData, runs: numRuns };
}
export function encodeUnsignedRleInt64(input) {
    if (input.length === 0) {
        return { data: new BigUint64Array(0), runs: 0 };
    }
    const runLengths = [];
    const runValues = [];
    let currentRunLength = 0;
    let currentValue = input[0];
    for (let i = 0; i < input.length; i++) {
        const nextValue = input[i];
        if (nextValue === currentValue) {
            currentRunLength++;
        }
        else {
            // End of the current run, record it
            runLengths.push(currentRunLength);
            runValues.push(currentValue);
            // Start a new run
            currentValue = nextValue;
            currentRunLength = 1;
        }
    }
    // Record the final run after the loop finishes
    runLengths.push(currentRunLength);
    runValues.push(currentValue);
    // Combine lengths and values into the final structured output array (BigUint64Array)
    const numRuns = runLengths.length;
    const encodedData = new BigUint64Array(numRuns * 2);
    // Populate the first half with lengths, converting the run length numbers to bigint for storage in the BigUint64Array.
    for (let i = 0; i < numRuns; i++) {
        encodedData[i] = BigInt(runLengths[i]);
    }
    // Populate the second half with values, offset by the total number of runs
    encodedData.set(runValues, numRuns);
    return { data: encodedData, runs: numRuns };
}
export function encodeUnsignedRleFloat64(input) {
    if (input.length === 0) {
        return { data: new Float64Array(0), runs: 0 };
    }
    const runLengths = [];
    const runValues = [];
    let currentRunLength = 0;
    let currentValue = input[0];
    for (let i = 0; i < input.length; i++) {
        const nextValue = input[i];
        if (nextValue === currentValue) {
            currentRunLength++;
        }
        else {
            // End of the current run, record it
            runLengths.push(currentRunLength);
            runValues.push(currentValue);
            // Start a new run
            currentValue = nextValue;
            currentRunLength = 1;
        }
    }
    // Record the final run after the loop finishes
    runLengths.push(currentRunLength);
    runValues.push(currentValue);
    // Combine lengths and values into the final structured output array (Float64Array)
    const numRuns = runLengths.length;
    // The final array is twice the size of the number of runs
    const encodedData = new Float64Array(numRuns * 2);
    // Populate the first half with lengths
    encodedData.set(runLengths, 0);
    // Populate the second half with values, offset by the total number of runs
    encodedData.set(runValues, numRuns);
    return { data: encodedData, runs: numRuns };
}
export function encodeZigZagDeltaInt32(data) {
    if (data.length === 0) {
        return new Uint32Array(0);
    }
    const encodedData = new Uint32Array(data.length);
    let previousValue = data[0];
    encodedData[0] = encodeZigZagInt32Value(previousValue);
    for (let i = 1; i < data.length; i++) {
        const currentValue = data[i];
        const delta = currentValue - previousValue;
        const encodedDelta = encodeZigZagInt32Value(delta);
        // Store the encoded delta back into the array
        encodedData[i] = encodedDelta;
        // Update the previous value tracker for the next iteration's delta calculation
        previousValue = currentValue;
    }
    return encodedData;
}
export function encodeZigZagDeltaInt64(data) {
    if (data.length === 0) {
        return new BigUint64Array(0);
    }
    const encodedData = new BigUint64Array(data.length);
    let previousValue = data[0];
    encodedData[0] = encodeZigZagInt64Value(previousValue);
    for (let i = 1; i < data.length; i++) {
        const currentValue = data[i];
        const delta = currentValue - previousValue;
        const encodedDelta = encodeZigZagInt64Value(delta);
        // Store the encoded delta back into the array
        encodedData[i] = encodedDelta;
        // Update the previous value tracker for the next iteration's delta calculation
        previousValue = currentValue;
    }
    return encodedData;
}
export function encodeZigZagDeltaFloat64(data) {
    if (data.length === 0) {
        return;
    }
    let previousValue = data[0];
    data[0] = encodeZigZagFloat64Value(previousValue);
    for (let i = 1; i < data.length; i++) {
        const currentValue = data[i];
        const delta = currentValue - previousValue;
        const encodedDelta = encodeZigZagFloat64Value(delta);
        // Store the encoded delta back into the array
        data[i] = encodedDelta;
        // Update the previous value tracker for the next iteration's delta calculation
        previousValue = currentValue;
    }
}
export function encodeZigZagRleInt32(input) {
    if (input.length === 0) {
        return { data: new Uint32Array(0), runs: 0, numTotalValues: 0 };
    }
    const zigzagEncodedStream = [];
    // Step 1: Apply Zigzag Encoding to all values
    for (let i = 0; i < input.length; i++) {
        zigzagEncodedStream.push(encodeZigZagInt32Value(input[i]));
    }
    // zigzagEncodedStream now holds the intermediate stream of zigzag values
    // Step 2: Apply RLE to the stream of zigzag-encoded values
    const runLengths = [];
    const runZigZagValues = [];
    let currentRunLength = 0;
    let currentValue = zigzagEncodedStream[0];
    for (let i = 0; i < zigzagEncodedStream.length; i++) {
        const nextValue = zigzagEncodedStream[i];
        if (nextValue === currentValue) {
            currentRunLength++;
        }
        else {
            runLengths.push(currentRunLength);
            runZigZagValues.push(currentValue);
            currentValue = nextValue;
            currentRunLength = 1;
        }
    }
    // Record the final run
    runLengths.push(currentRunLength);
    runZigZagValues.push(currentValue);
    // Step 3: Combine lengths and values into the final structured output array
    const numRuns = runLengths.length;
    // The final array uses Uint32Array for lengths AND values
    const encodedData = new Uint32Array(numRuns * 2);
    // Populate the first half with lengths
    encodedData.set(runLengths, 0);
    // Populate the second half with zigzagged values
    encodedData.set(runZigZagValues, numRuns);
    return {
        data: encodedData,
        runs: numRuns,
        numTotalValues: input.length, // Total original values count
    };
}
export function encodeZigZagRleInt64(input) {
    if (input.length === 0) {
        return { data: new BigUint64Array(0), runs: 0, numTotalValues: 0 };
    }
    const zigzagEncodedStream = [];
    // Step 1: Apply Zigzag Encoding to all values
    for (let i = 0; i < input.length; i++) {
        zigzagEncodedStream.push(encodeZigZagInt64Value(input[i]));
    }
    // zigzagEncodedStream now holds the intermediate stream of zigzag values
    // Step 2: Apply RLE to the stream of zigzag-encoded values
    const runLengths = [];
    const runZigZagValues = [];
    let currentRunLength = 0;
    let currentValue = zigzagEncodedStream[0];
    for (let i = 0; i < zigzagEncodedStream.length; i++) {
        const nextValue = zigzagEncodedStream[i];
        if (nextValue === currentValue) {
            currentRunLength++;
        }
        else {
            runLengths.push(currentRunLength);
            runZigZagValues.push(currentValue);
            currentValue = nextValue;
            currentRunLength = 1;
        }
    }
    // Record the final run
    runLengths.push(currentRunLength);
    runZigZagValues.push(currentValue);
    // Step 3: Combine lengths and values into the final structured output array
    const numRuns = runLengths.length;
    // The final array uses BigUint64Array for lengths AND values
    const encodedData = new BigUint64Array(numRuns * 2);
    // Populate the first half with lengths (converting numbers back to BigUint64Array format)
    for (let i = 0; i < numRuns; i++) {
        encodedData[i] = BigInt(runLengths[i]);
    }
    // Populate the second half with zigzagged values
    encodedData.set(runZigZagValues, numRuns);
    return {
        data: encodedData,
        runs: numRuns,
        numTotalValues: input.length, // Total original values count
    };
}
export function encodeZigZagRleFloat64(input) {
    if (input.length === 0) {
        return { data: new Float64Array(0), runs: 0, numTotalValues: 0 };
    }
    const zigzagEncodedStream = [];
    // Step 1: Apply Float-based Zigzag Encoding to all values
    for (let i = 0; i < input.length; i++) {
        zigzagEncodedStream.push(encodeZigZagFloat64Value(input[i]));
    }
    // zigzagEncodedStream now holds the intermediate stream of zigzag values (as floats acting as integers)
    // Step 2: Apply RLE to the stream of zigzag-encoded values
    const runLengths = [];
    const runZigZagValues = [];
    let currentRunLength = 0;
    let currentValue = zigzagEncodedStream[0];
    for (let i = 0; i < zigzagEncodedStream.length; i++) {
        const nextValue = zigzagEncodedStream[i];
        if (nextValue === currentValue) {
            currentRunLength++;
        }
        else {
            runLengths.push(currentRunLength);
            runZigZagValues.push(currentValue);
            currentValue = nextValue;
            currentRunLength = 1;
        }
    }
    // Record the final run
    runLengths.push(currentRunLength);
    runZigZagValues.push(currentValue);
    // Step 3: Combine lengths and values into the final structured output array
    const numRuns = runLengths.length;
    // The final array uses Float64Array for lengths AND values
    const encodedData = new Float64Array(numRuns * 2);
    // Populate the first half with lengths
    encodedData.set(runLengths, 0);
    // Populate the second half with zigzagged values
    encodedData.set(runZigZagValues, numRuns);
    return {
        data: encodedData,
        runs: numRuns,
        numTotalValues: input.length, // Total original values count
    };
}
/**
 * This is not really a encode, but more of a decode method...
 */
export function encodeDeltaInt32(data) {
    if (data.length === 0) {
        return;
    }
    for (let i = data.length - 1; i >= 1; i--) {
        data[i] = data[i] - data[i - 1];
    }
}
export function encodeComponentwiseDeltaVec2(data) {
    if (data.length < 2)
        return new Uint32Array(data);
    const encoded = new Uint32Array(data.length);
    // Reverse iterate to avoid overwriting data needed for delta computation
    for (let i = data.length - 2; i >= 2; i -= 2) {
        const deltaX = data[i] - data[i - 2];
        const deltaY = data[i + 1] - data[i - 1];
        encoded[i] = encodeZigZagInt32Value(deltaX);
        encoded[i + 1] = encodeZigZagInt32Value(deltaY);
    }
    // Encode first vertex last (after computing all deltas)
    encoded[0] = encodeZigZagInt32Value(data[0]);
    encoded[1] = encodeZigZagInt32Value(data[1]);
    return encoded;
}
export function encodeComponentwiseDeltaVec2Scaled(data, scale) {
    if (data.length < 2)
        return new Uint32Array(data);
    const encoded = new Uint32Array(data.length);
    // First, inverse scale all values (tile space -> original space)
    for (let i = 0; i < data.length; i++) {
        encoded[i] = Math.round(data[i] / scale);
    }
    // Then apply componentwise delta encoding (same as non-scaled version)
    // Reverse iterate to avoid overwriting data needed for delta computation
    for (let i = encoded.length - 2; i >= 2; i -= 2) {
        const deltaX = encoded[i] - encoded[i - 2];
        const deltaY = encoded[i + 1] - encoded[i - 1];
        encoded[i] = encodeZigZagInt32Value(deltaX);
        encoded[i + 1] = encodeZigZagInt32Value(deltaY);
    }
    // Encode first vertex last (after computing all deltas)
    encoded[0] = encodeZigZagInt32Value(encoded[0]);
    encoded[1] = encodeZigZagInt32Value(encoded[1]);
    return encoded;
}
// HM TODO:
// zigZagDeltaOfDeltaDecoding
export function encodeZigZagRleDeltaInt32(values) {
    if (values.length === 0) {
        return { data: new Uint32Array(0), runs: 0, numTotalValues: 0 };
    }
    const runLengths = [];
    const encodedDeltas = [];
    // The decoder explicitly sets decodedValues[0] = 0 and uses previousValue = 0.
    // Therefore, we initialize our 'previous' tracker to 0 to calculate the first delta correctly.
    let previousValue = 0;
    // Variables to track the current run
    let currentDelta = null;
    let currentRunLength = 0;
    for (let i = 0; i < values.length; i++) {
        const value = values[i];
        const delta = value - previousValue;
        previousValue = value;
        if (currentDelta === null) {
            // First element initialization
            currentDelta = delta;
            currentRunLength = 1;
        }
        else if (delta === currentDelta) {
            // Continuation of the current run
            currentRunLength++;
        }
        else {
            // The run has broken (delta changed)
            // 1. Push the length of the previous run
            runLengths.push(currentRunLength);
            // 2. ZigZag encode the previous delta and push it
            encodedDeltas.push(encodeZigZagInt32Value(currentDelta));
            // Start the new run
            currentDelta = delta;
            currentRunLength = 1;
        }
    }
    // Flush the final run remaining after the loop finishes
    if (currentDelta !== null) {
        runLengths.push(currentRunLength);
        encodedDeltas.push(encodeZigZagInt32Value(currentDelta));
    }
    const numRuns = runLengths.length;
    // The decoder expects 'data' to be: [RunLength 1, RunLength 2... | Value 1, Value 2...]
    // Size is numRuns * 2 (First half lengths, second half values)
    const data = new Uint32Array(numRuns * 2);
    for (let i = 0; i < numRuns; i++) {
        data[i] = runLengths[i]; // First half: Run Lengths
        data[i + numRuns] = encodedDeltas[i]; // Second half: ZigZag Encoded Deltas
    }
    return {
        data: data,
        runs: numRuns,
        numTotalValues: values.length,
    };
}
export function encodeRleDeltaInt32(values) {
    if (values.length === 0) {
        return { data: new Uint32Array(0), runs: 0, numTotalValues: 0 };
    }
    const runLengths = [];
    const deltas = [];
    // The decoder logic relies on: decodedValues[0] = 0; previousValue = 0;
    // So the encoder must assume the sequence starts relative to 0.
    let previousValue = 0;
    // Track the current run of deltas
    let currentDelta = null;
    let currentRunLength = 0;
    for (let i = 0; i < values.length; i++) {
        const value = values[i];
        const delta = value - previousValue;
        previousValue = value;
        if (currentDelta === null) {
            // Initialize first run
            currentDelta = delta;
            currentRunLength = 1;
        }
        else if (delta === currentDelta) {
            // Continue current run
            currentRunLength++;
        }
        else {
            // Delta changed: flush the previous run
            runLengths.push(currentRunLength);
            deltas.push(currentDelta);
            // Start new run
            currentDelta = delta;
            currentRunLength = 1;
        }
    }
    // Flush the final run
    if (currentDelta !== null) {
        runLengths.push(currentRunLength);
        deltas.push(currentDelta);
    }
    const numRuns = runLengths.length;
    // Pack into Uint32Array: [ RunLength 1...N | Delta 1...N ]
    const data = new Uint32Array(numRuns * 2);
    for (let i = 0; i < numRuns; i++) {
        data[i] = runLengths[i];
        data[i + numRuns] = deltas[i];
    }
    return {
        data: data,
        runs: numRuns,
        numTotalValues: values.length,
    };
}
export function encodeDeltaRleInt32(input) {
    if (input.length === 0) {
        return { data: new Uint32Array(0), runs: 0, numValues: 0 };
    }
    const deltasAndEncoded = [];
    let previousValue = 0;
    // Step 1 & 2: Calculate Deltas and Zigzag Encode them
    for (let i = 0; i < input.length; i++) {
        const currentValue = input[i];
        const delta = currentValue - previousValue;
        const encodedDelta = encodeZigZagInt32Value(delta);
        deltasAndEncoded.push(encodedDelta);
        previousValue = currentValue;
    }
    // deltasAndEncoded now holds the intermediate stream of zigzagged deltas
    // Step 3: Apply RLE to the stream of zigzag-encoded deltas
    const runLengths = [];
    const runZigZagDeltas = [];
    let currentRunLength = 0;
    let currentRunValue = deltasAndEncoded[0];
    for (let i = 0; i < deltasAndEncoded.length; i++) {
        const nextValue = deltasAndEncoded[i];
        if (nextValue === currentRunValue) {
            currentRunLength++;
        }
        else {
            runLengths.push(currentRunLength);
            runZigZagDeltas.push(currentRunValue);
            currentRunValue = nextValue;
            currentRunLength = 1;
        }
    }
    // Record the final run
    runLengths.push(currentRunLength);
    runZigZagDeltas.push(currentRunValue);
    // Step 4: Combine lengths and values into the final structured output array
    const numRuns = runLengths.length;
    const encodedData = new Uint32Array(numRuns * 2);
    // Populate the first half with lengths
    for (let i = 0; i < numRuns; i++) {
        encodedData[i] = runLengths[i];
    }
    // Populate the second half with zigzagged deltas
    // Uint32Array.set() works with standard number arrays
    encodedData.set(runZigZagDeltas, numRuns);
    return {
        data: encodedData,
        runs: numRuns,
        numValues: input.length, // Total original values count
    };
}
export function encodeDeltaRleInt64(input) {
    if (input.length === 0) {
        return { data: new BigUint64Array(0), runs: 0, numValues: 0 };
    }
    const deltasAndEncoded = [];
    let previousValue = 0n;
    // Step 1 & 2: Calculate Deltas and Zigzag Encode them
    for (let i = 0; i < input.length; i++) {
        const currentValue = input[i];
        const delta = currentValue - previousValue;
        const encodedDelta = encodeZigZagInt64Value(delta);
        deltasAndEncoded.push(encodedDelta);
        previousValue = currentValue;
    }
    // deltasAndEncoded now holds the intermediate stream of zigzagged deltas
    // Step 3: Apply RLE to the stream of zigzag-encoded deltas
    const runLengths = [];
    const runZigZagDeltas = [];
    let currentRunLength = 0;
    let currentValue = deltasAndEncoded[0];
    for (let i = 0; i < deltasAndEncoded.length; i++) {
        const nextValue = deltasAndEncoded[i];
        if (nextValue === currentValue) {
            currentRunLength++;
        }
        else {
            runLengths.push(currentRunLength);
            runZigZagDeltas.push(currentValue);
            currentValue = nextValue;
            currentRunLength = 1;
        }
    }
    // Record the final run
    runLengths.push(currentRunLength);
    runZigZagDeltas.push(currentValue);
    // Step 4: Combine lengths and values into the final structured output array
    const numRuns = runLengths.length;
    const encodedData = new BigUint64Array(numRuns * 2);
    // Populate the first half with lengths (converting numbers back to BigUint64Array for storage)
    for (let i = 0; i < numRuns; i++) {
        encodedData[i] = BigInt(runLengths[i]);
    }
    // Populate the second half with zigzagged deltas
    encodedData.set(runZigZagDeltas, numRuns);
    return {
        data: encodedData,
        runs: numRuns,
        numValues: input.length, // Total original values count
    };
}
//# sourceMappingURL=integerEncodingUtils.js.map