/*************************************************************************
 * Block lattice operations
 */

#include "comdefs.h"
#include "generic.h"

/* set_blocking_leve( int blev[ndim] ) sets the global structures
 * for reducing the current lattice size by a factors given in blev,
 * so that, for example, nx -> nx/2^blev[XUP].
 * Affects ONLY the gather, forallsites, etc. operations, does not
 * touch individual variables.
 *
 * int * make_block_map( int blev[ndim] ) makes a index array from the
 * current_blocking_level to blocking level blev.  Thus, it returns
 * an array of size current lattice size.  It can go up or down,
 * if up it fills the illegal sites with -(1<<30).
 */

/* static variables for the blocking operations */

typedef struct block {
  int level[NDIM];                  /* blocking levels  */
  struct block * next;              /* ptr to next block level */
  lattice_struct  lattice;          /* saved lattice structure */
  node_struct node;                 /* saved node structure */
  node_struct * allnodes;           /* ptr to node array for all nodes */
  site_struct * site;               /* ptr to site struct */
  comlist_struct * comlist;         /* and comlist for the gather */
  int *neighb[NDIRS];               /* neighbour arrays */
} block_buf;

static block_buf * b_buf = NULL;    /* pointer to all of the saved blocking vars */
static block_buf * base;            /* will point to the base level */
static int n_blocks = 0;            /* number of blocking levels */

extern comlist_struct *comlist;      /* ptr to comlist */
extern node_struct *allnodes;

void set_blocking_level( int blev[NDIM] )
{
  int dir,i,found,d;
  block_buf *b,*p;

  /* check first if it is the current level, nothing to do */
  found = 1;
  foralldir(dir) 
    if (current_blocking_level[dir] != blev[dir]) found = 0;
  if (found) return;

  /* Now something is happening */

  if (b_buf == NULL) {
    /* first time in, save the basic level here */
    base = b_buf = (block_buf *)memalloc(1, sizeof(block_buf) );
    foralldir(dir) base->level[dir] = 0;
    for (dir=0; dir<NDIRS; dir++) base->neighb[dir] = neighb[dir];
    
    base->lattice = lattice;    /* copy the lattice struct */
    base->node    = node;       /* copy the node struct */
    base->allnodes= allnodes;   /* copy the address of the node array */
    base->site    = site;       /* and the address of the site array too */    
    base->comlist = comlist;    /* and comlist */
    base->next    = NULL;
  }
  
  /* ok, now chase the list and check if blev is defined */
  
  found = 0;
  for (b=b_buf; (!found) && b != NULL ; b=b->next) {
    found = 1;
    foralldir(dir) found = (found && (b->level[dir] == blev[dir]));
    p = b;
  }

  if (found) {
    /* now the gather was found, copy the arrays */

    lattice  = p->lattice;
    node     = p->node;
    allnodes = p->allnodes;
    site     = p->site;
    comlist  = p->comlist;
    foralldir(dir) current_blocking_level[dir] = p->level[dir];
    for (dir=0; dir<NDIRS; dir++) neighb[dir] = p->neighb[dir];

    /* printf(" blocking level %d %d %d set up\n",
       blev[XUP], blev[YUP], blev[ZUP]);
    */
    fflush(stdout);
     
  } else {
    
    /* now it was NOT found -- make new level */
    
    p->next = b = (block_buf *)memalloc(1, sizeof(block_buf) );

    b->lattice.volume = 1;
    foralldir(dir) {
      current_blocking_level[dir] = b->level[dir] = blev[dir];
      
      /* check if this is at all legal */
      if (base->lattice.size[dir] % (1 << blev[dir])) {
	printf("Blocking error: cannot divide lattice by factors ");
	foralldir(d) {
	  printf("%d",blev[d]);
	  if (d > 0) printf(" x ");
	}
	printf("\n");
	halt("Blocking error");
      }
      
      b->lattice.size[dir] = (base->lattice.size[dir]) / (1 << blev[dir]);
      b->lattice.volume *= b->lattice.size[dir];

    }

    lattice = b->lattice;

    /* make the structures -- THIS INITIALIZES THE ARRAYS */

    /* printf(" blocking level %d %d %d set up, making arrays\n",
     *          blev[XUP], blev[YUP], blev[ZUP]);
     */

    make_lattice_arrays( &(b->lattice) );

    /* and copy now the stuff to buffer */
    for (dir=0; dir<NDIRS; dir++) b->neighb[dir] = neighb[dir];

    b->node    = node;       /* copy the node struct */
    b->allnodes= allnodes;   /* copy the address of the node array */
    b->site    = site;       /* and the address of the site array too */    
    b->comlist = comlist;    /* comlist */
    b->next    = NULL;

  }
}
  
  
/****************************************************************/

void set_blocking_all( int lev )
{
  int b[NDIM],dir;

  foralldir(dir) b[dir] = lev;
  set_blocking_level( b );
}


/**************************************************************
 * Make mapping from current to blev
 */

int *make_blocking_map( int newlev[NDIM] )
{
  int dir,i,is_ok,x[NDIM],step[NDIM],oldlev[NDIM];
  node_struct newnode;
  int *map;

  /* allocate map */
  map = (int *)memalloc( node.sites, sizeof(int) );

  /* calculate the relative change */
  foralldir(dir) step[dir] = newlev[dir] - 
    (oldlev[dir] = current_blocking_level[dir]);

  /* set new blocking */
  set_blocking_level( newlev );
  /* catch the current node */
  newnode = node;
  /* and just reset the level */
  set_blocking_level( oldlev );

  /* and loop over */

  forallsites(i) {
    is_ok = 1;
    foralldir(dir) {
      if (step[dir] == 0) {
	/* no change, coordinate as is */
	x[dir] = coordinate(i,dir);
      } else if (step[dir] < 0) {
	/* down, new is 2^n denser/larger than the old */
	x[dir] = (coordinate(i,dir) << (-step[dir]));
      } else {
	/* up, new is smaller than old */
	/* if (coordinate(i,dir) % (1<<step[dir])) is_ok = 0; * does not map *
	   else */
	x[dir] = coordinate(i,dir) >> step[dir];
      }
    }
    if (is_ok) map[i] = node_index( x, &newnode );
    else map[i] = -(1<<30);

  }
  return( map );
}
