/*
   -- 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_mic(magma_int_t m, magma_int_t n, 
		magmaDoubleComplex_ptr dA, size_t dA_offset, magma_int_t ldda,
		magma_int_t *ipiv, magma_int_t *info,
		magma_queue_t queue )
{
/*  -- micMAGMA (version 0.1) --
    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.
    
    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 on the GPU, dimension (LDDA,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.

    LDDA   (input) INTEGER
           The leading dimension of the array A.  LDDA >= 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
	         if INFO = -7, internal GPU 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)

	magmaDoubleComplex c_one     = MAGMA_Z_MAKE(  1.0, 0.0 );
	magmaDoubleComplex c_neg_one = MAGMA_Z_MAKE( -1.0, 0.0 );

	magma_int_t iinfo, nb;
	magma_int_t mindim;
	magma_int_t i, rows, s, lddwork;

	magma_int_t *d_ipiv;

	magmaDoubleComplex *work, *next_panel;

	magma_err_t err;

	*info = 0;
	int jj;

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

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

	if (m == 0 || n == 0)
		return MAGMA_SUCCESS;

	mindim = min(m, n);
	//if(mindim >= 24000)
	//	nb = 720;
	//else 
		nb = 480;
	//nb     = 120;//720;//480;//720;//240;//480;//128;//256;//magma_get_zgetrf_nb(m);
	s      = mindim / nb;

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

	if (nb < 1 || nb >= min(m,n)) 
	{
		// use CPU code
		err = magma_zmalloc_pinned( &work, m*n );
		if ( err != MAGMA_SUCCESS ) {
			*info = MAGMA_ERR_HOST_ALLOC;
			return *info;
		}

		chk( magma_zgetmatrix( m, n, dA, dA_offset, ldda, work, 0, m, queue ));
		lapackf77_zgetrf(&m, &n, work, &m, ipiv, info);
		chk( magma_zsetmatrix( m, n, work, 0, m, dA, dA_offset, ldda, queue ));

		magma_free_pinned(work);
	}
	else 
	{
		// use hybrid blocked code
		lddwork = ldda;

		//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_pinned( &work, ldda * nb ) ) {
			*info = MAGMA_ERR_HOST_ALLOC;
			return *info;
		}

		if ( MAGMA_SUCCESS != magma_zmalloc_pinned( &next_panel, ldda * nb ) ) {
			*info = MAGMA_ERR_HOST_ALLOC;
			return *info;
		}

		for(i = 0; i < s; i++)
		{
			magma_kernel_sync();

			if ( i>0 ){
				magma_ztrsmgemm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit, 
						nb, n - (i+1) * nb, 
						c_one, inA(i - 1, i - 1), 0, ldda, 
						inA(i - 1, i+1), 0, ldda,
						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 );
				//donwload look ahead panel
				magma_zgetmatrix_async(m - (i - 1) * nb, nb, inA(i - 1, i), 0, ldda, next_panel, 0, lddwork, queue, &event[0]);
				magma_event_sync(event[0]);
				
				//update of look ahead panel on cpu
				ztrsm_( "L", "L", "N", "U", 
						&nb, &nb, 
						&c_one, work, &lddwork, 
						next_panel, &lddwork);

				rows = m - i * nb;
				zgemm_( "N", "N", 
						&rows, &nb, &nb, 
						&c_neg_one, work + nb, &lddwork,
						next_panel, &lddwork, 
						&c_one,     next_panel + nb, &lddwork );

				//send back look ahead panel to mic
				magma_zsetmatrix_async(m - (i - 1) * nb, nb, next_panel, 0, lddwork, inA(i - 1, i), 0, ldda, queue, &event[1]);
				magma_event_sync(event[1]);
	
			}

			// download i-th panel
			magma_zgetmatrix_async(m - i * nb, nb, inA(i, i), 0, ldda, work, 0, lddwork, queue, &event[0]);
			magma_event_sync(event[0]);

			// do the cpu part
			rows = m - i * nb;
			lapackf77_zgetrf( &rows, &nb, work, &lddwork, ipiv + i * nb, &iinfo);
			if ( (*info == 0) && (iinfo > 0) )
				*info = iinfo + i*nb;

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

			magma_zlaswp(n, inA(i, 0), ldda, 1, nb, d_ipiv + i * nb, 1, queue);

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

			magma_kernel_sync();

			// upload i-th panel
			magma_zsetmatrix_async(m - i * nb, nb, work, 0, lddwork, inA(i, i), 0, ldda, queue, &event[1]);
			magma_event_sync(event[1]);

			// non-parallel computations -- last block
			if(s <= (i + 1)) {
				magma_ztrsmgemm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit, 
						nb, n - s * nb, 
						c_one, inA(i, i), 0, ldda, 
						inA(i, i+1), 0, ldda, 
						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);
		rows = m - s * nb;
		
		if(nb0 > 0) {
			magma_zgetmatrix(rows, nb0, inA(s, s), 0, ldda, work, 0, lddwork, queue);

			// do the cpu part
			lapackf77_zgetrf( &rows, &nb0, work, &lddwork, ipiv+s*nb, &iinfo);
			if ( (*info == 0) && (iinfo > 0) )
				*info = iinfo + s*nb;

			//transfer ipiv matrix
			magma_zsetintarray_async(mindim, 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, lddwork, inA(s, s), 0, ldda, queue);
			if((n - s * nb - nb0) > 0)
				magma_ztrsm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit, 
						nb0, n-s*nb-nb0, 
						c_one, inA(s,s),     0, ldda, 
						inA(s,s) + nb0 * ldda, 0, ldda, queue);
		}

		magma_free(d_ipiv);
		magma_free_pinned( work );
		magma_free_pinned( next_panel );
	}

	magma_queue_sync(queue);

	return *info;
	/* End of MAGMA_ZGETRF_MIC */
}

#undef inA
