import { array_swap_one } from "../../collection/array/array_swap_one.js";
import { FLT_EPSILON_64 } from "../FLT_EPSILON_64.js";

// Note the choice of eps is reasonable but somewhat arbitrary.
const epsilon = 256 * FLT_EPSILON_64;

/**
 * Solve linear equation Ax = b using Gaussian Elimination with Partial Pivoting for a 2x2 system.
 * If the matrix is found to be singular within a small numerical tolerance close to machine precision then 0 is returned.
 * @param {number[]|Float32Array|Float64Array} A 2x2 matrix, column-major flat: [a00, a10, a01, a11]; modified during the solve
 * @param {number[]|Float32Array|Float64Array} x
 * @param {number[]|Float32Array|Float64Array} b
 * @returns {boolean} `true` iff solution is found, false otherwise
 */
export function solve_linear_system_GEPP_2x2(A, x, b) {

    // Column-major index map:
    //   A[0] = a00 (row 0, col 0)
    //   A[1] = a10 (row 1, col 0)
    //   A[2] = a01 (row 0, col 1)
    //   A[3] = a11 (row 1, col 1)

    // Check if any of the matrix coefficients is zero.
    // If so then swap rows/columns to form an upper triangular matrix without having to use GEPP.
    let cols_swapped = false;
    if (A[0] === 0 || A[1] === 0 || A[2] === 0 || A[3] === 0) {

        // zero in either entry of the 2nd column?
        if (A[2] === 0 || A[3] === 0) {
            // swap columns (column 0 is [A[0], A[1]], column 1 is [A[2], A[3]])
            array_swap_one(A, 0, 2);
            array_swap_one(A, 1, 3);

            cols_swapped = true;
        }

        // zero in a00?
        if (A[0] === 0) {
            // swap rows (row 0 entries are at indices 0 and 2; row 1 entries at 1 and 3)
            array_swap_one(A, 0, 1);
            array_swap_one(A, 2, 3);
            array_swap_one(b, 0, 1);
        }

    } else {

        // None of the matrix coefficients are exactly zero.
        // Use GEPP to form upper triangular matrix, i.e. so that a10 == 0.
        // Select pivot by looking at largest absolute value in a00, a10
        if (Math.abs(A[0]) < Math.abs(A[1])) {
            // swap rows so the largest coefficient in first column is in the first row
            array_swap_one(A, 0, 1);
            array_swap_one(A, 2, 3);
            array_swap_one(b, 0, 1);
        }

        const f = -A[1] / A[0];

        A[3] += A[2] * f;
        b[1] += b[0] * f;

    }

    // Have now an exact zero in a10.
    // Need to check for singularity by looking at a11.

    if (Math.abs(A[3]) < epsilon) {
        // matrix is singular within small numerical tolerance
        return false;
    }

    // Solve the triangular system
    if (A[3] !== 0) {
        x[1] = b[1] / A[3];
    } else {
        return false;
    }

    if (A[0] !== 0) {
        x[0] = (b[0] - A[2] * x[1]) / A[0];
    } else {
        return false;
    }

    // other failures in solution?
    if (!Number.isFinite(x[0]) || !Number.isFinite(x[1])) {
        return false;
    }

    // If necessary swap solution vector rows.
    if (cols_swapped) {
        array_swap_one(x, 0, 1);
    }

    return true;
}
