/*
*===============================================================================
*
* d/wilson_d501/shmem_comm.c 
*
*-------------------------------------------------------------------------------
*
* Copyright (C) 2006 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 <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/shm.h>
#include <unistd.h>
#ifndef NO_MPI
#include <mpi.h>
#endif
#include "neighb.h"
#include "memory.h"

#ifdef BGL
#include "bglmemmap.h"
#include "rts.h"

extern void send_f(char* fifo, double* data, int n);
extern void recv_f(char* fifo, double* data, int n);
extern int test_stat(const void* stat_addr, int byte);


void fifo_send(int f, int mu, int n, double* data)
{
  if (n!=6)
  {
    printf("error\n");
    exit(1);
  }
  if (mu==3)
  {
    unsigned int wr_offs=
      ((unsigned int*) (send_fifo4_addr))[1];
    unsigned int wr_offs_wrapped=wr_offs & fifo4_mask;
    char* wr_ptr=send_fifo4_addr+wr_offs_wrapped;    
/*     printf("[%i] W s0 %x s1 %x (%i) \n", my_rank, send_fifo4_addr[0], send_fifo4_addr[1], f); */
    printf("[%i] WRITE TO %x\n", my_rank, wr_ptr);
    for (int i=0; i<12; i++)
      ((double*) wr_ptr)[i]=data[i];
    wr_offs+=128;
    ((unsigned int*) (send_fifo4_addr))[1]=wr_offs;
    return;
  }

  send_f(send_fifo_addr[f][mu], (double*) (&send_header[f][mu]), 1);

  send_f(send_fifo_addr[f][mu], data, n);
  send_f(send_fifo_addr[f][mu], data, 1); // dummy

}

void fifo_recv(int f, int mu, int n, double* data)
{
  if (mu==3)
  {
    if (n!=6)
    {
      printf("error\n");
      exit(1);
    }
    unsigned int rd_offs=
      ((unsigned int*) (recv_fifo4_addr))[0];
    unsigned int rd_offs_wrapped=rd_offs & fifo4_mask;
    char* rd_ptr=recv_fifo4_addr+rd_offs_wrapped;    

    printf("[%i] RECV FROM %x\n", my_rank, rd_ptr);
    for (int i=0; i<12; i++)
      data[i]=((double*) rd_ptr)[i];
    rd_offs+=128;
    ((unsigned int*) (recv_fifo4_addr))[0]=rd_offs;
    return;
  }


  int stat=test_stat(stat_addr, recv_stat_byte_offs[f][mu]);
  printf("RECV STAT %i\n", stat);fflush(stdout);
  printf("F ADDR %x\n", recv_fifo_addr[f][mu]);fflush(stdout);

  recv_f(recv_fifo_addr[f][mu], (double*) (&recv_header[f][mu]), 1);

  printf("F ADDR %x\n", recv_fifo_addr[f][mu]);fflush(stdout);
  recv_f(recv_fifo_addr[f][mu], data, n);
}

int recv_fifo_stat(int f, int mu)
{
  if (mu==3)
  {
    unsigned int wr_offs=
      ((unsigned int*) (recv_fifo4_addr))[1];
    unsigned int rd_offs=
      ((unsigned int*) (recv_fifo4_addr))[0];
    return (wr_offs-rd_offs)/128;
  }
  return test_stat(stat_addr, recv_stat_byte_offs[f][mu]);
}

int send_fifo_stat(int f, int mu)
{
  if (mu==3)
  {
    unsigned int wr_offs=
      ((unsigned int*) (recv_fifo4_addr))[1];
    unsigned int rd_offs=
      ((unsigned int*) (recv_fifo4_addr))[0];
    return (wr_offs-rd_offs)/128;
  }
  return test_stat(stat_addr, send_stat_byte_offs[f][mu]);
}

#else

#define FIFO_SIZE 24576 /* size in bytes */

/* FIFO format:  */
/*   0  :  read_ptr   (in bytes)       */
/*   4  :  write_ptr                   */
/*   8  :  data                        */
/* never fill completely!              */

volatile char* send_fifo[2][4];
volatile char* recv_fifo[2][4];

void shmem_comm_init()
{
  int shm_id;
  errno=0;

  size_t shm_total_size=8*(FIFO_SIZE+20);
  shm_id=shmget(IPC_PRIVATE, shm_total_size, IPC_CREAT | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
  if (errno)
    die("Cannot shmget");
  errno=0;

  char* mem=(char*) shmat(shm_id, 0, 0);
  if (errno)
    printf("SHMAT: ERROR %i (id %i): %s\n", errno, my_rank, strerror(errno));
#ifndef NO_MPI
  for (int i=0; i<8; i++)
  {
    send_fifo[0][i]=mem+i*(FIFO_SIZE+20);
    ((int*) (send_fifo[0][i]))[0]=0;
    ((int*) (send_fifo[0][i]))[1]=0;
    ((int*) (send_fifo[0][i]))[2]=0;
  }
  MPI_Barrier(MPI_COMM_WORLD);
  MPI_Status stat;
  int nb_id;
  for (int f=0; f<2; f++)
    for (int mu=0; mu<4; mu++)
      if (grid[mu]>1)
      {
	MPI_Sendrecv(&shm_id, sizeof(int), MPI_BYTE, grid_nb[f][mu], 0,
		     &nb_id, sizeof(int), MPI_BYTE, grid_nb[1-f][mu], 0,
		     MPI_COMM_WORLD, &stat);
	char* mem_nb=(char*) shmat(nb_id, 0, 0);
	if (errno)
	  printf("SHMAT2: ERROR %i (id %i): %s\n", errno, my_rank, strerror(errno));
	recv_fifo[1-f][mu]=mem_nb+(4*f+mu)*(FIFO_SIZE+20);
      }

  MPI_Barrier(MPI_COMM_WORLD);
#endif
}

void fifo_recv(int f, int mu, int n, double* data)
{
  n*=16; /*  now n is number of bytes */
  char* datab=(char*) data;
  volatile char* recv_ptr=recv_fifo[f][mu]+8+((int*) (recv_fifo[f][mu]))[0];
  volatile char* recv_ptr_base=recv_fifo[f][mu]+8;
  volatile char* recv_ptr_last=recv_fifo[f][mu]+8+FIFO_SIZE+1;
  int N=n;
  if (mu!=3)
    N+=16;
  for (int i=0; i<n+16; i++)
  {
    while (((int*) (recv_fifo[f][mu]))[0]==((int*) (recv_fifo[f][mu]))[1])
      usleep(1);
    if (i<16 && mu!=3)  /* first 16 bytes header */
      recv_ptr++;
    else
      *datab++=*recv_ptr++;
    if (recv_ptr==recv_ptr_last)
      recv_ptr=recv_ptr_base;
    ((int*) (recv_fifo[f][mu]))[0]=(((int*) (recv_fifo[f][mu]))[0]+1)%(FIFO_SIZE+1);
  }
}

void fifo_send(int f, int mu, int n, double* data)
{
  n*=16; /*  now n is number of bytes */
  char* datab=(char*) data;
  volatile char* send_ptr=send_fifo[f][mu]+8+((int*) (send_fifo[f][mu]))[1];
  volatile char* send_ptr_base=send_fifo[f][mu]+8;
  volatile char* send_ptr_last=send_fifo[f][mu]+8+FIFO_SIZE+1;
  int i0=0;
  if (mu!=3)
    i0-=16;
  for (int i=i0; i<n; i++)  /* add (fake) header */
  {
    while (((((int*) (send_fifo[f][mu]))[1]+1)%(FIFO_SIZE+1))==
	   ((int*) (send_fifo[f][mu]))[0])
      usleep(1);
    if (i<0 && mu!=3)  /* header */
      *send_ptr++=0xff;
    else
      *send_ptr++=*datab++;
    if (send_ptr==send_ptr_last)
      send_ptr=send_ptr_base;
    ((int*) (send_fifo[f][mu]))[1]=(((int*) (send_fifo[f][mu]))[1]+1)%(FIFO_SIZE+1);
  }
}


int recv_fifo_stat(int f, int mu)
{
  int s=((int*) (recv_fifo[f][mu]))[1]-((int*) (recv_fifo[f][mu]))[0];
  if (mu==3)
  if (s<0)
    return (FIFO_SIZE+s)/1;
  else
    return s/1;
  if (s<0)
    return (FIFO_SIZE+s)/96;
  else
    return s/128;
}





#endif

/*  transform U to Hinnerks format (mu run slowest) */
void transf_U(double* U)
{
  double* U_copy=new_gc();
  int volume=lattice[0]*lattice[1]*lattice[2]*lattice[3];
  for (int i=0; i<72*volume; i++)
    U_copy[i]=U[i];
  double* U_ptr=U_copy;
  for (int x=0; x<volume; x++)
    for (int mu=0; mu<4; mu++)
      for (int i=0; i<18; i++)
	U[i+18*(x+volume*mu)]=*U_ptr++;
  delete_gc(U_copy);
}
void exchange_U(double* U)
{
  int volume=lattice[0]*lattice[1]*lattice[2]*lattice[3];
  double* U_copy=new_gc();
  for (int mu=0; mu<4; mu++)
    if (boundary[mu])
    {
      double* U_ptr=U_copy;
      for (int x=0; x<volume; x++)
      {
	int y[4];
	coords(x, y, -1);
	if (y[mu]==lattice[mu]-1 && boundary[mu])
	{
	  int b=b_idx(y, mu);
	  for (int a=0; a<18; a++)
	    U_copy[18*b+a]=U[18*(4*x+mu)+a];
	  U_ptr+=18;
	}
      }
      MPI_Status stat;
      MPI_Sendrecv(U_copy, 18*sizeof(double)*bnd_size[mu], MPI_BYTE, grid_nb[0][mu], 0,
		   U+18*(4*volume+bnd_offs[mu]), 18*sizeof(double)*bnd_size[mu], MPI_BYTE, grid_nb[1][mu], 0,
		   MPI_COMM_WORLD, &stat);
    }
  delete_gc(U_copy);
}
