/*
   -- micMAGMA (version 0.1) --
   Univ. of Tennessee, Knoxville
   Univ. of California, Berkeley
   Univ. of Colorado, Denver
   April 2012

 @author Khairul Kabir
 @author Azzam Haidar

 @precisions normal z -> s d c

*/

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

magma_err_t
magma_zpotrf3( magma_uplo_t   uplo, magma_int_t    n,
                  magmaDoubleComplex_ptr hA, magma_int_t lda,
                  magmaDoubleComplex_ptr dA, magma_int_t ldda,
                  magma_err_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   
    =======   
    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) INTEGER
            = MagmaUpper:  Upper triangle of dA is stored;   
            = MagmaLower:  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 GPU, 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 dA(a_1, a_2)    (dA + (a_1) + (a_2)*(ldda))
        #define hA(a_1, a_2)    (hA + (a_1) + (a_2)*(lda))

        magmaDoubleComplex  z_one = MAGMA_Z_MAKE(  1.0, 0.0 );
        magmaDoubleComplex mz_one = MAGMA_Z_MAKE( -1.0, 0.0 );

        double    one =  1.0;
        double  m_one = -1.0;

        magma_int_t i, old_i, rows, old_rows, trsmrows, old_trsmrows;
        magma_int_t crosspoint, blk_i, blk_ib, blk_nb;
	magma_int_t curr_nb, prev_nb, next_nb;
        magma_event_t event[3] = {NULL, NULL, NULL};                                                            

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

	if(n >= 35520)
		curr_nb = 1920;
	else if(n >= 23520)
		curr_nb = 1680;
	else if (n >= 14160) 
		curr_nb = 1200;
	else if(n >= 12240)
		curr_nb = 960;
	else if(n < 3360 || n >= 7680)
		curr_nb = 720;
	else
		curr_nb = 480;

        if (n > curr_nb ) {

		// crosspoint = 0 mean that next panel update is done on CPU all the time
		// crosspoint = N mean that next panel update is done on MIC all the time
		crosspoint = 5760;

        	//#define ENABLE_TIMER
        	#if defined ENABLE_TIMER
		double tsend, tpanel, therk, tmtc, tcchol, tctrsm, tctmp, tmnp, tcnp;
		#endif

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

		//instead of sending whole matrix send block by block
		//magma_zsetmatrix_async( n - curr_nb, n - curr_nb, hA(curr_nb, curr_nb), 0, lda, dA(curr_nb, curr_nb), 0, ldda, queue, &event[0] );
		blk_nb = 480;
		for(blk_i = curr_nb; blk_i < n; blk_i += blk_ib) {
			blk_ib = (blk_nb < (n - blk_i)) ? blk_nb : (n - blk_i);
			magma_zsetmatrix_async( n - blk_i, blk_ib, hA(blk_i, blk_i), 0, lda, dA(blk_i, blk_i), 0, ldda, queue, &event[0] );
		}	

		#if defined (ENABLE_TIMER)
		magma_event_sync(event[0]);
		tsend = magma_wtime() - tsend;
		#endif

                #if defined (ENABLE_TIMER)
		printf("sending time: %lf \n", tsend * 1000);
                printf("\n\n %10s %10s %10s %10s  %10s %10s %10s %10s  %10s  %10s %10s  %10s\n", 
                                "i", "curr_nb", "row", "panel", "ctrsm", "CH+TRSM", "CPU", "dsyrk", "CPU_np", "MIC_np", "ctm P", "mtc");
                printf("     ====================================================================================================\n");
                #endif 

                old_i = 0; old_rows = n;
		next_nb = curr_nb;

                for (i = 0; (i + next_nb) < n; i += curr_nb) {
                
                        #if defined (ENABLE_TIMER)
                        therk = tmtc = tcchol = tctrsm = tctmp = tmnp = tcnp = 0.0;
                        #endif

			prev_nb = curr_nb;
			curr_nb = next_nb;

                        rows  = n - i;
			trsmrows=rows - curr_nb;
			
			if((rows - curr_nb) >= 33600)
				next_nb = 2400;
			else if((rows - curr_nb) >= 29760)
				next_nb = 1920;
			else if((rows - curr_nb) >= 22160)
				next_nb = 1680;
			else if((rows - curr_nb) >= 13440)
				next_nb = 960;
			else if((rows - curr_nb) >= 9120)
				next_nb = 720;
			else
				next_nb = 480;

                        if(i > 0){
                                #if defined (ENABLE_TIMER)
                                therk = magma_wtime();
                                #endif
                                
				magma_kernel_sync();
                                magma_zherk( MagmaLower, MagmaNoTrans,
                                                old_trsmrows - curr_nb, prev_nb,  
                                                m_one, dA(old_i + prev_nb + curr_nb, old_i ), 0, ldda,  
                                                one, dA(old_i + prev_nb + curr_nb, old_i + prev_nb + curr_nb), 0, ldda, queue);

                                #if defined (ENABLE_TIMER)
                                magma_kernel_sync();
                                therk = magma_wtime() - therk;
                                #endif
                        
                                #if defined (ENABLE_TIMER)
				tmtc = magma_wtime();
                                #endif
                               
				magma_zgetmatrix_async(rows, curr_nb, dA(i, i), 0, ldda, hA(i, i), 0, lda, queue, &event[0]);
				magma_event_sync(event[0]);
                               
                                #if defined (ENABLE_TIMER)
                                tmtc = magma_wtime() - tmtc;
                                #endif

				//CPU do the look ahead panel first then factorize it
				if((i + curr_nb) < n && old_rows > crosspoint) {
                        
					#if defined (ENABLE_TIMER)
                        		tcnp = magma_wtime();
                        		#endif

                                	// CPU do the next panel
					blasf77_zgemm( MagmaNoTransStr, MagmaConjTransStr,
							&rows, &curr_nb, &prev_nb, 
							&mz_one, hA(i, i - prev_nb), &lda, hA(i, i - prev_nb), &lda, 
							&z_one, hA(i, i), &lda);

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

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

			lapackf77_zpotrf(MagmaLowerStr, &curr_nb, hA(i , i), &lda, info);

			if ( *info != 0 ) {
				assert( *info > 0 );
				*info += i;
				break;
			}

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

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

			trsmrows=rows - curr_nb;
			if(trsmrows > 0) {        
				blasf77_ztrsm(MagmaRightStr, MagmaLowerStr, MagmaConjTransStr, MagmaNonUnitStr, &trsmrows, &curr_nb, &z_one, hA(i, i), &lda, hA(i+curr_nb, i), &lda);
			}

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

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

			// ======================================================
			//            SEND panel to MIC
			// ======================================================
			magma_zsetmatrix_async(rows, curr_nb, hA(i, i), 0, lda, dA(i, i), 0, ldda, queue, &event[0]);
			magma_event_sync(event[0]);

                        #if defined (ENABLE_TIMER)
                        tctmp = magma_wtime() - tctmp;
                        #endif
                        
                        // CPU UPDATE NEXT PANEL if rows > crosspoint 
                        // rows > crosspoint:   CPU do next panel then send R
                        // rows <= crosspoint: MIC send R then do next panel

                        if ((i + curr_nb + next_nb) < n) {
                                if(rows <= crosspoint){
                                        #if defined (ENABLE_TIMER)
                                        tmnp = magma_wtime();
                                        #endif
                                        // MIC is WORKING on next panel 
                                        magma_zgemm( MagmaNoTrans, MagmaConjTrans,
                                                        trsmrows, next_nb, curr_nb, 
                                                        mz_one, dA(i + curr_nb, i), 0, ldda, dA(i + curr_nb, i), 0, ldda, 
                                                        z_one, dA(i + curr_nb, i + curr_nb), 0, ldda, queue);

                                        magma_kernel_sync();
                                        #if defined (ENABLE_TIMER)
                                        magma_kernel_sync();
                                        tmnp = magma_wtime() - tmnp;
                                        #endif
                                }
                        }
                        else {
                                magma_zherk( MagmaLower, MagmaNoTrans,
                                                trsmrows, curr_nb, 
                                                m_one, dA(i + curr_nb, i   ), 0, ldda, 
                                                one,   dA(i + curr_nb, i + curr_nb), 0, ldda, queue);
                                magma_kernel_sync();
                        }

                        old_i  = i;
                        old_rows = rows;
                        old_trsmrows = 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\n",
                                               i, curr_nb, rows, tcchol * 1000, tctrsm*1000, (tcchol+tctrsm)*1000, (tcchol+tctrsm+tcnp+tmtc+tctmp)*1000,  
                                        therk * 1000, tcnp * 1000, tmnp * 1000, tctmp * 1000, tmtc * 1000);
                        #endif

                }
        } else {
                i = 0;
        }

        /* Use unblocked code to factor the last or only block. */
        if (i < n) {
                rows = n - i;
		if(i != 0) {
			magma_zgetmatrix(rows, rows, dA(i, i), 0, ldda, hA(i, i), 0, lda, queue);
		}
                lapackf77_zpotrf(lapack_const(uplo), &rows, hA(i, i), &lda, info);
		if ( *info != 0 ) {
			assert( *info > 0 );
			*info += i;
		}
        }


        return *info;
} /* magma_zgeqrf2_mic */
