/*  lil-gp Genetic Programming System, version 1.0, 11 July 1995
 *  Copyright (C) 1995  Michigan State University
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of version 2 of the GNU General Public License as
 *  published by the Free Software Foundation.
 * 
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *  
 *  Douglas Zongker       (zongker@isl.cps.msu.edu)
 *  Dr. Bill Punch        (punch@isl.cps.msu.edu)
 *
 *  Computer Science Department
 *  A-714 Wells Hall
 *  Michigan State University
 *  East Lansing, Michigan  48824
 *  USA
 *  
 */

#include <lilgp.h>

/* initialize_topology()
 *
 * reads the parameter database and builds the exchange table.
 */

void initialize_topology ( multipop *mpop )
{
     char pnamebuf[100], pnamebuf2[100];
     char *param, *param2, *param3;
     int i, j, k;
     int errors = 0;

     if ( mpop->size == 1 )
     {
	  /* singlepop problem -- no topology needed. */
          mpop->exch = NULL;
          mpop->exchanges = -1;
          return;
     }

     oprintf ( OUT_SYS, 30, "building subpopulation exchange topology:\n" );
     
     param = get_parameter ( "multiple.exchanges" );
     if ( param == NULL )
     {
	  /* multipop problem, but no exchanges specified. */
	  
          mpop->exch = NULL;
          mpop->exchanges = 0;
          return;
     }
     else
     {
          mpop->exchanges = atoi ( param );
          if ( mpop->exchanges < 0 )
               error ( E_FATAL_ERROR, "\"exchanges\" must be nonnegative." );
     }

     mpop->exch = (exchange *)MALLOC ( mpop->exchanges * sizeof ( exchange ) );
     
     for ( i = 0; i < mpop->exchanges; ++i )
     {
	  /** read the destination subpop. **/
	  
          sprintf ( pnamebuf, "exch[%d].to", i+1 );
          param = get_parameter ( pnamebuf );
          if ( param == NULL )
          {
               ++errors;
               error ( E_ERROR, "\"%s\" must be set.", pnamebuf );
          }
          else
          {
               mpop->exch[i].to = atoi ( param ) - 1;
               if ( mpop->exch[i].to < 0 || mpop->exch[i].to >= mpop->size )
               {
                    ++errors;
                    error ( E_ERROR, "\"%s\" is out of range.\n", pnamebuf );
               }
          }

	  /** read how the individuals to be replaced in the destination
	    subpop are selected. **/
	  
          sprintf ( pnamebuf, "exch[%d].toselect", i+1 );
          mpop->exch[i].tosc = get_parameter ( pnamebuf );
          if ( mpop->exch[i].tosc == NULL )
          {
               ++errors;
               error ( E_ERROR, "\"%s\" must be set.", pnamebuf );
          }
          else
          {
               if ( ! exists_select_method ( mpop->exch[i].tosc ) )
               {
                    ++errors;
                    error ( E_ERROR, "\"%s\": \"%s\" is not a selection method.",
                           pnamebuf, mpop->exch[i].tosc );
               }
          }

	  /** read how many individuals are to be exchanged in this
	    manner. **/
	  
          sprintf ( pnamebuf, "exch[%d].count", i+1 );
          param = get_parameter ( pnamebuf );
          if ( param == NULL )
          {
               ++errors;
               error ( E_ERROR, "\"%s\" must be set.", pnamebuf );
          }
          else
          {
               mpop->exch[i].count = atoi ( param );
               if ( mpop->exch[i].count < 0 )
               {
                    ++errors;
                    error ( E_ERROR, "\"%s\" must be nonnegative.", pnamebuf );
               }
          }

	  /** check to see if "from" is specified without a "tree[#]". **/
	  
          sprintf ( pnamebuf, "exch[%d].from", i+1 );
          param = get_parameter ( pnamebuf );
          if ( param )
          {
               /** if "from" is specified, then we're copying whole individuals
		 from one subpop to another. **/

	       /* these arrays are not needed. */
               mpop->exch[i].from = NULL;
               mpop->exch[i].as = NULL;
	       /* allocate an array of one string (to hold the selection
		  method). */
               mpop->exch[i].fromsc = (char **)MALLOC ( sizeof ( char * ) );

	       /* the subpop that individuals are taken from. */
               mpop->exch[i].copywhole = atoi ( param ) - 1;
               if ( mpop->exch[i].copywhole < 0 ||
                    mpop->exch[i].copywhole >= mpop->size )
               {
                    ++errors;
                    error ( E_ERROR, "\"%s\" is out of range.", pnamebuf );
               }

	       /* the selection method used to pick the individuals from the
		  source subpop. */
               sprintf ( pnamebuf, "exch[%d].fromselect", i+1 );
               mpop->exch[i].fromsc[0] = get_parameter ( pnamebuf );
               if ( mpop->exch[i].fromsc[0] == NULL )
               {
                    ++errors;
                    error ( E_ERROR, "\"%s\" must be set.", pnamebuf );
               }
               else
               {
                    if ( ! exists_select_method ( mpop->exch[i].fromsc[0] ) )
                    {
                         ++errors;
                         error ( E_ERROR, "\"%s\": \"%s\" is not a selection method.",
                                pnamebuf, mpop->exch[i].fromsc[0] );
                    }
               }
          }
          else
          {

               /** since "from" is not defined, we're taking trees from different
		 subpops and merging them to create a composite individual to place
		 in the destination subpop. **/
	       
               mpop->exch[i].copywhole = -1;
	       /* this array lists, for each tree, which subpop it comes from. */
               mpop->exch[i].from = (int *)MALLOC ( tree_count * sizeof ( int ) );
	       /* this array keeps track of when two trees are supposed to always
		  come from the same individual (not just the same subpop). */
               mpop->exch[i].as = (int *)MALLOC ( tree_count * sizeof ( int ) );
	       /* this array holds the selection method strings used for each
		  tree. */
               mpop->exch[i].fromsc = (char **)MALLOC ( tree_count * sizeof ( char * ) );

	       /* get the default selection method, if one is specified. */
               sprintf ( pnamebuf, "exch[%d].fromselect", i+1 );
               param3 = get_parameter ( pnamebuf );
               
               for ( j = 0; j < tree_count; ++j )
               {
		    /** for each tree, attempt to read the "from" and
		      "fromselect" parameters. **/
		    
                    sprintf ( pnamebuf, "exch[%d].from.tree[%d]", i+1, j );
                    param = get_parameter ( pnamebuf );
                    sprintf ( pnamebuf2, "exch[%d].fromselect.tree[%d]",
                             i+1, j );
                    param2 = get_parameter ( pnamebuf2 );

                    if ( param == NULL && param2 == NULL )
                    {
			 /* neither is set, we're supposed to leave this
			    tree untouched in the destination individual. */
			 
                         mpop->exch[i].from[j] = -1;
                         mpop->exch[i].as[j] = -1;
                         mpop->exch[i].fromsc[j] = NULL;
                    }
                    else if ( param2 == NULL )
                    {
                         /* only "from" is set, examine param3 for default
			    selection method. */

			 /* source subpop. */
                         mpop->exch[i].from[j] = atoi ( param ) - 1;
                         if ( mpop->exch[i].from[j] < 0 || mpop->exch[i].from[j] >= mpop->size )
                         {
                              ++errors;
                              error ( E_ERROR, "\"%s\" is out of range.", pnamebuf );
                         }

			 /* no default set, error. */
                         if ( param3 == NULL )
                         {
                              ++errors;
                              error ( E_ERROR, "\"%s\" must be set.", pnamebuf2 );
                         }
                         else
                         {
                              mpop->exch[i].as[j] = -1;
                              if ( ! exists_select_method ( param3 ) )
                              {
                                   ++errors;
                                   error ( E_ERROR, "\"%s\": \"%s\" is not a selection method.",
                                          pnamebuf, param3 );
                              }
                         }
                         mpop->exch[i].fromsc[j] = param3;
                    }
                    else if ( param == NULL )
                    {
                         /* only "fromselect" is set; it better be of the form
			    "as_#". */
                         
                         if ( strncmp ( param2, "as_", 3 ) == 0 )
                         {
                              mpop->exch[i].from[j] = -1;
                              mpop->exch[i].fromsc[j] = NULL;
			      /* "as" stores which tree this one comes from the
				 same subpop as. */
                              mpop->exch[i].as[j] = atoi ( param2 + 3 );
                              if ( mpop->exch[i].as[j] < 0 ||
                                  mpop->exch[i].as[j] >= tree_count )
                              {
                                   ++errors;
                                   error ( E_ERROR, "\"%s\" is out of range.", pnamebuf2 );
                              }
                         }
                         else
                         {
                              ++errors;
                              error ( E_ERROR, "\"%s\" must be \"as_#\".", pnamebuf2 );
                         }
                    }
                    else
                    {
                         /* they're both set. */

                         mpop->exch[i].as[j] = -1;
                         mpop->exch[i].from[j] = atoi ( param ) - 1;
                         if ( mpop->exch[i].from[j] < 0 || mpop->exch[i].from[j] >= mpop->size )
                         {
                              ++errors;
                              error ( E_ERROR, "\"%s\" is out of range.", pnamebuf );
                         }
                         mpop->exch[i].fromsc[j] = param2;
                         if ( ! exists_select_method ( param2 ) )
                         {
                              ++errors;
                              error ( E_ERROR, "\"%s\": \"%s\" is not a selection method.",
                                     pnamebuf2, param2 );
                         }
                    }
               }

	       /* now we need to resolve any chains of "as_" references: if
		  tree 2 comes from the same individual as tree 1, and tree 1
		  comes from the same individual as tree 0, we need to change that
		  to say that both 2 and 1 come from tree 0.

		  also detect circular references. */
	       
               for ( j = 0; j < tree_count; ++j )
               {
                    if ( mpop->exch[i].as[j] == -1 )
                         continue;
                    k = mpop->exch[i].as[j];
                    while ( k != -1 )
                    {
                         if ( k == j )
                         {
                              ++errors;
                              error ( E_ERROR, "Circular reference resolving \"exch[%d].fromselect.tree[%d]\".",
                                     i+1, j );
                              j = tree_count;
                              break;
                         }
                         mpop->exch[i].as[j] = k;
                         k = mpop->exch[i].as[k];
                    }
                    k = mpop->exch[i].as[j];
                    if ( mpop->exch[i].from[k] == -1 && mpop->exch[i].as[k] == -1 )
                         mpop->exch[i].as[j] = -1;
               }
          }

          
#ifdef DEBUG
	  /* print out information on this exchange. */
          printf ( "exchange %d:\n", i+1 );
          printf ( "to: %d; count: %d; select: %s\n", mpop->exch[i].to,
                  mpop->exch[i].count, mpop->exch[i].tosc );
          if ( mpop->exch[i].copywhole == -1 )
          {
               for ( j = 0; j < tree_count; ++j )
               {
                    param = mpop->exch[i].fromsc[j];
                    printf ( "   %3d:  from: %3d   as: %3d   select: %s\n",
                            j, mpop->exch[i].from[j], mpop->exch[i].as[j],
                            param==NULL?"NULL":param );
               }
          }
          else
          {
               param = mpop->exch[i].fromsc[0];
               printf ( "copywhole: %d   select: %s\n",
                       mpop->exch[i].copywhole, param==NULL?"NULL":param );
          }
#endif
     }

     /* if any errors occurred then stop now. */
     if ( errors )
          error ( E_FATAL_ERROR, "Errors occurred while building topology.  Aborting." );

     /* print out the summary of exchanges. */
     oprintf ( OUT_SYS, 30, "    %d exchange(s) total.\n", mpop->exchanges );
     for ( i = 0; i < mpop->exchanges; ++i )
     {
          oprintf ( OUT_SYS, 30, "    exchange %d:\n", i+1 );
          oprintf ( OUT_SYS, 30, "        replace %d individual(s) in subpop %d (selected by %s)\n",
                   mpop->exch[i].count, mpop->exch[i].to+1, mpop->exch[i].tosc );
          if ( mpop->exch[i].copywhole != -1 )
               oprintf ( OUT_SYS, 30, "        with individual(s) from subpop %d (selected by %s)\n",
                        mpop->exch[i].copywhole+1, mpop->exch[i].fromsc[0] );
          else
               for ( j = 0; j < tree_count; ++j )
               {
                    if ( mpop->exch[i].from[j] == -1 )
                    {
                         if ( mpop->exch[i].as[j] == -1 )
                              oprintf ( OUT_SYS, 30, "        tree %d: leaving original tree\n", j );
                         else
                              oprintf ( OUT_SYS, 30, "        tree %d: from same individual as tree %d\n", j, mpop->exch[i].as[j] );
                    }
                    else
                         oprintf ( OUT_SYS, 30, "        tree %d: from subpop %d (selected by %s)\n", j,
                                  mpop->exch[i].from[j]+1, mpop->exch[i].fromsc[j] );
               }
     }

}

/* free_topology()
 *
 * this frees the topology table.
 */

void free_topology ( multipop *mpop )
{
     int i;
     for ( i = 0; i < mpop->exchanges; ++i )
     {
          if ( mpop->exch[i].from )
               FREE ( mpop->exch[i].from );
          if ( mpop->exch[i].as )
               FREE ( mpop->exch[i].as );
          FREE ( mpop->exch[i].fromsc );
     }
     if ( mpop->exch )
          FREE ( mpop->exch );
}

/* exchange_subpopulations()
 *
 * this performs the actual exchanges, using the information stored
 * in the exchange table.
 */

void exchange_subpopulations ( multipop *mpop )
{
     int i, j, k;
     sel_context *tocon;
     sel_context **fromcon;
     select_context_func_ptr select_con;
     int tp, *fp;
     int ti, *fi;

     /** arrays used for composite individuals. **/

     /* fromcon[j] holds the selection context used to pick individual
	to take tree j from. */
     fromcon = (sel_context **)MALLOC ( tree_count * sizeof ( sel_context * ) );
     /* fp[j] holds the population from which to take tree j from. */
     fp = (int *)MALLOC ( tree_count * sizeof ( int ) );
     /* fi[j] holds the individual from which to take tree j from. */
     fi = (int *)MALLOC ( tree_count * sizeof ( int ) );

     for ( i = 0; i < mpop->exchanges; ++i )
     {
#ifdef DEBUG
          printf ( "working on exch[%d]\n", i+1 );
#endif

	  /* where individuals are going. */
          tp = mpop->exch[i].to;

	  /* set up selection method to pick individuals to be replaced. */
          select_con = get_select_context ( mpop->exch[i].tosc );
          tocon = select_con ( SELECT_INIT, NULL, mpop->pop[tp],
                              mpop->exch[i].tosc );

	  /* are we copying whole individuals or creating composites? */
          if ( mpop->exch[i].copywhole > -1 )
          {
	       /*** copying whole individuals. ***/

	       /* the source subpop. */
               fp[0] = mpop->exch[i].copywhole;

	       /* selection method for choosing individuals from source
		  subpop. */
               select_con = get_select_context ( mpop->exch[i].fromsc[0] );
               fromcon[0] = select_con ( SELECT_INIT, NULL, mpop->pop[fp[0]],
                                        mpop->exch[i].fromsc[0] );

               for ( k = 0; k < mpop->exch[i].count; ++k )
               {
		    /** pick an individual to be replaced that has not already
		      been replaced during this exchange cycle. **/
                    do
                    {
                         ti = tocon->select_method ( tocon );
                    }
                    while ( mpop->pop[tp]->ind[ti].flags & FLAG_NEWEXCH );

		    /* pick an individual from the source subpop. */
                    fi[0] = fromcon[0]->select_method ( fromcon[0] );
                    
#ifdef DEBUG
                    printf ( "COPYING WHOLE INDIVIDUAL: ind %d subpop %d --> ind %d subpop %d\n",
                            fi[0], fp[0], ti, tp );
#endif
                    
                    /** remove the old iondividual from the population. **/
                    for ( j = 0; j < tree_count; ++j )
                    {
			 /* always dereference ERCs when removing trees
			    from the population. */
                         reference_ephem_constants ( mpop->pop[tp]->ind[ti].tr[j].data, -1 );
                         free_tree ( mpop->pop[tp]->ind[ti].tr+j );
                    }

		    /* copy the individual. */
                    duplicate_individual ( mpop->pop[tp]->ind+ti,
                                           mpop->pop[fp[0]]->ind+fi[0] );

		    /* reference the ERCs in the new individual. */
                    for ( j = 0; j < tree_count; ++j )
                         reference_ephem_constants ( mpop->pop[tp]->ind[ti].tr[j].data, 1 );

		    /* mark the individual as just coming from an exchange. */
                    mpop->pop[tp]->ind[ti].flags = FLAG_NEWEXCH;
               }

	       /* all done with this exchange, delete the selection context. */
               fromcon[0]->context_method ( SELECT_CLEAN, fromcon[0],
                                           NULL, NULL );
          }
          else
          {
               /*** creating composite individuals. ***/

	       /** create selection contexts for each tree. **/
               for ( j = 0; j < tree_count; ++j )
               {
		    /* does this tree need a context? */
                    if ( mpop->exch[i].fromsc[j] )
                    {
#ifdef DEBUG
                         printf ( "getting selection context for tree %d (%s)\n",
                                 j, mpop->exch[i].fromsc[j] );
#endif
			 /* create it. */
                         select_con = get_select_context ( mpop->exch[i].fromsc[j] );
                         fromcon[j] = select_con ( SELECT_INIT, NULL,
                                                  mpop->pop[mpop->exch[i].from[j]],
                                                  mpop->exch[i].fromsc[j] );
                    }
                    else
			 /* don't need one. */
                         fromcon[j] = NULL;
               }

               for ( k = 0; k < mpop->exch[i].count; ++k )
               {
		    /** select an individual to be replaced that hasn't already
		      been during this exchange cycle. **/
                    do
                    {
                         ti = tocon->select_method ( tocon );
                    }
                    while ( mpop->pop[tp]->ind[ti].flags & FLAG_NEWEXCH );
                    
#ifdef DEBUG
                    printf ( "SELECTED TREE %d FOR REPLACEMENT.\n", ti );
                    print_individual ( mpop->pop[tp]->ind+ti, stdout );
#endif
		    /** now select the individuals that we will merge to
		      replace trees of the destination individual. **/
                    for ( j = 0; j < tree_count; ++j )
                    {
			 /* we don't need to do a selection for a particular
			    tree if (1) it uses the same individual as another
			    tree or (2) it doesn't get replaced in the destination
			    individual. */

                         fp[j] = mpop->exch[i].from[j];
                         if ( fp[j] != -1 )
                         {
                              fi[j] = fromcon[fp[j]]->select_method ( fromcon[fp[j]] );
#ifdef DEBUG
                              printf ( "selecting using (%s) from subpop %d (for tree %d): individual %d\n",
                                      mpop->exch[i].fromsc[j], fp[j], j, fi[j] );
                              print_individual ( mpop->pop[fp[j]]->ind+fi[j], stdout );
#endif
                         }
                    }

		    /** now resolve "as_" references in the fp and fi arrays. */
                    for ( j = 0; j < tree_count; ++j )
                         if ( fp[j] == -1 )
                         {
                              if ( mpop->exch[i].as[j] == -1 )
				   /* tree j doesn't get replaced, so set both
				      values to -1. */
                                   fp[j] = fi[j] = -1;
                              else
                              {
				   /* tree j comes from the same individual as
				      some other tree. */
                                   fp[j] = fp[mpop->exch[i].as[j]];
                                   fi[j] = fi[mpop->exch[i].as[j]];
                              }
                         }

#ifdef DEBUG
                    printf ( "the fp,fi arrays are:\n" );
                    for ( j = 0; j < tree_count; ++j )
                         printf ( "   %3d:  fp = %3d    fi = %4d\n", j, fp[j], fi[j] );
#endif

                    /** replace the appropriate parts of the old tree. **/
                    for ( j = 0; j < tree_count; ++j )
                    {
			 /* skip trees that don't get replaced. */
                         if ( fp[j] == -1 )
                              continue;

			 /* dereference ERCs in old tree. */
                         reference_ephem_constants ( mpop->pop[tp]->ind[ti].tr[j].data, -1 );
			 /* delete old tree. */
                         free_tree ( mpop->pop[tp]->ind[ti].tr+j );
			 /* copy new tree. */
                         copy_tree ( mpop->pop[tp]->ind[ti].tr+j, mpop->pop[fp[j]]->ind[fi[j]].tr+j );
			 /* reference ERCs in new tree. */
                         reference_ephem_constants ( mpop->pop[tp]->ind[ti].tr[j].data, 1 );
                    }
#ifdef COEVOLUTION
    error ( E_FATAL_ERROR, "Can't do COEVOLUTION and multi-pop experiments\n       together at this time, sorry.\n");
#else
		    /* evaluate the fitness of the new composite individual. */
                    app_eval_fitness ( mpop->pop[tp]->ind+ti );
#endif
                    mpop->pop[tp]->ind[ti].flags = FLAG_NEWEXCH;

#ifdef DEBUG
                    printf ( "the new individual is:\n" );
                    print_individual ( mpop->pop[tp]->ind+ti, stdout );
#endif
               }

	       /* destroy source selection contexts. */
               for ( j = 0; j < tree_count; ++j )
                    if ( fromcon[j] )
                         fromcon[j]->context_method ( SELECT_CLEAN,
                                                     fromcon[j], NULL, NULL );
          }

	  /* destroy destination selection context. */
          tocon->context_method ( SELECT_CLEAN, tocon, NULL, NULL );
     }

     FREE ( fromcon );
     FREE ( fp );
     FREE ( fi );

     /* erase all the NEWEXCH flags. */
     for ( i = 0; i < mpop->size; ++i )
          for ( j = 0; j < mpop->pop[i]->size; ++j )
               mpop->pop[i]->ind[j].flags &= ~FLAG_NEWEXCH;
     
}
                   
/* rebuild_exchange_topology()
 *
 * rebuilds the exchange table.  called from user code after making changes
 * to the parameters governing exchanges.
 */

void rebuild_exchange_topology ( multipop *mpop )
{
     free_topology ( mpop );
     initialize_topology ( mpop );
}