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

       @precisions normal z -> s d c

*/
#include "common_magma.h"


extern "C" magma_int_t
magma_zpotrf_mmic(int num_mics, magma_uplo_t uplo, magma_int_t n, 
                 magmaDoubleComplex **d_lA, magma_int_t ldda, 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   
    =======   
    ZPOTRF computes the Cholesky factorization of a complex Hermitian   
    positive definite matrix dA.   

    The factorization has the form   
       dA = U**H * U,  if UPLO = 'U', or   
       dA = L  * L**H,  if UPLO = 'L',   
    where U is an upper triangular matrix and L is lower triangular.   

    This is the block version of the algorithm, calling Level 3 BLAS.   

    Arguments   
    =========   
    UPLO    (input) CHARACTER*1   
            = 'U':  Upper triangle of dA is stored;   
            = 'L':  Lower triangle of dA is stored.   

    N       (input) INTEGER   
            The order of the matrix dA.  N >= 0.   

    dA      (input/output) COMPLEX_16 array on the mic, dimension (LDDA,N)   
            On entry, the Hermitian matrix dA.  If UPLO = 'U', the leading   
            N-by-N upper triangular part of dA contains the upper   
            triangular part of the matrix dA, and the strictly lower   
            triangular part of dA is not referenced.  If UPLO = 'L', the   
            leading N-by-N lower triangular part of dA contains the lower   
            triangular part of the matrix dA, and the strictly upper   
            triangular part of dA is not referenced.   

            On exit, if INFO = 0, the factor U or L from the Cholesky   
            factorization dA = U**H * U or dA = L * L**H.   

    LDDA     (input) INTEGER   
            The leading dimension of the array dA.  LDDA >= max(1,N).
            To benefit from coalescent memory accesses LDDA must be
            dividable by 16.

    INFO    (output) INTEGER   
            = 0:  successful exit   
            < 0:  if INFO = -i, the i-th argument had an illegal value   
            > 0:  if INFO = i, the leading minor of order i is not   
                  positive definite, and the factorization could not be   
                  completed.   
    =====================================================================   */

	#define dlA(id, i, j)  (d_lA[id] + (j) * ldda + (i))
	#define dlP(id, i, j)  (d_lP[id] + (j) * ldda + (i))

	#define panel(j)  (panel + (j))
	#define tmppanel(j)  (tmppanel + (j))
	#define tmpprevpanel(j)  (tmpprevpanel + (j))

	magma_int_t     j, nb, lnb, d, id, j_local, j_local2, blkid, crosspoint, prevtrsmrows;
	magmaDoubleComplex c_one     = MAGMA_Z_ONE;
        magmaDoubleComplex  z_one = MAGMA_Z_MAKE(  1.0, 0.0 );
        magmaDoubleComplex  mz_one = MAGMA_Z_MAKE( -1.0, 0.0 );
	magmaDoubleComplex c_neg_one = MAGMA_Z_NEG_ONE;
	magmaDoubleComplex *panel, *tmppanel0, *tmppanel1, *tmppanel, *tmpprevpanel;
	double          d_one     =  1.0;
	double          d_neg_one = -1.0;
	magmaDoubleComplex *d_lP[MagmaMaxDevices], *dlpanel;
	magma_int_t rows, trsmrows, myrows, n_id, nmic, n_local[MagmaMaxDevices], lddat_local[MagmaMaxDevices], lddat, ldpanel;
	magma_event_t event[MagmaMaxDevices][4];

        double    one =  1.0;
        double  m_one = -1.0;

	double tt;

	*info = 0;
	if ( uplo != MagmaUpper && uplo != MagmaLower ) {
		*info = -1;
	} else if (n < 0) {
		*info = -2;
	} else if (ldda < max(1,n)) {
		*info = -4;
	}
	if (*info != 0) {
		magma_xerbla( __func__, -(*info) );
		return *info;
	}

	nb = magma_get_zpotrf_nb(n);

	lnb = 480;
	ldpanel = ldda;
	if (MAGMA_SUCCESS != magma_zmalloc_pinned( &panel, 2 * lnb * ldpanel )) {
		*info = MAGMA_ERR_HOST_ALLOC;
		return *info;
	}

	tmppanel0 = panel;
	tmppanel1 = tmppanel0 + lnb * ldpanel;

	if ((nb <= 1) || (nb >= n)) {
		//Use unblocked code.
		magma_setdevice(0);
		magma_zgetmatrix( n, n, dlA(0, 0, 0), 0, ldda, panel, 0, ldpanel, queue);
		lapackf77_zpotrf( lapack_const(uplo), &n, panel, &ldpanel, info);
		magma_zsetmatrix( n, n, panel, 0, ldpanel, dlA(0, 0, 0), 0, ldda, queue );
	} else {
		//double tt = magma_wtime();

		for( d = 0; d < num_mics; d++ ) {
			magma_setdevice(d);

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

			if (MAGMA_SUCCESS != magma_zmalloc( &d_lP[d], 2 * lnb * ldda )) {
				for( j = 0; j < d; j++ ) {
					magma_setdevice(j);
					magma_free( d_lP[d] );
				}
				*info = MAGMA_ERR_DEVICE_ALLOC;
				return *info;
			}

			for( j = 0; j < 4; j++ ) {
				event[d][j] = NULL;
			}
		}

		//tt = magma_wtime() - tt;
		//printf("%d	%lf\n", n, tt);

		//#define ENABLE_TIMER
		#if defined (ENABLE_TIMER)
		double therk[4], tmtc, tcchol, tctrsm, tctm, tmnp, tcnp;
		printf("\n\n %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n", 
				"j", "nb", "row", "mtc", "CPU_np", "panel", "ctrsm", "CH+TRSM", "CPU", "dsyrk[0]", "dsyrk[1]", "dsyrk[2]", "dsyrk[3]", "ctm P", "MIC_np");
		printf("     ====================================================================================================\n");
		#endif 

		//Use blocked code.
		if (uplo == MagmaUpper) 
		{    
		} else {

			blkid = -1;
			crosspoint = 0;//n; //n -- > mic always does next panel, 0 --> cpu always does next panel

			if(num_mics == 2)
				crosspoint = 10560;
			else if(num_mics == 3)
				crosspoint = 16320;
			else
				crosspoint = 0;

			// Compute the Cholesky factorization A = L*L' 
			for (j = 0; (j + lnb) < n; j += lnb) {

				#if defined (ENABLE_TIMER)
				therk[0] = therk[1] = therk[2] = therk[3] = tmtc = tcchol = tctrsm = tctm = tmnp = tcnp = 0.0;
				#endif

				blkid += 1;
				tmppanel = (blkid % 2 == 0) ? tmppanel0 : tmppanel1;
				rows = n - j;
				
				//Set the local index where the current panel is
				j_local = j / (nb * num_mics) * nb + j % nb;
				// Set the mic number that holds the current panel 
				id = (j / nb) % num_mics;
				magma_setdevice(id);

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

				//start sending the panel to cpu
				magma_zgetmatrix_async( rows, lnb,
						dlA(id, j, j_local), 0, ldda,
						tmppanel(j), 0, ldpanel, queue, &event[id][0] );

				magma_event_sync( event[id][0] );

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

				if(prevtrsmrows > crosspoint) {
					#if defined (ENABLE_TIMER)
					tcnp = magma_wtime();
					#endif

					tmpprevpanel = ((blkid - 1) % 2) == 0 ? tmppanel0 : tmppanel1;

					blasf77_zgemm( MagmaNoTransStr, MagmaConjTransStr,
							&rows, &lnb, &lnb,
							&mz_one, tmpprevpanel(j), &ldpanel,
							tmpprevpanel(j), &ldpanel,
							&z_one,     tmppanel(j), &ldpanel );

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

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

				lapackf77_zpotrf(MagmaLowerStr, &lnb, tmppanel(j), &ldpanel, info);
				if (*info != 0) {
					*info = *info + j;
					break;
				}

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

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

				trsmrows = rows - lnb;

				if(trsmrows > 0) {
					blasf77_ztrsm(MagmaRightStr, MagmaLowerStr, MagmaConjTransStr, MagmaNonUnitStr, &trsmrows, &lnb, &z_one, tmppanel(j), &ldpanel, tmppanel(j + lnb), &ldpanel);
				}

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

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

				magma_int_t la_id;
				//send the diagonal to mics
				la_id = ((j + lnb) % nb == 0) ? (id + 1) % num_mics : id;
				d = la_id;

				for(nmic = 0; nmic < num_mics; nmic++, d = (d + 1) % num_mics ) {
					magma_setdevice(d);
					if( d == id ) {
						dlpanel = dlA(d, j, j_local);
					} else {
						dlpanel = (blkid % 2 == 0) ? dlP(d, 0, 0) : dlP(d, 0, 0) + lnb * ldda;
					}
						
					magma_zsetmatrix_async(rows, lnb,
							tmppanel(j),    0, ldpanel,
							dlpanel, 0, ldda, queue, &event[d][1] );
				}

                                #if defined (ENABLE_TIMER)
				d = ((j + lnb) % nb == 0) ? (id + 1) % num_mics : id;
				for(nmic = 0; nmic < num_mics; nmic++, d = (d + 1) % num_mics ) {
					magma_setdevice(d);
					magma_event_sync( event[d][1] );
				}

				tctm = magma_wtime() - tctm;
                                #endif

				if ( (j + lnb) < n) {
					magma_int_t offset = 0;
					magma_int_t row_offset = 0;
					//do gemm on look ahead panel
					if(j + lnb + lnb < n) {
						if(trsmrows <= crosspoint) {
                                			#if defined (ENABLE_TIMER)
							tmnp = magma_wtime();
                                			#endif

							magma_setdevice(la_id);
							magma_event_sync( event[la_id][1] );

							magma_int_t j_local2 = (j + lnb) / (nb * num_mics) * nb + (j + lnb) % nb;
							if( la_id == id ) {
								dlpanel = dlA(la_id, j + lnb, j_local);
							} else {
								dlpanel = (blkid % 2 == 0) ? dlP(la_id, 0, 0) + lnb : dlP(la_id, 0, 0) + lnb * ldda + lnb;
							}

							magma_zgemm( MagmaNoTrans, MagmaConjTrans,
									trsmrows, lnb, lnb, 
									m_one, dlpanel, 0, ldda, dlpanel, 0, ldda, 
									one, dlA(la_id, j + lnb, j_local2), 0, ldda, queue);

                                			#if defined (ENABLE_TIMER)
							magma_kernel_sync();
							tmnp = magma_wtime() - tmnp;
                                			#endif
						}
							
						offset = j + lnb + lnb;
						row_offset = lnb + lnb;
					} else {
						offset = j + lnb;
						row_offset = lnb;
					}

					//syrk on multiple mic
					d = (la_id + 1) % num_mics;
					for(nmic = 0; nmic < num_mics; nmic++, d = (d + 1) % num_mics ) {
						magma_setdevice(d);
						magma_event_sync( event[d][1] );
						if(d == la_id) {
							magma_kernel_sync();
						}

						//update the entire trailing matrix
						if( d == id ) {
							dlpanel = dlA(d, j + row_offset, j_local);
						} else {
							dlpanel = (blkid % 2 == 0) ? dlP(d, 0, 0) + row_offset : dlP(d, 0, 0) + lnb * ldda + row_offset;
						}

                                		#if defined (ENABLE_TIMER)
						therk[d] = magma_wtime();
                                		#endif

						if(n - offset > 0) {
							magma_zherk_mmic(MagmaLower, MagmaTrans, n - offset, lnb,
									m_one, dlpanel, ldda, 0,
									one, d_lA[d], ldda,  offset,
									num_mics, nb, d);
						} 

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

				}
			
				prevtrsmrows = trsmrows;

				#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 %10.3lf(%d)\n",
						j, nb, rows, tmtc * 1000, tcnp*1000, tcchol*1000, tctrsm * 1000, (tcchol + tctrsm) * 1000, 
						(tmtc+tcnp+tcchol+tctrsm)*1000, therk[0] * 1000, therk[1] * 1000, therk[2] * 1000, therk[3] * 1000, tctm * 1000,  
						tmnp * 1000, la_id);
				fflush(0);
				#endif
	

			}

			//cholesky for the last block
			if(j < n) {
				rows = n - j;
				id = (j / nb) % num_mics;
				magma_setdevice(id);
				magma_kernel_sync();

				//Set the local index where the current panel is
				j_local = j / (nb * num_mics) * nb + j % nb;
				
				magma_zgetmatrix(rows, rows, dlA(id, j, j_local), 0, ldda, panel(j), 0, ldpanel, queue);
				lapackf77_zpotrf(MagmaLowerStr, &rows, panel(j), &ldpanel, info);
				magma_zsetmatrix(rows, rows, panel(j), 0, ldpanel, dlA(id, j, j_local), 0, ldpanel, queue);
			}

		} // end of else not upper

		//clean up
		for( d = 0; d < num_mics; d++ ) {
			magma_setdevice(d);
			magma_free( d_lP[d] );
		}

	} // end of not lapack

	// free workspace
	magma_free_pinned( panel );

	return *info;
} /* magma_zpotrf_mmic */
