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

       @precisions normal z -> s d c

*/

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

magma_err_t
magma_zgetrf(magma_int_t m, magma_int_t n, 
	     magmaDoubleComplex *a, magma_int_t lda, 
             magma_int_t *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
    =======
    ZGETRF 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 MIC passed as input. MIC 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_16 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;

	magmaDoubleComplex_ptr dA;
	magma_int_t *d_ipiv;

	magmaDoubleComplex *work;
	magmaDoubleComplex c_one     = MAGMA_Z_ONE;
	magmaDoubleComplex c_neg_one = MAGMA_Z_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_zgetrf_nb(m);

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

	if ( (nb <= 1) || (nb >= min(m,n)) ) {
		/* Use CPU code. */
		lapackf77_zgetrf(&m, &n, a, &lda, ipiv, info);
	} else {
		/* Use hybrid blocked code. */
		magma_int_t maxm, ldda;
		magma_int_t i, rows, cols, s = min(m, n)/nb;
        
		maxm = ((m + 63)/64)*64;
		maxm = maxm | 64;

		ldda = maxm;

		work = a;
	
		//memory allocate for d_ipiv                                                                     
		if ( MAGMA_SUCCESS != magma_imalloc( &d_ipiv, ldda )) {
			*info = MAGMA_ERR_DEVICE_ALLOC;
			return *info;
		}

		if (MAGMA_SUCCESS != magma_zmalloc( &dA, n*ldda ))
		{
			/* alloc failed so call non-MIC-resident version */ 
			// magma_int_t rval = magma_zgetrf_m(m, n, a, lda, ipiv, info);
			// if( *info >= 0 ) magma_zgetrf_piv(m, n, a, lda, ipiv, info);
			printf("The out of MIC memory code still has to be ported\n");
			return *info;
		}

		magma_zsetmatrix_async( m, n-nb, a + nb*lda, 0, lda, dA, nb*ldda, ldda, queue, &event[0] );
        
		lapackf77_zgetrf( &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_zgetmatrix_async( m-i*nb, nb, inA(i,i), 0, ldda, work, 0, lda, queue, &event[1] );

				magma_zgemm( 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_zgetrf( &rows, &nb, work, &lda, ipiv+i*nb, &iinfo);
			}

			if (*info == 0 && iinfo > 0)
				*info = iinfo + i*nb;

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

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

			if (i!=0)
				magma_zlaswp(i*nb, inA(i, 0), ldda, 1, nb, d_ipiv + i * nb, 1, queue);
			magma_zlaswp(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_kernel_sync();

			// do the small non-parallel computations
			magma_ztrsm( 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_zgemm( 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_zgemm( 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;
			cols = maxm - s*nb;
    
			magma_zgetmatrix( rows, nb0, inA(s,s), 0, ldda, work, 0, lda, queue );
    
			// do the cpu part
			lapackf77_zgetrf( &rows, &nb0, work, &lda, ipiv+s*nb, &iinfo);
			if (*info == 0 && iinfo > 0)
				*info = iinfo + s*nb;

			//transfer ipiv matrix
			magma_zsetintarray_async(m, ipiv, 0, d_ipiv, 0, queue, &event[1]);
			magma_event_sync(event[1]);

			magma_zlaswp(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_zsetmatrix(rows, nb0, work, 0, lda, inA(s, s), 0, ldda, queue);

			magma_ztrsm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit,
					nb0, n-s*nb-nb0,
					c_one, inA(s,s),     0, ldda,
					inA(s,s)+nb0, 0, ldda, queue);
		}
        
		magma_zgetmatrix( m, n, dA, 0, ldda, a, 0, lda, queue );
     
		magma_free(d_ipiv);
		magma_free( dA );
	}
    
	return *info;
} /* magma_zgetrf */

#undef inA
