//


import { array_copy } from "../../../../core/collection/array/array_copy.js";
import { isPowerOfTwo } from "../../../../core/math/isPowerOfTwo.js";
import { lerp } from "../../../../core/math/lerp.js";
import { max2 } from "../../../../core/math/max2.js";
import { max3 } from "../../../../core/math/max3.js";
import { min3 } from "../../../../core/math/min3.js";
import { downsample_float_array } from "./downsample_float_array.js";

/**
 *
 * @param {number[]} input
 * @param {number} input_offset
 * @param {number} input_size
 * @param {number} f
 * @return {number}
 */
function array_sampler_linear(input, input_offset, input_size, f) {
    const t = f * (input_size - 1);

    const fraction = t % 1;

    if (fraction === 0) {
        return input[input_offset + t];
    }

    const i0 = Math.floor(t);
    const i1 = i0 + 1;

    const s0 = input[input_offset + i0];
    const s1 = input[input_offset + i1];

    return lerp(s0, s1, fraction);
}

/**
 *
 * @param {number[]|Float32Array} input0
 * @param {number} input0_offset
 * @param {number} input0_size
 * @param {number[]|Float32Array} input1
 * @param {number} input1_offset
 * @param {number} input1_size
 */
function compute_float_array_curve_linear_sampling_relative_error(
    input0, input0_offset, input0_size,
    input1, input1_offset, input1_size
) {

    const largest_size = max2(input0_size, input1_size);

    let max_error = 0;

    let min = Number.POSITIVE_INFINITY;
    let max = Number.NEGATIVE_INFINITY;

    for (let i = 0; i < largest_size; i++) {
        const f = i / (largest_size - 1);

        const s0 = array_sampler_linear(input0, input0_offset, input0_size, f);
        const s1 = array_sampler_linear(input1, input1_offset, input1_size, f);

        const error = Math.abs(s0 - s1);

        if (error > max_error) {
            max_error = error;
        }

        min = min3(min, s0, s1);
        max = max3(max, s0, s1);
    }

    const span = max - min;

    if (span === 0) {
        // avoid division by 0
        return 0;
    }

    return max_error / span;
}

/**
 *
 * @param {number[]} output
 * @param {number} output_offset
 * @param {number[]} input
 * @param {number} input_offset
 * @param {number} input_size
 * @param {number} [error]
 * @return {number}
 */
export function downsample_float_array_curve_by_error(output, output_offset, input, input_offset, input_size, error = 0.03) {
    if (!isPowerOfTwo(input_size)) {
        throw new Error(`Input size must be a power of two, instead was '${input_size}'`);
    }

    const buffer_0 = new Float32Array(input_size >>> 1); // first step needs half the size of the original buffer
    const buffer_1 = new Float32Array(input_size >>> 2); // next step needs quarter

    let current = input;
    let current_offset = input_offset;
    let current_size = input_size;

    let next = buffer_0;
    let next_offset = 0;
    let next_size = buffer_0.length;

    let current_error = 0;

    while (current_error <= error && current_size > 1) {
        downsample_float_array(next, next_offset, current, current_offset, current_size);

        current_error = compute_float_array_curve_linear_sampling_relative_error(
            input, input_offset, input_size,
            next, next_offset, next_size
        );

        if (current_error > error) {
            // reached best possible reduction
            break;
        }

        current = next;
        current_offset = next_offset;
        current_size = next_size;

        // perform swap
        if (next === buffer_0) {
            next = buffer_1;
        } else {
            next = buffer_0;
        }

        next_offset = 0;
        // next will be half the current size
        next_size = current_size >>> 1;
    }


    // copy current buffer to output
    array_copy(current, current_offset, output, output_offset, current_size);

    return current_size;
}
