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

       @generated d Fri Nov 22 14:21:36 2013
*/

// 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_dbulge.h"
#include "testings.h"

#if defined(USEMKL)
#include <mkl_service.h>
#endif
#if defined(USEACML)
#include <omp.h>
#endif

#define PRECISION_d


extern "C"  magma_int_t 
magma_dsyevdx_2stage(char jobz, char range, char uplo, magma_int_t n,
		     double *a, magma_int_t lda, double vl, double vu, magma_int_t il, magma_int_t iu,
		     magma_int_t *m, double *w, double *work, magma_int_t lwork,
		     double *rwork, magma_int_t lrwork, magma_int_t *iwork, magma_int_t liwork, magma_int_t *info);

extern "C"  magma_int_t 
magma_dsyevdx_2stage_m(int nrgpu, char jobz, char range, char uplo, magma_int_t n,
		       double *a, magma_int_t lda, double vl, double vu, magma_int_t il, magma_int_t iu,
		       magma_int_t *m, double *w, double *work, magma_int_t lwork,
		       double *rwork, magma_int_t lrwork, magma_int_t *iwork, magma_int_t liwork, magma_int_t *info);

extern "C" magma_int_t
magma_dsytrd_bsy2trc( int THREADS, int WANTZ, char uplo, int NE, int n, int NB, 
		      double *A, int LDA, double *D, double *E, magmaDouble_ptr dT1, 
		      size_t dT1_offset, int ldt1);

extern "C" magma_int_t
magma_dsytrd_bsy2trc_v5(magma_int_t threads, magma_int_t wantz, char uplo, 
			magma_int_t ne, magma_int_t n, magma_int_t nb,
                        double *A, magma_int_t lda, double *D, double *E,
                        magmaDouble_ptr dT1, size_t dT1_offset, magma_int_t ldt1);

#if defined(PRECISION_z) || defined(PRECISION_d)
extern "C" void cmp_vals(int n, double *wr1, double *wr2, double *nrmI, double *nrm1, double *nrm2);
extern "C" void dcheck_eig_(char *JOBZ, int  *MATYPE, int  *N, int  *NB,
			    double* A, int  *LDA, double *AD, double *AE, double *D1, double *EIG,
			    double *Z, int  *LDZ, double *WORK, double *RWORK, double *RESU);
#endif

/* ////////////////////////////////////////////////////////////////////////////
   -- Testing dsytrd_sy2sb
*/
int main( int argc, char** argv)
{
    real_Double_t time, tband;
    double             eps, gflops, gpu_perf, gpu_time;
    double *h_A, *h_R, *h_work;
    magmaDouble_ptr dT1;
    double *tau;
    double *D, *E;

    /* Matrix size */
    magma_int_t N = 0, n2, lda, lwork,ldt,lwork0;
    magma_int_t size[10] = {1024,2048,3072,4032,5184,6016,7040,8064,9088,10112};

    magma_int_t i, j, k, info, checkres, once = 0;
    magma_int_t ione     = 1;
    magma_int_t ISEED[4] = {0,0,0,1};
    const char *uplo = MagmaLowerStr;

    magma_int_t WANTZ=0;
    magma_int_t THREADS=1;
    magma_int_t NE = 0;
    magma_int_t NB = 0;
    magma_int_t ngpu = 1;

    checkres  = getenv("MAGMA_TESTINGS_CHECK") != NULL;

    if (argc != 1){
        for(i = 1; i<argc; i++){
            if (strcmp("-N", argv[i])==0) {
                N = atoi(argv[++i]);
                once = 1;
            }
            else if (strcmp("-NB", argv[i])==0) {
                NB = atoi(argv[++i]);
            }
            else if (strcmp("-threads", argv[i])==0) {
                THREADS = atoi(argv[++i]);
            }
            else if (strcmp("-ngpu", argv[i])==0) {
                ngpu = atoi(argv[++i]);
            }
            else if (strcmp("-wantz", argv[i])==0) {
                WANTZ = atoi(argv[++i]);
            }
            else if (strcmp("-NE", argv[i])==0) {
                NE = atoi(argv[++i]);
            }
            else if ( strcmp("-c", argv[i]) == 0 ) {
                checkres = 1;
            }
            else if (strcmp("-U", argv[i])==0)
                uplo = MagmaUpperStr;
            else if (strcmp("-L", argv[i])==0)
                uplo = MagmaLowerStr;
        }
        if ( N > 0 )
            printf("  testing_dsytrd_sy2sb -L|U -N %d -NB %d   -wantz %d   -threads %d    check %d \n\n", 
		   N, NB, WANTZ, THREADS, checkres);
        else
        {
            printf("\nUsage: \n");
            printf("  testing_dsytrd_sy2sb -L|U -N %d -NB  -wantz -threads \n\n", 1024);
            exit(1);
        }
    }
    else {
        printf("\nUsage: \n");
        printf("  testing_dsytrd_sy2sb -L|U -N %d\n\n", 1024);
        N = size[9];
    }
        
    printf ("HELLOOOOOOOO\n");

    eps = lapackf77_dlamch( "E" );
    lda = N;
    ldt = N;
    n2  = lda * N; 
    if(NB<1)
        NB  = 64; //64; //magma_get_dsytrd_sy2sb_nb(N);

    if(NE<1)
        NE  = N; //64; //magma_get_dsytrd_sy2sb_nb(N);

    /* We suppose the magma NB is bigger than lapack NB */
    lwork0 = N*NB; 

    /* 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 || num < 1 ) {
      fprintf( stderr, "magma_get_devices failed: %d\n", err );
      exit(-1);
    }
    err = magma_queue_create( device, &queue );
    if ( err != 0 ) {
      fprintf( stderr, "magma_queue_create failed: %d\n", err );
      exit(-1);
    }

    /* Allocate host memory for the matrix */
    TESTING_MALLOC( h_A,    double, lda*N );
    TESTING_MALLOC( tau,    double, N-1   );

    TESTING_MALLOC( D,    double, N );
    TESTING_MALLOC( E,    double, N );

    TESTING_MALLOC_HOST( h_R,    double, lda*N );
    TESTING_MALLOC_HOST( h_work, double, lwork0 );

    /* Allocate memory on the device */
    TESTING_MALLOC_DEV( dT1,  double, (N*NB) );

    printf("  N    GPU GFlop/s   \n");
    printf("=====================\n");
    for(i=0; i<10; i++){
        if ( !once ) {
            N = size[i];
        }
        lda  = N;
        n2   = N*lda;
        gflops = FLOPS_DSYTRD( (double)N ) / 1e9;
        
        // if(WANTZ) gflops = 2.0*gflops;

        /* ====================================================================
           Initialize the matrix
           =================================================================== */
        lapackf77_dlarnv( &ione, ISEED, &n2, h_A );

        // Make the matrix hermitian 
        {
            magma_int_t i, j;
            for(i=0; i<N; i++) {
                MAGMA_D_SET2REAL( h_A[i*lda+i], ( MAGMA_D_REAL(h_A[i*lda+i]) ) );
                for(j=0; j<i; j++)
                    h_A[i*lda+j] = MAGMA_D_CNJG(h_A[j*lda+i]);
            }
        }

        lapackf77_dlacpy( MagmaUpperLowerStr, &N, &N, h_A, &lda, h_R, &lda );

        /* ====================================================================
           Performs operation using MAGMA
           =================================================================== */
        char *jobz = (char*)MagmaVecStr;
        char range = 'A';
        magma_int_t fraction_ev = 100;
        magma_int_t il,iu,m1,m2;
        double vl,vu;

        if (fraction_ev == 0){
            il = N / 10;
            iu = N / 5+il;
        }
        else {
            il = 1;
            iu = (int)(fraction_ev*N);
            if (iu < 1) iu = 1;
        }

        double *hh_work;
        double *rwork;
        magma_int_t *iwork;
    
        time = magma_wtime();
        magma_dsytrd_sy2sb(MagmaLower, N, NB, h_R, lda, tau, h_work, lwork0, 
                           dT1, 0, THREADS, &info, queue);
        tband = magma_wtime() - time;

        printf("  Finish BAND  N %d  NB %d  ngpu %d timing= %lf \n" ,
               N, NB, ngpu, tband);

        // magma_dsytrd_bsy2trc_v5(THREADS, WANTZ, uplo[0], NE, N, NB, h_R, lda, D, E, dT1, 0, ldt);
	return 0;

/*
        magma_dsytrd_sy2sb(uplo[0], N, NB, h_R, lda, tau, h_work, lwork, dT1, THREADS, &info);
        time = magma_wtime() - time;
        printf("  Finish BAND  N %d  NB %d  ngpu %d timing= %lf \n" ,N,NB,ngpu, time);
        magma_dsytrd_bsy2trc(THREADS, WANTZ, uplo[0], NE, N, NB, h_R, lda, D, E, dT1, ldt);
*/

        magma_int_t nb = magma_get_dsytrd_nb(N);
        magma_int_t lwork = magma_dbulge_get_lq2(N) + 2*N + N*N;
        magma_int_t lrwork = 1 + 5*N +2*N*N;
        magma_int_t liwork = 3 + 5*N;
       
        TESTING_MALLOC_HOST(hh_work, double,  lwork);
        TESTING_MALLOC_HOST(  rwork,             double, lrwork);
        TESTING_MALLOC_HOST(  iwork,        magma_int_t, liwork);

        if(ngpu==1){
            printf("calling dsyevdx_2stage 1 GPU\n");
            //magma_dsyevdx_2stage(jobz[0], range, uplo[0], N, h_R, lda, vl, vu, il, iu, &m1, D, 
	    //			 hh_work, lwork, rwork, lrwork, iwork, liwork, &info);
        }else{
            printf("calling dsyevdx_2stage_m %d GPU\n",ngpu);
            return 0;
            //magma_dsyevdx_2stage_m(ngpu,jobz[0], range, uplo[0], N, h_R, lda, vl, vu, il, 
	    //			   iu, &m1, D, hh_work, lwork, rwork, lrwork, iwork, liwork, &info);
        }

        time = magma_wtime() - time;

        gpu_perf = gflops / time;
        gpu_time = time;

        /* =====================================================================
           Check the factorization
           =================================================================== */
        /*
        if ( checkres ) {
            FILE        *fp ;

            printf("Writing input matrix in matlab_i_mat.txt ...\n");
            fp = fopen ("matlab_i_mat.txt", "w") ;
            if( fp == NULL ){ printf("Couldn't open output file\n"); exit(1);}

            for(j=0; j<N; j++)
                for(k=0; k<N; k++)
                    {
                        #if defined(PRECISION_z) || defined(PRECISION_c)
                        fprintf(fp, "%5d %5d %11.8f %11.8f\n", k+1, j+1, 
                                h_A[k+j*lda].x, h_A[k+j*lda].y);
                        #else
                        fprintf(fp, "%5d %5d %11.8f\n", k+1, j+1, h_A[k+j*lda]);
                        #endif
                    }
            fclose( fp ) ;

          printf("Writing output matrix in matlab_o_mat.txt ...\n");
          fp = fopen ("matlab_o_mat.txt", "w") ;
          if( fp == NULL ){ printf("Couldn't open output file\n"); exit(1);}

          for(j=0; j<N; j++)
            for(k=0; k<N; k++)
              {
                #if defined(PRECISION_z) || defined(PRECISION_c)
                fprintf(fp, "%5d %5d %11.8f %11.8f\n", k+1, j+1,
                        h_R[k+j*lda].x, h_R[k+j*lda].y);
                #else
                fprintf(fp, "%5d %5d %11.8f\n", k+1, j+1, h_R[k+j*lda]);
                #endif
              } 
          fclose( fp ) ;

        }*/



        /* =====================================================================
           Print performance and error.
           =================================================================== */
#if defined(CHECKEIG)
#if defined(PRECISION_z)  || defined(PRECISION_d)
        if ( checkres ) {
            printf("  Total N %5d  flops %6.2f  timing %6.2f seconds\n", (int) N, gpu_perf, gpu_time );
            char JOBZ;
            if(WANTZ==0) 
                    JOBZ='N';
            else
                    JOBZ = 'V';
            double nrmI=0.0, nrm1=0.0, nrm2=0.0;
            int    lwork2 = 256*N;
            double *work2     = (double *) malloc (lwork2*sizeof(double));
            double *rwork2     = (double *) malloc (N*sizeof(double));
            double *D2          = (double *) malloc (N*sizeof(double));
            double *AINIT    = (double *) malloc (N*lda*sizeof(double));
            memcpy(AINIT, h_A, N*lda*sizeof(double));
            /* compute the eigenvalues using lapack routine to be able to compare to it and used as ref */
            time = magma_wtime();
            i= min(12,THREADS);

#if defined(USEMKL)
            mkl_set_num_threads( i );
#endif
#if defined(USEACML)
            omp_set_num_threads(i);
#endif

#if defined(PRECISION_z) || defined (PRECISION_c)
            lapackf77_dsyev( "N", "L", &N, h_A, &lda, D2, work2, &lwork2, rwork2, &info );
#else
            lapackf77_dsyev( "N", "L", &N, h_A, &lda, D2, work2, &lwork2, &info );
#endif
            ///* call eigensolver for our resulting tridiag [D E] and for Q */
            //dstedc_withZ('V', N, D, E, h_R, lda);
            ////dsterf_( &N, D, E, &info); 
            ////
            time = magma_wtime() - time;
            printf("  Finish CHECK - EIGEN   timing= %lf  threads %d \n", time, i);

            /*
            for(i=0;i<10;i++)
                printf(" voici lpk D[%d] %e\n",i,D2[i]);
            */

            //double mydz=0.0,mydo=1.0;
            //double *Z = (double *) malloc(N*lda*sizeof(double));
           // dgemm_("N","N",&N,&N,&N,&mydo,h_R,&lda,h_A,&lda,&mydz,Z,&lda);


           /* compare result */
           cmp_vals(N, D2, D, &nrmI, &nrm1, &nrm2);


           double *WORKAJETER;
           double *RWORKAJETER, *RESU;
           WORKAJETER  = (double *) malloc( (2* N * N + N) * sizeof(double) );
           RWORKAJETER = (double *) malloc( N * sizeof(double) );
           RESU        = (double *) malloc(10*sizeof(double));
           int MATYPE;
           memset(RESU,0,10*sizeof(double));

 
           MATYPE=3;
           double NOTHING=0.0;
           time = magma_wtime();
           // check results
           dcheck_eig_(&JOBZ, &MATYPE, &N, &NB, AINIT, &lda, &NOTHING, &NOTHING, D2 , D, h_R, &lda, WORKAJETER, RWORKAJETER, RESU );
           time = magma_wtime() - time;
           printf("  Finish CHECK - results timing= %lf \n", time);
#if defined(USEMKL)
           mkl_set_num_threads( 1 );
#endif
#if defined(USEACML)
           omp_set_num_threads(1);
#endif

           printf("\n");
           printf(" ================================================================================================================\n");
           printf("   ==> INFO voici  threads=%d    N=%d    NB=%d   WANTZ=%d\n", (int) THREADS, (int) N, (int) NB, (int) WANTZ);
           printf(" ================================================================================================================\n");
           printf("            DSBTRD                : %15s \n", "STATblgv9withQ    ");
           printf(" ================================================================================================================\n");
           if(WANTZ>0)
              printf(" | A - U S U' | / ( |A| n ulp )   : %15.3E   \n",RESU[0]); 
           if(WANTZ>0)
              printf(" | I - U U' | / ( n ulp )         : %15.3E   \n", RESU[1]);
           printf(" | D1 - EVEIGS | / (|D| ulp)      : %15.3E   \n",  RESU[2]);
           printf(" max | D1 - EVEIGS |              : %15.3E   \n",  RESU[6]);
           printf(" ================================================================================================================\n\n\n");
       
           printf(" ****************************************************************************************************************\n");
           printf(" * Hello here are the norm  Infinite (max)=%e  norm one (sum)=%e   norm2(sqrt)=%e *\n",nrmI, nrm1, nrm2);
           printf(" ****************************************************************************************************************\n\n");

        } 
#endif         
#endif  

      printf("  Total N %5d  flops %6.2f        timing %6.2f seconds\n", (int) N, gpu_perf, gpu_time );
      printf("============================================================================\n\n\n");

      if ( once )
          break;
    }

    /* Memory clean up */
    TESTING_FREE( h_A );
    TESTING_FREE( tau ); 

    TESTING_FREE( D   );
    TESTING_FREE( E   );

    TESTING_FREE_HOST( h_R ); 
    TESTING_FREE_HOST( h_work ); 

    /* Shutdown */
    magma_queue_destroy( queue );
    magma_finalize();

    return EXIT_SUCCESS;
}
