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

//#define ENABLE_TIMER
magma_err_t
magma_zgeqrf2_mic( magma_int_t m, magma_int_t n,
                magmaDoubleComplex_ptr dA, size_t dA_offset, magma_int_t ldda,
                magmaDoubleComplex *tau, 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
                =======
                ZGEQRF computes a QR factorization of a complex 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) COMPLEX_16 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) COMPLEX_16 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 complex scalar, and v is a complex 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, (dA_offset + (a_1) + (a_2)*(ldda))
        #define htmpR(i_)       (htmpR + (i_)*(nb)*(nb))
        #define tmppanel(i_)    (tmppanel + (i_))
        #define tmpnextpanel(i_)    (tmpnextpanel + (i_))
        #define tmpprevpanel(i_)    (tmpprevpanel + (i_))

        magmaDoubleComplex c_one  = MAGMA_Z_ONE ;
        magmaDoubleComplex c_zero = MAGMA_Z_ZERO;
        magmaDoubleComplex_ptr micwork, dwork, dworkvt;
        magmaDoubleComplex  *dT0, *dT1, *dT;
        magmaDoubleComplex  *work, *hwork, *hT, *htmpR;
        magmaDoubleComplex  *tmppanel, *tmppanel0, *tmppanel1, *tmpnextpanel, *tmpprevpanel;

        magma_int_t i, k, ldwork, lddwork, old_i, rows, old_rows, blkid;
        magma_int_t lddworkvt, crosspoint;
        magma_int_t nbmin, nx, nb;
        magma_int_t lwork, lhtmppanel, lhqrwork, lhT, lhR;
        magma_int_t dTsiz, dlarfbwsiz, micworksiz;
	magma_int_t curr_ib, next_ib, prev_ib, minnb, numblk;


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

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

	//#define ENABLE_TIMER

	if(m <=  10320) {
		minnb = 240;
		crosspoint = 0;
	} else {
		minnb = 480;
		crosspoint = 9600;
	}

	// PARAMETER TUNING
	if(m > 33840){
		nb = 1200;
	} else if(m > 23040){
		nb = 960;
	} else if(m > 13920){
		nb = 720;
	} else 
		nb = minnb;

        // 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 = 9600;
	numblk = min(m, n) / minnb;

        ldwork    = m;
        lddwork   = n;
        lddworkvt = lddwork;

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

        // CPU WORK will be storing two consecutive temporaly 
        // panel (size=2*m*nb) followed by T (nb*nb) followed 
        // by temporary storing of the all upper part of Vs which is diag(R),
        // we will have temp R (n*nb)
        // followed  by work for QR and DLARFB (n*nb)        
        lhtmppanel  = 2 * m * nb;
        lhT         = nb * nb; // size for storing T
        lhR         = (((min(m,n))/minnb)+1) *nb*nb;
        lhqrwork    = n * nb; // should be nb*nb
        lwork       = lhtmppanel + lhqrwork + lhT + lhR;
	//llastneededwork   =  nx*nx*2;
	//lwork = max(llastneededwork,lwork);
        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_zmalloc( &micwork, micworksiz )) {
                *info = MAGMA_ERR_DEVICE_ALLOC;
                return *info;
        }

        if ( MAGMA_SUCCESS != magma_zmalloc_pinned( &work, lwork ) ) {
                *info = MAGMA_ERR_HOST_ALLOC;
                magma_free( micwork );
                return *info;
        }

        // setup pointer
        dT0       = micwork;
        dT1       = dT0   + nb*nb;
	dwork     = dT1   + nb*nb;
        dworkvt   = dwork + n*nb; 
        tmppanel0 = work;
        tmppanel1 = work + m*nb;
        hT        = work + lhtmppanel;
        htmpR     = work + lhtmppanel + lhT;
        hwork     = work + lhtmppanel + lhT + lhR;

        double trfb, tmtc, tqr, trft, tctmr, tctmv, tctmt, tmnp, tcnp;

        if (k >= (minnb * numblk)) {
                #if defined (ENABLE_TIMER)
                printf("\n\n %10s %10s %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 R", "ctm V", "ctm T", "ctm","mtc");
                printf("     ====================================================================================================\n");
                #endif 

                /* Use blocked code initially */
                
		next_ib = minnb;

                old_i    = 0; 
                nb       = min(k, nb); 
                old_rows = m;
                blkid    = -1;

                curr_ib  = next_ib;
                for (i = 0; (i + next_ib) < (numblk * minnb); i += curr_ib) {
                        rows    = m - i;
                        curr_ib = next_ib;

			if((rows - curr_ib) > 33840){
                            next_ib = 1200;
			} else if((rows - curr_ib) > 23040){
                            next_ib = 960;
                        } else if((rows - curr_ib) > 13920){
                            next_ib = 720;
                        } else 
                            next_ib = minnb;//480;

                        blkid = blkid + 1; 

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

                        #if defined (ENABLE_TIMER)
                        trfb = tmtc = tqr = trft = tctmr = tctmv = tctmt = 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
                                dT = (blkid-1)%2 == 0 ? dT0 : dT1;
                                magma_zlarfb_gemm( MagmaLeft, MagmaConjTrans, MagmaForward, MagmaColumnwise,
                                                   m - old_i, n - old_i - curr_ib - prev_ib, prev_ib,
                                                   dA(old_i, old_i), ldda, 
                                                   dT, 0, nb,
                                                   dA(old_i, old_i + curr_ib + prev_ib), ldda, dwork, 0, 
                                                   lddwork, dworkvt, 0, lddworkvt, queue);
                                
				#if defined (ENABLE_TIMER)
                                magma_kernel_sync();
                                trfb = magma_wtime() - trfb;
                                #endif
                        }


                        //if( (i==0) || (rows <= crosspoint) ){
                        if( (blkid < 2) || (old_rows <= crosspoint) ){
                                // i=0 ==> first time
                                // or MIC was updating the next panel and so need to copy it to CPU
                                #if defined (ENABLE_TIMER)
                                tmtc = magma_wtime();
                                #endif
                                magma_zgetmatrix_async(rows, curr_ib, dA(i, i), ldda, tmppanel(i), 0, ldwork, 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(i > 0 && (i + curr_ib) < (numblk * minnb) && old_rows > crosspoint) {
                        if(blkid > 1 && (i + curr_ib) < (numblk * minnb) && old_rows > crosspoint) {
                                #if defined (ENABLE_TIMER)
                                tcnp = magma_wtime();
                                #endif

                                tmpprevpanel = (blkid - 1)%2 == 0 ? tmppanel0 : tmppanel1;
                                // CPU do the next panel, bring it from the MIC
                                magma_zgetmatrix_async(old_rows, curr_ib, dA(old_i, old_i+prev_ib), ldda, tmppanel(old_i), 0, ldwork, queue, &event[0]);
                                magma_event_sync(event[0]);
                                lapackf77_zlarfb( "L", "C", "F", "C", 
                                                   &old_rows, &curr_ib, &prev_ib, 
                                                   tmpprevpanel(old_i), &ldwork,
                                                   hT, &nb,
                                                   tmppanel(old_i), &ldwork,
                                                   hwork, &curr_ib);

                                // send the top prev_ib*curr_ib of the updated panel to the MIC 
                                magma_zsetmatrix_async( prev_ib, curr_ib, tmppanel(old_i), 0, ldwork,
                                                        dA(old_i, old_i+prev_ib), ldda, queue, &event[1]); 
                                magma_event_sync(event[1]);

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



                        #if defined (ENABLE_TIMER)
                        tqr = magma_wtime();
                        #endif
                        lapackf77_zgeqrf(&rows, &curr_ib, tmppanel(i), &ldwork, tau+i, hwork, &lhqrwork, 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_zlacpy("A", &curr_ib, &curr_ib, tmppanel(i), &ldwork, htmpR(blkid), &nb);
                        lapackf77_zlaset("U", &curr_ib, &curr_ib, &c_zero, &c_one, tmppanel(i), &ldwork);
                        magma_zsetmatrix_async(rows, curr_ib, tmppanel(i), 0, ldwork, dA(i,i), 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(magmaDoubleComplex));
                        lapackf77_zlarft( MagmaForwardStr, MagmaColumnwiseStr, 
                                        &rows, &curr_ib, 
                                        tmppanel(i), &ldwork, tau+i, hT, &nb);

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

                        // ======================================================
                        //            SEND T to MIC
                        // ======================================================
                        // sending T 
                        dT = blkid%2 == 0 ? dT0 : dT1;
                        magma_zsetmatrix_async(curr_ib, curr_ib, 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 send R then do next panel

                        /* Wait the dlarfb of step old_i has been finished, then 
                         * send the upper portion of V (which is R) of step old_i 
                         * to be stored in its place in dA */
                        if (((i > 0) && ( rows <= crosspoint)) || blkid == 1) {
                                 // if MIC is updating next panel
                                magma_kernel_sync();
                                #if defined (ENABLE_TIMER)
                                tctmr = magma_wtime();
                                #endif
                                // sending R of previous step i_old
                                magma_zsetmatrix_async( prev_ib, prev_ib, htmpR(blkid-1), 0, nb,
                                                dA(old_i, old_i), ldda, queue, &event[1]); 
                                magma_event_sync(event[1]);

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


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

                                        #if defined (ENABLE_TIMER)
                                        magma_kernel_sync();
                                        tmnp = magma_wtime() - tmnp;
                                        #endif
                                }
                        }
                        else {
                                /* 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_zlarfb_gemm( MagmaLeft, MagmaConjTrans, MagmaForward, MagmaColumnwise,
                                                rows, n-i-curr_ib, curr_ib, 
                                                dA(i, i   ), ldda, dT, 0,  nb, 
                                                dA(i, i+curr_ib), ldda, dwork, 0, lddwork, dworkvt, 0, lddworkvt, queue);
                                
				magma_zsetmatrix(curr_ib, curr_ib, htmpR(blkid), 0, nb, dA(i,i), ldda, queue);
                        }


                        /* Wait the dlarfb of step old_i has been finished, then 
                         * send the upper portion of V (which is R) of step old_i 
                         * to be stored in its place in dA */
                        //if ((i > 0) && ( rows > crosspoint)) {
                        if ((i > 0) && (blkid != 1) && ( old_rows > crosspoint)) {
                                // if CPU updating next panel
                                magma_kernel_sync();
                                #if defined (ENABLE_TIMER)
                                tctmr = magma_wtime();
                                #endif
                                // sending R of previous step i_old
                                magma_zsetmatrix_async( prev_ib, prev_ib, htmpR(blkid - 1), 0, nb,
                                                dA(old_i, old_i), ldda, queue, &event[1]); 
                                magma_event_sync(event[1]);

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

                        old_i  = i;
                        old_rows = rows;
                        
			prev_ib = curr_ib;

                        #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\n",
                                               i, curr_ib, rows, tqr * 1000, trft*1000, (trft+tqr)*1000, (trft+tqr+tcnp+tmtc+tctmv+tctmt)*1000,  
                                        trfb * 1000, tcnp * 1000, tmnp * 1000, tctmr * 1000, tctmv * 1000, tctmt * 1000,
                                        (tctmv+tctmt+tctmr)*1000, tmtc * 1000);
                        #endif
                }
        } else {
                i = 0;
        }

        magma_free(micwork);
        
        /* Use unblocked code to factor the last or only block. */
        if (i < k) {
		curr_ib   = n-i;
                rows      = m-i;
		// store the unblocked factor in work and so use work+curr_ib*rows as the workspace
                lhqrwork  = lwork - rows*curr_ib;
                magma_zgetmatrix(rows, curr_ib, dA(i, i), ldda, work, 0, rows, queue);
                lapackf77_zgeqrf(&rows, &curr_ib, work, &rows, tau+i, work + curr_ib*rows, &lhqrwork, info);
                magma_zsetmatrix(rows, curr_ib, work, 0, rows, dA(i, i), ldda, queue);
        }

        magma_free_pinned(work);
        return *info;
} /* magma_zgeqrf2_mic */
