/*
!===============================================================================
!
! clover_mult_ao_sse.c
!
!-------------------------------------------------------------------------------
!
! Copyright (C) 2013 Hinnerk Stueben
!
! This file is part of BQCD -- Berlin Quantum ChromoDynamics program
!
! BQCD is free software: you can redistribute it and/or modify
! it under the terms of the GNU General Public License as published by
! the Free Software Foundation, either version 3 of the License, or
! (at your option) any later version.
!
! BQCD is distributed in the hope that it will be useful,
! but WITHOUT ANY WARRANTY; without even the implied warranty of
! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
! GNU General Public License for more details.
!
! You should have received a copy of the GNU General Public License
! along with BQCD.  If not, see <http://www.gnu.org/licenses/>.
!
!-------------------------------------------------------------------------------
*/
#ifdef USE_SIMD_SSE

# include <emmintrin.h>
# include <pmmintrin.h>
# include "c_defs.h"

# define cLoad(b)   _mm_load_pd((double*) &(b))
# define cLoadr(b)  _mm_loadr_pd((double*) &(b))
# define cLoad1(b)  _mm_load1_pd((double*) &(b))
# define cLoads(b)  _mm_load_sd((double*) &(b))
# define cStore(a,b) _mm_store_pd((double*) &(a), b)
# define cAdd(b, c) _mm_add_pd(b, c)
# define cSub(b, c) _mm_sub_pd(b, c)
# define cMul(b, c) _mm_mul_pd(b, c)
# define cAddSub(b, c) _mm_addsub_pd(b, c)

# define cLoadX(a, b, c)   STRCAT(a,rr) = cAdd(cLoad1(Re(b)), cLoad1(Re(c))); \ 
                           STRCAT(a,ii) = cAdd(cLoad1(Im(b)), cLoad1(Im(c))) 

# define cLoadXX(a, b, c)  STRCAT(a,rr) = cSub(cLoad1(Re(b)), cLoad1(Re(c))); \ 
                           STRCAT(a,ii) = cSub(cLoad1(Im(b)), cLoad1(Im(c))) 

# define cStoreX(a, b, c)  cStore(a, cAdd(b, c))
# define cStoreXX(a, b, c) cStore(a, cSub(b, c))

# define cConjg(a)          cMul(conjg, a)
# define cConjgr(a)         cMul(conjgr, a)


# define cCmplxMul(b, c)      cAddSub(cMul(cLoad(b), STRCAT(c,rr)), cMul(cLoadr(b), STRCAT(c,ii)))

# define cCmplxMulConjg(b, c) cAddSub(cMul(cConjg(cLoad(b)), STRCAT(c,rr)), cMul(cConjgr(cLoadr(b)), STRCAT(c,ii)))

# define cCmplxMulRe(b, c)    _mm_hsub_pd(cMul(cLoads(Re(b)), STRCAT(c,rr)), cMul(cLoads(Re(b)), STRCAT(c,ii)))

# define cCmplxMulIm(b, c)    _mm_hsub_pd(cMul(cLoads(Im(b)), STRCAT(c,rr)), cMul(cLoads(Im(b)), STRCAT(c,ii)))


# define cProd(a, b, c)       a = cCmplxMul(b, c) 

# define cProdConjg(a, b, c)  a = cCmplxMulConjg(b, c)

# define cProdRe(a, b, c)     a = cCmplxMulRe(b, c)


# define cAddProd(a, b, c)    a = cAdd(a, cCmplxMul(b, c))

# define cAddProdConjg(a,b,c) a = cAdd(a, cCmplxMulConjg(b, c))

# define cAddProdRe(a, b, c)  a = cAdd(a, cCmplxMulRe(b, c))

# define cAddProdIm(a, b, c)  a = cAdd(a, cCmplxMulIm(b, c))


# define aa(s, c, i) aa_[i][c-1][s-1]
# define  x(s, c, i)  x_[i][c-1][s-1]

#ifdef BLOCK 
void clover_mult_ao_block_(COMPLEX (*aa_)[2][18], COMPLEX (*x_)[3][4], int *i1, int *i2)
#else
void clover_mult_ao_(COMPLEX (*aa_)[2][18], COMPLEX (*x_)[3][4], int *volh)
#endif
{
    int i;
    __m128d x1rr, x2rr, x3rr, x4rr, x5rr, x6rr;
    __m128d x1ii, x2ii, x3ii, x4ii, x5ii, x6ii;
    __m128d y1, y2, y3, y4, y5, y6;
    __m128d xx1rr, xx2rr, xx3rr, xx4rr, xx5rr, xx6rr;
    __m128d xx1ii, xx2ii, xx3ii, xx4ii, xx5ii, xx6ii;
    __m128d yy1, yy2, yy3, yy4, yy5, yy6;

    const __m128d conjg  = _mm_setr_pd(ONE, -ONE);
    const __m128d conjgr = _mm_set_pd(ONE, -ONE);
    const __m128d sign   = _mm_set_pd(-ONE, -ONE);

#ifndef BLOCK
    TIMING_START(timing_bin_clover_mult_ao);
#endif

    aa_--;
    x_--;

#ifdef BLOCK
    for (i = *i1; i <= *i2; i++)
#else
    # pragma omp parallel for \
    private (x1rr, x2rr, x3rr, x4rr, x5rr, x6rr) \
    private (x1ii, x2ii, x3ii, x4ii, x5ii, x6ii) \
    private (y1, y2, y3, y4, y5, y6) \
    private (xx1rr, xx2rr, xx3rr, xx4rr, xx5rr, xx6rr) \
    private (xx1ii, xx2ii, xx3ii, xx4ii, xx5ii, xx6ii) \
    private (yy1, yy2, yy3, yy4, yy5, yy6)

    for (i = 1; i <= *volh; i++)
#endif
    {
	cLoadX(x1, x(1, 1, i), x(3, 1, i));
	cLoadX(x2, x(1, 2, i), x(3, 2, i));
	cLoadX(x3, x(1, 3, i), x(3, 3, i));
	cLoadX(x4, x(2, 1, i), x(4, 1, i));
	cLoadX(x5, x(2, 2, i), x(4, 2, i));
	cLoadX(x6, x(2, 3, i), x(4, 3, i));

	cProdRe(y1, aa(1,1,i), x1);
	cAddProd(y1, aa(2,1,i), x2);
	cAddProd(y1, aa(3,1,i), x3);
	cAddProd(y1, aa(4,1,i), x4);
	cAddProd(y1, aa(5,1,i), x5);
	cAddProd(y1, aa(6,1,i), x6);

	cProdConjg(y2, aa(2,1,i), x1);
	cAddProdIm(y2, aa(1,1,i), x2);
	cAddProd(y2, aa(7,1,i), x3);
	cAddProd(y2, aa(8,1,i), x4);
	cAddProd(y2, aa(9,1,i), x5);
	cAddProd(y2, aa(10,1,i), x6);

	cProdConjg(y3, aa(3,1,i), x1);
	cAddProdConjg(y3, aa(7,1,i), x2);
	cAddProdRe(y3, aa(11,1,i), x3);
	cAddProd(y3, aa(12,1,i), x4);
	cAddProd(y3, aa(13,1,i), x5);
	cAddProd(y3, aa(14,1,i), x6);

	cProdConjg(y4, aa(4,1,i), x1);
	cAddProdConjg(y4, aa(8,1,i), x2);
	cAddProdConjg(y4, aa(12,1,i), x3);
	cAddProdIm(y4, aa(11,1,i), x4);
	cAddProd(y4, aa(15,1,i), x5);
	cAddProd(y4, aa(16,1,i), x6);

	cProdConjg(y5, aa(5,1,i), x1);
	cAddProdConjg(y5, aa(9,1,i), x2);
	cAddProdConjg(y5, aa(13,1,i), x3);
	cAddProdConjg(y5, aa(15,1,i), x4);
	cAddProdRe(y5, aa(17,1,i), x5);
	cAddProd(y5, aa(18,1,i), x6);

	cProdConjg(y6, aa(6,1,i), x1);
	cAddProdConjg(y6, aa(10,1,i), x2);
	cAddProdConjg(y6, aa(14,1,i), x3);
	cAddProdConjg(y6, aa(16,1,i), x4);
	cAddProdConjg(y6, aa(18,1,i), x5);
	cAddProdIm(y6, aa(17,1,i), x6);

	cLoadXX(xx1, x(1, 1, i), x(3, 1, i));
	cLoadXX(xx2, x(1, 2, i), x(3, 2, i));
	cLoadXX(xx3, x(1, 3, i), x(3, 3, i));
	cLoadXX(xx4, x(2, 1, i), x(4, 1, i));
	cLoadXX(xx5, x(2, 2, i), x(4, 2, i));
	cLoadXX(xx6, x(2, 3, i), x(4, 3, i));

	cProdRe(yy1, aa(1,2,i), xx1);
	cAddProd(yy1, aa(2,2,i), xx2);
	cAddProd(yy1, aa(3,2,i), xx3);
	cAddProd(yy1, aa(4,2,i), xx4);
	cAddProd(yy1, aa(5,2,i), xx5);
	cAddProd(yy1, aa(6,2,i), xx6);

	cProdConjg(yy2, aa(2,2,i), xx1);
	cAddProdIm(yy2, aa(1,2,i), xx2);
	cAddProd(yy2, aa(7,2,i), xx3);
	cAddProd(yy2, aa(8,2,i), xx4);
	cAddProd(yy2, aa(9,2,i), xx5);
	cAddProd(yy2, aa(10,2,i), xx6);

	cProdConjg(yy3, aa(3,2,i), xx1);
	cAddProdConjg(yy3, aa(7,2,i), xx2);
	cAddProdRe(yy3, aa(11,2,i), xx3);
	cAddProd(yy3, aa(12,2,i), xx4);
	cAddProd(yy3, aa(13,2,i), xx5);
	cAddProd(yy3, aa(14,2,i), xx6);

	cProdConjg(yy4, aa(4,2,i), xx1);
	cAddProdConjg(yy4, aa(8,2,i), xx2);
	cAddProdConjg(yy4, aa(12,2,i), xx3);
	cAddProdIm(yy4, aa(11,2,i), xx4);
	cAddProd(yy4, aa(15,2,i), xx5);
	cAddProd(yy4, aa(16,2,i), xx6);

	cProdConjg(yy5, aa(5,2,i), xx1);
	cAddProdConjg(yy5, aa(9,2,i), xx2);
	cAddProdConjg(yy5, aa(13,2,i), xx3);
	cAddProdConjg(yy5, aa(15,2,i), xx4);
	cAddProdRe(yy5, aa(17,2,i), xx5);
	cAddProd(yy5, aa(18,2,i), xx6);

	cProdConjg(yy6, aa(6,2,i), xx1);
	cAddProdConjg(yy6, aa(10,2,i), xx2);
	cAddProdConjg(yy6, aa(14,2,i), xx3);
	cAddProdConjg(yy6, aa(16,2,i), xx4);
	cAddProdConjg(yy6, aa(18,2,i), xx5);
	cAddProdIm(yy6, aa(17,2,i), xx6);

	cStoreX(x(1, 1, i), y1, yy1);
	cStoreX(x(1, 2, i), y2, yy2);
	cStoreX(x(1, 3, i), y3, yy3);
	cStoreX(x(2, 1, i), y4, yy4);
	cStoreX(x(2, 2, i), y5, yy5);
	cStoreX(x(2, 3, i), y6, yy6);

	cStoreXX(x(3, 1, i), y1, yy1);
	cStoreXX(x(3, 2, i), y2, yy2);
	cStoreXX(x(3, 3, i), y3, yy3);
	cStoreXX(x(4, 1, i), y4, yy4);
	cStoreXX(x(4, 2, i), y5, yy5);
	cStoreXX(x(4, 3, i), y6, yy6);
    }
    
#ifndef BLOCK
    TIMING_STOP(timing_bin_clover_mult_ao);
#endif
}

#elif USE_SIMD_QPX
# include "clover_mult_ao_sse_r4.c"
#endif
