#include <math.h>
#include "mex.h"

double max(double x, double y)
{
    if (x > y)
        return x;
    else
        return y;
}

double absolute(double x)
{
    if (x >= -x)
        return x;
    else
        return -x;
}

void permuteInt(int *x, int p, int q)
{
    double temp;
    temp = x[p];
    x[p] = x[q];
    x[q] = temp;
}

void permute(double *x, int p, int q)
{
    double temp;
    temp = x[p];
    x[p] = x[q];
    x[q] = temp;
}

void permuteRows(double *x, int p, int q,int n)
{
    int i;
    double temp;
    for(i = 0; i < n; i++)
    {
        temp = x[p+i*n];
        x[p+i*n] = x[q+i*n];
        x[q+i*n] = temp;
    }
}

void permuteCols(double *x, int p, int q,int n)
{
    int i;
    double temp;
    for(i = 0; i < n; i++)
    {
        temp = x[i+p*n];
        x[i+p*n] = x[i+q*n];
        x[i+q*n] = temp;
    }
}

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    int n,sizL[2],sizD[2],i,j,q,s,
    *P;
    
    double mu,gamma,xi,delta,beta,maxVal,theta,
    *c,    *H, *L, *D, *A;
    
    /* Input */
    H = mxGetPr(prhs[0]);
    if (nrhs == 1)
    {
        mu = 1e-12;
    }
    else
    {
        mu = mxGetScalar(prhs[1]);
    }
    
    /* Compute Sizes */
    n = mxGetDimensions(prhs[0])[0];
    
    /* Form Output */
    sizL[0] = n;
    sizL[1] = n;
    plhs[0] = mxCreateNumericArray(2,sizL,mxDOUBLE_CLASS,mxREAL);
    L = mxGetPr(plhs[0]);
    sizD[0] = n;
    sizD[1] = 1;
    plhs[1] = mxCreateNumericArray(2,sizD,mxDOUBLE_CLASS,mxREAL);
    D = mxGetPr(plhs[1]);
    plhs[2] = mxCreateNumericArray(2,sizD,mxINT32_CLASS,mxREAL);
    P = mxGetPr(plhs[2]);
    
    /* Initialize */
    c = mxCalloc(n*n,sizeof(double));
    A = mxCalloc(n*n,sizeof(double));
    
    for (i = 0; i < n; i++)
    {
        P[i] = i;
        for (j = 0;j < n; j++)
        {
            A[i+n*j] = H[i+n*j];
        }
    }
    
    gamma = 0;
    for (i = 0; i < n; i++)
    {
        L[i+n*i] = 1;
        c[i+n*i] = A[i+n*i];
    }
    
    /* Compute modification parameters */
    gamma = -1;
    xi = -1;
    for (i = 0; i < n; i++)
    {
        gamma = max(gamma,absolute(A[i+n*i]));
        for (j = 0;j < n; j++)
        {
            //printf("A(%d,%d) = %f, %f\n",i,j,A[i+n*j],absolute(A[i+n*j]));
            if (i != j)
                xi = max(xi,absolute(A[i+n*j]));
        }
    }
    delta = mu*max(gamma+xi,1);
    
    if (n > 1)
    {
        beta = sqrt(max(gamma,max(mu,xi/sqrt(n*n-1))));
    }
    else
    {
        beta = sqrt(max(gamma,mu));
    }
    
    for (j = 0; j < n; j++)
    {
        
    /* Find q that results in Best Permutation with j */
        maxVal = -1;
        q = 0;
        for(i = j; i < n; i++)
        {
            if (absolute(c[i+n*i]) > maxVal)
            {
                maxVal = max(maxVal,absolute(c[i+n*i]));
                q = i;
            }
        }
        
        /* Permute D,c,L,A,P */
        permute(D,j,q);
        permuteInt(P,j,q);
        permuteRows(c,j,q,n);
        permuteCols(c,j,q,n);
        permuteRows(L,j,q,n);
        permuteCols(L,j,q,n);
        permuteRows(A,j,q,n);
        permuteCols(A,j,q,n);
        
        for(s = 0; s <= j-1; s++)
            L[j+n*s] = c[j+n*s]/D[s];
        
        for(i = j+1; i < n; i++)
        {
            c[i+j*n] = A[i+j*n];
            for(s = 0; s <= j-1; s++)
            {
                c[i+j*n] -= L[j+n*s]*c[i+n*s];
            }
        }
        
        theta = 0;
        if (j < n-1 && j > 0)
        {
            for(i = j+1;i < n; i++)
                theta = max(theta,absolute(c[i+n*j]));
        }
        
        D[j] = max(absolute(c[j+n*j]),max(delta,theta*theta/(beta*beta)));
        
        if (j < n-1)
        {
            for(i = j+1; i < n; i++)
            {
                c[i+n*i] = c[i+n*i] - c[i+n*j]*c[i+n*j]/D[j];
            }
        }
        
    }
    
    for(i = 0; i < n; i++)
        P[i]++;
    
    mxFree(c);
    mxFree(A);
}