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

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

extern void *amalloc(size_t size,int p);

extern complex spinor_prod(int vol,int icom,spinor *pk,spinor *pl);
extern float spinor_prod_re(int vol,int icom,spinor *pk,spinor *pl);
extern float norm_square(int vol,int icom,spinor *pk);
extern void mulc_spinor_add(int vol,spinor *pk,spinor *pl,complex z);
//extern void project(int vol,int icom,spinor *pk,spinor *pl);
extern float normalize(int vol,int icom,spinor *pk);
extern void rotate(int vol,int n,spinor **ppk,complex v[]);
extern void mulg5(int vol,spinor *pk);
extern void mulmg5(int vol,spinor *pk);
extern void mulg5_spinor_sub(int vol,spinor *pk,spinor *pl);

extern complex spinor_prod_bgq(int vol,int icom,spinor *pk,spinor *pl);
extern float spinor_prod_re_bgq(int vol,int icom,spinor *pk,spinor *pl);
extern float norm_square_bgq(int vol,int icom,spinor *pk);
extern void mulc_spinor_add_bgq(int vol,spinor *pk,spinor *pl,complex z);
//extern void project_bgq(int vol,int icom,spinor *pk,spinor *pl);
extern float normalize_bgq(int vol,int icom,spinor *pk);
extern void rotate_bgq(int vol,int n,spinor **ppk,complex v[]);
extern void mulg5_bgq(int vol,spinor *pk);
extern void mulmg5_bgq(int vol,spinor *pk);
extern void mulg5_spinor_sub_bgq(int vol,spinor *pk,spinor *pl);

#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
#define VOLUME 512

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

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_linalg( ) {
    printf( "\nCompare LINALG\n" );

    InitRandom( 11 );

    const int vol=1;
    const int icom=0;

    complex fact;
    fact.re = FILL_VALUE;
    fact.im = FILL_VALUE;

    complex res_c_orig;
    float   res_r_orig;
    complex res_c_qpx;
    float   res_r_qpx;

    spinor vecK_orig;
    spinor vecL_orig;
    spinor vecK_qpx;
    spinor vecL_qpx;

    const int rot_n = 4;
    const int rot_vol = 4;
    complex rot_factors[rot_n*rot_n];
    spinor** rot_spinors_orig;
    spinor** rot_spinors_qpx;
    rot_spinors_orig = amalloc(rot_vol*sizeof(spinor*),3);
    rot_spinors_qpx = amalloc(rot_vol*sizeof(spinor*),3);
    *rot_spinors_orig = amalloc(rot_n*rot_vol*sizeof(spinor),6);
    *rot_spinors_qpx = amalloc(rot_n*rot_vol*sizeof(spinor),6);

    *(rot_spinors_orig+1) = (*rot_spinors_orig)+rot_vol;
    *(rot_spinors_orig+2) = (*rot_spinors_orig+1)+rot_vol;
    *(rot_spinors_orig+3) = (*rot_spinors_orig+2)+rot_vol;

    *(rot_spinors_qpx+1) = (*rot_spinors_qpx)+rot_vol;
    *(rot_spinors_qpx+2) = (*rot_spinors_qpx+1)+rot_vol;
    *(rot_spinors_qpx+3) = (*rot_spinors_qpx+2)+rot_vol;

    for( int j=0; j<rot_n; ++j ) {
        for( int k=0; k<rot_n; ++k ) {
            rot_factors[j*rot_n+k].re = FILL_VALUE;
            rot_factors[j*rot_n+k].im = FILL_VALUE;

            fill_spinor( (*rot_spinors_orig+j)+k );
            *((*rot_spinors_qpx+j)+k) = *((*rot_spinors_orig+j)+k);

//            diff_spinor( (*rot_spinors_orig+j)+k, (*rot_spinors_qpx+j)+k );
        }
    }

    fill_spinor( &vecK_orig );
    fill_spinor( &vecL_orig );

    printf( "\n - spinor_prod\n" );
    vecK_qpx = vecK_orig;
    vecL_qpx = vecL_orig;
    res_c_orig = spinor_prod( vol, icom, &vecK_orig, &vecL_orig );
    res_c_qpx = spinor_prod_bgq( vol, icom, &vecK_qpx, &vecL_qpx );
    printf( "orig : %f+j*%f\nqpx  : %f+j*%f\n", res_c_orig.re, res_c_orig.im, res_c_qpx.re, res_c_qpx.im );

    printf( "\n - spinor_prod_re\n" );
    //vecK_qpx = vecK_orig;
    //vecL_qpx = vecL_orig;
    res_r_orig = spinor_prod_re( vol, icom, &vecK_orig, &vecL_orig );
    res_r_qpx = spinor_prod_re_bgq( vol, icom, &vecK_qpx, &vecL_qpx );
    printf( "orig : %f\nqpx  : %f\n", res_r_orig, res_r_qpx );

    printf( "\n - norm_square\n" );
    //vecK_qpx = vecK_orig;
    //vecL_qpx = vecL_orig;
    res_r_orig = norm_square( vol, icom, &vecK_orig );
    res_r_qpx = norm_square_bgq( vol, icom, &vecK_qpx );
    printf( "orig : %f\nqpx  : %f\n", res_r_orig, res_r_qpx );

    printf( "\n - mulc_spinor_add\n" );
    vecK_qpx = vecK_orig;
    vecL_qpx = vecL_orig;
    mulc_spinor_add( vol, &vecK_orig, &vecL_orig, fact );
    mulc_spinor_add_bgq( vol, &vecK_qpx, &vecL_qpx, fact );
    diff_spinor( &vecK_orig, &vecK_qpx );
    //printf( "orig : %f\nqpx  :%f\n", res_r_orig, res_r_qpx );

    printf( "\n - normalize\n" );
    vecK_qpx = vecK_orig;
    //vecL_qpx = vecL_orig;
    res_r_orig = normalize( vol, icom, &vecK_orig );
    res_r_qpx = normalize_bgq( vol, icom, &vecK_qpx );
    printf( "orig : %f\nqpx  : %f\n", res_r_orig, res_r_qpx );
    diff_spinor( &vecK_orig, &vecK_qpx );

    printf( "\n - rotate\n" );
    //vecK_qpx = vecK_orig;
    //vecL_qpx = vecL_orig;
    rotate( rot_vol, rot_n, rot_spinors_orig, rot_factors );
    rotate_bgq( rot_vol, rot_n, rot_spinors_qpx, rot_factors );
    for( int j=0; j<rot_n; ++j )
        for( int k=0; k<rot_n; ++k )
            diff_spinor( &rot_spinors_orig[j][k], &rot_spinors_qpx[j][k] );

    printf( "\n - mulg5\n" );
    vecK_qpx = vecK_orig;
    //vecL_qpx = vecL_orig;
    mulg5( vol, &vecK_orig );
    mulg5_bgq( vol, &vecK_qpx );
    diff_spinor( &vecK_orig, &vecK_qpx );

    printf( "\n - mulmg5\n" );
    vecK_qpx = vecK_orig;
    //vecL_qpx = vecL_orig;
    mulmg5( vol, &vecK_orig );
    mulmg5_bgq( vol, &vecK_qpx );
    diff_spinor( &vecK_orig, &vecK_qpx );

    printf( "\n - mulg5_spinor_sub\n" );
    vecK_qpx = vecK_orig;
    vecL_qpx = vecL_orig;
    mulg5_spinor_sub( vol, &vecK_orig, &vecL_orig );
    mulg5_spinor_sub_bgq( vol, &vecK_qpx, &vecL_orig );
    diff_spinor( &vecK_orig, &vecK_qpx );
}

#if 0
void timings( ) {
    InitRandom( 11 );

    spinor spinor_orig[VOLUME];
    spinor spinor_qpx[VOLUME];
    
    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_pauli_multiply_all(pm_x,pm,x, perm1,work1, perm2,work2);
          _qpx_vectorpair_assign( x, pm_x );
      }
      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_pauli_multiply_all(pm_x,pm,x, perm1,perm2,work1,work2);
          _qpx_vectorpair_assign( x, pm_x );

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

          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);
}
#endif

/* main */

int main() {

#ifdef VERBOSE_DEBUG
      compare_linalg();
//#else 
//      timings( u );
#endif
    return 0;
}

