#include <stdio.h>
#include <time.h>

#include "su3.h"
#include "qpx.h"
#include "rnd_ziff.h"

#define _cv_print(a) \
    printf( "vecX = {%f+j%f, %f+j%f, %f+j%f}\n", a.c1.re, a.c1.im, a.c2.re, a.c2.im, a.c3.re, a.c3.im ) 

#define _cv_print2(a,b) \
    printf( "vecA = {%f+j%f, %f+j%f, %f+j%f}\n", a.c1.re, a.c1.im, a.c2.re, a.c2.im, a.c3.re, a.c3.im ); \
    printf( "vecB = {%f+j%f, %f+j%f, %f+j%f}\n", b.c1.re, b.c1.im, b.c2.re, b.c2.im, b.c3.re, b.c3.im ) 

#define FILL_VALUE rndgaussian()
#define ITERATIONS 2000000

extern void qoe_bgq(int *piup,int *pidn,su3 *u,spinor *pk);
extern void qoe(int *piup,int *pidn,su3 *u,spinor *pk);
extern void mul_pauli(pauli *m,weyl *s,weyl *r);
extern void fill_r12r34rs();
extern void dummy_Qhat_blk_BGQ(pauli* m, su3 *b, spinor* pk, int iter);

void fill_spinor( spinor* s ) {
    float* p = &((*s).c1.c1.re);
    for (int j=0;j<24;++j ) {
        *p = FILL_VALUE;
        p+=1;
    }
}

void fill_pauli( pauli* m, int method ) {
    float* p = &((*m).u[0]);
    if ( method==0 ) {
        for (int j=0;j<36;++j ) {
            *p = FILL_VALUE;
            p+=1;
        }
    }
    else {
    for (int j=0;j<36;++j ) {
        *p = 0;
        p+=1;
    }

    if ( method==1 ) {
        m->u[0] = FILL_VALUE;
        m->u[1] = FILL_VALUE;
        m->u[2] = FILL_VALUE;
        m->u[3] = FILL_VALUE;
        m->u[4] = FILL_VALUE;
        m->u[5] = FILL_VALUE;
    }
    else if ( method==2 ) {
        m->u[6] = FILL_VALUE;
        m->u[7] = FILL_VALUE;
        m->u[8] = FILL_VALUE;
        m->u[9] = FILL_VALUE;
        m->u[16] = FILL_VALUE;
        m->u[17] = FILL_VALUE;

        m->u[30] = FILL_VALUE;
        m->u[31] = FILL_VALUE;
        m->u[32] = FILL_VALUE;
        m->u[33] = FILL_VALUE;
        m->u[34] = FILL_VALUE;
        m->u[35] = FILL_VALUE;
    }
    else if ( method==3 ) {
        m->u[10] = FILL_VALUE;
        m->u[20] = FILL_VALUE;
        m->u[28] = FILL_VALUE;
    }
    else if ( method==4 ) {
        m->u[11] = FILL_VALUE;
        m->u[21] = FILL_VALUE;
        m->u[29] = FILL_VALUE;
    }
    else if ( method==5 ) {
        m->u[12] = FILL_VALUE;
        m->u[14] = FILL_VALUE;
        m->u[22] = FILL_VALUE;
    }
    else if ( method==6 ) {
        m->u[13] = FILL_VALUE;
        m->u[15] = FILL_VALUE;
        m->u[23] = FILL_VALUE;
    }
    else if ( method==7 ) {
        m->u[18] = FILL_VALUE;
        m->u[24] = FILL_VALUE;
        m->u[26] = FILL_VALUE;
    }
    else if ( method==8 ) {
        m->u[19] = FILL_VALUE;
        m->u[25] = FILL_VALUE;
        m->u[27] = FILL_VALUE;
    }
    }
}

void print_spinor( spinor* s ) {
    float* p = &((*s).c1.c1.re);
    for (int j=0;j<12;++j ) {
        printf( "%d = %f+j%f\n", j, *p, *(p+1) ); 
        p+=2;
    }
    printf( "\n" );
}

/* difference */

void diff_spinor( spinor* s1, spinor* s2 ) {
    float* p1 = &((*s1).c1.c1.re);
    float* p2 = &((*s2).c1.c1.re);
    for (int j=0;j<24;++j ) {
        printf( "%d diff=%f\n", j, *p1-*p2 ); 
        p1+=1;
        p2+=1;
    }
    printf( "\n" );
}

/* compare */

void compare_qoe_qeo( su3* u ) {
    printf( "\nCompare QOE - QEO\n" );

    InitRandom( 11 );

    spinor src[8];
    spinor src2[8];
    
    for ( int j=0; j<8; ++j ) {
        fill_spinor( &(src[j]) );
        src2[j] = src[j];
    }

    int piup[4];
    int pidown[4];
    
    for( int mu=0; mu<4; ++mu ) {
        piup[mu] = 2*mu;
        pidown[mu] = 2*mu+1;
    }

    printf( "\nOriginal oe\n" );
    qoe( piup, pidown, u, src );
    printf( "\nOriginal eo\n" );
    qeo( piup, pidown, u, src );

    printf( "\nQPX oe\n" );
    qoe_bgq( piup, pidown, u, src2 );
    printf( "\nQPX eo\n" );
    qeo_bgq( piup, pidown, u, src2 );

    for ( int j=0; j<8; ++j ) {
        printf( "spinor diff no %d\n", j );
        diff_spinor( &src[j], &src2[j] );
    }
}

void compare_sw() {
    printf( "\nCompare SW\n" );

    InitRandom( 11 );

    spinor src;
    fill_spinor( &src );
    pauli m;

    _qpx_pauli_declare(pm);
    _qpx_vectorpair_declare(x);
    _qpx_vectorpair_declare(pm_x);

    _qpx_vector_declare(perm1);
    _qpx_vector_declare(perm2);
    _qpx_vector_declare(work1);
    _qpx_vector_declare(work2);
      
    for( int meth=0; meth<9; ++meth ) {
        printf( "\n- method %d\n", meth );
        fill_pauli( &m, meth );
        
        spinor src_org = src;
        spinor src_qpx = src;
        
        printf( "Original\n" );
        mul_pauli( &m,(weyl*)(&(src_org.c1)),(weyl*)(&(src_org.c3)) );
        _cv_print2( src_org.c3, src_org.c4 );

        printf( "QPX\n" );
        _qpx_pauli_load_all(pm,m);
        //_qpx_vectorpair_au_load( x, src_qpx.c1, src_qpx.c2, work1, perm1 );
        _qpx_vectorpair_weyl_load( x, src_qpx, work1, perm1 );
        _qpx_pauli_multiply_all(pm_x,pm,x, perm1,work1, perm2,work2);
        _qpx_vectorpair_print( pm_x ); 
    }
}

void compare_qoe_sw_qeo ( su3* u ) {
    printf( "\nCompare QOE - SW - QEO\n" );

    InitRandom( 11 );

    spinor src[8];
    spinor src2[8];
    
    pauli m;

    _qpx_pauli_declare(pm);
    _qpx_vectorpair_declare(x);
    _qpx_vectorpair_declare(pm_x);

    _qpx_vector_declare(perm1);
    _qpx_vector_declare(perm2);
    _qpx_vector_declare(work1);
    _qpx_vector_declare(work2);

    for ( int j=0; j<8; ++j ) {
        fill_spinor( &(src[j]) );
        src2[j] = src[j];
    }
    
    fill_pauli( &m, 0 );

    int piup[4];
    int pidown[4];
    
    for( int mu=0; mu<4; ++mu ) {
        piup[mu] = 2*mu;
        pidown[mu] = 2*mu+1;
    }

    printf( "\nOriginal oe\n" );
    qoe( piup, pidown, u, src );
    printf( "\nOriginal sw\n" );    // hack because I cannot access rs from here
    mul_pauli( &m,(weyl*)(&(src[0].c1)),(weyl*)(&(src[0].c1)) );
    _cv_print2( src[0].c1, src[0].c2 );
    mul_pauli( &m,(weyl*)(&(src[0].c3)),(weyl*)(&(src[0].c3)) );
    _cv_print2( src[0].c3, src[0].c4 );
    printf( "\nOriginal eo\n" );
    qeo( piup, pidown, u, src );

    printf( "\nQPX oe\n" );
    qoe_bgq( piup, pidown, u, src2 );
    printf( "\nQPX sw\n" );         // hack - do the same here for comparison
    _qpx_pauli_load_all(pm,m);
    //_qpx_vectorpair_au_load( x, src2[0].c1, src2[0].c2, work1, perm1 );
    _qpx_vectorpair_weyl_load( x, src2[0], work1, perm1 );
    _qpx_pauli_multiply_all(pm_x,pm,x, perm1,work1, perm2,work2);
    //_qpx_vectorpair_au_store( src2[0].c1, src2[0].c2, pm_x, work1, work2 );
    _qpx_vectorpair_weyl_store( src2[0], pm_x, x, work1 );
    _cv_print2( src2[0].c1, src2[0].c2 );

    //_qpx_vectorpair_au_load( x, src2[0].c3, src2[0].c4, work1, perm1 );
    _qpx_vectorpair_weyl34_load( x, src2[0], work1, perm1 );
    _qpx_pauli_multiply_all(pm_x,pm,x, perm1,work1, perm2,work2);
    //_qpx_vectorpair_au_store( src2[0].c3, src2[0].c4, pm_x, work1, work2 );
    _qpx_vectorpair_weyl34_store( src2[0], pm_x, x, work1 );
    _cv_print2( src2[0].c3, src2[0].c4 );
    printf( "\nQPX eo\n" );
    qeo_bgq( piup, pidown, u, src2 );

    for ( int j=0; j<8; ++j ) {
        printf( "spinor diff no %d\n", j );
        diff_spinor( &src[j], &src2[j] );
    }
}

void timings( su3* u ) {
    InitRandom( 11 );

    spinor src[8];
    spinor src2[8];
    
    pauli m[2];

    _qpx_pauli_declare(pm);
    _qpx_vectorpair_declare(x);
    _qpx_vectorpair_declare(pm_x);

    _qpx_vector_declare(perm1);
    _qpx_vector_declare(perm2);
    _qpx_vector_declare(work1);
    _qpx_vector_declare(work2);

    for ( int j=0; j<8; ++j ) {
        fill_spinor( &(src[j]) );
        src2[j] = src[j];
    }
    
    fill_pauli( m, 0 );
    fill_pauli( &m[1], 0 );

    int piup[4];
    int pidown[4];
    
    for( int mu=0; mu<4; ++mu ) {
        piup[mu] = 2*mu;
        pidown[mu] = 2*mu+1;
    }


      int msec;
      clock_t start, diff;
      printf( "\nqoe - timings\n" );

      start = clock();
      for( int j=0; j<ITERATIONS; ++j ) 
          qoe( piup, pidown, u, src );
      diff = clock() - start;
      
      msec = diff * 1000 / CLOCKS_PER_SEC;
      printf("Original: Time taken %d seconds %d milliseconds\n", msec/1000, msec%1000);

      start = clock();
      for( int j=0; j<ITERATIONS; ++j ) 
          qoe_bgq( piup, pidown, u, src2 );
      diff = clock() - start;
      
      msec = diff * 1000 / CLOCKS_PER_SEC;
      printf("QPX: Time taken %d seconds %d milliseconds\n", msec/1000, msec%1000);


      printf( "\nqeo - timings\n" );
       
      start = clock();
      for( int j=0; j<ITERATIONS; ++j ) 
          qeo( piup, pidown, u, src );
      diff = clock() - start;
      
      msec = diff * 1000 / CLOCKS_PER_SEC;
      printf("Original: Time taken %d seconds %d milliseconds\n", msec/1000, msec%1000);

      start = clock();
      for( int j=0; j<ITERATIONS; ++j ) 
          qeo_bgq( piup, pidown, u, src2 );
      diff = clock() - start;
      
      msec = diff * 1000 / CLOCKS_PER_SEC;
      printf("QPX: Time taken %d seconds %d milliseconds\n", msec/1000, msec%1000);


      printf( "\npauli - timings\n" );

      start = clock();
      for( int j=0; j<ITERATIONS; ++j ) 
          mul_pauli( m,(weyl*)(&(src[0].c1)),(weyl*)(&(src[0].c1)) );
      diff = clock() - start;
      
      msec = diff * 1000 / CLOCKS_PER_SEC;
      printf("Original: Time taken %d seconds %d milliseconds\n", msec/1000, msec%1000);

      start = clock();
      for( int j=0; j<ITERATIONS; ++j ) {
          _qpx_pauli_load_all(pm,m[0]);
          //_qpx_vectorpair_au_load( x, src2[0].c1, src2[0].c2, work1, perm1 );
          _qpx_vectorpair_weyl_load( x, src2[0], work1, perm1 );
          _qpx_pauli_multiply_all(pm_x,pm,x, perm1,work1, perm2,work2);
          //_qpx_vectorpair_assign( x, pm_x );
          _qpx_vectorpair_weyl_store( src2[0], pm_x, x, work1 );
      }
      diff = clock() - start;
      
      msec = diff * 1000 / CLOCKS_PER_SEC;
      printf("QPX: Time taken %d seconds %d milliseconds\n", msec/1000, msec%1000);


      printf( "\nJOINT - timings\n" );

      start = clock();
      for( int j=0; j<ITERATIONS/4; ++j ) { 
          qoe( piup, pidown, u, src );
          mul_pauli( m,(weyl*)(&(src[0].c1)),(weyl*)(&(src[0].c1)) );
          mul_pauli( &m[1],(weyl*)(&(src[0].c3)),(weyl*)(&(src[0].c3)) );
          qeo( piup, pidown, u, src );
      }
      diff = clock() - start;
      
      msec = diff * 1000 / CLOCKS_PER_SEC;
      printf("Original: Time taken %d seconds %d milliseconds\n", msec/1000, msec%1000);

      start = clock();
      for( int j=0; j<ITERATIONS/4; ++j ) { 
          qoe_bgq( piup, pidown, u, src2 );

          _qpx_pauli_load_all(pm, m[0] );
          //_qpx_vectorpair_au_load( x, src2[0].c1, src2[0].c2, work1, perm1 );
          _qpx_vectorpair_weyl_load( x, src2[0], work1, perm1 );
          _qpx_pauli_multiply_all(pm_x,pm,x, perm1,perm2,work1,work2);
          //_qpx_vectorpair_assign( x, pm_x );
          _qpx_vectorpair_weyl_store( src2[0], pm_x, x, work1 );

          _qpx_pauli_load_all(pm, m[1] );
          //_qpx_vectorpair_au_load( x, src2[0].c3, src2[0].c4, work1, perm1 );
          _qpx_vectorpair_weyl34_load( x, src2[0], work1, perm1 );
          _qpx_pauli_multiply_all(pm_x,pm,x, perm1,perm2,work1,work2);
          //_qpx_vectorpair_assign( x, pm_x );
          _qpx_vectorpair_weyl34_store( src2[0], pm_x, x, work1 );

          qeo_bgq( piup, pidown, u, src2 );
      }
      // dummy_Qhat_blk_BGQ( M, u, src2, ITERATIONS/4 );
      diff = clock() - start;
      
      msec = diff * 1000 / CLOCKS_PER_SEC;
      printf("QPX: Time taken %d seconds %d milliseconds\n", msec/1000, msec%1000);
}

/* main */

int main() {
    InitRandom( 11 );

    // load proper su3 matrices
    const int num_su3 = 8;
    su3 u[num_su3]; // this is float

#if 0
    int o1,o2;
    o1 = 00123;
    o2 = 0123;
    printf( "octal test %d ~ %d \n", o1, o2 );
    o1 = 002367;
    o2 = 02367;
    printf( "octal test %d ~ %d \n", o1, o2 );
#endif

    size_t chk_size;
    //char* fname = "/work/jikp05/jikp0509/configs/qcdsf.733.00281.lime.contents/msg01.rec02.ildg-binary-data";
    char* fname = "/gpfs/scratch/userexternal/bglaessl/configs/b5p29kp13640-48x64/qcdsf.733.00281.lime.contents/msg01.rec02.ildg-binary-data";
    FILE* fpin = fopen( fname, "r" );
    if ( fpin != NULL ) {
        chk_size = fread( &(u[0].c11.re), sizeof(float), num_su3*18, fpin );
        printf( "read %lu floats\n", chk_size );
        fclose( fpin );

#if 0
        printf( "load su3 matrices - checked\n" );
        printf( "elem(0,11) = %f+j*%f\n", u[0].c11.re, u[0].c11.im );
        printf( "elem(0,12) = %f+j*%f\n", u[0].c12.re, u[0].c12.im );
        printf( "elem(0,13) = %f+j*%f\n", u[0].c13.re, u[0].c13.im );
        printf( "elem(0,21) = %f+j*%f\n", u[0].c21.re, u[0].c21.im );
        printf( "elem(0,22) = %f+j*%f\n", u[0].c22.re, u[0].c22.im );
        printf( "elem(0,23) = %f+j*%f\n", u[0].c23.re, u[0].c23.im );
        printf( "elem(0,31) = %f+j*%f\n", u[0].c31.re, u[0].c31.im );
        printf( "elem(0,32) = %f+j*%f\n", u[0].c32.re, u[0].c32.im );
        printf( "elem(0,33) = %f+j*%f\n\n", u[0].c33.re, u[0].c33.im );
#endif

#ifdef VERBOSE_DEBUG
      compare_qoe_qeo( u );
      compare_sw( );
      compare_qoe_sw_qeo( u );
#else 
      timings( u );
#endif
    }

    return 0;
}

