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

/**
 * Error bound during calculations, must be something reasonably large to avoid floating point rounding errors
 * @type {number}
 */
const SMALL_NUMBER = 1e-12;

const SCRATCH_SIZE = 16;
// avoid allocation for smaller systems by using a scratch
const scratch = new Float64Array(SCRATCH_SIZE);

/**
 * Factor linear equations Ax = b using LU decomposition A = LU where L is lower triangular matrix and U is upper triangular matrix.
 * Input is square matrix A, integer array of pivot indices index[0->n-1], and size of square matrix n.
 * Output factorization LU is in matrix A.
 * If error is found, method returns false.
 *
 * @param {number[]} A Square matrix, column-major flat layout (A[j*size + i] = element at row i, col j); modified in place to hold the LU factorization
 * @param {number[]|Uint32Array} index will be written to
 * @param {number} size Size of the matrix ( 4x4 matrix has size 4 )
 * @returns {boolean} true if factorization succeeded, false otherwise
 */
export function lu_factor_linear_system(A, index, size) {
    assert.isNonNegativeInteger(size,'size');

    let i, j, k;
    let maxI = 0;
    let largest, temp1, temp2, sum;

    const scale = size <= SCRATCH_SIZE ? scratch : new Float64Array(size);

    // Loop over rows to get implicit scaling information
    //
    for (i = 0; i < size; ++i) {
        for (largest = 0.0, j = 0; j < size; ++j) {
            if ((temp2 = Math.abs(A[j * size + i])) > largest) {
                largest = temp2;
            }
        }

        if (largest === 0.0) {
            // Unable to factor linear system

            return false;
        }
        scale[i] = 1.0 / largest;
    }
    //
    // Loop over all columns using Crout's method
    //
    for (j = 0; j < size; ++j) {
        const jOff = j * size;

        for (i = 0; i < j; ++i) {
            sum = A[jOff + i];
            for (k = 0; k < i; ++k) {
                sum -= A[k * size + i] * A[jOff + k];
            }
            A[jOff + i] = sum;
        }
        //
        // Begin search for largest pivot element
        //
        for (largest = 0.0, i = j; i < size; ++i) {
            sum = A[jOff + i];
            for (k = 0; k < j; ++k) {
                sum -= A[k * size + i] * A[jOff + k];
            }
            A[jOff + i] = sum;

            if ((temp1 = scale[i] * Math.abs(sum)) >= largest) {
                largest = temp1;
                maxI = i;
            }
        }
        //
        // Check for row interchange
        //
        if (j !== maxI) {
            for (k = 0; k < size; ++k) {
                const kOff = k * size;
                temp1 = A[kOff + maxI];
                A[kOff + maxI] = A[kOff + j];
                A[kOff + j] = temp1;
            }
            scale[maxI] = scale[j];
        }
        //
        // Divide by pivot element and perform elimination
        //
        index[j] = maxI;

        if (Math.abs(A[jOff + j]) <= SMALL_NUMBER) {
            // Unable to factor linear system
            return false;
        }

        if (j !== (size - 1)) {
            temp1 = 1.0 / A[jOff + j];
            for (i = j + 1; i < size; ++i) {
                A[jOff + i] *= temp1;
            }
        }
    }

    return true;
}
