#include "det.h"


// For reference see: `det_ge_real` in "R-4.2.1/src/modules/lapack/Lapack.c"
// of the R source code.
double det(
    /*       dim */ const int dimA,
    /*    matrix */ const double* A, const int ldA,
                    double* work_mem, int* info
) {
    // if working memory size query, return immediately
    if (work_mem == NULL) {
        *info = dimA * (dimA + 1);
        return 0.0;
    }

    // determinant of "zero size" matrix is 1 (by definition)
    if (dimA == 0) {
        return 1.0;
    }

    // copy `A` to (continuous) `work_mem` cause `dgetrf` works "in place"
    for (int i = 0; i < dimA; ++i) {
        memcpy(work_mem + i * dimA, A + i * ldA, dimA * sizeof(double));
    }

    // L U factorization of `A`
    int error = 0;
    int* ipvt = (int*)(work_mem + dimA * dimA);
    F77_CALL(dgetrf)(&dimA, &dimA, work_mem, &dimA, ipvt, &error);

    // check if an error occured which is the case iff `dgetrf` gives a negative error
    *info |= (error < 0) * error;
    // in both cases we return zero (ether the determinant is zero or error occured)
    if (error) {
        return 0.0;
    }

    // res <- det(A) = sign(P) * prod(diag(L)) where P is the pivoting permutation
    double res = 1.0;
    for (int i = 0; i < dimA; ++i) {
        res *= (ipvt[i] != (i + 1) ? -1.0 : 1.0) * work_mem[i * (dimA + 1)];
    }

    return res;
}

/**
 * R bindong to `det`
 */
extern SEXP R_det(SEXP A) {
    // check if A is a real valued square matrix
    if (!Rf_isReal(A) || !Rf_isMatrix(A) || Rf_nrows(A) != Rf_ncols(A)) {
        Rf_error("`A` must be a real valued squae matrix");
    }

    // allocate working memory
    int work_size;
    (void)det(Rf_nrows(A), NULL, Rf_nrows(A), NULL, &work_size);
    double* work_mem = (double*)R_alloc(work_size, sizeof(double));

    // compute determinant (followed only by if-statement, no protection required)
    int error = 0;
    SEXP res = Rf_ScalarReal(
        det(Rf_nrows(A), REAL(A), Rf_nrows(A), work_mem, &error)
    );

    if (error) {
        Rf_error("Encountered error code %d in `det`", error);
    }

    return res;
}
