/*
   -- 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 inAT(i,j) (dAT + dAT_offset + (i)*nb*lddat + (j)*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 maxm, maxn, mindim;
	magma_int_t i, rows, cols, s, lddat, lddwork;

	magma_int_t *d_ipiv;

	magmaDoubleComplex_ptr dAT, dAP;
	magmaDoubleComplex *work;

	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);
	nb     = 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 
	{
		size_t dAT_offset;

		// use hybrid blocked code
		maxm = ((m + 63)/64)*64;
		maxn = ((n + 63)/64)*64;
		maxm = maxm | 64;
		maxn = maxn | 64;

		lddat   = maxn;
		lddwork = maxm;

		dAT = dA;

		if ( MAGMA_SUCCESS != magma_zmalloc( &dAP, nb*maxm )) {
			*info = MAGMA_ERR_DEVICE_ALLOC;
			return *info;
		}

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

		if (0 && (m == n) && (m % 32 == 0) && (ldda%32 == 0))
		{
			printf("you should not see this\n");
			dAT_offset = dA_offset;
			magma_zinplace_transpose( dAT, dAT_offset, ldda, lddat, queue );
		}
		else 
		{
			dAT_offset = 0;
			if ( MAGMA_SUCCESS != magma_zmalloc( &dAT, maxm*maxn )) {
				magma_free( dAP );
				*info = MAGMA_ERR_DEVICE_ALLOC;
				return *info;
			}

			magma_ztranspose2( dAT, dAT_offset, lddat, dA, dA_offset,  ldda, m, n, queue );
			magma_kernel_sync();
		}

		if ( MAGMA_SUCCESS != magma_zmalloc_pinned( &work, maxm*nb ) ) {
			magma_free( dAP );
			if (! ((m == n) && (m % 32 == 0) && (ldda%32 == 0)) )
				magma_free( dAT );

			*info = MAGMA_ERR_HOST_ALLOC;
			return *info;
		}

		for( i=0; i<s; i++ )
		{
			// download i-th panel
			cols = maxm - i*nb;
			//magma_ztranspose( dAP, 0, cols, inAT(i,i), 0, lddat, nb, cols, queue );

			//magma_kernel_sync();

			if ( i>0 ){
				//magma_ztrsm( MagmaRight, MagmaUpper, MagmaNoTrans, MagmaUnit, 
				//		n - (i+1)*nb, nb, 
				//		c_one, inAT(i-1,i-1), 0, lddat, 
				//		inAT(i-1,i+1), 0, lddat, queue );
				magma_zgemm( MagmaNoTrans, MagmaNoTrans, 
						n-(i+1)*nb, m-i*nb, nb, 
						c_neg_one, inAT(i-1,i+1), 0, lddat, 
						inAT(i,  i-1), 0, lddat, 
						c_one,     inAT(i,  i+1), 0, lddat, queue );
			}

			//magma_zgetmatrix_async(m-i*nb, nb, dAP, 0, cols, 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;

			//magma_zsetmatrix_async(m-i*nb, nb, work, 0, lddwork, dAP, 0, maxm, queue, &event[1]);
			//magma_event_sync(event[1]);

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

			//magma_kernel_sync();
			//magma_zpermute_long2( dAT, dAT_offset, lddat, d_ipiv, ipiv, nb, i*nb, queue );
			//for (jj = 0; jj < nb; jj++) {
			//	*(ipiv + i*nb + jj) += i*nb;
			//}

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

			//magma_ztranspose(inAT(i,i), 0, lddat, dAP, 0, maxm, cols, nb, queue );
			//magma_kernel_sync();

			// do the small non-parallel computations
			if ( s > (i+1) ) {
				//magma_ztrsm( MagmaRight, MagmaUpper, MagmaNoTrans, MagmaUnit, 
				//		nb, nb, 
				//		c_one, inAT(i, i  ), 0, lddat,
				//		inAT(i, i+1), 0, lddat, queue);
				//magma_zgemm( MagmaNoTrans, MagmaNoTrans, 
				//		nb, m-(i+1)*nb, nb, 
				//		c_neg_one, inAT(i,   i+1), 0, lddat,
				//		inAT(i+1, i  ), 0, lddat, 
				//		c_one,     inAT(i+1, i+1), 0, lddat, queue );
			}
			else {
				//magma_ztrsm( MagmaRight, MagmaUpper, MagmaNoTrans, MagmaUnit, 
				//		n-s*nb, nb, 
				//		c_one, inAT(i, i  ), 0, lddat,
				//		inAT(i, i+1), 0, lddat, queue);
				//magma_zgemm( MagmaNoTrans, MagmaNoTrans, 
				//		n-(i+1)*nb, m-(i+1)*nb, nb,
				//		c_neg_one, inAT(i,   i+1), 0, lddat,
				//		inAT(i+1, i  ), 0, lddat, 
				//		c_one,     inAT(i+1, i+1), 0, lddat, queue );
			}
		}

		magma_int_t nb0 = min(m - s*nb, n - s*nb);
		rows = m - s*nb;
		cols = maxm - s*nb;

		//magma_ztranspose2( dAP, 0, maxm, inAT(s,s), 0, lddat, nb0, rows, queue);
		//magma_kernel_sync();
		//magma_zgetmatrix(rows, nb0, dAP, 0, maxm, 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(lddat, ipiv, 0, d_ipiv, 0, queue, &event[1]);
		//magma_event_sync(event[1]);

		//magma_zpermute_long2( dAT, dAT_offset, lddat, d_ipiv, ipiv, nb0, s*nb, queue );
		//for (jj = 0; jj < nb0; jj++) {
		//	*(ipiv + s*nb + jj) += s*nb;
		//}

		// upload i-th panel
		//magma_zsetmatrix(rows, nb0, work, 0, lddwork, dAP, 0, maxm, queue);
		//magma_ztranspose2( inAT(s,s), 0, lddat, dAP, 0, maxm, rows, nb0, queue );

		//magma_ztrsm( MagmaRight, MagmaUpper, MagmaNoTrans, MagmaUnit, 
		//		n-s*nb-nb0, nb0,
		//		c_one, inAT(s,s),     0, lddat, 
		//		inAT(s,s)+nb0, 0, lddat, queue);

		if (0 && (m == n) && (m % 32 == 0) && (ldda%32 == 0)) {
			printf("you should not see this\n");
			magma_zinplace_transpose( dAT, dAT_offset, lddat, ldda, queue );
		}
		else {
			magma_ztranspose2( dA, dA_offset, ldda, dAT, dAT_offset, lddat, n, m, queue );
			//magma_kernel_sync();
			magma_free( dAT );
		}

		magma_free( dAP );
		//magma_free(d_ipiv);
		magma_free_pinned( work );
	}

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

#undef inAT
