/*  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>



/* Read in an individual from a file, skipping any text in between */
void mod_read_tree_recurse ( int space, ephem_const **eind, FILE *fil, int tree,
			     char *string, int skip )
{
  function *f;
  int i, j;
  ephem_const *ep;

  if( skip )
    {
      i = 0;
      /*Read up until we get the first parenthesies */
      while( i != '(' )
	{
	  if( feof(fil) )
	    {
	      fseek( fil, 0L, SEEK_SET );
	    }
	  i = fgetc(fil);
	}
      if( i != '(' )
	{
	  error( E_FATAL_ERROR, "Ran out of file space while reading in saved tree");
	}
    }
  /* read up until a nonwhitespace character in file.   the nonwhitespace
     character is saved in string[0]. */
  while ( isspace(string[0]=fgetc(fil)) );
  /* get the next character. */
  i = fgetc ( fil );
  if ( isspace(i) )
    /* if the next character is whitespace, then string[0] is a
       one-character function name.  null-terminate the string. */
    string[1] = 0;
  else
    {
      /** if the next character is not whitespace, then string[0]
	is either an open parenthesis or the first character of a
	multi-character function name. **/
	  
      /* push the next character back. */
      ungetc ( i, fil );
      /* read the function name.  skip over an open parenthesis, if there
	 is one. */
      fscanf ( fil, "%s ", string+(string[0]!='(') );
    }
#ifdef DEBUG_READTREE
  fprintf ( stderr, "function name is [%s]\n", string );
#endif

  /* look up the function name in this tree's function set.  if the
     function is an ERC terminal (the name is of the form "name:ERCindex"),
     then place the ERC address in ep. */
  f = get_function_by_name ( tree, string, &ep, eind );
  /* add an lnode to the tree. */
  gensp_next(space)->f = f;
     
  switch ( f->type )
    {
    case TERM_NORM:
    case TERM_ARG:
    case EVAL_TERM:
      break;
    case TERM_ERC:
      /* record the ERC address as the next lnode in the array. */
      gensp_next(space)->d = ep;
      break;
    case FUNC_DATA:
    case EVAL_DATA:
      /** recursively read child functions, no skip nodes needed. **/
      for ( i = 0; i < f->arity; ++i )
	mod_read_tree_recurse ( space, eind, fil, tree, string, 0 );
      break;
    case FUNC_EXPR:
    case EVAL_EXPR:
      /** recursively read child functions, recording skip values. **/
      for ( i = 0; i < f->arity; ++i )
	{
	  /* save an lnode for the skip value. */
	  j = gensp_next_int ( space );
	  /* read the child tree. */
	  mod_read_tree_recurse ( space, eind, fil, tree, string, 0);
	  /* figure out how big the child tree was, and save that
	     number in the skip node. */
	  gensp[space].data[j].s = gensp[space].used-j-1;
	}
      break;
    }
}


/* generate_random_population()
 *
 * fills a population with randomly generated members.
 */

void generate_random_population ( population *p, int *mindepth,
				  int *maxdepth, int *method, FILE **dataum )
{
  int i, j, k, m;
  int attempts;
  int totalattempts = 0;
  int depth;
  int attempts_generation;
  tree *temp;
  int totalnodes = 0;
  int flag;
  char buf[2048];
  int ignore_limits;

  /* how many consecutive rejected trees we will tolerate before
     giving up. */
  attempts_generation = atoi ( get_parameter ( "init.random_attempts" ) );
  if ( attempts_generation <= 0 )
    error ( E_FATAL_ERROR,
	    "\"init.random_attempts\" must be positive." );

  ignore_limits = (get_parameter("init.ignore_limits")!=NULL);

  temp = (tree *)MALLOC ( tree_count * sizeof ( tree ) );
     
  k = 0;
  attempts = attempts_generation;
  while ( k < p->size )
    {
      /* total nodes in the individual being generated. */
      totalnodes = 0;
          
      for ( j = 0; j < tree_count; ++j )
	{
          
	  if ( attempts <= 0 )
	    error ( E_FATAL_ERROR,
		    "The last %d trees generated have been bad.  Giving up.",
		    attempts_generation );
               
	  --attempts;
	  ++totalattempts;

	  /* pick a depth on the depth ramp. */
	  depth = mindepth[j] + random_int ( &globrand, maxdepth[j] - mindepth[j] + 1 );

	  /* clear a generation space. */
	  gensp_reset ( 0 );

	  /** generate the tree. **/
	  switch ( method[j] )
	    {
	    case GENERATE_FULL:
	      generate_random_full_tree ( 0, depth, fset+tree_map[j].fset,
					  tree_map[j].return_type);
	      break;
	    case GENERATE_GROW:
	      generate_random_grow_tree ( 0, depth, fset+tree_map[j].fset,
					  tree_map[j].return_type);
	      break;
	    case GENERATE_HALF_AND_HALF:
	      if ( random_double(&globrand) < 0.5  )
		generate_random_full_tree ( 0, depth, fset+tree_map[j].fset,
					    tree_map[j].return_type);
	      else
		generate_random_grow_tree ( 0, depth, fset+tree_map[j].fset,
					    tree_map[j].return_type);
	      break;

	    case LOAD_FILE:
	      gensp_reset(0);
	      mod_read_tree_recurse( 0, NULL, dataum[j], j, buf, 1);
	      /* need to do something here about limit checking */
	      break;
	    }

	      /** throw away the tree if it's too big. **/
	      
	      /*printf("Attempted Tree: ");
		print_tree(gensp[0].data,stdout);*/
	      
	      /* first check the node limits. */
	      flag = 0;
	      m = tree_nodes ( gensp[0].data );


	  if (method[j]!=LOAD_FILE)
	    {
	      if ( tree_map[j].nodelimit > -1 && m > tree_map[j].nodelimit )
		{
		  --j;
		  continue;
		}
	      
	      /* now change the depth limits. */
	      if ( tree_map[j].depthlimit > -1 && tree_depth ( gensp[0].data ) > tree_map[j].depthlimit )
		{
		  --j;
		  continue;
		}
	      
	      /* count total nodes in the individual being created. */
	    }
	  totalnodes += m;
	  gensp_dup_tree ( 0, temp+j );
	}

      if (!ignore_limits)
	{
	  
	  /* is the individual over the total node limit (if one is set)? */
	  if ( ind_nodelimit > -1 && totalnodes > ind_nodelimit )
	    {
#ifdef DEBUG
	      printf ( "overall node limit violated.\n" );
#endif
	      /* yes, so delete it and try again. */
	      for ( j = 0; j < tree_count; ++j )
		free_tree ( temp+j );
	      continue;
	    }
	  
	  /* throw away the individual if it's a duplicate. */
	  for ( i = 0; i < k; ++i )
	    {
	      flag = 0;
	      for ( j = 0; j < tree_count && !flag; ++j )
		{
		  if ( temp[j].size == p->ind[i].tr[j].size )
		    {
		      if ( memcmp ( temp[j].data, p->ind[i].tr[j].data,
				    temp[j].size * sizeof ( lnode ) ) )
			{
			  flag = 1;
			}
		    }
		  else
		    flag = 1;
		}
	      if ( !flag )
		break;
	    }
	  if ( i < k )
	    {
#ifdef DEBUG
	      printf ( "duplicate individual: (same as %d)\n", i );
	      for ( j = 0; j < tree_count; ++j )
		{
		  printf ( "   tree %d: ", j );
		  print_tree ( temp[j].data, stdout );
		}
#endif
	      /* individual is a duplicate, throw it away. */
	      --attempts;
	      for ( j = 0; j < tree_count; ++j )
		free_tree ( temp+j );
	      continue;
	    }
	}

      /** we now have a good individual to put in the population. */

      /* copy the tree array. */
      memcpy ( p->ind[k].tr, temp, tree_count * sizeof ( tree ) );

      /* reference ERCs. */
      for ( j = 0; j < tree_count; ++j )
	reference_ephem_constants ( p->ind[k].tr[j].data, 1 );
          
#ifdef DUMP_POPULATION
      printf ( "individual %5d:\n", k );
      print_individual ( p->ind+k, stdout );
#endif
      /* mark individual as unevaluated. */
      p->ind[k].evald = EVAL_CACHE_INVALID;
      p->ind[k].flags = FLAG_NONE;

      attempts = attempts_generation;
      ++k;
          
    }

  FREE ( temp );
     
  oprintf ( OUT_SYS, 10,
	    "    %d trees were generated to fill the population of %d (%d trees).\n",
	    totalattempts, p->size, p->size * tree_count );

}

/* allocate_population()
 *
 * allocates a population structure with the given size.
 */

population *allocate_population ( int size )
{
  int i;
  population *p = (population *)MALLOC ( sizeof ( population ) );

  p->size = size;
  p->next = 0;
  /* allocate the array of individuals. */
  p->ind = (individual *)MALLOC ( size * sizeof ( individual ) );

  for ( i = 0; i < size; ++i )
    {
      /* each individual has a block of tree structures, allocate
	 those here. */
      p->ind[i].tr = (tree *)MALLOC ( tree_count * sizeof ( tree ) );
      p->ind[i].evald = EVAL_CACHE_INVALID;
      p->ind[i].flags = FLAG_NONE;
    }

  return p;
}

/* free_multi_population()
 *
 * frees the populations in a multipop structure.
 */

void free_multi_population ( multipop *mp )
{
  int i;
  for ( i = 0; i < mp->size; ++i )
    free_population ( mp->pop[i] );
  FREE ( mp->pop );
  FREE ( mp );
}

/* free_population()
 *
 * frees a population and all the individuals in it.
 */

void free_population ( population *p )
{
  int i, j;
  for ( i = 0; i < p->size; ++i )
    {
      for ( j = 0; j < tree_count; ++j )
	{
	  /* dereference ERCs. */
	  reference_ephem_constants ( p->ind[i].tr[j].data, -1 );
	  free_tree ( &(p->ind[i].tr[j]) );
	}
      FREE ( p->ind[i].tr );
    }
  FREE ( p->ind );
  FREE ( p );
}

/* initial_multi_population()
 *
 * randomly fills a multipop strcture with individuals.
 */

multipop *initial_multi_population ( void )
{
  char *param;
  char temp_name[256];
  char **tree_replace;
  multipop *mpop;
  int i;
  int *mindepth, *maxdepth, *method;
  char *cp;
  char pnamebuf[100];
  FILE **tree_data;
  FILE *fp;

  oputs ( OUT_SYS, 10, "creating initial population(s):\n" );

  mpop = (multipop *)MALLOC ( sizeof ( multipop ) );

  /* how many subpops are we supposed to have? */
  param = get_parameter ( "multiple.subpops" );
  mpop->size = atoi ( param );
  if ( mpop->size <= 0 )
    error ( E_FATAL_ERROR,
	    "\"%s\" is not a valid value for \"multiple.subpops\".",
	    param );

  /* allocate that many population pointers. */
  mpop->pop = (population **)MALLOC ( sizeof ( population* ) * mpop->size );

  /* read the depth ramp and generation method(s), which can be
     different for each tree. */
  tree_replace = (char **)MALLOC(sizeof(char *)*tree_count);
  tree_data = (FILE **)MALLOC( sizeof(FILE *)*tree_count);

  for( i = 0; i < tree_count; i++ )
    {
      sprintf( temp_name, "tree-replace[%d]", i );
      tree_replace[i] = get_parameter( temp_name );
    }

  for( i = 0; i < tree_count; i++ )
    {
      if( tree_replace[i] != NULL )
	{
	  tree_data[i] = fopen( tree_replace[i], "r" );
	  if( fp == NULL )
	    {
	      /* Error Handler */
	      error( E_FATAL_ERROR, "Unable to open file %s\n", tree_replace[i] );
	    }
	     
	} else {
	  tree_data[i] = NULL;
	}
    }

  mindepth = (int *)MALLOC ( tree_count * sizeof ( int ) );
  maxdepth = (int *)MALLOC ( tree_count * sizeof ( int ) );
  method = (int *)MALLOC ( tree_count * sizeof ( int ) );

  for ( i = 0; i < tree_count; ++i )
    {
      mindepth[i] = -1;
      maxdepth[i] = -1;

      /** read the depth ramp. **/
      sprintf ( pnamebuf, "init.tree[%d].depth", i );
      param = get_parameter ( pnamebuf );
      if ( !param )
	param = get_parameter ( "init.depth" );
      if ( param == NULL )
	error ( E_FATAL_ERROR, "\"init.tree[%d].depth\" must be specified.", i );

      /** parse the depth ramp ("min-max" or just "val"). **/
      mindepth[i] = strtol ( param, &cp, 10 );
      if ( *cp )
	{
	  if ( *cp == '-' )
	    {
	      maxdepth[i] = strtol ( cp+1, &cp, 10 );
	    }
	  if ( *cp )
	    {
	      error ( E_FATAL_ERROR, "\"init.tree[%d].depth\" is malformed.",
		      i );
	    }
	}
      if ( maxdepth[i] == -1 )
	maxdepth[i] = mindepth[i];

      if ( mindepth[i] > maxdepth[i] )
	error ( E_FATAL_ERROR, "\"init.tree[%d].depth\" is malformed.", i );

      /** read the method. **/
      sprintf ( pnamebuf, "init.tree[%d].method", i );
      param = get_parameter ( pnamebuf );
      if ( !param )
	param = get_parameter ( "init.method" );

      if ( param == NULL )
	error ( E_FATAL_ERROR, "\"init.tree[%d].method\" must be set.",
		i );

      if ( strcmp ( param, "half_and_half" ) == 0 )
	method[i] = GENERATE_HALF_AND_HALF;
      else if ( strcmp ( param, "full" ) == 0 )
	method[i] = GENERATE_FULL;
      else if ( strcmp ( param, "grow" ) == 0 )
	method[i] = GENERATE_GROW;
      else if ( strcmp ( param, "load" ) == 0 )
	method[i]= LOAD_FILE;
      else
	error ( E_FATAL_ERROR, "\"init.tree[%d].method\": \"%s\" is not a known generation method.",
		i, param );
    }

  /* generate each population. */
  for ( i = 0; i < mpop->size; ++i )
    mpop->pop[i] = initial_population ( mindepth, maxdepth, method, tree_data );

  for( i = 0; i < tree_count; i++ )
    {
      if( tree_data[i] != NULL )
	fclose(tree_data[i]);     
    }

  FREE ( mindepth );
  FREE ( maxdepth );
  FREE ( method );
  FREE ( tree_replace );
  FREE ( tree_data );

  oputs ( OUT_SYS, 10, "    initial population(s) complete.\n" );
          
  return mpop;
}

/* initial_population()
 *
 * creates a population structure and fills it with randomly
 * generated individuals.
 */

population *initial_population ( int *mindepth, int *maxdepth, int *method, FILE **dataum )
{
  population *pop;
  int pop_size;
  char *param;
     
  /* get the population size and allocate. */
     
  param = get_parameter ( "pop_size" );
  if ( param == NULL )
    error ( E_FATAL_ERROR,
	    "no value specified for \"pop_size\"." );
  pop_size = atoi ( param );
  pop = allocate_population ( pop_size );

  /* get the generation method and create the random population. */

  generate_random_population ( pop, mindepth, maxdepth, method, dataum );
     
  return pop;
}
     
