import { assert } from "../../../../core/assert.js";
import { scs3d_sample_linear } from "../../../graphics/texture/3d/scs3d_sample_linear.js";

/**
 * Semi-Lagrangian self-advection of a cell-centered 3D velocity field.
 *
 * For each cell, traces backward along the current velocity for one timestep, then
 * trilinearly samples each input velocity component at that back-traced position.
 * Stable for arbitrarily large `time_delta` (the trade-off is numerical diffusion;
 * the field gets blurrier each step).
 *
 * Inputs and outputs must be disjoint Float32Arrays of length `res_x * res_y * res_z`.
 * Cells flagged solid (non-zero in `solid`) keep their input value unchanged.
 *
 * @param {Float32Array[]} outputs   `[out_x, out_y, out_z]`, mutated.
 * @param {Float32Array[]} inputs    `[in_x, in_y, in_z]`, read; expected to alias the
 *                                   "current" velocity field that drives back-tracing.
 * @param {number} res_x
 * @param {number} res_y
 * @param {number} res_z
 * @param {number} time_delta        Simulation timestep, in the same units as velocity
 *                                   (grid-cells / second when used standalone).
 * @param {Uint8Array} solid         Required cell flags (zero-filled for a wall-free
 *                                   domain); non-zero cells are not updated.
 */
export function v3_grid_apply_advection_forward(outputs, inputs, res_x, res_y, res_z, time_delta, solid) {
    assert.equal(outputs.length, 3, "outputs is a 3-component array");
    assert.equal(inputs.length, 3, "inputs is a 3-component array");

    const out_x = outputs[0];
    const out_y = outputs[1];
    const out_z = outputs[2];

    const in_x = inputs[0];
    const in_y = inputs[1];
    const in_z = inputs[2];

    const cell_count = res_x * res_y * res_z;

    assert.greaterThanOrEqual(out_x.length, cell_count, "out_x covers grid");
    assert.greaterThanOrEqual(out_y.length, cell_count, "out_y covers grid");
    assert.greaterThanOrEqual(out_z.length, cell_count, "out_z covers grid");
    assert.greaterThanOrEqual(in_x.length, cell_count, "in_x covers grid");
    assert.greaterThanOrEqual(in_y.length, cell_count, "in_y covers grid");
    assert.greaterThanOrEqual(in_z.length, cell_count, "in_z covers grid");

    assert.notEqual(out_x, in_x, "out_x must not alias in_x");
    assert.notEqual(out_y, in_y, "out_y must not alias in_y");
    assert.notEqual(out_z, in_z, "out_z must not alias in_z");

    const slice_size = res_x * res_y;

    for (let z = 0; z < res_z; z++) {
        const z_off = z * slice_size;

        for (let y = 0; y < res_y; y++) {
            const y_off = y * res_x;

            for (let x = 0; x < res_x; x++) {
                const c = z_off + y_off + x;

                if (solid[c] !== 0) {
                    out_x[c] = in_x[c];
                    out_y[c] = in_y[c];
                    out_z[c] = in_z[c];
                    continue;
                }

                const v_x = in_x[c];
                const v_y = in_y[c];
                const v_z = in_z[c];

                // Trace back to where this parcel came from one timestep ago.
                const s_x = x - v_x * time_delta;
                const s_y = y - v_y * time_delta;
                const s_z = z - v_z * time_delta;

                out_x[c] = scs3d_sample_linear(in_x, res_x, res_y, res_z, s_x, s_y, s_z);
                out_y[c] = scs3d_sample_linear(in_y, res_x, res_y, res_z, s_x, s_y, s_z);
                out_z[c] = scs3d_sample_linear(in_z, res_x, res_y, res_z, s_x, s_y, s_z);
            }
        }
    }
}
