
#include <mpi.h>
#include <stdio.h>
#include <assert.h>

#undef _USE_MTEST_

#ifdef _USE_MTEST_
#include "mpitest.h"
#endif

int main( int argc, char *argv[] )
{
     int errs = 0;
     int np = 1;
     int errcodes[2];
     int world_rank;
     int world_size;
     MPI_Comm parent_comm;
     MPI_Comm spawn_comm;

#ifdef _USE_MTEST_
     MTest_Init(&argc, &argv);
#else
     MPI_Init(&argc, &argv);
#endif    

     MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
     MPI_Comm_size(MPI_COMM_WORLD, &world_size);

     printf("(%d) World: I am %d of %d\n", getpid(), world_rank, world_size);
    
     MPI_Comm_get_parent( &parent_comm );
    
     if(parent_comm == MPI_COMM_NULL) {

	  /* Let rank 0 and 1 each spawn one additional group: (thus two spawned groups in total) */
	  if(world_rank < 2) {
	       MPI_Comm_spawn((char*)"./spaiccreate3", MPI_ARGV_NULL, np, MPI_INFO_NULL, 0, MPI_COMM_SELF, &spawn_comm, errcodes);
	  } else {
	       spawn_comm = MPI_COMM_NULL;
	  }

     } else {
	  spawn_comm = parent_comm;	 
     }
    
    
     if(spawn_comm != MPI_COMM_NULL) {
    
	  int i;
	  int high;
	  int intra_rank;
	  int intra_size;
	  int leader;
	  int leader_buf[4];
	  MPI_Comm intra_comm;

	  if(parent_comm == MPI_COMM_NULL) high = 1;
	  else high = 0;

	  /* Merge each intercomm between the spawned groups into an intracomm: */
	  MPI_Intercomm_merge(spawn_comm, high, &intra_comm);

	  MPI_Comm_rank(intra_comm, &intra_rank);
	  MPI_Comm_size(intra_comm, &intra_size);

	  printf("(%d) Intra: I am %d of %d\n", getpid(), intra_rank, intra_size);

	  if(1) {
	       int inter_rank;
	       int inter_loc_size;
	       int inter_rem_size;
	       MPI_Comm inter_comm;

	       /* Determine the leader (rank 0 & 1 of the origin world): */
	       
	       if(parent_comm == MPI_COMM_NULL) leader = intra_rank;
	       else leader = -1;
	       
	       MPI_Allgather(&leader, 1, MPI_INT, leader_buf, 1, MPI_INT, intra_comm);
	       for(i=0; i<intra_size; i++) {
		    if(leader_buf[i] != -1) {
			 leader = i;
			 break;
		    }
	       }
	       
	       /* Create an intercomm between the two merged intracomms (and use the origin world as bridge/peer communicator): */
	       MPI_Intercomm_create(intra_comm, leader, MPI_COMM_WORLD, (world_rank+1)%2, 123, &inter_comm);
	     
	       MPI_Comm_rank(inter_comm, &inter_rank);
	       MPI_Comm_size(inter_comm, &inter_loc_size);
	       MPI_Comm_remote_size(inter_comm, &inter_rem_size);

	       printf("(%d) Inter: I am %d of %d + %d\n", getpid(), inter_rank, inter_loc_size, inter_rem_size);

	       /* Try a Barrier on this intercomm: */
	       MPI_Barrier(inter_comm);


	       if(1) {
		    int univ_rank;
		    int univ_size;
		    MPI_Comm univ_comm;

		    /* Merge this intercomm into a 'universe' intracomm that covers all procs: */
		    MPI_Intercomm_merge(inter_comm, 0, &univ_comm);
		    
		    MPI_Comm_rank(univ_comm, &univ_rank);
		    MPI_Comm_size(univ_comm, &univ_size);
		    
		    printf("(%d) Univs: I am %d of %d\n", getpid(), univ_rank, univ_size);
		    
		    /* Try a Barrier on this intracomm: */		    
		    MPI_Barrier(univ_comm);

		    MPI_Comm_free(&univ_comm);
	       }

	       MPI_Comm_free(&inter_comm);
	  }
	  MPI_Comm_free(&intra_comm);
	  MPI_Comm_free(&spawn_comm);
     }

#ifdef _USE_MTEST_
    /* Note that the MTest_Finalize get errs only over COMM_WORLD */
    /* Note also that both the parent and child will generate "No Errors"
       if both call MTest_Finalize */
     if (parent_comm == MPI_COMM_NULL) {
	MTest_Finalize( errs );
     }
#endif
     
     MPI_Finalize();
     return 0;
}
