/*
 *  -- MAGMA (version 1.1) --
 *     Univ. of Tennessee, Knoxville
 *     Univ. of California, Berkeley
 *     Univ. of Colorado, Denver
 *     April 2012
 *
 * @precisions normal z -> c d s
 *
 **/

// make sure that asserts are enabled
#ifdef NDEBUG
#undef NDEBUG
#endif
#include <assert.h>

// includes, system
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

// includes, project
#include "flops.h"
#include "magma.h"
#include "magma_lapack.h"
#include "magma_types.h"
#include "testings.h"

/* ////////////////////////////////////////////////////////////////////////////
   -- Testing ztrsm
*/
int main( int argc, char** argv)
{
    /* Initialize */
    magma_queue_t  queue;
    magma_device_t device;
    int num = 0;
    magma_err_t err;
    magma_init();
    err = magma_get_devices( &device, 1, &num );
    if ( err != MAGMA_SUCCESS or num < 1 ) {
      fprintf( stderr, "magma_get_devices failed: %d\n", err );
      exit(-1);
    }
    err = magma_queue_create( device, &queue );
    if ( err != MAGMA_SUCCESS ) {
      fprintf( stderr, "magma_queue_create failed: %d\n", err );
      exit(-1);
    }

    real_Double_t   gflops, mic_perf, mic_time, cpu_perf, cpu_time;
    double          mic_error, Cnorm, work[1];
    magma_int_t M, N, info;
    magma_int_t Ak;
    magma_int_t sizeA, sizeB;
    magma_int_t lda, ldb, ldda, lddb;
    magma_int_t ione     = 1;
    magma_int_t ISEED[4] = {0,0,0,1};
   
    magma_int_t *piv;

    magmaDoubleComplex *h_A, *h_B, *h_Bmic, *h_B1, *h_X2, *LU, *LUT;
    magmaDoubleComplex *d_A, *d_B;
    magmaDoubleComplex c_neg_one = MAGMA_Z_NEG_ONE;
    magmaDoubleComplex c_one = MAGMA_Z_ONE;
    magmaDoubleComplex alpha = MAGMA_Z_MAKE(  0.29, -0.86 );
    
    magma_opts opts;
    parse_opts( argc, argv, &opts );
    
    printf("    M     N   MIC Gflop/s (ms)   CPU Gflop/s (ms)  MIC error\n");
    printf("===============================================================\n");
    for( int i = 0; i < opts.ntest; ++i ) {
        for( int iter = 0; iter < opts.niter; ++iter ) {
            M = opts.msize[i];
            N = opts.nsize[i];
            gflops = FLOPS_ZTRSM(opts.side, M, N) / 1e9;

            if ( opts.side == MagmaLeft ) {
                lda = M;
                Ak = M;
            } else {
                lda = N;
                Ak = N;
            }
            
            ldb = M;
            
            ldda = ((lda+31)/32)*32;
            lddb = ((ldb+31)/32)*32;
            
            sizeA = lda*Ak;
            sizeB = ldb*N;
            
            TESTING_MALLOC_HOST( h_A,  magmaDoubleComplex, lda*Ak );
            TESTING_MALLOC_HOST( LU,      magmaDoubleComplex, lda*Ak );
            TESTING_MALLOC_HOST( LUT,  magmaDoubleComplex, lda*Ak );
            TESTING_MALLOC_HOST( h_B,  magmaDoubleComplex, ldb*N  );
            TESTING_MALLOC_HOST( h_B1,  magmaDoubleComplex, ldb*N );
            TESTING_MALLOC_HOST( h_X2,  magmaDoubleComplex, ldb*N );
            TESTING_MALLOC_HOST( h_Bmic, magmaDoubleComplex, ldb*N  );
            
            TESTING_MALLOC_DEV( d_A, magmaDoubleComplex, ldda*Ak );
            TESTING_MALLOC_DEV( d_B, magmaDoubleComplex, lddb*N  );
            
            /* Initialize the matrices */
            lapackf77_zlarnv( &ione, ISEED, &sizeA, LU );
            TESTING_MALLOC_HOST( piv, magma_int_t, lda/*N*/ ); 
            lapackf77_zgetrf( &Ak, &Ak, LU, &lda, piv, &info );
        
            int i, j;
            for(i=0;i<Ak;i++){
                for(j=0;j<Ak;j++){
                    LUT[j+i*lda] = LU[i+j*lda];
                }
            }

            lapackf77_zlacpy(MagmaUpperStr, &Ak, &Ak, LUT, &lda, LU, &lda);

            if(opts.uplo == MagmaLower){
                lapackf77_zlacpy(MagmaLowerStr, &Ak, &Ak, LU, &lda, h_A, &lda);
            }else{
                lapackf77_zlacpy(MagmaUpperStr, &Ak, &Ak, LU, &lda, h_A, &lda);
            }
            
            lapackf77_zlarnv( &ione, ISEED, &sizeB, h_B );
            memcpy(h_B1, h_B, sizeB*sizeof(magmaDoubleComplex));
           
	    
            /* =====================================================================
               Performs operation using MIC-BLAS
               =================================================================== */
            
	    magma_zsetmatrix( Ak, Ak, h_A, 0, lda, d_A, 0, ldda, queue );
            magma_zsetmatrix( M, N, h_B, 0, ldb, d_B, 0, lddb, queue );
            
            magma_ztrsm( opts.side, opts.uplo, opts.transA, opts.diag, 
                         M, N,
                         alpha, d_A, 0, ldda,
                         d_B, 0, lddb, queue );

            magma_zsetmatrix( M, N, h_B, 0, ldb, d_B, 0, lddb, queue );
            
            mic_time = magma_sync_wtime( NULL );
            magma_ztrsm( opts.side, opts.uplo, opts.transA, opts.diag,
                         M, N, 
                         alpha, d_A, 0, ldda,
                         d_B, 0, lddb, queue );
            mic_time = magma_sync_wtime( NULL ) - mic_time;
            mic_perf = gflops / mic_time;
            
            
            /* =====================================================================
               Performs operation using CPU BLAS
               =================================================================== */
            if ( opts.check ) {
		magma_zgetmatrix( M, N, d_B, 0, lddb, h_Bmic, 0, ldb, queue );
                cpu_time = magma_wtime();
                blasf77_ztrsm( lapack_const(opts.side), lapack_const(opts.uplo), lapack_const(opts.transA), lapack_const(opts.diag), 
                               &M, &N,
                               &alpha, h_A, &lda,
                               h_B, &ldb );
                cpu_time = magma_wtime() - cpu_time;
                cpu_perf = gflops / cpu_time;
            
            /* =====================================================================
               Check the result
               =================================================================== */
            // ||b - Ax|| / (||A||*||x||)
            
            memcpy(h_X2, h_Bmic, sizeB*sizeof(magmaDoubleComplex));

	    magmaDoubleComplex alpha2 = MAGMA_Z_DIV(  c_one, alpha );
            blasf77_ztrmm( lapack_const(opts.side), lapack_const(opts.uplo), lapack_const(opts.transA), lapack_const(opts.diag), 
                           &M, &N,
                           &alpha2, h_A, &lda,
                           h_X2, &ldb );

            blasf77_zaxpy( &sizeB, &c_neg_one, h_B1, &ione, h_X2, &ione );
            double norm1 =  lapackf77_zlange( "M", &M, &N, h_X2, &ldb, work );
            double normx =  lapackf77_zlange( "M", &M, &N, h_Bmic, &ldb, work );
            double normA =  lapackf77_zlange( "M", &Ak, &Ak, h_A, &lda, work );
            
            mic_error = norm1/(normx*normA);
            
            printf("%5d %5d   %7.2f (%7.2f)   %7.2f (%7.2f)        %8.2e\n",
                    (int) M, (int) N,
                    mic_perf, 1000.*mic_time,
                    cpu_perf,    1000.*cpu_time,
                    mic_error );
            
            } else {
            printf("%5d %5d   %7.2f (%7.2f)   --- (---)        ---\n",
                    (int) M, (int) N,
                    mic_perf, 1000.*mic_time);

	    }
            TESTING_FREE_HOST( h_A  );
            TESTING_FREE_HOST( LU  );
            TESTING_FREE_HOST( LUT );
            TESTING_FREE_HOST( h_B  );
            TESTING_FREE_HOST( h_Bmic );
            TESTING_FREE_HOST( h_B1  );
            TESTING_FREE_HOST( h_X2 );
            
            TESTING_FREE_DEV( d_A );
            TESTING_FREE_DEV( d_B );
        }
        if ( opts.niter > 1 ) {
            printf( "\n" );
        }
    }

    magma_queue_destroy( queue );
    magma_finalize();

    return 0;
}
