/*
 *  -- MAGMA (version 1.1) --
 *     Univ. of Tennessee, Knoxville
 *     Univ. of California, Berkeley
 *     Univ. of Colorado, Denver
 *     November 2011
 *
 *  @precisions normal z -> c d s
 *
 **/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include "flops.h"
#include "magma.h"
#include "magma_lapack.h"
#include "testings.h"

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, work[1];
    magma_int_t ione     = 1;
    magma_int_t ISEED[4] = {0,0,0,1};
    magma_int_t N, lda, sizeA, sizeX, sizeY, blocks, ldwork;
    magma_int_t incx = 1;
    magma_int_t incy = 1;
    magma_int_t nb   = 64;
    magmaDoubleComplex c_neg_one = MAGMA_Z_NEG_ONE;
    magmaDoubleComplex alpha = MAGMA_Z_MAKE(  1.5, -2.3 );
    magmaDoubleComplex beta  = MAGMA_Z_MAKE( -0.6,  0.8 );
    magmaDoubleComplex *A, *X, *Y, *Ymic;
    magmaDoubleComplex *dA, *dX, *dY, *dC_work;
    
    magma_opts opts;
    parse_opts( argc, argv, &opts );

    printf("    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 ) {
            N = opts.nsize[i];
            lda    = ((N + 31)/32)*32;
            sizeA  = N*lda;
            sizeX  = N*incx;
            sizeY  = N*incy;
            gflops = FLOPS_ZHEMV( N ) / 1e9;
            
            TESTING_MALLOC_HOST( A,       magmaDoubleComplex, sizeA );
            TESTING_MALLOC_HOST( X,       magmaDoubleComplex, sizeX );
            TESTING_MALLOC_HOST( Y,       magmaDoubleComplex, sizeY );
            TESTING_MALLOC_HOST( Ymic, magmaDoubleComplex, sizeY );
            
            TESTING_MALLOC_DEV( dA, magmaDoubleComplex, sizeA );
            TESTING_MALLOC_DEV( dX, magmaDoubleComplex, sizeX );
            TESTING_MALLOC_DEV( dY, magmaDoubleComplex, sizeY );
            
            blocks = (N + nb - 1) / nb;
            ldwork = lda * (blocks + 1);
            TESTING_MALLOC_DEV( dC_work, magmaDoubleComplex, ldwork );
            
            /* Initialize the matrix */
            lapackf77_zlarnv( &ione, ISEED, &sizeA, A );
            magma_zmake_hermitian( N, A, lda );
            lapackf77_zlarnv( &ione, ISEED, &sizeX, X );
            lapackf77_zlarnv( &ione, ISEED, &sizeY, Y );
            
            /* =====================================================================
               Performs operation using MIC
               =================================================================== */
            magma_zsetmatrix( N, N, A, 0, lda, dA, 0, lda, queue );
            //magma_zsetvector( N, X, incx, dX, incx );
            //magma_zsetvector( N, Y, incy, dY, incy );
            magma_zsetmatrix( N, 1, X, 0, N, dX, 0, N, queue );
            magma_zsetmatrix( N, 1, Y, 0, N, dY, 0, N, queue );
            
            magma_zhemv( opts.uplo, N, alpha, dA, 0, lda, dX, 0, incx, beta, dY, 0, incy, queue );
            //magma_zsetvector( N, Y, incy, dY, incy );
            magma_zsetmatrix( N, 1, Y, 0, N, dY, 0, N, queue );

            mic_time = magma_sync_wtime( 0 );
            magma_zhemv( opts.uplo, N, alpha, dA, 0, lda, dX, 0, incx, beta, dY, 0, incy, queue );
            mic_time = magma_sync_wtime( 0 ) - mic_time;
            mic_perf = gflops / mic_time;
           
	    if(opts.check) { 
		    //magma_zgetvector( N, dY, incy, Ymic, incy );
		    magma_zgetmatrix( N, 1, dY, 0, N, Ymic, 0, N, queue );

		    /* =====================================================================
		       Performs operation using CPU BLAS
		       =================================================================== */
		    cpu_time = magma_wtime();
		    blasf77_zhemv( lapack_const(opts.uplo), &N, &alpha, A, &lda, X, &incx, &beta, Y, &incy );
		    cpu_time = magma_wtime() - cpu_time;
		    cpu_perf = gflops / cpu_time;

		    /* =====================================================================
		       Check the result
		       =================================================================== */
		    blasf77_zaxpy( &N, &c_neg_one, Y, &incy, Ymic, &incy );
		    mic_error = lapackf77_zlange( "M", &N, &ione, Ymic, &N, work ) / N;

		    printf("%5d   %7.2f (%7.2f)    %7.2f (%7.2f)   %8.2e\n",
				    (int) N,
				    mic_perf, 1000.*mic_time,
				    cpu_perf,    1000.*cpu_time,
				    mic_error );
	    } else {
		    printf("%5d   %7.2f (%7.2f)    --- (---)   ---\n",
				    (int) N,
				    mic_perf, 1000.*mic_time);

	    }
	    TESTING_FREE_HOST( A );
	    TESTING_FREE_HOST( X );
	    TESTING_FREE_HOST( Y );
	    TESTING_FREE_HOST( Ymic );

	    TESTING_FREE_DEV( dA );
	    TESTING_FREE_DEV( dX );
	    TESTING_FREE_DEV( dY );
	    TESTING_FREE_DEV( dC_work );
	}
	if ( opts.niter > 1 ) {
		printf( "\n" );
	}
    }

    magma_queue_destroy( queue );    
    magma_finalize();

    return 0;
}
