/*
   -- 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 "common_magma.h"
#define PRECISION_z

magma_int_t
magma_zgetri_mic( magma_int_t n, magmaDoubleComplex *dA, magma_int_t ldda,
                  magma_int_t *ipiv, magmaDoubleComplex *dwork, magma_int_t lwork,
                  magma_int_t *info, magma_queue_t queue )
{
/*  -- micMAGMA (version 1.1) --
       Univ. of Tennessee, Knoxville
       Univ. of California, Berkeley
       Univ. of Colorado, Denver
       November 2011

    Purpose
    =======
    ZGETRI computes the inverse of a matrix using the LU factorization
    computed by ZGETRF. This method inverts U and then computes inv(A) by
    solving the system inv(A)*L = inv(U) for inv(A).
    
    Note that it is generally both faster and more accurate to use ZGESV,
    or ZGETRF and ZGETRS, to solve the system AX = B, rather than inverting
    the matrix and multiplying to form X = inv(A)*B. Only in special
    instances should an explicit inverse be computed with this routine.

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

    dA      (input/output) COMPLEX_16 array on the MIC, dimension (LDDA,N)
            On entry, the factors L and U from the factorization
            A = P*L*U as computed by ZGETRF_MIC.
            On exit, if INFO = 0, the inverse of the original matrix A.

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

    IPIV    (input) INTEGER array, dimension (N)
            The pivot indices from ZGETRF; for 1<=i<=N, row i of the
            matrix was interchanged with row IPIV(i).

    DWORK   (workspace/output) COMPLEX_16 array on the MIC, dimension (MAX(1,LWORK))
  
    LWORK   (input) INTEGER
            The dimension of the array DWORK.  LWORK >= N*NB, where NB is
            the optimal blocksize returned by magma_get_zgetri_nb(n).
            
            Unlike LAPACK, this version does not currently support a
            workspace query, because the workspace is on the MIC.

    INFO    (output) INTEGER
            = 0:  successful exit
            < 0:  if INFO = -i, the i-th argument had an illegal value
            > 0:  if INFO = i, U(i,i) is exactly zero; the matrix is
                  singular and its cannot be computed.

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

    #define dA(i, j)  (dA + (i) + (j)*ldda)
    #define dL(i, j)  (dL + (i) + (j)*lddl)
    
    /* Local variables */
    magmaDoubleComplex c_zero     = MAGMA_Z_ZERO;
    magmaDoubleComplex c_one     = MAGMA_Z_ONE;
    magmaDoubleComplex c_neg_one = MAGMA_Z_NEG_ONE;
    magmaDoubleComplex *dL = dwork;
    magma_int_t lddl = n;
    magma_int_t nb   = magma_get_zgetri_nb(n);
    magma_int_t j, jmax, jb, jp;
    
    *info = 0;
    if (n < 0)
        *info = -1;
    else if (ldda < max(1,n))
        *info = -3;
    else if ( lwork < n*nb )
        *info = -6;

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

    /* Quick return if possible */
    if ( n == 0 )
        return *info;

    //buffer added by kk    
    magmaDoubleComplex *buffer;
    if (MAGMA_SUCCESS != magma_zmalloc_pinned( &buffer, n*nb )) {
        *info = MAGMA_ERR_HOST_ALLOC;
        return *info;
    }
 
    /* Invert the triangular factor U */
    magma_ztrtri_mic( MagmaUpper, MagmaNonUnit, n, dA, ldda, info, queue );
    magma_kernel_sync();
    if ( *info != 0 )
        return *info;
    
    jmax = ((n-1) / nb)*nb;
    for( j = jmax; j >= 0; j -= nb ) {
        jb = min( nb, n-j );
        
        // copy current block column of L to work space,
        // then replace with zeros in A.
        //magmablas_zlacpy( MagmaUpperLower, n-j, jb,
          //                dA(j,j), ldda,
            //              dL(j,0), lddl );
        //magmablas_zlaset( MagmaLower, n-j, jb, dA(j,j), ldda );
	
	//added by kk
	magma_zgetmatrix(n - j, jb, dA(j, j), 0, ldda, buffer, 0, n, queue);
	magma_zsetmatrix(n - j, jb, buffer, 0, n, dL(j, 0), 0, lddl, queue);
	magma_int_t rows = n - j;
	for(magma_int_t kk = 0; kk < jb; kk++)
		for(magma_int_t ll = kk + 1; ll < rows; ll++)
			buffer[kk * n + ll] = 0;
	magma_zsetmatrix(n - j, jb, buffer, 0, n, dA(j, j), 0, ldda, queue);
        
        // compute current block column of Ainv
        // Ainv(:, j:j+jb-1)
        //   = ( U(:, j:j+jb-1) - Ainv(:, j+jb:n) L(j+jb:n, j:j+jb-1) )
        //   * L(j:j+jb-1, j:j+jb-1)^{-1}
        // where L(:, j:j+jb-1) is stored in dL.
        if ( j+jb < n ) {
            magma_zgemm( MagmaNoTrans, MagmaNoTrans, n, jb, n-j-jb,
                         c_neg_one, dA(0,j+jb), 0, ldda,
                                    dL(j+jb,0), 0, lddl,
                         c_one,     dA(0,j),    0, ldda, queue );
        }
        magma_ztrsm( MagmaRight, MagmaLower, MagmaNoTrans, MagmaUnit,
                     n, jb, c_one,
                     dL(j,0), 0, lddl,
                     dA(0,j), 0, ldda, queue );
    }

    // Apply column interchanges
    for( j = n-2; j >= 0; --j ) {
        jp = ipiv[j] - 1;
        if ( jp != j ) {
            magma_zswap( n, dA(0,j), 1, dA(0,jp), 1, queue);
	    //magma_kernel_sync();
        }
    }
   
    magma_kernel_sync();
    magma_free_pinned(buffer);
    return *info;
}
