/*
    -- MAGMA (version 1.2.1) --
       Univ. of Tennessee, Knoxville
       Univ. of California, Berkeley
       Univ. of Colorado, Denver
       November 2012

       @generated d Fri Nov 22 14:21:13 2013

*/

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


magma_int_t
magma_dgetrf_mmic(magma_int_t num_mics, 
                 magma_int_t m, magma_int_t n, 
                 magmaDouble_ptr *d_lA, magma_int_t ldda,
                 magma_int_t *ipiv, magma_int_t *info, magma_queue_t queue)
{
/*  -- MAGMA (version 1.2.1) --
       Univ. of Tennessee, Knoxville
       Univ. of California, Berkeley
       Univ. of Colorado, Denver
       June 2012

    Purpose
    =======

    DGETRF 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
    =========

    NUM_MICS 
            (input) INTEGER
            The number of MICS to be used for the factorization.

    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) DOUBLE_PRECISION array on the MIC, 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
                  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(id, i, j) (d_lA[(id)] + (j) * nb * ldda + (i) * nb)
	#define tmppanel(i_) (tmppanel + (i_ * nb))
	#define tmpprevpanel(i_) (tmpprevpanel + (i_ * nb))
	
	double c_one     = MAGMA_D_ONE;
	double c_neg_one = MAGMA_D_NEG_ONE;

	magma_int_t err, iinfo, nb, n_local[MagmaMaxDevices];
	magma_int_t mindim;
	magma_int_t i, j, d, rows, s, lddat, lddwork;
	magma_int_t id, i_local, i_local2, nb0, nb1, blkid, crosspoint;
	double *d_lAT[MagmaMaxDevices], *d_lAP[MagmaMaxDevices];
	double *d_panel[MagmaMaxDevices], *work, *tmppanel0, *tmppanel1, *tmppanel, *tmpprevpanel, *panel_local[MagmaMaxDevices];
	magma_int_t *d_ipiv[MagmaMaxDevices];

	magma_event_t event[MagmaMaxDevices][2];

	//Check arguments 
	*info = 0;
	if (m < 0)
		*info = -2;
	else if (n < 0)
		*info = -3;
	else if (ldda < max(1,m))
		*info = -5;

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

	//Quick return if possible
	if (m == 0 || n == 0)
		return *info;

	//Function Body 
	mindim = min(m, n);
	nb     = magma_get_dgetrf_nb(m);

	if (nb <= 1 || nb >= n) {
		//Use CPU code.
		err = magma_dmalloc_pinned( &work, m * n );
		if ( work == NULL ) {
			*info = MAGMA_ERR_HOST_ALLOC;
			return *info;
		}
		magma_dgetmatrix( m, n, d_lA[0], 0, ldda, work, 0, m, queue);
		lapackf77_dgetrf(&m, &n, work, &m, ipiv, info);
		magma_dsetmatrix( m, n, work, 0, m, d_lA[0], 0, ldda, queue);
		magma_free_pinned(work);
	} else {

		//#define ENABLE_TIMER
		
		#if defined (ENABLE_TIMER)
		double ttrsmgemm[4], tmtc, ttrf, tctm, tswap, tmnp, tcnp;
		#endif

                #if defined (ENABLE_TIMER)
                printf("\n\n %10s %10s %10s %10s  %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n", 
                                "i", "nb", "row", "mtc", "CPU_np", "trf", "ctm", "CPU", "trsmgemm0", "trsmgemm1", "trsmgemm2", "trsmgemm3", "tswap", "MIC_np");
                printf("     ========================================================================================================================\n");
                #endif 

		//Use hybrid blocked code.
		if( num_mics > ceil((double)n / nb) ) {
			printf( " * too many MICs for the matrix size, using %d MICs\n", (int) num_mics );
			*info = -1;
			return *info;
		}

		//set crosspoint
		crosspoint = 0;
		//if(num_mics == 4)
		//	crosspoint = n;
		//else if (num_mics == 3)
		//	crosspoint = 0;//14640;
		//else if (num_mics == 2)
		//	crosspoint = 0;
		//else
		//	crosspoint = 0;

		//double tt = magma_wtime();
		//allocate workspace for each MIC
		for(i = 0; i < num_mics; i++){

			magma_setdevice(i);

			//local-n and local-ld
			n_local[i] = (( n / nb) / num_mics) * nb;
			if (i < (n / nb) % num_mics)
				n_local[i] += nb;
			else if (i == (n / nb) % num_mics)
				n_local[i] += n % nb;
			panel_local[i] = NULL;

			//workspaces
			if (MAGMA_SUCCESS != magma_dmalloc( &d_lAP[i], 2 * ldda * nb )) {
				for( j = 0; j < i; j++ ) {
					magma_setdevice(j);
					magma_free( d_lAP[j]   );
					magma_free( d_panel[j] );
				}

				panel_local[i] = inA(i, 0, 0);
				*info = MAGMA_ERR_DEVICE_ALLOC;
				return *info;
			}

			if (MAGMA_SUCCESS != magma_imalloc( &d_ipiv[i], mindim )) {
				for( j = 0; j <= i; j++ ) {
					magma_setdevice(j);
					magma_free( d_lAP[j] );
				}
				for( j = 0; j < i; j++ ) {
					magma_setdevice(j);
					magma_free( d_ipiv[j] );
				}

				*info = MAGMA_ERR_DEVICE_ALLOC;
				return *info;
			}
		}

		//cpu workspace
		lddwork = ldda;
		if (MAGMA_SUCCESS != magma_dmalloc_pinned( &work, 2 * lddwork * nb )) {
			for(i = 0; i < num_mics; i++ ) {
				magma_setdevice(i);
				magma_free( d_lAP[i]   );
			}
			*info = MAGMA_ERR_HOST_ALLOC;
			return *info;
		}

		//tt = magma_wtime() - tt;
		//printf("allocation time: %lf\n", tt);

		tmppanel0 = work;
		tmppanel1 = work + nb * lddwork;
		
		s = mindim / nb;
		blkid = -1;
		
		for( i = 0; i < s; i++ )
		{
			#if defined (ENABLE_TIMER)
			ttrsmgemm[0] = ttrsmgemm[1] = ttrsmgemm[2] = ttrsmgemm[3] = tmtc = ttrf = tctm= tmnp = tcnp = tswap = 0.0;
			#endif

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

			//Set the MIC number that holds the current panel
			id = i % num_mics;
			magma_setdevice(id);
			
			//Set the local index where the current panel is
			i_local = i / num_mics;
			rows  = m - i * nb;

			magma_int_t previ = i - 1;
			
			if(i == 0 || (rows + nb) <= crosspoint) {
				
				#if defined (ENABLE_TIMER)
				tmtc = magma_wtime();
				#endif

				//start sending the panel to cpu
				magma_dgetmatrix_async( rows, nb,
						inA(id, i, i_local), 0, ldda,
						tmppanel(i),      0, lddwork, queue, &event[id][1] );

				//stnchrnoize i-th panel from id-th mic into work
				magma_event_sync(event[id][1] );
			
				#if defined (ENABLE_TIMER)
				tmtc = magma_wtime() - tmtc;
				#endif
			} else {
				//do look ahead panel on CPU
				#if defined (ENABLE_TIMER)
				tcnp = magma_wtime();
				#endif

				tmpprevpanel = ((blkid - 1) % 2) == 0 ? tmppanel0 : tmppanel1;
				//start sending the panel to cpu
				magma_dgetmatrix_async( rows + nb, nb,
						inA(id, i - 1, i_local), 0, ldda,
						tmppanel(previ),      0, lddwork, queue, &event[id][1] );

				//stnchrnoize i-th panel from id-th mic into work
				magma_event_sync(event[id][1] );

				//gemm and trsm
				dtrsm_( "L", "L", "N", "U",
						&nb, &nb,
						&c_one, tmpprevpanel(previ), &lddwork,
						tmppanel(previ), &lddwork);

				dgemm_( "N", "N",
						&rows, &nb, &nb,
						&c_neg_one, tmpprevpanel(i), &lddwork,
						tmppanel(previ), &lddwork,
						&c_one,     tmppanel(i), &lddwork );

				//send upper part
				magma_dsetmatrix_async(nb, nb, tmppanel(previ), 0, lddwork, inA(id, previ, i_local), 0, ldda, queue, &event[id][1]);
				magma_event_sync(event[id][1] );

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

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

			//i-th panel factorization
			lapackf77_dgetrf( &rows, &nb, tmppanel(i), &lddwork, ipiv + i * nb, &iinfo);

			if ( (*info == 0) && (iinfo > 0) ) {
				*info = iinfo + i * nb;
				//break;
			}

			for( d = 0; d < num_mics; d++ ) {
				//storage for panel
				if( d == id ) {
					//the panel belong to this mic 
					panel_local[d] = inA(d, i, i_local);
				} else {
					//the panel belong to another mic
					panel_local[d] = (blkid % 2) == 0 ? d_lAP[d] : d_lAP[d] + ldda * nb;
				}
			}

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

			magma_int_t nmic = 0, n_id = (i + 1) % num_mics;

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

			//transfer pivot array
			for(nmic = 0, d = n_id; nmic < num_mics; nmic++, d = (d + 1) % num_mics) {
				magma_setdevice(d);
				magma_dsetintarray_async(mindim, ipiv, 0, d_ipiv[d], 0, queue, &event[d][1]);
			}

			//start sending the panel to all the mics
			for(nmic = 0, d = n_id; nmic < num_mics; nmic++, d = (d + 1) % num_mics) {
				magma_setdevice(d);
				magma_dsetmatrix_async( rows, nb,
						tmppanel(i),    0, lddwork,
						panel_local[d], 0, ldda, queue, &event[d][0] );
			}

			#if defined (ENABLE_TIMER)
			for(nmic = 0, d = n_id; nmic < num_mics; nmic++, d = (d + 1) % num_mics) {
				magma_setdevice(d);
				magma_event_sync( event[d][0] );
			}
			tctm = magma_wtime() - tctm;
			#endif

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

			for(nmic = 0, d = n_id; nmic < num_mics; nmic++, d = (d + 1) % num_mics) {
				magma_setdevice(d);
				magma_event_sync(event[d][1]);
				if(d == id) {
					magma_dlaswp(i_local * nb, inA(d, i, 0), ldda, 1, nb, d_ipiv[d] + i * nb, 1, queue);
					magma_dlaswp(n_local[d] - (i_local + 1) * nb, inA(d, i, i_local + 1), ldda, 1, nb, d_ipiv[d] + i * nb, 1, queue);

				} else {
					magma_dlaswp(n_local[d], inA(d, i, 0), ldda, 1, nb, d_ipiv[d] + i * nb, 1, queue);
				}
			}

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

			#if defined (ENABLE_TIMER)
			for(nmic = 0, d = n_id; nmic < num_mics; nmic++, d = (d + 1) % num_mics) {
				magma_setdevice(d);
				magma_kernel_sync();
			}
			tswap = magma_wtime() - tswap;
			#endif

			if(s > (i + 1)) {
				if(rows <= crosspoint) {
					//synchronization
					magma_setdevice(n_id);
					magma_event_sync( event[n_id][0] );
					magma_kernel_sync();
					if( n_id == id ) {
						//next column 
						i_local2 = i_local + 1;
					} else {
						//next column
						i_local2 = i_local;
						if( n_id < id ) i_local2++;
					}
					
					#if defined (ENABLE_TIMER)
					tmnp = magma_wtime();
					#endif

					//do the lookahead panel
					magma_dtrsmgemm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit, 
							nb, nb, c_one,
							panel_local[n_id], 0,       ldda,
							inA(n_id, i, i_local2), 0, ldda,
							MagmaNoTrans, MagmaNoTrans, 
							m - (i + 1) * nb, nb, nb, 
							c_neg_one, panel_local[n_id] + nb, 0,         ldda,
							inA(n_id, i, i_local2), 0, ldda, 
							c_one,     inA(n_id, i + 1, i_local2), 0,        ldda, queue );
				
					#if defined (ENABLE_TIMER)
					magma_kernel_sync();
					tmnp = magma_wtime() - tmnp;
					#endif
				}
			}

			for(nmic = 0, d = (n_id + 1) % num_mics; nmic < num_mics; nmic++, d = (d + 1) % num_mics) {
				
				#if defined (ENABLE_TIMER)
				ttrsmgemm[d] = magma_wtime();
				#endif
				
				//storage for panel
				if( d == id ) {
					//next column 
					i_local2 = i_local + 1;
				} else {
					//next column
					i_local2 = i_local;
					if( d < id ) i_local2++;
				}

				//synchronization
				magma_setdevice(d);
				magma_event_sync( event[d][0] );
				magma_kernel_sync();

				if( d == (id + 1) % num_mics) {
					//owns the next column, look-ahead the column
					if(s <= (i + 1)) {
						nb1 = n_local[d] - nb * (s / num_mics);
						if( d < s % num_mics ) nb1 -= nb;
					}
					else {
						nb1 = n_local[d] - i_local2 *nb - nb;
						i_local2 += 1;
					}
				} else {
					//update the entire trailing matrix
					nb1 = n_local[d] - i_local2 * nb;
				}

				//mic updating the trailing matrix
				if(nb1 > 0) {
					magma_dtrsmgemm( MagmaLeft, MagmaLower, MagmaNoTrans, MagmaUnit, 
							nb, nb1, c_one,
							panel_local[d], 0,       ldda,
							inA(d, i, i_local2), 0, ldda,
							MagmaNoTrans, MagmaNoTrans, 
							m - (i + 1) * nb, nb1, nb, 
							c_neg_one, panel_local[d] + nb, 0,         ldda,
							inA(d, i, i_local2), 0, ldda, 
							c_one,     inA(d, i + 1, i_local2), 0,        ldda, queue );

				}


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

			} //end of mic updates

			#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 %10.3lf %10.3lf(%d)\n",
					i, nb, rows, tmtc * 1000, tcnp*1000, ttrf*1000, tctm * 1000,  (tmtc+tcnp+ttrf+tctm)*1000, 
					ttrsmgemm[0] * 1000, ttrsmgemm[1] * 1000, ttrsmgemm[2] * 1000, ttrsmgemm[3] * 1000, tswap * 1000, tmnp * 1000, n_id);
			fflush(0);
			#endif
	
		} //end of for i=1..s 

		//Set the MIC number that holds the last panel
		id = s % num_mics;

		//Set the local index where the last panel is
		i_local = s / num_mics;

		//size of the last diagonal-block
		nb0 = min(m - s * nb, n - s * nb);
		rows = m    - s * nb;

		if( nb0 > 0 ) {
			//send the last panel to cpu
			magma_setdevice(id);
			magma_dgetmatrix( rows, nb0, inA(id, s, i_local), 0, ldda, tmppanel0, 0, lddwork, queue );

			//factor on cpu
			lapackf77_dgetrf( &rows, &nb0, tmppanel0, &lddwork, ipiv + s * nb, &iinfo);
			if ( (*info == 0) && (iinfo > 0) )
				*info = iinfo + s * nb;

			//transfer pivot array
			for(d = 0; d < num_mics; d++) {
				magma_setdevice(d);
				magma_dsetintarray_async(mindim, ipiv, 0, d_ipiv[d], 0, queue, &event[id][1]);
			}

			for(d = 0; d < num_mics; d++) {
				magma_setdevice(d);
				magma_event_sync(event[d][1]);
				magma_dlaswp(n_local[d], inA(d, s, 0), ldda, 1, nb0, d_ipiv[d] + s * nb, 1, queue);
			}

			for (int jj = 0; jj < nb0; jj++) {
				*(ipiv + s * nb + jj) += s * nb;
			}


			magma_setdevice(id);
			magma_kernel_sync();

			magma_dsetmatrix_async( rows, nb0,
					tmppanel0,    0, lddwork,
					inA(id, s, i_local), 0, ldda, queue, &event[id][0] );

			magma_event_sync(event[id][0]);

			//send the factor to mics
			for( d = 0; d < num_mics; d++ ) {
				magma_setdevice(d);
				magma_kernel_sync();
			}
		}

		//clean up
		for( d = 0; d < num_mics; d++ ) {
			magma_setdevice(d);
			magma_free( d_lAP[d]   );
			magma_free( d_ipiv[d] );
		} //end of for d=1,..,num_mics

		magma_free_pinned( work );
	}

	magma_setdevice(0);

	return *info;       
	// End of MAGMA_DGETRF_MMIC 
}

#undef inAT
