/*
    -- micMAGMA (version 0.1) --
       Univ. of Tennessee, Knoxville
       Univ. of California, Berkeley
       Univ. of Colorado, Denver
       April 2012

       @generated c Fri Nov 22 14:21:14 2013

*/

#include <stdio.h>
#include "common_magma.h"

// =========================================================================
// definitions of non-GPU-resident multi-GPU subroutines
/* non-gpu-resident interface to multiple GPUs */
//extern "C" magma_int_t
//magma_cgetrf_m(magma_int_t num_gpus0, magma_int_t m, magma_int_t n, magmaFloatComplex *a, magma_int_t lda,
//               magma_int_t *ipiv, magma_int_t *info);

/* to apply pivoting from the previous big panel on CPU */
//extern "C" magma_int_t
//magma_cgetrf_piv(magma_int_t num_gpus, magma_int_t m, magma_int_t n, magmaFloatComplex *a, magma_int_t lda,
//                 magma_int_t *ipiv, magma_int_t *info);
// =========================================================================

#include "../testing/flops.h"
#define PRECISION_s
#if defined(PRECISION_z) || defined(PRECISION_c)
#define FLOPS(m, n) ( 6. * FMULS_GETRF(m, n) + 2. * FADDS_GETRF(m, n) )
#else
#define FLOPS(m, n) (      FMULS_GETRF(m, n) +      FADDS_GETRF(m, n) )
#endif


magma_err_t
magma_cgetrf2(magma_int_t m, magma_int_t n, 
	      magmaFloatComplex *a, magma_int_t lda, 
              magma_int_t *ipiv, 
	      magmaFloatComplex_ptr dA, magma_int_t ldda, magma_int_t *d_ipiv,
	      magma_int_t *info, magma_queue_t queue )
{
/*  -- micMAGMA (version 0.3) --
       Univ. of Tennessee, Knoxville
       Univ. of California, Berkeley
       Univ. of Colorado, Denver
       April 2012

    Purpose
    =======
    CGETRF computes an LU factorization of a general M-by-N matrix A
    using partial pivoting with row interchanges.  This version does not
    require work space on the GPU passed as input. GPU memory is allocated
    in the routine.

    The factorization has the form
       A = P * L * U
    where P is a permutation matrix, L is lower triangular with unit
    diagonal elements (lower trapezoidal if m > n), and U is upper
    triangular (upper trapezoidal if m < n).

    This is the right-looking Level 3 BLAS version of the algorithm.

    Arguments
    =========
    M       (input) INTEGER
            The number of rows of the matrix A.  M >= 0.

    N       (input) INTEGER
            The number of columns of the matrix A.  N >= 0.

    A       (input/output) COMPLEX array, dimension (LDA,N)
            On entry, the M-by-N matrix to be factored.
            On exit, the factors L and U from the factorization
            A = P*L*U; the unit diagonal elements of L are not stored.

            Higher performance is achieved if A is in pinned memory, e.g.
            allocated using magma_malloc_pinned.

    LDA     (input) INTEGER
            The leading dimension of the array A.  LDA >= max(1,M).

    IPIV    (output) INTEGER array, dimension (min(M,N))
            The pivot indices; for 1 <= i <= min(M,N), row i of the
            matrix was interchanged with row IPIV(i).

    INFO    (output) INTEGER
            = 0:  successful exit
            < 0:  if INFO = -i, the i-th argument had an illegal value
                  or another error occured, such as memory allocation failed.
            > 0:  if INFO = i, U(i,i) is exactly zero. The factorization
                  has been completed, but the factor U is exactly
                  singular, and division by zero will occur if it is used
                  to solve a system of equations.

    =====================================================================    */

#define inA(i,j) (dA + dA_offset + (j)*nb*ldda + (i)*nb)

    real_Double_t time;

    magmaFloatComplex *work;
    magmaFloatComplex c_one     = MAGMA_C_ONE;
    magmaFloatComplex c_neg_one = MAGMA_C_NEG_ONE;
    magma_int_t     iinfo, jj, nb, dA_offset = 0;

    *info = 0;

    if (m < 0)
        *info = -1;
    else if (n < 0)
        *info = -2;
    else if (lda < max(1,m))
        *info = -4;

    if (*info != 0) {
        magma_xerbla( __func__, -(*info) );
        return *info;
    }

    /* Quick return if possible */
    if (m == 0 || n == 0)
        return *info;

    nb = 480; // magma_get_cgetrf_nb(m);

    magma_event_t event[2] = {NULL, NULL};

    if ( (nb <= 1) || (nb >= min(m,n)) ) {
        /* Use CPU code. */
        lapackf77_cgetrf(&m, &n, a, &lda, ipiv, info);
    } else {
        /* Use hybrid blocked code. */
        magma_int_t i, rows, s = min(m, n)/nb;
        
        work = a;
	
	//magma_kernel_sync();
	//time = magma_wtime();

	magma_csetmatrix_async( m, n-nb, a + nb*lda, 0, lda, dA, nb*ldda, ldda, queue, &event[0] );
        
        lapackf77_cgetrf( &m, &nb, work, &lda, ipiv, &iinfo);
	magma_event_sync(event[0]);
        for( i = 0; i < s; i++ )
        {
            
            if (i>0)
	      {
		// download i-th panel
		magma_kernel_sync();
		magma_cgetmatrix_async( m-i*nb, nb, inA(i,i), 0, ldda, work, 0, lda, queue, &event[1] );

                magma_cgemm( MagmaNoTrans, MagmaNoTrans, 
                             m-i*nb, n-(i+1)*nb, nb, 
                             c_neg_one, inA(i,  i-1), 0, ldda,
			                inA(i-1,i+1), 0, ldda, 
                             c_one,     inA(i,  i+1), 0, ldda, queue );

                // do the cpu part
                rows = m - i*nb;
		magma_event_sync(event[1]);
                lapackf77_cgetrf( &rows, &nb, work, &lda, ipiv+i*nb, &iinfo);
            }
            if (*info == 0 && iinfo > 0)
                *info = iinfo + i*nb;

	    //transfer ipiv
	    magma_csetintarray_async((i+1)*nb, ipiv, 0, d_ipiv, 0, queue, &event[1]);
	    magma_event_sync(event[1]);

	    magma_csetmatrix_async(m - i * nb, nb, work, 0, lda, inA(i, i), 0, ldda, queue, &event[1]);

	    if (i!=0)
	      magma_claswp(i*nb, inA(i, 0), ldda, 1, nb, d_ipiv + i * nb, 1, queue);
	    magma_claswp(n-i*nb-nb, inA(i, i+1), ldda, 1, nb, d_ipiv + i * nb, 1, queue);

	    for (jj = 0; jj < nb; jj++) {
	      *(ipiv + i*nb + jj) += i*nb;
	    }

	    magma_event_sync(event[1]);

            // do the small non-parallel computations
	    magma_ctrsm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit,
			 nb, n - (i+1)*nb,
			 c_one, inA(i, i), 0, ldda,
			 inA(i, i+1), 0, ldda, queue );

            if (s > (i+1)){
	      magma_cgemm( MagmaNoTrans, MagmaNoTrans, 
			   m-(i+1)*nb, nb, nb, 
			   c_neg_one, inA(i+1, i  ), 0, ldda,
			              inA(i,   i+1), 0, ldda,
			   c_one,     inA(i+1, i+1), 0, ldda, queue );
            }
            else{
                magma_cgemm( MagmaNoTrans, MagmaNoTrans, 
                             m-(i+1)*nb, n-(i+1)*nb, nb,
                             c_neg_one, inA(i+1, i  ), 0, ldda,
			                inA(i,   i+1), 0, ldda,
                             c_one,     inA(i+1, i+1), 0, ldda, queue );
            }
        }
        
        magma_int_t nb0 = min(m - s*nb, n - s*nb);
        if ( nb0 > 0 ) {
            rows = m - s*nb;
    
            magma_cgetmatrix( rows, nb0, inA(s,s), 0, ldda, work, 0, lda, queue );
    
            // do the cpu part
            lapackf77_cgetrf( &rows, &nb0, work, &lda, ipiv+s*nb, &iinfo);
            if (*info == 0 && iinfo > 0)
                *info = iinfo + s*nb;
	    
	    //transfer ipiv matrix
	    magma_csetintarray_async(m, ipiv, 0, d_ipiv, 0, queue, &event[1]);
	    magma_event_sync(event[1]);

	    magma_claswp(n, inA(s, 0), ldda, 1, nb0, d_ipiv + s * nb, 1, queue);
	    magma_kernel_sync();
	    for (jj = 0; jj < nb0; jj++) {
	      *(ipiv + s*nb + jj) += s*nb;
	    }

	    // upload i-th panel
	    magma_csetmatrix(rows, nb0, work, 0, lda, inA(s, s), 0, ldda, queue);

	    magma_ctrsm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit,
			 nb0, n-s*nb-nb0,
			 c_one, inA(s,s),     0, ldda,
			        inA(s,s)+nb0, 0, ldda, queue);
        }
        
	magma_cgetmatrix( m, n, dA, 0, ldda, a, 0, lda, queue );
     
	/*
	time = magma_wtime() - time;
	float gflops, perf;
	gflops = FLOPS( (float)m, (float)n ) / 1e9;
	perf = gflops / time;
	printf("\nPerformance = %f GFlop/s\n", perf);
	*/
    }
    
    return *info;
} /* magma_cgetrf */

#undef inA
