import { v2_length } from "../../vec2/v2_length.js";


/**
 *
 * @param {number[]} mat4
 * @param {number} row_index
 * @param {number} column_index
 * @return {number}
 * @ignore
 */
function coeff(mat4, row_index, column_index) {
    return mat4[row_index + column_index * 4];
}

const sin = Math.sin;
const cos = Math.cos;
const atan2 = Math.atan2;

/**
 *
 * returns the Euler-angles of the rotation matrix \c *this using the convention defined by the triplet (`a0`,`a1`,`a2`)
 *
 * Each of the three parameters `a0`, `a1`, `a2` represents the respective rotation axis as an integer in {0,1,2}.
 * For instance, in:
 * ```
 * Vector3f ea = mat.eulerAngles(2, 0, 2);
 * ```
 * "2" represents the z axis and "0" the x axis, etc. The returned angles are such that
 * we have the following equality:
 * ```
 * mat == AngleAxisf(ea[0], Vector3f::UnitZ())
 *      * AngleAxisf(ea[1], Vector3f::UnitX())
 *      * AngleAxisf(ea[2], Vector3f::UnitZ());
 * ```
 * This corresponds to the right-multiply conventions (with right hand side frames).
 *
 * The returned angles are in the ranges [0:pi]x[-pi:pi]x[-pi:pi].

 * NOTE: ported from Eigen C++ library
 *
 * @see https://gitlab.com/libeigen/eigen/-/blob/master/Eigen/src/Geometry/EulerAngles.h
 * @see https://stackoverflow.com/questions/11514063/extract-yaw-pitch-and-roll-from-a-rotationmatrix
 *
 * @param {number[]} output
 * @param {number[]|Float32Array|mat4} m4 source matrix to extract rotation from
 * @param {number} a0 axis index (0 = X, 1 = Y, 2 = Z)
 * @param {number} a1 axis index
 * @param {number} a2 axis index
 */
export function eulerAnglesFromMatrix(
    output, m4,
    a0, a1, a2
) {

    const odd = ((a0 + 1) % 3 === a1) ? 0 : 1;

    const i = a0;
    const j = (a0 + 1 + odd) % 3;
    const k = (a0 + 2 - odd) % 3;

    if (a0 === a2) {
        output[0] = atan2(coeff(m4, j, i), coeff(m4, k, i));

        if ((odd && output[0] < 0) || ((~odd) && output[0] > 0)) {
            if (output[0] > 0) {
                output[0] -= Math.PI;
            } else {
                output[0] += Math.PI;
            }
            const s2 = v2_length(coeff(m4, j, i), coeff(m4, k, i));
            output[1] = -atan2(s2, coeff(m4, i, i));
        } else {
            const s2 = v2_length(coeff(m4, j, i), coeff(m4, k, i));
            output[1] = atan2(s2, coeff(m4, i, i));
        }

        // With a=(0,1,0), we have i=0; j=1; k=2, and after computing the first two angles,
        // we can compute their respective rotation, and apply its inverse to M. Since the result must
        // be a rotation around x, we have:
        //
        //  c2  s1.s2 c1.s2                   1  0   0
        //  0   c1    -s1       *    M    =   0  c3  s3
        //  -s2 s1.c2 c1.c2                   0 -s3  c3
        //
        //  Thus:  m11.c1 - m21.s1 = c3  &   m12.c1 - m22.s1 = s3

        const s1 = sin(output[0]);
        const c1 = cos(output[0]);

        output[2] = atan2(c1 * coeff(m4, j, k) - s1 * coeff(m4, k, k), c1 * coeff(m4, j, j) - s1 * coeff(m4, k, j));

    } else {

        output[0] = atan2(coeff(m4, j, k), coeff(m4, k, k));

        const c2 = v2_length(coeff(m4, i, i), coeff(m4, i, j));

        if ((odd && output[0] < 0) || ((~odd) && output[0] > 0)) {
            if (output[0] > 0) {
                output[0] -= Math.PI;
            } else {
                output[0] += Math.PI;
            }
            output[1] = atan2(-coeff(m4, i, k), -c2);
        } else {
            output[1] = atan2(-coeff(m4, i, k), c2);
        }

        const s1 = sin(output[0]);
        const c1 = cos(output[0]);

        output[2] = atan2(s1 * coeff(m4, k, i) - c1 * coeff(m4, j, i), c1 * coeff(m4, j, j) - s1 * coeff(m4, k, j));

    }

    if (odd === 0) {
        // invert result
        output[0] = -output[0];
        output[1] = -output[1];
        output[2] = -output[2];
    }

}
