#include "Bdef.h"
/*
 *  This topology supports trees with arbitrary numbers of branches at
 *  each step.  The following pictures show the tree that should be visualized
 *  when examining the algorithm.
 *
 *    TREE GLOBAL OP, NBRANCHES = 2     *    TREE GLOBAL OP, NBRANCHES = 3
 *                                      *
 * i=4   &______________                *
 *       |              \               *
 * i=2   &______         &______        * i=3     &______________________
 *       |      \        |      \       *         |          \           \
 * i=1   &__     &__     &__     &__    * i=1     &______     &______     &__
 *       |  \    |  \    |  \    |  \   *         |  \   \    |  \   \    |  \
 *       0   1   2   3   4   5   6   7  *         0   1   2   3   4   5   6   7
 */

void tree_comb(ctxt, scope, nbranches, MatInf, buff, rdest1, cdest1, Xmvpk, Xmvupk, Xmvupk_op)
BLACSCONTEXT  *ctxt;
char  scope;
int  nbranches;
MATINFO  *MatInf;
char  *buff;
int  rdest1;
int  cdest1;
MVPK  Xmvpk;
MVPK  Xmvupk;
MVUPKOP  Xmvupk_op;
/*
 *  -- V1.0 BLACS routine --
 *  University of Tennessee, February 28, 1995
 *  Written by Clint Whaley.
 *
 *  Purpose
 *  =======
 *  Perform a element-by-element combine on vectors.
 *  If rdest1 = -1, the answer will be left on all participating processes.
 *  Otherwise, only the process at grid coordinates {rdest1, cdest1} will
 *  have the final answer.  Other Processes will have intermediate (useless)
 *  values.
 *
 *  Arguments
 *  =========
 *  CTXT      (input) pointer to BLACSCONTEXT
 *            The BLACS context where operation is taking place.
 *
 *  SCOPE     (input) char
 *            Limit the scope of the operation.
 *            = 'r' :   Operation is performed by a process row.
 *            = 'c' :   Operation is performed by a process column.
 *            = 'a' :   Operation is performed by all processes in grid.
 *
 *  NBRANCHES (input) int
 *            Indicates the degree of the tree to use (see picture above).
 *
 *  MATINF    (input) ptr to MATINFO (structure)
 *            This structure contains information about the matrices to be
 *            operated on.  The number of matrices, and what each matrix is
 *            depends on the operation being performed.
 *
 *  BUFF      (workspace) Pointer to char
 *            This space used to hold a column of data while it is being
 *            operated on.
 *
 *  RDEST1    (input) int
 *            Process row coordinate of node to receive the answer.
 *            If RDEST1 == -1, all nodes in scope receive the answer.
 *
 *  CDEST1    (input) int
 *            Process column coordinate of node to receive the answer.
 *            If RDEST1 == -1, CDEST is ignored.
 *  Xmvpk     (input) pointer to packing function
 *            Points to the packing function appropriate for a given operation.
 *
 *  Xmvupk    (input) pointer to unpacking function
 *            Points to the unpacking function appropriate for a given
 *            operation.
 *
 * Xmvupk_op  (input) pointer to operation/unpacking function
 *            Points to a function which unpacks the columns of the received
 *            matrix, and stores the result of the operation in the output
 *            matrix (or matrices) pointed to by MATINF.
 *
 * ------------------------------------------------------------------------
 */
{
   char *getbuff();
   void Ssend2d00();
   void Srecv2d00();

   int nnodes, msgid, dest, rdest, cdest, i, j;
   int nrcvs=0;	  /* Number of ReCeiVeS to do */
   int REBS;	  /* should info be RE-BroadcaSt? */
   int rightedge; /* right-most receiving node */
   int mydist;    /* my distance from destination node */

   if ( (REBS = (rdest1 == -1)) ) rdest1 = cdest1 = 0;

   switch (scope)
   {
   case 'r':
      nnodes = ctxt->npcol;
      if (nnodes < 2) return;
      mydist = (nnodes+ctxt->mycol-cdest1)%nnodes;
      msgid = Mrid(ctxt);
      break;
   case 'c':
      nnodes = ctxt->nprow;
      if (nnodes < 2) return;
      mydist = (nnodes+ctxt->myrow-rdest1)%nnodes;
      msgid = Mcid(ctxt);
      break;
   case 'a':
      nnodes = ctxt->Ng;
      if (nnodes < 2) return;
      dest = Mvkpnum(ctxt, rdest1, cdest1);
      mydist = (nnodes+ctxt->vIam-dest)%nnodes;
      msgid = Maid(ctxt);
      break;
   default :
      return;
   }

   rdest = ctxt->myrow;
   cdest = ctxt->mycol;
   if (nbranches == FULLCON) nbranches = nnodes;
   rightedge = nnodes - 1 - (nnodes-1)%nbranches;

   for (i=1; (i < nnodes); i *= nbranches)
   {

      if (mydist%nbranches)	/* nodes that send to other nodes */
      {

         switch (scope)
         {
         case 'r':
            cdest = (cdest1 + (mydist-mydist%nbranches)*i) % nnodes;
	    break;
	 case 'c':
	    rdest = (rdest1 + (mydist-mydist%nbranches)*i) % nnodes;
	    break;
	 case 'a':
            j = (dest + (mydist-mydist%nbranches)*i) % nnodes;
            Mvpcoord(ctxt, j, rdest, cdest);
	 }
/*
 *       Pack message, and send it
 */
         Xmvpk(MatInf);
         Ssend2d00(ctxt, rdest, cdest, msgid);
	 break;		/* I'm done */
      }
      else
      {
         if (mydist != rightedge) nrcvs = nbranches - 1;
         else nrcvs = (nnodes + i - 1) / i - rightedge - 1;
         mydist /= nbranches;
         rightedge /= nbranches;
         rightedge -= (rightedge % nbranches);

         for (j=0; j < nrcvs; j++)
         {
            Srecv2d00(ctxt, msgid);
            Xmvupk_op(MatInf, buff);
         }
      }
   }

/*
 * Broadcast answer to everyone if RDEST == -1
 */
   if (REBS)
   {
      if (mydist == 0)
      {
         Xmvpk(MatInf);
         Stree_bs(ctxt, scope, 2);
      }
      else
      {
         Stree_br(ctxt, scope, 2, rdest1, cdest1);
         Xmvupk(MatInf);
      }
   }
} /* end tree_comb */
