/*
   -- 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"

magma_err_t
magma_cgetrf(magma_int_t m, magma_int_t n, 
		magmaFloatComplex_ptr A, magma_int_t lda,
		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
    =======
    CGETRF 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 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 indA(i,j) (dA + (j) * ldda + (i))
	#define inA(i,j) (A + (j) * lda + (i))

	magmaFloatComplex c_one     = MAGMA_C_MAKE(  1.0, 0.0 );
	magmaFloatComplex c_neg_one = MAGMA_C_MAKE( -1.0, 0.0 );

	magma_int_t i, iinfo, mindim, cols, rows, s, ldda, maxm, one = 1;

	magma_int_t *d_ipiv;

	magmaFloatComplex *dA;
	magma_int_t nb_min, curr_nb, prev_nb, next_nb, crosspoint;

	magma_err_t err;

	*info = 0;
	int jj;

	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;
	}

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

	mindim = min(m, n);
	nb_min = 480;
	
	if(mindim >= 24000)
		curr_nb = 1200;
	else if(mindim >= 10560)
		curr_nb = 960;
	else if(mindim >= 4800)
		curr_nb = 720;
	else 
		curr_nb = 480;
	//nb     = magma_get_cgetrf_nb(m);
	if(2 * curr_nb >= min(m,n)) 
	{
		// use CPU code
		lapackf77_cgetrf(&m, &n, A, &lda, ipiv, info);
	}
	else 
	{
		// use hybrid blocked code
		s = mindim / nb_min;

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

		maxm = ((m + 63)/64)*64;
		maxm = maxm | 64;
		ldda = maxm;

		//#define ENABLE_TIMER
		float talloc, tsend, tpanel, ttrsmgemm, tmtc, tipiv, tgetrf, tmswap, tcswap, tctm, tcnp, tmnp;
		#if defined (ENABLE_TIMER)
		talloc = magma_wtime();
		#endif

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

		if ( MAGMA_SUCCESS != magma_cmalloc( &dA, (ldda * n) ) ) {
			*info = MAGMA_ERR_DEVICE_ALLOC;
			printf("The out of MIC memory code still has to be ported\n");
			return *info;
		}

		#if defined (ENABLE_TIMER)
		talloc = magma_wtime() - talloc;
		#endif

		#if defined (ENABLE_TIMER)
		tsend = magma_wtime();
		#endif

		magma_csetmatrix_async( m, n - curr_nb, A + curr_nb * lda, 0, lda, dA + curr_nb * ldda,0, ldda, queue, &event[0] );
		magma_event_sync(event[0]);

		#if defined (ENABLE_TIMER)
		magma_event_sync(event[0]);
		tsend = magma_wtime() - tsend;
		#endif
		
		#if defined (ENABLE_TIMER)
		printf("\nAllocation time: %f ms, sending time: %f ms\n", talloc * 1000, tsend * 1000);
		printf("\n\n %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n",
			"i", "curr_nb", "row", "tipiv", "tctm", "tmswap", "tcswap", "tmnp", "ttrsmgemm", "CPU", "tmtc", "tcnp");
		printf("     ====================================================================================================\n");
		#endif
		prev_nb = next_nb = curr_nb;
		rows = m;

		for(i = 0; (i + next_nb) < (s * nb_min); i += curr_nb)
		{
			#if defined (ENABLE_TIMER)
			ttrsmgemm = tmtc = tgetrf = tmswap = tcswap = tctm = tmnp = tcnp = tipiv = 0.0;
			#endif

			prev_nb = curr_nb;
			curr_nb = next_nb;

			if((rows - curr_nb) >= 13920)
				next_nb = 960;
			else if((rows - curr_nb) >= 8400)
				next_nb = 720;
			else 
				next_nb = 480;

			if(i > 0) {
				#if defined (ENABLE_TIMER)
				ttrsmgemm = magma_wtime();
				#endif

				magma_kernel_sync();
				magma_ctrsmgemm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit, 
						prev_nb, n - i - curr_nb, 
						c_one, indA(i - prev_nb, i - prev_nb), 0, ldda, 
						indA(i - prev_nb, i + curr_nb), 0, ldda, 
						MagmaNoTrans, MagmaNoTrans, 
						m - i, n - i - curr_nb, prev_nb, 
						c_neg_one, indA(i, i - prev_nb), 0, ldda, 
						indA(i - prev_nb,  i + curr_nb), 0, ldda, 
						c_one,     indA(i,  i + curr_nb), 0, ldda, queue );


				#if defined (ENABLE_TIMER)
				magma_kernel_sync();
				ttrsmgemm = magma_wtime() - ttrsmgemm;
				#endif

				#if defined (ENABLE_TIMER)
				tmtc = magma_wtime();
				#endif

				//donwload look ahead panel
				magma_cgetmatrix_async(m, curr_nb, indA(0, i), 0, ldda, inA(0, i), 0, lda, queue, &event[0]);
				//magma_cgetmatrix_async(m - i + prev_nb, curr_nb, indA(i - prev_nb, i), 0, ldda, inA(i - prev_nb, i), 0, lda, queue, &event[0]);
				magma_event_sync(event[0]);

				#if defined (ENABLE_TIMER)
				tmtc = magma_wtime() - tmtc;
				#endif

				rows = m - i;
				if((rows + prev_nb) > crosspoint) {
					#if defined (ENABLE_TIMER)
					tcnp = magma_wtime();
					#endif

					ctrsm_( "L", "L", "N", "U",
							&prev_nb, &curr_nb,
							&c_one, inA(i - prev_nb, i - prev_nb), &lda,
							inA(i - prev_nb, i), &lda);

					cgemm_( "N", "N",
							&rows, &curr_nb, &prev_nb,
							&c_neg_one, inA(i, i - prev_nb), &lda,
							inA(i - prev_nb, i), &lda,
							&c_one,     inA(i, i), &lda);

					#if defined (ENABLE_TIMER)
					tcnp = magma_wtime() - tcnp;
					#endif
				}
			}

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

			// do the cpu part
			lapackf77_cgetrf( &rows, &curr_nb, inA(i, i), &lda, ipiv + i, &iinfo);

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

			#if defined (ENABLE_TIMER)
			tgetrf = magma_wtime() - tgetrf;
			#endif


			#if defined (ENABLE_TIMER)
			tipiv = magma_wtime();
			#endif

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


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

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

			// upload panel
			magma_csetmatrix_async(m - i, curr_nb, inA(i, i), 0, lda, indA(i, i), 0, ldda, queue, &event[1]);

			#if defined (ENABLE_TIMER)
			magma_event_sync(event[1]);
			tctm = magma_wtime() - tctm;
			#endif
			
			#if defined (ENABLE_TIMER)
			tmswap = magma_wtime();
			#endif
		
			magma_claswp(n - i - curr_nb, indA(i, i + curr_nb), ldda, 1, curr_nb, d_ipiv + i, 1, queue);
			#if defined (ENABLE_TIMER)
			magma_kernel_sync();
			tmswap = magma_wtime() - tmswap;
			#endif


			#if defined (ENABLE_TIMER)
			tcswap = magma_wtime();
			#endif

			claswp_(&i, inA(i, 0), &lda, &one, &curr_nb, ipiv + i, &one);

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

			magma_event_sync(event[1]);

			#if defined (ENABLE_TIMER)
			tcswap = magma_wtime() - tcswap;
			#endif

			//mic will do look ahead update update
			if((i + curr_nb + next_nb) < (s * nb_min)) {
				if(rows <= crosspoint) {
					#if defined (ENABLE_TIMER)
					tmnp = magma_wtime();
					#endif

					magma_ctrsmgemm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit, 
							curr_nb, next_nb, 
							c_one, indA(i, i), 0, ldda, 
							indA(i, i + curr_nb), 0, ldda, 
							MagmaNoTrans, MagmaNoTrans, 
							m - i - curr_nb, next_nb, curr_nb, 
							c_neg_one, indA(i + curr_nb, i), 0, ldda, 
							indA(i,  i + curr_nb), 0, ldda, 
							c_one,     indA(i + curr_nb,  i + curr_nb), 0, ldda, queue );

					magma_kernel_sync();

					#if defined (ENABLE_TIMER)
					tmnp = magma_wtime() - tmnp;
					#endif
				}
			} else {
				// non-parallel computations -- last block
				magma_ctrsmgemm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit, 
						curr_nb, n - (i + curr_nb), 
						c_one, indA(i, i), 0, ldda, 
						indA(i, i+curr_nb), 0, ldda, 
						MagmaNoTrans, MagmaNoTrans, 
						m - (i + curr_nb), n - (i + curr_nb), curr_nb,
						c_neg_one, indA(i + curr_nb,   i), 0, ldda,
						indA(i, i+curr_nb  ), 0, ldda, 
						c_one,     indA(i+curr_nb, i+curr_nb), 0, ldda, queue );
				magma_kernel_sync();
		
			}

			//copy the data
			//if(n - i - curr_nb > 0) {
			//	magma_kernel_sync();
			//	#if defined (ENABLE_TIMER)
			//	float t = magma_wtime();
			//	#endif
			//	magma_cgetmatrix_async(prev_nb, n - i - curr_nb, indA(i - prev_nb, i + curr_nb), 0, ldda, inA(i - prev_nb, i + curr_nb), 0, lda, queue, &event[0]);
			//	#if defined (ENABLE_TIMER)
			//	magma_event_sync(event[0]);
			//	tmtc += magma_wtime() - t;
			//	#endif
			//}

			#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, tipiv * 1000, tctm*1000, tmswap * 1000, tcswap * 1000, tmnp * 1000, ttrsmgemm * 1000, (tmtc + tcnp + tgetrf + tipiv + tctm)*1000, 
					tmtc * 1000, tcnp * 1000);
			#endif

		}

		magma_event_sync(event[0]);
		magma_int_t nb0 = min(m - i, n - i);
		
		if(nb0 > 0) {
			rows = m - i;
			cols = n - i;
			magma_cgetmatrix(m, n - i, indA(0, i), 0, ldda, inA(0, i), 0, lda, queue);

			// do the cpu part
			lapackf77_cgetrf( &rows, &cols, inA(i, i), &lda, ipiv+i, &iinfo);
			if ( (*info == 0) && (iinfo > 0) )
				*info = iinfo + i;

			cols = i;
			claswp_(&i, inA(i, 0), &lda, &one, &rows, ipiv + i, &one);
			for (jj = 0; jj < nb0; jj++) {
				*(ipiv + i + jj) += i;
			}
		}

		magma_free(d_ipiv);
		magma_free( dA);
	}

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

#undef inA
