/************************* comdefs.h *************************************
 * Header file to define global (and hidden from user) variables
 * and define macros etc.
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include <string.h>
#include <float.h>
#include <memory.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/times.h>
#include <sys/resource.h>
#include <time.h>


#ifdef CAN_DO_ALLOCA
#include <alloca.h>  /* needed for alloca */
#endif

#ifdef MPI
#include <mpi.h>
#endif

#ifdef __GNUC__
#define INLINE inline
#else
#define INLINE 
#endif

#include "radix.h"

/* This version divides the lattice by factors of two in any of the
   four directions.  It prefers to divide the longest dimensions,
   which mimimizes the area of the surfaces.  Similarly, it prefers
   to divide dimensions which have already been divided, thus not
   introducing more off-node directions.

   This requires that the lattice volume be divisible by the number
   of nodes, which is a power of two.

   With the "GRAYCODE" option the node numbers are gray coded so that
   adjacent lattice regions will physically be on adjacent nodes
   in a hypercube architecture

   With the "EVENFIRST" option the even sites are listed contiguously
   in the first part of the fields, and the odd sites in the last part.
*/

#ifndef NO_EVENFIRST   /* use evenfirst in checkerboard-type parallel update */
#define EVENFIRST 
#endif
/* #define GRAYCODE */

/* Ensure correct sharing of variables */
#ifdef CONTROL
#define EXTERN
#else 
#define EXTERN extern
#endif

/* Define parity variables */

#define EVEN 0x01
#define ODD 0x02
#define EVENODD 0x03
#define ALL EVENODD


/* Directions, and a macro to give the opposite direction */
/* Also define NDIRS = number of directions */

#ifndef DIMENSION 
#define NDIM 3
#else
#define NDIM DIMENSION
#endif

#define NDIRS (2*NDIM)		       	/* number of directions */

#if NDIM > 1
#define XUP 0
#define YUP 1
#define XDOWN (NDIRS-1-XUP)
#define YDOWN (NDIRS-1-YUP)
#if NDIM > 2
#define ZUP 2
#define ZDOWN (NDIRS-1-ZUP)
#if NDIM > 3
#define TUP 3
#define TDOWN (NDIRS-1-TUP)
#endif
#endif
#endif

#define opp_dir(dir)  (NDIRS-1-(dir))	/* Opposite direction */
#define is_up_dir(dir) (dir < NDIM)     /* is it up-direction */

#define coordinate(i,dir) site[i].x[dir]
#define xcoord(i) coordinate(i,XUP)
#define ycoord(i) coordinate(i,YUP)
#define zcoord(i) coordinate(i,ZUP)
#define tcoord(i) coordinate(i,TUP)

/************** some typedefs -- lattice, node, and site specific */

typedef struct lattice {
  int volume,size[NDIM];
} lattice_struct;

typedef struct node {
  int sites,evensites,oddsites;
  int xmin[NDIM],nodesize[NDIM];   /* coordinate min and max values */
  int down_node[NDIM],up_node[NDIM]; /* indices of nodes up and down to each direction */
  int latfield_size;               /* used in allocating latfields */
} node_struct;

typedef struct site {
  int parity, index, x[NDIM];
} site_struct;

/* Structure to keep track of outstanding sends and receives */
typedef struct msg_tag_struct {
  int flag,dir;         /* status of the msg, direction */
  double start_time;    /* start time for this gather */
  int size,nsites;      /* Info about comms - used only in scatter */
  char *buf;            /* buffer for the send messages */
  char *field;          /* ptr to latfield - used only in scatter */
  int *sitelist;        /* site list pointer - used only by scatter */
  struct msg_tag_struct *next; /* next tag in the possible list */
#ifdef MPI
  MPI_Request mpi;  	/* message id returned by system call */
#endif
} msg_tag;

/* define COMLINK structures: this is defined for all 
 *  nn-gathers.  Each node contains
 *  comlink[dirs], which contains a list of
 *  send/receive node structs for each of the gathers.
 *
 *  sendnode contains sitelist[], which is the list
 *  of sites which has to be copied to send buffer (allocated)
 *  The sites are ALWAYS ODD FIRST!  Thus, when neighb. of
 *  even sites are collected, odd sites are moved (and offset = std.)
 *  For even sites we collect odd ones. 
 */

typedef struct sendnode {
  int node;               /* node index to send to */
  int n_even, n_odd, n;   /* number of sites to be sent */
  int *sitelist;          /* list of sites to be sent */
  struct sendnode *next;
} send_struct;

typedef struct receivenode {
  int node;               /* node index to receive from */
  int n_even, n_odd, n;   /* number of sites to be received */
  int offset;             /* offset of the fields in latfield */
  struct receivenode *next;
} receive_struct;

typedef struct comlist {
  send_struct * to_node;    
  receive_struct * from_node;
  int n_send,n_receive; 
} comlist_struct;

/*********************************************************/
/* These routines check if we need to do fetching or not.
 */

#ifdef MPI

#define CHECK_GATHER_FIELDS   /* define this to have additional check */

typedef struct gather_status_arr {
  int status;
  unsigned char gathered[NDIRS];
#ifdef CHECK_GATHER_FIELDS
  /* these are used to check if there is forgotten mark_changed */
  unsigned int check_even[NDIRS], check_odd[NDIRS];  
#endif
} gather_status_arr;

#define GATHER_STATUS_SIZE sizeof(struct gather_status_arr)

void gather_status_reset( char *field, int size );
void gather_mark_dirty( char *field, int size, int parity );
int is_already_gathered( char *field, int size, int dir, int parity );
void gather_mark_gathered( char *field, int size, int dir, int parity );

/* Routine for marking the field 'dirty' */
#define mark_changed( a, parity )		      			\
  gather_mark_dirty( (char *)a, ((char *)&(a[1])) - ((char *)&(a[0])), parity )

#else
/*** NON-MPI routines ***/

#define GATHER_STATUS_SIZE 0

#define mark_changed( a, parity ) /* nothing */

#endif


/***************** Critical global variables defined here ********/

EXTERN int *neighb[NDIRS];        /* neighbour arrays */
EXTERN site_struct *site;         /* site array hangs here */
EXTERN lattice_struct lattice;    /* lattice defn */
EXTERN node_struct node;          /* and node information */
EXTERN int this_node;             /* number of this node */
EXTERN int number_of_nodes;

EXTERN int current_blocking_level[NDIM];  /* currently running blocking level */
EXTERN lattice_struct base_lattice;       /* base lattice struct */

/************* MACROS for latfield *****************/

#define nb(dir,i) neighb[dir][i]

#define new_latfield( typ ) \
  (typ *)latfield_alloc( sizeof(typ) )

#define new_latfield_size( siz ) (char *)latfield_alloc( siz )

#define free_latfield( field ) if (field != NULL) free( field )

char *copy_latfield_func( char *f, int siz);
#define copy_latfield( f, typ ) (typ *)copy_latfield_func((char *)f, sizeof(typ) )

/*------------ Do we have alloca? ----------------*/
#ifdef CAN_DO_ALLOCA
/* allocate the tmp_latfield from the stack */
static char *tmp_latf_ptr_;
#define tmp_latfield( typ ) \
  ( (tmp_latf_ptr_ = alloca( node.latfield_size * sizeof(typ)  \
			     + GATHER_STATUS_SIZE)) == NULL ?  \
    (typ *)halt("alloca() error") : (typ *)tmp_latf_ptr_ )
#define free_tmp( ptr )  /* nothing */

#else /* now not can alloca */
#define tmp_latfield( typ ) new_latfield( typ )
#define free_tmp( ptr )     free_latfield( ptr )
#endif

/*------------- General field blocking -----------*/

#define block_field( field, b, fr ) \
  block_field_prg( field, ((char *)&(field[1])) - ((char *)&(field[0])), b, fr )

/************* Gathering ***************************/

/* async gather and wait */
#define start_get( a, dir, parity ) \
  start_gather( (char *)a, ((char *)&(a[1])) - ((char *)&(a[0])), dir, parity )

#define wait_get( tg ) wait_gather( tg )

/* synchronous gather */
#define get_field( a, dir, parity ) wait_get( start_get( a, dir, parity ) )

/* async scatter and wait */
#define start_put( a, dir, parity )					\
  start_scatter( (char *)a, ((char *)&(a[1])) - ((char *)&(a[0])), dir, parity )

#define wait_put( tg ) wait_scatter( tg )

/************* MACROS for looping ******************/

#define forbothparities(parity) for (parity=EVEN; parity<=ODD; parity++)

#define forallsites(i) for(i=0; i<node.sites; ++i)

#ifdef EVENFIRST
#define forevensites(i) for(i=0; i<node.evensites; ++i)
#define foroddsites(i) for(i=node.evensites; i<node.sites; ++i)
#define forparity(i,choice) \
  for(i = (((choice) & EVEN) ? 0 : node.evensites);		\
      i<(((choice) & ODD) ? node.sites : node.evensites) ; i++)
/* #define forparity(i,choice)			     \
 * for( i=(((choice) & EVEN) ? 0 : node.evensites);  \
 *      i< (((choice) & ODD) ? node.sites : node.evensites); \
 *      i++)
 */
#else  /* EVENFIRST */
#define forevensites(i) \
   for(i=0; i<node.sites; i++) if(site[i].parity==EVEN)
#define foroddsites(i) \
   for(i=0; i<node.sites; i++) if(site[i].parity==ODD)
#define forparity(i,choice) \
   for(i=0;i<node.sites;i++)if( (site[i].parity & (choice)) != 0)
#endif	/* end ifdef EVENFIRST */

#define is_site_parity(i, choice) ((site[i].parity & (choice)) != 0)

/************* Loop + wait ************************/

#ifdef MPI
/* First, MPI defines: these first loop over sites which
 * don't have off-site neighbours, then the neighbouring sites
 */
static int wait_loop_,wait_i_,wait_dir1_,wait_dir2_;
#define forallsites_wait(i,tag)					           	\
if (tag != NULL) { wait_dir1_=tag->dir; wait_loop_=1; } else wait_loop_=0;      \
for(wait_i_=0; wait_i_<=wait_loop_; wait_i_++, tag = wait_gather( tag ))        \
forallsites(i) if ((!wait_loop_) || ((wait_i_) ^ (nb(wait_dir1_,i) < node.sites) ))

#define forallsites_wait2(i,tag1,tag2)							\
if (tag1 != NULL) wait_dir1_=tag1->dir;							\
if (tag2 != NULL) wait_dir2_=tag2->dir;							\
if (tag1 != NULL || tag2 != NULL) wait_loop_=1; else wait_loop_=0;			\
if (tag1 == NULL) wait_dir1_ = wait_dir2_;    /* short circuit these */                 \
if (tag2 == NULL) wait_dir2_ = wait_dir1_;                                              \
for(wait_i_=0; wait_i_<=wait_loop_; 							\
    wait_i_++, tag1 = wait_gather(tag1), tag2 = wait_gather(tag2))			\
forallsites(i) if ((!wait_loop_) || ((wait_i_) ^ (nb(wait_dir1_,i) < node.sites &&	\
                                                  nb(wait_dir2_,i) < node.sites) ))

#define forparity_wait(i,par,tag)							\
if (tag != NULL) { wait_dir1_=tag->dir; wait_loop_=1; } else wait_loop_=0;		\
for(wait_i_=0; wait_i_<=wait_loop_; wait_i_++, tag = wait_gather( tag ))		\
forparity(i,par) if ((!wait_loop_) || ((wait_i_) ^ (nb(wait_dir1_,i) < node.sites) ))

/********* now, make gather with arbitrary waits -- defined in com_mpi */
void initialize_wait_arrays();
unsigned int setup_wait_arr( msg_tag *t[], int ntag );
#define NA_MAX 10                  /* arbitrarily 10 gathers */
EXTERN unsigned char *wait_arr_;
static unsigned int  site_mask_;

/* here wait_loop_ is 0 or 1 */
#define forallsites_waitA(i,tag,ntag)					\
for (site_mask_ = setup_wait_arr( tag, ntag ),				\
     wait_loop_ = (site_mask_ != 0), wait_i_=0;				\
     wait_i_<=wait_loop_; wait_gather_arr(tag,ntag), wait_i_++)		\
forallsites(i) if (((site_mask_ & wait_arr_[i]) != 0) == wait_i_)

/* here wait_loop_ is 0 or 1 */
#define forparity_waitA(i,parity,tag,ntag)				\
for (site_mask_ = setup_wait_arr( tag, ntag ),				\
     wait_loop_ = (site_mask_ != 0), wait_i_=0;				\
     wait_i_<=wait_loop_; wait_gather_arr(tag,ntag), wait_i_++)		\
forparity(i,parity) if (((site_mask_ & wait_arr_[i]) != 0) == wait_i_)


#ifdef OLD_WAIT_ARR
int setup_wait_arr( unsigned char *wait_arr, msg_tag *to[], msg_tag *ti[], int ntag );
EXTERN msg_tag *waitA_tags[NA_MAX+1];

#define forallsites_waitA(i,tag,ntag)						\
for (wait_loop_ = setup_wait_arr( wait_arr_, waitA_tags, tag, ntag ),	\
     wait_i_=0; wait_i_<=wait_loop_; wait_gather(waitA_tags[wait_i_]), wait_i_++ ) \
forallsites(i) if (wait_i_ == wait_arr_[i])

#define forparity_waitA(i,parity,tag,ntag)				\
for (wait_loop_ = setup_wait_arr( wait_arr_, waitA_tags, tag, ntag ),	\
     wait_i_=0; wait_i_<=wait_loop_; wait_gather(waitA_tags[wait_i_]), wait_i_++ ) \
forparity(i,parity) if (wait_i_ == wait_arr_[i])
#endif

/* now, if we define updates inside bulk most of the above stuff is
 * superfluous.  Let it be for compatibility though
 */
#ifdef NODE_UPDATE
int active_link(int i,int dir);
int active_site(int i);
#endif


/************************************************************************/

#else
/** Non-MPI versions **/
#define forallsites_wait(i,tag)             forallsites(i)
#define forallsites_wait2(i,tag1,tag2)      forallsites(i)
#define forparity_wait(i,par,tag)           forparity(i,par) 
#define forallsites_wait3(i,tag1,tag2,tag3) forallsites(i)
#define forallsites_waitA(i,tag,ntag)	    forallsites(i)
#define forparity_waitA(i,parity,tag,ntag)  forparity(i,parity)

#ifdef NODE_UPDATE
#define active_link(i,dir) 1
#define active_site(i,dir) 1
#endif


#endif
		    

/*********************************************************/

void zero_arr(int x[NDIM]);

#define forallcoordinates(x) \
   for(zero_arr(x); is_allowed_coord(x,&lattice); step_coord(x,&lattice) )

#define foralldir(dir) for(dir=0; dir<NDIM; dir++)
#define opp_parity(parity) (0x3 & (((parity)<<1)|((parity)>>1)))
/* Switches EVEN and ODD, leaves EVENODD*/

int is_allowed_coord(int x[NDIM],lattice_struct *l);
void step_coord(int x[NDIM],lattice_struct *l);

/********** Some helpers *********************************/

/*********************************************************/

/* Communications routines */
void send_field(void *,int,int);
void receive_field(void *,int);
char * machine_type();

void g_sync();
void g_vecintsum(int *,int,int);
void g_floatsum(float *,int);
void g_vecfloatsum(float *,int,int);
void g_doublesum(double *,int);
void g_vecdoublesum(double *,int,int);
void g_floatmax(float *);
void g_doublemax(double *);
void broadcast_field(void *p,int siz);
void broadcast_float(float *);
void broadcast_double(double *);
void broadcast_int(int *);
void send_integer(int node,int *address);
void receive_integer(int *);
double dclock();
void terminate(); void finishrun();

char *memalloc(int n, int size);
char *latfield_alloc(int size);

msg_tag *start_gather(char *field, int size, int dir, int parity );
msg_tag *wait_gather(msg_tag *mbuf);
void wait_gather_arr(msg_tag *mbuf[],int n);

msg_tag *start_scatter(char *field, int size, int dir, int parity );
msg_tag *wait_scatter(msg_tag *mbuf);


void copy_lat_data_to_node( void *dat, int dsize, 
			    int xmin[NDIM], int xmax[NDIM], void *t, int node );
void copy_lat_slice( void *dat, int dsize, int dir, int slice, void *t);


void setup_lattice(int size[NDIM]);
void make_lattice_arrays(lattice_struct * l);
void initialize_machine();
void make_gathers();
char *machine_type();
int mynode(),numnodes();
int node_number(int loc[NDIM]), node_index(int loc[NDIM],node_struct *s);
int is_on_node(int loc[NDIM]);

void set_blocking_level(int b[NDIM]);
void set_blocking_all(int d);
int *make_blocking_map(int b[NDIM]);
#define reset_blocking_level() set_blocking_all(0)

void report_comm_timers();

#ifdef RADIX_F
#define g_radixsum g_floatsum
#define g_vecradixsum g_vecfloatsum
#define broadcast_radix broadcast_float
#elif defined(RADIX_D)
#define g_radixsum g_doublesum
#define g_vecradixsum g_vecdoublesum
#define broadcast_radix broadcast_double
#else
  no radix
#endif

#define g_veccomplexsum(a, b, c)  g_vecradixsum((radix *)a, 2*(b), c)
#define g_complexsum(a, b) g_vecradixsum((radix *)(a), 2, b)


/**** Other protos ****/

