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

   @generated c Fri Nov 22 14:21:13 2013
*/

#include "common_magma.h"
#define dA(i, j) (dA+(j)*ldda + (i))

magma_int_t
magma_ctrtri_mic(magma_uplo_t uplo, magma_diag_t diag, magma_int_t n,
             magmaFloatComplex *dA, magma_int_t ldda, magma_int_t *info, magma_queue_t queue)
{
/*  -- MAGMA (version 1.1) --
       Univ. of Tennessee, Knoxville
       Univ. of California, Berkeley
       Univ. of Colorado, Denver
       November 2011

    Purpose
    =======
    ZTRTRI computes the inverse of a real upper or lower triangular
    matrix dA.

    This is the Level 3 BLAS version of the algorithm.

    Arguments
    =========
    UPLO    (input) CHARACTER*1
            = 'U':  A is upper triangular;
            = 'L':  A is lower triangular.

    DIAG    (input) CHARACTER*1
            = 'N':  A is non-unit triangular;
            = 'U':  A is unit triangular.

    N       (input) INTEGER
            The order of the matrix A.  N >= 0.

    dA      (input/output) COMPLEX array ON THE GPU, dimension (LDDA,N)
            On entry, the triangular matrix A.  If UPLO = 'U', the
            leading N-by-N upper triangular part of the array dA contains
            the upper triangular matrix, and the strictly lower
            triangular part of A is not referenced.  If UPLO = 'L', the
            leading N-by-N lower triangular part of the array dA contains
            the lower triangular matrix, and the strictly upper
            triangular part of A is not referenced.  If DIAG = 'U', the
            diagonal elements of A are also not referenced and are
            assumed to be 1.
            On exit, the (triangular) inverse of the original matrix, in
            the same storage format.

    LDDA    (input) INTEGER
            The leading dimension of the array dA.  LDDA >= max(1,N).

    INFO    (output) INTEGER
            = 0: successful exit
            < 0: if INFO = -i, the i-th argument had an illegal value
            > 0: if INFO = i, dA(i,i) is exactly zero.  The triangular
                    matrix is singular and its inverse cannot be computed.
                 (Singularity check is currently disabled.)

    ===================================================================== */

    /* Local variables */
    //char uplo_[2] = {uplo, 0};
    //char diag_[2] = {diag, 0};
    magma_int_t     nb, nn, j, jb;
    //magmaFloatComplex c_zero     = MAGMA_C_ZERO;
    magmaFloatComplex c_one      = MAGMA_C_ONE;
    magmaFloatComplex c_neg_one  = MAGMA_C_NEG_ONE;
    magmaFloatComplex *work;

    //int upper  = lapackf77_lsame(uplo_, "U");
    //int nounit = lapackf77_lsame(diag_, "N");

    *info = 0;

    //if ((! upper) && (! lapackf77_lsame(uplo_, "L")))
    if (uplo != MagmaUpper)
        *info = -1;
    else if (diag != MagmaNonUnit)
        *info = -2;
    else if (n < 0)
        *info = -3;
    else if (ldda < max(1,n))
        *info = -5;

    if (*info != 0) {
        magma_xerbla( __func__, -(*info) );
        return *info;
    }

    /* Check for singularity if non-unit */
    /* cannot do here with matrix dA on GPU -- need kernel */
    /*
    if (nounit) {
        for ( j=0; j<n; ++j ) {
            if ( MAGMA_C_EQUAL( *dA(j,j), c_zero )) {
                *info = j+1;  // Fortran index
                return *info;
            }
        }
    }
    */

    /* Determine the block size for this environment */
    nb = magma_get_cpotrf_nb(n);
    
    if (MAGMA_SUCCESS != magma_cmalloc_pinned( &work, nb*nb )) {
        *info = MAGMA_ERR_HOST_ALLOC;
        return *info;
    }
    
    magma_event_t stream[2] = {NULL, NULL};

    if (nb <= 1 || nb >= n) {
        magma_cgetmatrix( n, n, dA, 0, ldda, work, 0, n, queue );
        lapackf77_ctrtri(lapack_const(uplo), lapack_const(diag), &n, work, &n, info);
        magma_csetmatrix( n, n, work, 0, n, dA, 0, ldda, queue );
    }
    else {
        if (uplo == MagmaUpper) {
            /* Compute inverse of upper triangular matrix */
            for (j=0; j < n; j += nb) {
                jb = min(nb, (n-j));

                /* Compute rows 1:j-1 of current block column */
                magma_ctrmm( MagmaLeft, MagmaUpper,
                             MagmaNoTrans, MagmaNonUnit, j, jb,
                             c_one, dA(0,0), 0, ldda, dA(0, j), 0, ldda, queue);

                magma_ctrsm( MagmaRight, MagmaUpper,
                             MagmaNoTrans, MagmaNonUnit, j, jb,
                             c_neg_one, dA(j,j), 0, ldda, dA(0, j), 0, ldda, queue);

                magma_cgetmatrix_async( jb, jb,
                                        dA(j, j), 0, ldda,
                                        work,     0, jb, queue, &stream[1] );

                magma_event_sync( stream[1] );

                /* Compute inverse of current diagonal block */
                lapackf77_ctrtri(MagmaUpperStr, lapack_const(diag), &jb, work, &jb, info);

		magma_kernel_sync();
                magma_csetmatrix_async( jb, jb,
                                        work,     0, jb,
                                        dA(j, j), 0, ldda, queue, &stream[0] );
                magma_event_sync( stream[0] );
            }
        }
        else {
            /* Compute inverse of lower triangular matrix */
            nn=((n-1)/nb)*nb+1;

            for(j=nn-1; j>=0; j=j-nb) {
                jb=min(nb,(n-j));

                if((j+jb) < n) {
                    /* Compute rows j+jb:n of current block column */
                    magma_ctrmm( MagmaLeft, MagmaLower,
                                 MagmaNoTrans, MagmaNonUnit, (n-j-jb), jb,
                                 c_one, dA(j+jb,j+jb), 0, ldda, dA(j+jb, j), 0, ldda, queue);

                    magma_ctrsm( MagmaRight, MagmaLower,
                                 MagmaNoTrans, MagmaNonUnit, (n-j-jb), jb,
                                 c_neg_one, dA(j,j), 0, ldda, dA(j+jb, j), 0, ldda, queue);
                }

                magma_cgetmatrix_async( jb, jb,
                                        dA(j, j), 0, ldda,
                                        work,     0, jb, queue, &stream[1] );

                magma_event_sync( stream[1] );

                /* Compute inverse of current diagonal block */
                lapackf77_ctrtri(MagmaLowerStr, lapack_const(diag), &jb, work, &jb, info);
        
		magma_kernel_sync();
                magma_csetmatrix_async( jb, jb,
                                        work,     0, jb,
                                        dA(j, j), 0, ldda, queue, &stream[0] );
                magma_event_sync( stream[0] );
            }
        }
    }

    magma_free_pinned( work );

    return *info;
}
