/*
   -- 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) * ldda + (i))
	#define dwork(i_) (dwork + (i_))
	#define tmppanel(i_) (tmppanel + (i_))
	#define rmpanel(i_) (rmpanel + (i_))
	#define tmpprevpanel(i_) (tmpprevpanel + (i_))

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

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

	magma_int_t *d_ipiv;

	magmaDoubleComplex *work, *dwork, *tmppanel0, *tmppanel1, *tmppanel, *tmpprevpanel, *rmpanel;
	magma_int_t nb_max, nb_min, curr_nb, prev_nb, blkid, next_nb, old_rows, crosspoint;

	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_max = 1440;
	nb_min = 480;
	
	//if(mindim >= 24000)
	//	nb = 720;
	//else 
		nb = nb_min;//480;//960;//480;
	//nb     = 120;//720;//480;//720;//240;//480;//128;//256;//magma_get_zgetrf_nb(m);
	
	s      = mindim / nb_min;
	crosspoint = 5280;

	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 
	{

		//#define ENABLE_TIMER
		double ttrsmgemm, ttrsm, tgemm, tmtc, tipiv, tgetrf, tswap, tctm, tcnp, tmnp, tep;

		// 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( &dwork, (ldda * nb_max) ) ) {
			*info = MAGMA_ERR_DEVICE_ALLOC;
			return *info;
		}

		if ( MAGMA_SUCCESS != magma_zmalloc_pinned( &work, (2 * ldda * nb_max) ) ) {
			*info = MAGMA_ERR_HOST_ALLOC;
			return *info;
		}

		#if defined (ENABLE_TIMER)
		printf("\n\n %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s  %10s\n",
			"i", "curr_nb", "row", "panel", "tipiv", "CPU_np", "CPU", "trsmgemm", "mtswap", "MIC_np", "ctm P", "mtc");
		printf("     ====================================================================================================\n");
		#endif


		tmppanel0 = work;
		tmppanel1 = work + nb_max * ldda;
		
		curr_nb = prev_nb = nb_min;

		blkid = -1;
		next_nb = nb_min;

		for(i = 0; (i + next_nb) < (s * nb); i += curr_nb)
		{
			//magma_kernel_sync();
				
			rows = m - i;
			blkid += 1;

			tmppanel = (blkid % 2) == 0 ? tmppanel0 : tmppanel1;

			#if defined (ENABLE_TIMER)
			ttrsmgemm = tmtc = tgetrf = tswap = tctm = tmnp = tcnp = tipiv = 0.0;
			#endif

			curr_nb = next_nb;

			if((rows - curr_nb) >= 22560)
				next_nb = 1440;
			else if((rows - curr_nb) >= 15360)
				next_nb = 1200;
			else if((rows - curr_nb) >= 12000)
				next_nb = 960;
			else if((rows - curr_nb) >= 7680)
				next_nb = 720;
			else 
				next_nb = 480;

			if ( i>0 ){

				tmpprevpanel = ((blkid - 1) % 2) == 0 ? tmppanel0 : tmppanel1;
				
				#if defined (ENABLE_TIMER)
				ttrsmgemm = get_time();
				#endif

				magma_ztrsmgemm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit, 
						prev_nb, n - i - curr_nb, 
						c_one, dwork, 0, ldda, 
						inA(i - prev_nb, i + curr_nb), 0, ldda, 
						MagmaNoTrans, MagmaNoTrans, 
						m - i, n - i - curr_nb, prev_nb, 
						c_neg_one, dwork + prev_nb, 0, ldda, 
						inA(i - prev_nb,  i + curr_nb), 0, ldda, 
						c_one,     inA(i,  i + curr_nb), 0, ldda, queue );
					
				#if defined (ENABLE_TIMER)
				magma_kernel_sync();
				ttrsmgemm = get_time() - ttrsmgemm;
				#endif


				#if defined (ENABLE_TIMER)
				tcnp = get_time();
				#endif

				magma_zsetmatrix_async(m - i + prev_nb, prev_nb, tmpprevpanel(i - prev_nb), 0, lddwork, inA(i - prev_nb, i - prev_nb), 0, ldda, queue, &event[1]);

				if(blkid > 1 && (i + curr_nb) < (s * nb_min) && old_rows > crosspoint) {
					//donwload look ahead panel
					magma_zgetmatrix_async(m - i + prev_nb, curr_nb, inA(i - prev_nb, i), 0, ldda, tmppanel(i - prev_nb), 0, lddwork, queue, &event[0]);
					magma_event_sync(event[0]);

					rows = m - i;
					ztrsm_( "L", "L", "N", "U",
							&prev_nb, &curr_nb,
							&c_one, tmpprevpanel(i - prev_nb), &lddwork,
							tmppanel(i - prev_nb), &lddwork);

					zgemm_( "N", "N",
							&rows, &curr_nb, &prev_nb,
							&c_neg_one, tmpprevpanel(i), &lddwork,
							tmppanel(i - prev_nb), &lddwork,
							&c_one,     tmppanel(i), &lddwork );

					magma_zsetmatrix_async(prev_nb, curr_nb, tmppanel(i - prev_nb), 0, lddwork, inA(i - prev_nb, i), 0, ldda, queue, &event[1]);

				}

				magma_event_sync(event[1]);

				#if defined (ENABLE_TIMER)
				tcnp = get_time() - tcnp;
				#endif
			}
			
			//if(i == 0) {
			if(blkid < 2 || old_rows <= crosspoint) {
				#if defined (ENABLE_TIMER)
				tmtc = get_time();
				#endif

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

				#if defined (ENABLE_TIMER)
				tmtc = get_time() - tmtc;
				#endif
			}

			#if defined (ENABLE_TIMER)
			tgetrf = get_time();
			#endif

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

			#if defined (ENABLE_TIMER)
			tgetrf = get_time() - tgetrf;
			#endif
				
			#if defined (ENABLE_TIMER)
			tipiv = get_time();
			#endif

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

			#if defined (ENABLE_TIMER)
			tipiv = get_time() - tipiv;
			#endif

			magma_kernel_sync();

			
			#if defined (ENABLE_TIMER)
			tswap = get_time();
			#endif
			
			magma_zlaswp(n, inA(i, 0), ldda, 1, curr_nb, d_ipiv + i, 1, queue);

			#if defined (ENABLE_TIMER)
			magma_kernel_sync();
			tswap = get_time() - tswap;
			#endif

			#if defined (ENABLE_TIMER)
			tctm = get_time();
			#endif

			// upload i-th panel
			if((i + curr_nb + next_nb) < (s * nb_min)) {
				magma_zsetmatrix_async(m - i, curr_nb, tmppanel(i), 0, lddwork, dwork, 0, ldda, queue, &event[1]);
			}

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

			magma_event_sync(event[1]);
			magma_kernel_sync();

			#if defined (ENABLE_TIMER)
			tctm = get_time() - tctm;
			#endif
			
			//mic will do just first look ahead update update
			if((i + curr_nb + next_nb) < (s * nb_min)) {
				if(blkid < 1 || rows <= crosspoint) {
					#if defined (ENABLE_TIMER)
					tmnp = get_time();
					#endif

					magma_ztrsmgemm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit, 
						curr_nb, next_nb, 
						c_one, dwork, 0, ldda, 
						inA(i, i + curr_nb), 0, ldda, 
						MagmaNoTrans, MagmaNoTrans, 
						m - i - curr_nb, next_nb, curr_nb, 
						c_neg_one, dwork + curr_nb, 0, ldda, 
						inA(i,  i + curr_nb), 0, ldda, 
						c_one,     inA(i + curr_nb,  i + curr_nb), 0, ldda, queue );
					
					magma_kernel_sync();

					#if defined (ENABLE_TIMER)
					tmnp = get_time() - tmnp;
					#endif
				}
			} else {
				// upload i-th panel
				magma_zsetmatrix_async(m - i, curr_nb, tmppanel(i), 0, lddwork, inA(i, i), 0, ldda, queue, &event[1]);
				magma_event_sync(event[1]);

				// non-parallel computations -- last block
				magma_ztrsmgemm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit, 
						curr_nb, n - (i + curr_nb), 
						c_one, inA(i, i), 0, ldda, 
						inA(i, i+curr_nb), 0, ldda, 
						MagmaNoTrans, MagmaNoTrans, 
						m - (i + curr_nb), n - (i + curr_nb), curr_nb,
						c_neg_one, inA(i + curr_nb,   i), 0, ldda,
						inA(i, i+curr_nb  ), 0, ldda, 
						c_one,     inA(i+curr_nb, i+curr_nb), 0, ldda, queue );
		
			}

			prev_nb = curr_nb;
			old_rows = rows;

			#if defined (ENABLE_TIMER)
			printf("%10d %10d %10d %10.3lf %10.3lf %10.3lf %10.3lf %10.3lf %10.3lf %10.3lf %10.3lf %10.3lf\n",
					i, curr_nb, rows, tgetrf * 1000, tipiv*1000, tcnp * 1000, (tgetrf + tipiv + tcnp)*1000, ttrsmgemm * 1000, 
					tswap * 1000, tmnp * 1000, tctm * 1000, tmtc * 1000);
			#endif

		}

		magma_int_t nb0 = min(m - i, n - i);
		rows = m - i;
		
		if(nb0 > 0) {
			magma_zgetmatrix(rows, nb0, inA(i, i), 0, ldda, tmppanel0, 0, lddwork, queue);

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

			//transfer ipiv matrix
			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, nb0, d_ipiv + i, 1, queue);
			magma_kernel_sync();
			for (jj = 0; jj < nb0; jj++) {
				*(ipiv + i + jj) += i;//s*nb;
			}

			// upload i-th panel
			magma_zsetmatrix(rows, nb0, tmppanel0, 0, lddwork, inA(i, i), 0, ldda, queue);
			if((n - i - nb0) > 0)
				magma_ztrsm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit, 
						nb0, n-i-nb0, 
						c_one, inA(i,i),     0, ldda, 
						inA(i,i) + nb0 * ldda, 0, ldda, queue);
		}

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

	magma_queue_sync(queue);

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

#undef inA
