/*
   -- 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_zpotrf_mic( magma_uplo_t   uplo, magma_int_t    n,
                  magmaDoubleComplex_ptr dA, size_t dA_offset, 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 tmppanel(i_)    (tmppanel + (i_))
        #define tmpprevpanel(i_)    (tmpprevpanel + (i_))

        magmaDoubleComplex c_one  = MAGMA_Z_ONE ;
        magmaDoubleComplex c_zero = MAGMA_Z_ZERO;
        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;

        magmaDoubleComplex  *work, *hwork;
        magmaDoubleComplex  *tmppanel, *tmppanel0, *tmppanel1, *tmpprevpanel;

        magma_int_t i, ldwork, lddwork, old_i, rows, old_rows, trsmrows, old_trsmrows, blkid;
        magma_int_t crosspoint;
        magma_int_t nbmin, nx, nb;
        magma_int_t lwork, lhtmppanel;

        *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 = 2400;//480;//960;//512;

        // 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 * n * nb;
        lwork       = lhtmppanel;

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

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

        nbmin = 2;
        nx    = 2 * nb;
        ldwork = n;
        // setup pointer
        tmppanel0 = work;
        tmppanel1 = work + n*nb;

        double therk, tmtc, tcchol, tctrsm, tctmp, tmnp, tcnp;
        
        // 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 = n; // DO NOT CHANGE CROSSPOINT THIS VERSION DO NOT SUPPORT IT
        if(crosspoint<n) {
            printf("error not supported\n");
            exit(-1);
        }

        //#define ENABLE_TIMER


        if (nb >= nbmin && nb < n && nx < n) {
                #if defined (ENABLE_TIMER)
                printf("\n\n %10s %10s %10s  %10s %10s %10s %10s  %10s  %10s %10s  %10s\n", 
                                "i", "row", "panel", "ctrsm", "CH+TRSM", "CPU", "dsyrk", "CPU_np", "MIC_np", "ctm P", "mtc");
                printf("     ====================================================================================================\n");
                #endif 

                /* Use blocked code initially */

                old_i = 0; nb = min(n, nb); old_rows = n;
                for (i = 0; i < n - nx; i += nb) {
                        blkid = i/nb; 
                        rows  = n - i;
                        magma_kernel_sync();
                        tmppanel = blkid%2 == 0 ? tmppanel0 : tmppanel1;

                        #if defined (ENABLE_TIMER)
                        therk = tmtc = tcchol = tctrsm = tctmp = tmnp = tcnp = 0.0;
                        #endif

                        if (i>0){
                                #if defined (ENABLE_TIMER)
                                therk = get_time();
                                #endif
                                
                                magma_zherk( MagmaLower, MagmaNoTrans,
                                                old_trsmrows - nb, nb,  
                                                m_one, dA(old_i + 2 * nb, old_i ), 0, ldda,  
                                                one, dA(old_i + 2 * nb, old_i + 2 * nb), 0, ldda, queue);

                                #if defined (ENABLE_TIMER)
                                magma_kernel_sync();
                                therk = get_time() - therk;
                                #endif
                        }


                        if( (i==0) || (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 = get_time();
                                #endif
                               
                                magma_zgetmatrix_async(nb, nb, dA(i, i), 0, ldda, tmppanel(i), 0, ldwork, queue, &event[0]);
                                magma_event_sync(event[0]);
                               
                                #if defined (ENABLE_TIMER)
                                tmtc = get_time() - tmtc;
                                #endif
                        }

                        /*
                        //CPU do the look ahead panel first then factorize it
                        if(i > 0 && i < (n - nx) && old_rows > crosspoint) {
                                #if defined (ENABLE_TIMER)
                                tcnp = get_time();
                                #endif

                                tmpprevpanel = (blkid - 1)%2 == 0 ? tmppanel0 : tmppanel1;
                                // CPU do the next panel, bring it from the MIC
                                magma_zgetmatrix_async(rows, nb, dA(i, i), 0, ldda, tmppanel(i), 0, ldwork, queue, &event[0]);
                                magma_event_sync(event[0]);

                                blasf77_zgemm( MagmaNoTransStr, MagmaConjTransStr,
                                                &rows, &nb, &nb, 
                                                &mz_one, tmpprevpanel(i), &ldwork, tmpprevpanel(i), &ldwork, 
                                                &z_one, tmppanel(i), &ldwork);

                                #if defined (ENABLE_TIMER)
                                magma_kernel_sync();
                                tcnp = get_time() - tcnp;
                                #endif
                        }
                        */
                        #if defined (ENABLE_TIMER)
                        tcchol = get_time();
                        #endif

                        lapackf77_zpotrf(MagmaLowerStr, &nb, tmppanel(i), &ldwork, info);
                        if ( *info != 0 ) {
                                assert( *info > 0 );
                                *info += i;
                                break;
                        }
                        trsmrows=rows - nb;

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

                        /*
                        //  TRSM on CPU for the panel
                        #if defined (ENABLE_TIMER)
                        tctrsm = get_time();
                        #endif

                        if(trsmrows > 0) {        
                            blasf77_ztrsm(MagmaRightStr, MagmaLowerStr, MagmaConjTransStr, MagmaNonUnitStr, &trsmrows, &nb, &z_one, tmppanel(i), &ldwork, tmppanel(i) + nb, &ldwork);
                        }

                        #if defined (ENABLE_TIMER)
                        tctrsm = get_time() - tctrsm;
                        #endif
                        */

                        // ======================================================
                        //            SEND panel to MIC
                        // ======================================================
                        #if defined (ENABLE_TIMER)
                        tctmp = get_time();
                        #endif
                        magma_zsetmatrix_async(nb, nb, tmppanel(i), 0, ldwork, dA(i,i), 0, ldda, queue, &event[0]);
                        magma_event_sync(event[0]);
                        #if defined (ENABLE_TIMER)
                        tctmp = get_time() - tctmp;
                        #endif
                        

                        
                        // TRSM on the MIC
                        #if defined (ENABLE_TIMER)
                        tctrsm = get_time();
                        #endif

                        if(trsmrows > 0) {        
                            magma_ztrsm(MagmaRight, MagmaLower, MagmaConjTrans, MagmaNonUnit, trsmrows, nb, z_one, dA(i,i), 0, ldda, dA(i+nb, i), 0, ldda, queue);
                        }

                        #if defined (ENABLE_TIMER)
                        tctrsm = get_time() - tctrsm;
                        #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+nb < n-nx) {
                                // Apply H' to A(i:m,i+nb:i+2*nb) from the left: next panel 
                                if(rows <= crosspoint){
                                        #if defined (ENABLE_TIMER)
                                        tmnp = get_time();
                                        #endif
                                        // MIC is WORKING on next panel 
                                        magma_zgemm( MagmaNoTrans, MagmaConjTrans,
                                                        trsmrows, nb, nb, 
                                                        mz_one, dA(i + nb, i), 0, ldda, dA(i + nb, i), 0, ldda, 
                                                        z_one, dA(i + nb, i + nb), 0, ldda, queue);

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

                        old_i  = i;
                        old_rows = rows;
                        old_trsmrows = trsmrows;

                        #if defined (ENABLE_TIMER)
                        printf("%10d %10d %10.3lf  %10.3lf  %10.3lf %10.3lf %10.3lf  %10.3lf %10.3lf  %10.3lf %10.3lf\n",
                                               i, 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;
                //printf("POTRF: i %d rows: %d tmp: %lx ld: %d\n", i, rows, (unsigned long)tmppanel(i), ldwork);
                magma_zgetmatrix(rows, rows, dA(i, i), 0, ldda, tmppanel0, 0, rows, queue);
                lapackf77_zpotrf(MagmaLowerStr, &rows, tmppanel0, &rows, info);
                magma_zsetmatrix(rows, rows, tmppanel0, 0, rows, dA(i, i), 0, ldda, queue);
        }

        magma_free_pinned(work);

        return *info;
} /* magma_zgeqrf2_mic */
