/*
*===============================================================================
*
* d/wilson_d511/neighb.c 
*
*-------------------------------------------------------------------------------
*
* Copyright (C) 2007 Thomas Streuer 
*
* 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/>.
*
*-------------------------------------------------------------------------------
*/
#include <mpi.h>
#include <mpp/shmem.h>
#include <stdio.h>
#include <stdlib.h>

#include "defs.h"
#include "neighb.h"

w_data w_dirac_data[2];

int* neighb_U_loc;
double* lattice_boundary[MOD_NUM];
double* lattice_boundary_local[MOD_NUM];
long      neighb_mod_offs[2][4];
long      mod_mem_offset[MOD_NUM];
long      neighb_mod[2][4];
long* ipf_dirac_state;
int* bqcd_nn;
int volh_tot, volume;
int module;
int mod_bnd[2][4];
unsigned char mpi_boundary;
double* wrap_inp,* wrap_outp,* wrap_U;

MPI_Status comm_status;

extern void w_dirac_n_d_(double*, double*, double*,
			 int, int, w_data*);
extern void w_dirac_d_d_(double*, double*, double*,
			 int, int, w_data*);


int get_nn(int x, int oe, int mu, int f)
{
  int idx=x+volh_tot*(oe+2*(mu+4*f));
  return bqcd_nn[idx]-1;
}

void multg5(double* v)
{
  for (int i=0; i<3*volh_tot; i++)
  {
    double t1=v[0];
    double t2=v[1];
    v[0]=v[4];
    v[1]=v[5];
    v[4]=t1;
    v[5]=t2;
    t1=v[2];
    t2=v[3];
    v[2]=v[6];
    v[3]=v[7];
    v[6]=t1;
    v[7]=t2;
    v+=8;
  }
}
		   

/* even <- odd */
void w_dirac_even_(
  double* U, double* in, double* out, int* m)
{
  w_dirac_n_d_(U, in-12*volume, out, volh_tot, volume/2, &(w_dirac_data[0]));
}

void w_dirac_even_dag_(
  double* U, double* in, double* out, int* m)
{
  w_dirac_d_d_(U, in-12*volume, out, volh_tot, volume/2, &(w_dirac_data[0]));
}
		   
/* odd <- even */
void w_dirac_odd_(
  double* U, double* in, double* out, int* m)
{
  w_dirac_n_d_(U+volh_tot*18, in, out, volh_tot, volume/2, &(w_dirac_data[1]));
}
		   
void w_dirac_odd_dag_(
  double* U, double* in, double* out, int* m)
{
  w_dirac_d_d_(U+volh_tot*18, in, out, volh_tot, volume/2, &(w_dirac_data[1]));
}
		   


void wilson_init_(int* l, int* g, int* vh, int* nn, int* nnpe)
{
  MPI_Comm_rank(MPI_COMM_WORLD, &module);
  int mpi_size;
  MPI_Comm_size(MPI_COMM_WORLD, &mpi_size);
  volume=l[0]*l[1]*l[2]*l[3];
  volh_tot=*vh;
  bqcd_nn=nn;

  int position[4];    // position in grid
  position[0]=module%g[0];
  position[1]=(module/g[0])%g[1];
  position[2]=(module/(g[0]*g[1]))%g[2];
  position[3]=(module/(g[0]*g[1]*g[2]))%g[3];

  int parity=((module%g[0])*l[0]+
	      ((module/g[0])%g[1])*l[1]+
	      ((module/(g[0]*g[1]))%g[2])*l[2]+
	      ((module/(g[0]*g[1]*g[2]))%g[3])*l[3])%2;
  neighb_U_loc=(int*) shmalloc(12*(MOD_VOL+1)*sizeof(int));


  w_dirac_data[0].neighb_U_loc=neighb_U_loc;
  w_dirac_data[1].neighb_U_loc=neighb_U_loc+6*MOD_VOL;
  w_dirac_data[0].bnd_list=(int*) shmalloc(MOD_BND*sizeof(int));
  w_dirac_data[1].bnd_list=(int*) shmalloc(MOD_BND*sizeof(int));
  w_dirac_data[0].cpu_num=module;
  w_dirac_data[1].cpu_num=module;


  if (mpi_size>(2<<(32-MOD_COORD_LEN)) || volume>(2<<MOD_COORD_LEN))
  {
    printf("wilson: wrong value for MOD_COORD_LEN\n");
    exit(1);
  }

  if (MOD_BND_X>65535 || MOD_BND_Y>65535 || MOD_BND_Z>65535 || MOD_BND_T>65535)
  {
    printf("boundary too large\n");
    exit(1);
  }
  if (l[0]%2!=0)
  {
    printf("lattice_x must be even\n");
    exit(1);
  }
  if (MOD_LAT_X!=l[0] || MOD_LAT_Y!=l[1] || MOD_LAT_Z!=l[2] || MOD_LAT_T!=l[3] ||
      MOD_NUM_X!=g[0] || MOD_NUM_Y!=g[1] || MOD_NUM_Z!=g[2] || MOD_NUM_T!=g[3])
  {
    printf("wrong lattice\n");
    exit(1);
  } 

  const int mpi_grid[4]={MPI_NUM_X, MPI_NUM_Y, MPI_NUM_Z, MPI_NUM_T};

  mpi_boundary=0;
  int mpi_partitions=MPI_NUM_X*MPI_NUM_Y*MPI_NUM_Z*MPI_NUM_T;
  int partition_master=(module/(mpi_size/mpi_partitions))*
    (mpi_size/mpi_partitions);
  int partition_last=((module/(mpi_size/mpi_partitions))+1)*
    (mpi_size/mpi_partitions)-1;
  for (int mu=0; mu<4; mu++)
  {
    if (mpi_grid[mu]>1 && (position[mu]+1)%(g[mu]/mpi_grid[mu])==0)
      mpi_boundary|=1 << mu;
    if (mpi_grid[mu]>1 && position[mu]%(g[mu]/mpi_grid[mu])==0)
      mpi_boundary|=1 << (4+mu);
  }

  mod_bnd[0][0]=MOD_BND_X/2+(MOD_BND_X%2)*parity ;
  mod_bnd[1][0]=MOD_BND_X/2+(MOD_BND_X%2)*(1-parity);
  mod_bnd[0][1]=MOD_BND_Y/2+(MOD_BND_Y%2)*parity;
  mod_bnd[1][1]=MOD_BND_Y/2+(MOD_BND_Y%2)*(1-parity);
  mod_bnd[0][2]=MOD_BND_Z/2+(MOD_BND_Z%2)*parity;
  mod_bnd[1][2]=MOD_BND_Z/2+(MOD_BND_Z%2)*(1-parity);
  mod_bnd[0][3]=MOD_BND_T/2+(MOD_BND_T%2)*parity;
  mod_bnd[1][3]=MOD_BND_T/2+(MOD_BND_T%2)*(1-parity);

  for (int f=0; f<2; f++)
    for (int mu=0; mu<4; mu++)
      if (mod_bnd[f][mu]>0 && mod_bnd[f][mu]<8)
      {
	printf("boundary too small\n");
	exit(1);
      }


  w_dirac_data[0].boundary[0]=(long) mod_bnd[0][0] + (long) mod_bnd[0][1] *65536l +
    (long) (mod_bnd[0][2]) *4294967296l + (long) (mod_bnd[0][3]) *281474976710656l;
  w_dirac_data[0].boundary[1]=(long) mod_bnd[1][0] + (long) mod_bnd[1][1] *65536l +
    (long) (mod_bnd[1][2]) *4294967296l + (long) (mod_bnd[1][3]) *281474976710656l;
  w_dirac_data[1].boundary[0]=(long) mod_bnd[1][0] + (long) mod_bnd[1][1] *65536l +
    (long) (mod_bnd[1][2]) *4294967296l + (long) (mod_bnd[1][3]) *281474976710656l;
  w_dirac_data[1].boundary[1]=(long) mod_bnd[0][0] + (long) mod_bnd[0][1] *65536l +
    (long) (mod_bnd[0][2]) *4294967296l + (long) (mod_bnd[0][3]) *281474976710656l;



  int* nb_ptr=neighb_U_loc;
  for (int a=0; a<volume; a++)
  {
    for (int mu=0; mu<4; mu++)
    {
      int oe, m;
      if (a<volume/2)
	oe=0;
      else
	oe=1;
      if (get_nn(a % (volume/2), oe, mu, 0)<volume/2)
      {
	m=module;
	nb_ptr[mu]=get_nn(a % (volume/2), oe, mu, 0)+(1-oe)*volume/2;
      }
      else
      {
	m=(module+1)%mpi_size;
	nb_ptr[mu]=0;
      }
      nb_ptr[mu]+=m << MOD_COORD_LEN;

      if (get_nn(a % (volume/2), oe, mu, 1)<volume/2)
      {
	nb_ptr[4+2*mu]=get_nn(a % (volume/2), oe, mu, 1)+(1-oe)*volume/2;
	m=module;
      }
      else
      {
	m=(module+1)%mpi_size;
	nb_ptr[4+2*mu]=0;
      }
      nb_ptr[4+2*mu]+=m << MOD_COORD_LEN;

      if (oe==0)
	nb_ptr[5+2*mu]=get_nn(a % (volume/2), oe, mu, 1)+(1-oe)*volh_tot;
      else
	nb_ptr[5+2*mu]=get_nn(a % (volume/2), oe, mu, 1)+(1-oe)*volh_tot-volh_tot;
    }
    nb_ptr+=12;
  }


  for (int i=0; i<MOD_BND; i++)
    w_dirac_data[0].bnd_list[i]=0;
  for (int i=0; i<MOD_BND; i++)
    w_dirac_data[1].bnd_list[i]=0;

  for (int oe=0; oe<2; oe++)
  {
    int b_idx[2][4];
    
    b_idx[0][0]=0;
    b_idx[0][1]=mod_bnd[oe][0];
    b_idx[0][2]=b_idx[0][1]+mod_bnd[oe][1];
    b_idx[0][3]=b_idx[0][2]+mod_bnd[oe][2];
    b_idx[1][0]=b_idx[0][3]+mod_bnd[oe][3];
    b_idx[1][1]=b_idx[1][0]+mod_bnd[1-oe][0];
    b_idx[1][2]=b_idx[1][1]+mod_bnd[1-oe][1];
    b_idx[1][3]=b_idx[1][2]+mod_bnd[1-oe][2];
    
    for (int x=0; x<volume/2; x++)
      for (int fw=0; fw<2; fw++)
	for (int mu=0; mu<4; mu++)
	  if (get_nn(x, 1-oe, mu, 1-fw)>=volume/2)
	    w_dirac_data[oe].bnd_list[b_idx[fw][mu]++]=x+(1-oe)*(volume/2);
  }

  neighb_mod[0][3]=nnpe[67];
  neighb_mod[0][2]=nnpe[49];
  neighb_mod[0][1]=nnpe[43];
  neighb_mod[0][0]=nnpe[41];
  neighb_mod[1][3]=nnpe[13];
  neighb_mod[1][2]=nnpe[31];
  neighb_mod[1][1]=nnpe[37];
  neighb_mod[1][0]=nnpe[39];


  lattice_boundary[module]=(double*) shmalloc(6*MOD_BND*2*sizeof(double));
  lattice_boundary_local[module]=(double*) shmalloc(6*MOD_BND*2*sizeof(double));

    
  ipf_dirac_state=(long*) shmalloc(2048*16);

  for (int i=partition_master; i<=partition_last; i++)
    if (module!=partition_master)
      if (i==module)
	mod_mem_offset[i]=(long) ipf_dirac_state-(long) shmem_ptr((void*) ipf_dirac_state, partition_master);
      else
	mod_mem_offset[i]=(long) shmem_ptr((void*) ipf_dirac_state, i)-(long) shmem_ptr((void*) ipf_dirac_state, partition_master);
    else
      if (i==module)
	mod_mem_offset[i]=(long) ipf_dirac_state-(long) ipf_dirac_state;
      else
	mod_mem_offset[i]=(long) shmem_ptr((void*) ipf_dirac_state, i)-(long) ipf_dirac_state;



  *ipf_dirac_state=0;

  ipf_dirac_state-=mod_mem_offset[module]/sizeof(long);


  for (int i=0; i<8; i++)
  {
    neighb_mod_offs[0][i]=mod_mem_offset[neighb_mod[0][i]]-mod_mem_offset[module];
  }
  wrap_inp=(double*) shmalloc(12*volh_tot*2*sizeof(double));
  wrap_outp=(double*) shmalloc(12*volh_tot*2*sizeof(double));
  wrap_U=(double*) shmalloc(2*8*9*volh_tot*2*sizeof(double));
}
