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

   @precisions normal z -> s d c

*/

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

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 work_ref(a_1)  ( work + (a_1))
	#define hwork          ( work + (nb) * (m))

	magmaDoubleComplex_ptr dwork;
	magmaDoubleComplex_ptr dworkvt;
	magmaDoubleComplex  *work, *nextP, *pwork;

	magma_int_t i, k, ldwork, lddwork, old_i, old_ib, rows;
	magma_int_t lddworkvt;
	magma_int_t nbmin, nx, ib, nb;
	magma_int_t lhwork, lwork;

	*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;

	//if(m >= 17000)
	//	nb = 1024;
	//else
		nb = 512;

	lwork  = (m + n) * nb;
	lhwork = lwork - (m) * nb;


	if ( MAGMA_SUCCESS != magma_zmalloc( &dwork, n * nb )) {
		*info = MAGMA_ERR_DEVICE_ALLOC;
		return *info;
	}

	if ( MAGMA_SUCCESS != magma_zmalloc( &dworkvt, m * nb )) {
		*info = MAGMA_ERR_DEVICE_ALLOC;
		return *info;
	}

	//next panel
	if ( MAGMA_SUCCESS != magma_zmalloc_pinned( &nextP, m * nb )) {
		*info = MAGMA_ERR_HOST_ALLOC;
		magma_free(dwork);
		return *info;
	}

	if ( MAGMA_SUCCESS != magma_zmalloc_pinned( &pwork, m * nb )) {
		*info = MAGMA_ERR_HOST_ALLOC;
		magma_free(dwork);
		return *info;
	}

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

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

	nbmin = 2;
	nx    = 2 * nb;
	ldwork = m;
	lddwork= n;
	lddworkvt= lddwork;

	double trfb, tmtc, tqr, trft, tctmr, tctmv, tctmt, tnp;

	if (nb >= nbmin && nb < k && nx < k) {
		/* Use blocked code initially */
		old_i = 0; old_ib = nb;

		//printf("\n\n %10s %10s %10s  %10s %10s %10s %10s  %10s  %10s %10s  %10s  %10s %10s\n", "i", "row", "panel", "dlarft", "QR+T", "CPU", "dlarfb", "nxtpU", "ctm R", "ctm V", "ctm T", "ctm","mtc");
		//printf("     ====================================================================================================\n");

		for (i = 0; i < k - nx; i += nb) {
			ib = min(k - i, nb);
			rows = m - i;

			magma_kernel_sync();
			//trfb = tmtc = tqr = trft = tctmr = tctmv = tctmt = tnp = 0.0;

			if (i>0){
				/* Apply H' to A(i:m,i+2*ib:n) from the left */
				
				//trfb = get_time();

				magma_zlarfb_gemm( MagmaLeft, MagmaConjTrans, MagmaForward, MagmaColumnwise,
						m - old_i, n - old_i - 2 * old_ib, old_ib,
						dA(old_i, old_i), ldda, dwork, 0, lddwork,
						dA(old_i, old_i + 2 * old_ib), ldda, dwork, old_ib, lddwork, dworkvt, 0, lddworkvt, queue);
				
				//magma_kernel_sync();
				//trfb = get_time() - trfb;
			}

			//tmtc = get_time();

			magma_zgetmatrix_async(rows, ib, dA(i, i), ldda, work_ref(i), 0, ldwork, queue, &event[0]);
			magma_event_sync(event[0]);

			//tmtc = get_time() - tmtc;

			//tqr = get_time();

			lapackf77_zgeqrf(&rows, &ib, work_ref(i), &ldwork, tau+i, hwork, &lhwork, info);
			
			//tqr = get_time() - tqr;

			/* Form the triangular factor of the block reflector
			   H = H(i) H(i+1) . . . H(i+ib-1) */
			
			//trft = get_time();

			memset(hwork,0,ib*ib*sizeof(magmaDoubleComplex));
			lapackf77_zlarft( MagmaForwardStr, MagmaColumnwiseStr, 
					&rows, &ib, 
					work_ref(i), &ldwork, tau+i, hwork, &ib);

			//trft = get_time() - trft;

			//tctmv = get_time();

			zpanel_to_q( MagmaUpper, ib, work_ref(i), ldwork, hwork+ib*ib );
			//magma_zsetmatrix(rows, ib, work_ref(i), 0, ldwork, dA(i,i), ldda, queue);
			magma_zsetmatrix_async(rows, ib, work_ref(i), 0, ldwork, dA(i,i), ldda, queue, &event[0]);
			magma_event_sync(event[0]);
			zq_to_panel( MagmaUpper, ib, work_ref(i), ldwork, hwork+ib*ib );

			//tctmv = get_time() - tctmv;

			if (i > 0) {
				magma_kernel_sync();

				//tctmr = get_time();

				magma_zsetmatrix_async( old_ib, old_ib, work_ref(old_i), 0, ldwork,
						dA(old_i, old_i), ldda, queue, &event[1]); 
				magma_event_sync(event[1]);

				//tctmr = get_time() - tctmr;
			}

			if (i + ib < n) 
			{
				
				//tctmt = get_time();

				magma_zsetmatrix(ib, ib, hwork, 0, ib, dwork, 0, lddwork, queue);

				tctmt = get_time() - tctmt;

				tnp = get_time();
				
				if (i+nb < k-nx) {
					//download the next panel
					magma_zgetmatrix_async(rows, ib, dA(i, i + ib), ldda, nextP, 0, m, queue, &event[0]);
					magma_event_sync(event[0]);

					zlarfb_( lapack_const(MagmaLeft), lapack_const(MagmaConjTrans), lapack_const(MagmaForward), lapack_const(MagmaColumnwise),
							&rows, &ib, &ib, 
							work_ref(i), &ldwork, hwork,  &ib, 
							nextP, &m, pwork, &m);

					magma_zsetmatrix_async(rows, ib, nextP, 0, m, dA(i, i + ib), ldda, queue, &event[0]);
					magma_event_sync(event[0]);


					/* Apply H' to A(i:m,i+ib:i+2*ib) from the left */
					//magma_zlarfb_gemm( MagmaLeft, MagmaConjTrans, MagmaForward, MagmaColumnwise,
					//		rows, ib, ib, 
					//		dA(i, i   ), ldda, dwork,0,  lddwork, 
					//		dA(i, i+ib), ldda, dwork,ib, lddwork, dworkvt, 0, lddworkvt, queue);
				}
				else {

					magma_zlarfb_gemm( MagmaLeft, MagmaConjTrans, MagmaForward, MagmaColumnwise,
							rows, n-i-ib, ib, 
							dA(i, i   ), ldda, dwork,0,  lddwork, 
							dA(i, i+ib), ldda, dwork,ib, lddwork, dworkvt, 0, lddworkvt, queue);

					magma_zsetmatrix(ib, ib, work_ref(i), 0, ldwork, dA(i,i), ldda, queue);
				
				}
				//}

				//magma_kernel_sync();
				//tnp = get_time() - tnp;

				old_i  = i;
				old_ib = ib;
			}

			//printf("%10d %10d %10.3lf  %10.3lf  %10.3lf %10.3lf %10.3lf  %10.3lf  %10.3lf %10.3lf %10.3lf %10.3lf %10.3lf\n", i, rows, tqr * 1000, trft*1000, (trft+tqr)*1000, (trft+tqr+tmtc)*1000,  trfb * 1000, tnp * 1000,  tctmr * 1000, tctmv * 1000, tctmt * 1000,(tctmv+tctmt+tctmr)*1000, tmtc * 1000);
		}
	} else {
		i = 0;
	}

	magma_free(dwork);
	magma_free(dworkvt);
	
	//printf("\n\n");


	/* Use unblocked code to factor the last or only block. */
	if (i < k) {
		ib   = n-i;
		rows = m-i;
		magma_zgetmatrix(rows, ib, dA(i, i), ldda, work, 0, rows, queue);

		lhwork = lwork - rows*ib;
		lapackf77_zgeqrf(&rows, &ib, work, &rows, tau+i, work+ib*rows, &lhwork, info);

		magma_zsetmatrix(rows, ib, work, 0, rows, dA(i, i), ldda, queue);
	}

	magma_free_pinned(work);
	magma_free_pinned(nextP);
	magma_free_pinned(pwork);

	return *info;
} /* magma_zgeqrf2_mic */
