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

 @author Khairul Kabir
 @author Azzam Haidar

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

*/

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

//#define ENABLE_TIMER
magma_err_t
magma_dgeqrf3( magma_int_t m, magma_int_t n,
                double *hA, magma_int_t lda, double *dA, magma_int_t ldda,
                double *tau, double *hwork, 
		magma_int_t lwork, 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
                =======
                DGEQRF computes a QR factorization of a real M-by-N matrix A:
                A = Q * R.

                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.

                dA      (input/output) DOUBLE_PRECISION array on the GPU, dimension (LDDA,N)
                On entry, the M-by-N matrix dA.
                On exit, the elements on and above the diagonal of the array
                contain the min(M,N)-by-N upper trapezoidal matrix R (R is
                upper triangular if m >= n); the elements below the diagonal,
                with the array TAU, represent the orthogonal matrix Q as a
                product of min(m,n) elementary reflectors (see Further
                Details).

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

                TAU     (output) DOUBLE_PRECISION array, dimension (min(M,N))
                The scalar factors of the elementary reflectors (see Further
                Details).

                INFO    (output) INTEGER
                = 0:  successful exit
                < 0:  if INFO = -i, the i-th argument had an illegal value
                if INFO = -9, internal GPU memory allocation failed.

                Further Details
                ===============

                The matrix Q is represented as a product of elementary reflectors

                Q = H(1) H(2) . . . H(k), where k = min(m,n).

                Each H(i) has the form

                H(i) = I - tau * v * v'

                where tau is a real scalar, and v is a real vector with
                v(1:i-1) = 0 and v(i) = 1; v(i+1:m) is stored on exit in A(i+1:m,i),
                and tau in TAU(i).
                =====================================================================    */

        #define dA(a_1, a_2)    (dA + + (a_1) + (a_2)*(ldda))
        #define hA(a_1, a_2)    (hA + (a_1) + (a_2)*(lda))

        double c_one  = MAGMA_D_ONE ;
        double c_zero = MAGMA_D_ZERO;
        double *micwork, *dwork, *dworkvt;
        double  *dT0, *dT1, *dT;
        double  *hqrwork, *hT, *htmpR;

        magma_int_t i, k, lddwork, old_i, rows, old_rows, blkid;
        magma_int_t lddworkvt, crosspoint;
        magma_int_t lhT, lhR;
        magma_int_t dTsiz, dlarfbwsiz, micworksiz;
	magma_int_t curr_nb, next_nb, prev_nb, minnb, nb, numblk;
        magma_event_t event[3] = {NULL, NULL, NULL};                                                            

        *info = 0;
        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;
        }

        k = min(m, n);
        if (k == 0)
                return MAGMA_SUCCESS;

	// PARAMETER TUNING
	minnb = 240;
	if(m > 22560){
		nb = 960;
	} else if(m > 19200){
		nb = 720;
	} else if(m > 10560){
		nb = 480;
	} else 
		nb = 240;

	if(m > 24000){
		curr_nb = 720;
	} else if(m > 10560) {
		curr_nb = 480;
	}else { 
		curr_nb = 240;
	}

	numblk = min(m, n) / minnb;

        if (k > curr_nb) {
		//#define ENABLE_TIMER
		#if defined ENABLE_TIMER
        	double talloc, tsend, trfb, tmtc, tqr, trft, tctmv, tmnp, tcnp, tmtc1;
		#endif

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

		// 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 = 0;//10080;

		// we will have temp R (n*nb)
		lhT         = nb * nb; // size for storing T
		lhR         = nb * nb;
		dTsiz      = 2*nb*nb;
		dlarfbwsiz = n * nb + min(m, n) * nb;  // min(m,n)*nb is for dworkvt used by dlarfb
		micworksiz = dTsiz + dlarfbwsiz;

		if ( MAGMA_SUCCESS != magma_dmalloc( &micwork, micworksiz )) {
		        *info = MAGMA_ERR_DEVICE_ALLOC;
		        return *info;
		}

		if ( MAGMA_SUCCESS != magma_dmalloc_pinned( &hqrwork, (lhT + lhR + nb * n))) {
		        *info = MAGMA_ERR_HOST_ALLOC;
		        return *info;
		}
		// setup pointer
		dT0       = micwork;
		dT1       = dT0   + nb*nb;
		dwork     = dT1   + nb*nb;
		dworkvt   = dwork + n*nb; 
        	hT        = hqrwork + n * nb;
        	htmpR     = hT + lhT;

		lddworkvt = lddwork = n;


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

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

		magma_dsetmatrix_async( m, n - curr_nb, hA(0, curr_nb), 0, lda, dA(0, curr_nb), 0, ldda, queue, &event[2] );

		#if defined (ENABLE_TIMER)
		magma_event_sync(event[2]);
		tsend = magma_wtime() - tsend;
		#endif
		
                #if defined (ENABLE_TIMER)
		printf("allocation time: %lf ms, sending time: %lf ms\n", talloc * 1000, tsend * 1000);
                printf("\n\n %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n", 
                                "i", "curr_ib", "row", "panel", "dlarft", "QR+T", "CPU", "dlarfb", "CPU_np", "MIC_np", "ctm V", "mtc", "mtc1");
                printf("     ====================================================================================================\n");
                #endif 

                blkid    = -1;
                old_i    = 0; 
                old_rows = m;
                next_nb = curr_nb;

                for (i = 0; (i + next_nb) < (numblk * minnb); i += curr_nb) {
			blkid += 1;
                        rows    = m - i;
			prev_nb = curr_nb;
                        curr_nb = next_nb;

			if((rows - curr_nb) > 22560){
                            next_nb = 960;
                        } else if((rows - curr_nb) > 19200){
                            next_nb = 720;
                        } else if((rows - curr_nb) > 10560){
                            next_nb = 480;
                        } else 
                            next_nb = 240;

                        #if defined (ENABLE_TIMER)
                        trfb = tmtc = tqr = trft = tctmv = tmnp = tcnp = 0.0;
                        #endif

                        if(i > 0){
                                // Apply H' to A(i:m,i+2*nb:n) from the left
                                #if defined (ENABLE_TIMER)
                                trfb = magma_wtime();
                                #endif
                                magma_kernel_sync();
                                dT = (blkid-1)%2 == 0 ? dT0 : dT1;
                                magma_dlarfb_gemm( MagmaLeft, MagmaTrans, MagmaForward, MagmaColumnwise,
                                                   m - old_i, n - old_i - curr_nb - prev_nb, prev_nb,
                                                   dA(old_i, old_i), 0, ldda, 
                                                   dT, 0, nb,
                                                   dA(old_i, old_i + curr_nb + prev_nb), 0, ldda, dwork, 0, 
                                                   lddwork, dworkvt, 0, lddworkvt, queue);
                                
				#if defined (ENABLE_TIMER)
                                magma_kernel_sync();
                                trfb = magma_wtime() - trfb;
                                #endif

				//next panel is copied back to CPU
                                #if defined (ENABLE_TIMER)
                                tmtc = magma_wtime();
                                #endif
                                magma_dgetmatrix_async(m, curr_nb, dA(0, i), 0, ldda, hA(0, i), 0, lda, queue, &event[0]);
                                magma_event_sync(event[0]);
                               
                                #if defined (ENABLE_TIMER)
                                tmtc = magma_wtime() - tmtc;
                                #endif

				//do the look ahead panel then factorize it
				if(old_rows > crosspoint) {
                                	#if defined (ENABLE_TIMER)
                                	tcnp = magma_wtime();
                                	#endif

					// CPU do the next panel
					lapackf77_dlarfb( "L", "C", "F", "C", 
							&old_rows, &curr_nb, &prev_nb, 
							hA(old_i, old_i), &lda,
							hT, &nb,
							hA(old_i, old_i + prev_nb), &lda,
							hqrwork, &n);

                                	#if defined (ENABLE_TIMER)
                                	tcnp = magma_wtime() - tcnp;
                                	#endif
					//restore R
					lapackf77_dlacpy("A", &prev_nb, &prev_nb, htmpR, &nb, hA(old_i, old_i), &lda);
				}

			}

                        #if defined (ENABLE_TIMER)
                        tqr = magma_wtime();
                        #endif
                        lapackf77_dgeqrf(&rows, &curr_nb, hA(i, i), &lda, tau+i, hwork, &lwork, info);
                        
                        #if defined (ENABLE_TIMER)
                        tqr = magma_wtime() - tqr;
                        #endif
			
			// ======================================================
                        //            SEND V to MIC
                        // ======================================================
                        // store the upper part of V (the R) then put zero 
                        // on the upper part of V then send it to MIC
                        // keep in mind that R need to be send it alsolater when
                        // MIC finish the dlarfb that use the V with zero on upper
                        lapackf77_dlacpy("A", &curr_nb, &curr_nb, hA(i, i), &lda, htmpR, &nb);
                        lapackf77_dlaset("U", &curr_nb, &curr_nb, &c_zero, &c_one, hA(i, i), &lda);
			//sending V
                        magma_dsetmatrix_async(rows, curr_nb, hA(i, i), 0, lda, dA(i, i), 0, ldda, queue, &event[0]);

                        // Form the triangular factor of the block reflector
                        //   H = H(i) H(i+1) . . . H(i+nb-1) 

                        #if defined (ENABLE_TIMER)
                        trft = magma_wtime();
                        #endif 
        
                        memset(hT, 0, nb * nb * sizeof(double));
                        lapackf77_dlarft( MagmaForwardStr, MagmaColumnwiseStr, 
                                        &rows, &curr_nb, 
                                        hA(i, i), &lda, tau+i, hT, &nb);

                        #if defined (ENABLE_TIMER)
                        trft = magma_wtime() - trft;
                        tctmv = magma_wtime();
                        #endif
		
			//sync sending first whole matrix	
			if(i == 0)
				magma_event_sync(event[2]);

                        // ======================================================
                        //            SEND T to MIC
                        // ======================================================
                        dT = (blkid%2 == 0) ? dT0 : dT1;
                        magma_dsetmatrix_async(curr_nb, curr_nb, hT, 0, nb, dT, 0, nb, queue, &event[0]);
                        magma_event_sync(event[0]);

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

                        // CPU UPDATE NEXT PANEL if rows > crosspoint 
                        // rows > crosspoint:   CPU do next panel then send R
                        // rows <= crosspoint: MIC do next panel

                        if ((i + curr_nb + next_nb) < (numblk * minnb)) {
                                // Apply H' to A(i:m,i+nb:i+2*nb) from the left: next panel
                                if(rows <= crosspoint){
                                        #if defined (ENABLE_TIMER)
                                        tmnp = magma_wtime();
                                        #endif
                                        // MIC is WORKING on next panel 
                                        magma_dlarfb_gemm( MagmaLeft, MagmaTrans, MagmaForward, MagmaColumnwise,
                                                           rows, next_nb, curr_nb, 
                                                           dA(i, i), 0, ldda, dT, 0,  nb, 
                                                           dA(i, i+curr_nb), 0, ldda, dwork, 0, lddwork, dworkvt, 0, lddworkvt, queue);

                                        magma_kernel_sync();

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

					//restore R
					lapackf77_dlacpy("A", &curr_nb, &curr_nb, htmpR, &nb, hA(i, i), &lda);
                                }
                        }
                        else {
                                //magma_event_sync(event[0]);
                                //Apply H' form left: last step in the loop, apply H' to the last corner (last two block) of the
                                //matrix because this loop is finished and then do not go through the main dlafrb
                                //defined on the top of the loop. Note that the last two block is done on CPU -A  
                                magma_dlarfb_gemm( MagmaLeft, MagmaTrans, MagmaForward, MagmaColumnwise,
                                                rows, n-i-curr_nb, curr_nb, 
                                                dA(i, i), 0, ldda, dT, 0,  nb, 
                                                dA(i, i+curr_nb), 0, ldda, dwork, 0, lddwork, dworkvt, 0, lddworkvt, queue);
				//restore R
				lapackf77_dlacpy("A", &curr_nb, &curr_nb, htmpR, &nb, hA(i, i), &lda);
				magma_kernel_sync();
                        }

                        old_i  = i;
                        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 %10.3lf\n",
                                               i, curr_nb, rows, tqr * 1000, trft*1000, (trft+tqr)*1000, (tmtc + tcnp + tqr + trft + tctmv)*1000,  
                                        trfb * 1000, tcnp * 1000, tmnp * 1000, tctmv * 1000, tmtc * 1000, tmtc1 * 1000);
                        #endif
                }
        } else {
                i = 0;
        }

        /* Use unblocked code to factor the last or only block. */
        if (i < k) {
                rows      = m - i;
		curr_nb   = n - i;
		if(i != 0) {
			magma_dgetmatrix(m, curr_nb, dA(0, i), 0, ldda, hA(0, i), 0, lda, queue);
			//free memory
			magma_free(micwork);
			magma_free_pinned(hqrwork);
		}
                lapackf77_dgeqrf(&rows, &curr_nb, hA(i, i), &lda, tau+i, hwork, &lwork, info);
        }

        return *info;
} /* magma_dgeqrf*/
