/* ga_iface.cc */

#include "ga_iface.h"
#include "element.h"
#include "EvStdMetric.hh"
#include "integer.h"
#include "root.h"
#include "function.h"
#include "element.h"

const char nIndividuals_label[] = "nindividuals";
const char generation_label[] = "generation";
const char steady_state_label[] = "steady_state";
const char exit_cond_label[] = "exit_condition";

double steady_state_max_iters = 100;
double evaluation_max_iters = 100;

/* iteration functions */
void set_child_a( dc_label * );
void set_child_f( dc_label * );
void set_child_df( dc_label * );

bool ga_optimizer::init( void ) {
  if( pMut + pCrossover > 1.0 ) {
    dc_trace( TRACE_ERROR ) {
      cerr << "ga_optimizer::init -- illegal pMut=" << pMut 
	   << " and pCrossover " << pCrossover 
	   << " sum to value greater than 1\n";
    }
    return true;
  }

  /**** 1 -- find steady_state and exit conditions, and nIndividuals. 
    check types */
  list<long unsigned int> ga_deps;
  steady_state = ( dc_element * )lookup_child( steady_state_label, true );
  if( steady_state == nil ||  ( steady_state->type() != Element_t || 
				steady_state->get_rtype() != Boolean_t ) ) {
    dc_trace( TRACE_ERROR ) {
      if( steady_state ) {
	cerr << "ga_optimizer::init -- " << steady_state->full_type() 
	     << " must be a boolean element\n";
      } else {
	cerr << "ga_optimizer::init -- missing " << steady_state_label
	     << ".  must be a boolean element\n";
      }
    }
    return true;
  }
  
  exit_cond = ( dc_element * )lookup_child( exit_cond_label, true );
  if( exit_cond == nil || ( exit_cond->type() != Element_t || 
			    exit_cond->get_rtype() != Boolean_t ) ) {
    dc_trace( TRACE_ERROR ) {
      if( exit_cond ) {
	cerr << "ga_optimizer::init -- " << exit_cond->full_type() 
	     << " must be a boolean element\n";
      } else {
	cerr << "ga_optimizer::init -- missing " << exit_cond_label
	     << ".  must be a boolean element\n";
      }
    }
    return true;
  }
    
  nIndividuals = ( dc_element * )lookup_child( nIndividuals_label, true );
  if( nIndividuals == nil || ( nIndividuals->type() != Element_t || 
			       nIndividuals->get_rtype() != Int_t ) ) {
    dc_trace( TRACE_ERROR ) {
      if( nIndividuals ) {
	cerr << "ga_optimizer::init -- " << nIndividuals->full_type() 
	     << " must be an integer element\n";
      } else {
	cerr << "ga_optimizer::init -- missing " << nIndividuals_label
	     << ".  must be an integer element\n";
      }
      return true;
    }
  }

  int nerrs;
  if( fitness == nil ) {
    dc_trace( TRACE_ERROR ) {
      cerr << "ga_optimizer::init -- missing performance function";
    }
    return true;
  }
  if( ( nerrs = fitness->rehash() ) != 0 ) {
    dc_trace( TRACE_ERROR ) {
      cerr << "ga_optimizer::init -- " << nerrs 
	   << "errors hashing performance function ( " << *fitness << " )\n";
    }
    return true;
  }
  if( fitness->get_rtype() != Real_t ) {
    dc_trace( TRACE_ERROR ) {
      cerr << "ga_optimizer::init -- performance function ( "
	   << *fitness << " ) does not have type real.  type is "
	   << dc_type_string[fitness->get_rtype()] << "\n";
    }
    return true;
  }

  /**** 2 -- set all children to active from dormant frozen */
  for_children( set_child_a, true );
  if( ( nerrs = rehash_root() ) != 0 ) {
    cerr << nerrs << "ga_optimizer::init -- errors resetting\n";
  }

  /**** 3 -- set up inputs */
  if( inputs.find_inputs( this ) ) {
    dc_trace( TRACE_ERROR ) {
      cerr << "ga_optimizer::init -- error linking inputs\n";
    }
    return true;
  }

  /**** 4 -- set up ga */
  gPerformanceFunction = this;
  gPerformanceType = -1;

  fSearchDimension = inputs.nBits();
  fBestPerformance = WORST_PERFORMANCE;
  dc_int *ps = ( dc_int * )nIndividuals->get();
  if( ps == nil ) {
    dc_trace( TRACE_ERROR ) {
      cerr << "ga_optimizer::init -- failed to get valid integer from " 
	   << nIndividuals->full_type() << "\n";
    }
  }

  int popsize =( long int )ps->get_mag();
  
  Algor = new EvMikeAlgor<EvBitGenome>( popsize, nGens, pCrossover, pMut, 
					mutVar );

  generation->set( 0 );

  dc_trace( TRACE_FEW ) {
    cout << "INITED " << full_type() << "\n";
    cout << "fitness = ( " << *fitness << " )\n";
    cout << "PopulationSize = " << popsize << "\n";
    cout << "NumGens = " << nGens << "\n";
    cout << "pCrossover = " << pCrossover << "\n";
    cout << "pMut = " << pMut << "\n";
    cout << "mutVar = " << mutVar << "\n";
    cout << "\nInputs: " << inputs.nBits() << "\n" << inputs << "\n";
  }

  return false;
}

bool ga_optimizer::run( void ) {
  best = Algor->ExecAndReturn();
  
  return false;
}

bool ga_optimizer::finish( void ) {
  Reset();
  
  if( inputs.set( best.Genome() ) ) {
    dc_trace( TRACE_ERROR ) {
      cerr << "ga_optimizer::finish -- failed to set inputs\n";
    }
    return true;
  }
  dc_trace( TRACE_FEW ) {
    cout << "BEST GENOME\n" << inputs << "\n";
  }

  return false;
}

bool ga_optimizer::output( void ) {
  return false;
}

void ga_optimizer::cleanup( void ) {
  for_children( set_child_df, true );
  if( Algor ) delete( Algor );
  Algor = nil;
  gPerformanceFunction = nil;
}

/******************************************************************************/

double ga_optimizer::Evaluate( const StdBitString &bstring ) {
  Reset();

  //  cerr << "Evaluating (" << bstring << ")\n";
  if( inputs.set( bstring ) ) {
    dc_trace( TRACE_ERROR ) {
      cerr << "ga_optimizer::Evaluate -- failed to set inputs with genome ( " 
	   << bstring << " )\n";
    }
    return WORST_PERFORMANCE;
  }

  double performanceValue = Simulate();
  if( performanceValue > fBestPerformance ) {
    fBestPerformance = performanceValue;
  }

  return( performanceValue );
}

double ga_optimizer::Simulate( void ) {
  double performance = 0;

  while( 1 ) {
    performance += Step( time_step ) * time_step;

    dc_data *e = exit_cond->get();
    if( !e || e->type() != Boolean_t ) {
      cerr << "ga_optimizer::Simulate -- exit condition ( " << *exit_cond
	   << " ) failed to evaluate to boolean\n";
      return WORST_PERFORMANCE;
    }
    bool exit_b = ( ( dc_boolean * )e )->get();
    if( exit_b ) break;
  }

  dc_trace( TRACE_MANY ) {
    cout << "Perf = " << performance << " at time " << root.iter_counter->t()
	 << ".  Best = " << fBestPerformance << "\n";
  }
  
  return performance;
}

double ga_optimizer::Step( double stepSize ) {
  double t = 0;
  while( t < stepSize ) {
    t += advance_time( stepSize - t );
  }

  dc_data *d;
  d = fitness->evaluate();
  if( !d || d->type() != Real_t ) {
    dc_trace( TRACE_ERROR ) {
      cerr << "ga_optimizer::Simulate -- fitness function ( " << *exit_cond
	   << " ) failed to evaluate to boolean\n";
    }
    return WORST_PERFORMANCE;
  }
  double perf = ( ( dc_real * )d )->get_mag();
  if( d->is_temporary() ) delete( d );

  return perf;
}

void ga_optimizer::Reset( void ) {
  root.iter_counter->reset();

  /* advance time to steady state */
  double t = 0;
  while( 1 ) {
    dc_data *d = steady_state->get();
    if( d == nil /* || d->type() != Boolean_t */ ) {
      dc_trace( TRACE_ERROR ) {
 	cerr << "ga_optimizer::Reset -- evaluation of  steady state condition ("
 	     << *steady_state << " ) failed\n";
      }
      return;// true;
    }
    
    if( ( ( ( dc_boolean * )d )->get() ) ) {
      dc_trace( TRACE_MANY ) {
 	cout << "ga_optimizer::Reset -- steady state reached at time " << t
 	     << "\n";
      }
      break;
    } else if( t >= steady_state_max_iters ) {
      dc_trace( TRACE_ERROR ) {
 	cerr << "ga_optimizer::Reset -- exited after time " 
	     << steady_state_max_iters << " before steady state reached\n";
      }
      break;
    }
    
    t += advance_time( time_step );
  }
}

/******************************************************************************/

ga_optimizer::ga_optimizer( void ) {
  steady_state = exit_cond = nil;

  nIndividuals = nil;
  fitness = nil; 
  Algor = nil;
  
  generation = new dc_int( 0, generation_label, this );  

  pCrossover        = 0.3;
  pMut              = 0.5;
  mutVar            = 1.0;
  //  tau           = 0.1;
  int_acc           = 0.001;
  time_step         = 1.0;
  fBestPerformance  = WORST_PERFORMANCE;
  nGens             = 10;
}

ga_optimizer::~ga_optimizer( void ) {
  if( fitness ) delete( fitness );
  if( Algor ) delete( Algor );
  clear_inputs();
  clear_output_clauses();
}

void ga_optimizer::set_fitness_fn( dc_func *f ) { 
  if( fitness ) delete( fitness ); 
  fitness = f;
  if( fitness ) fitness->set_owner( this );
}

bool ga_optimizer::set_pCrossover( double d ) {
  if( d < 0 || d > 1 ) return true;
  pCrossover = d;
  return false;
}

bool ga_optimizer::set_pMut( double d ) {
  if( d < 0 || d > 1 ) return true;
  pMut = d;
  return false;
}

bool ga_optimizer::set_mutVar( double d ) {
  if( d < 0 ) return true;
  mutVar = d;
  return false;
}

bool ga_optimizer::set_time_step( double d ) {
  if( d > 0 ) {
    time_step = d;
    return false;
  }
  return true;
}

bool ga_optimizer::set_int_accuracy( double d ) { 
  if( d > 0 ) {
    int_acc = d;
    return false;
  }
  return true;
}

bool ga_optimizer::set_nGens( int i ) {
  if( i > 0 ) {
    nGens = i;
    return false;
  }
  return true;
}

bool ga_optimizer::execute( void ) {
  if( init() ) { 
    dc_trace( TRACE_ERROR ) { 
      cerr << "ga_optimizer::execute -- error initing " << full_type() << "\n";
    }
    cleanup();
    return true;
  }
  dc_trace( TRACE_FEW ) {
    cout << "ga_optimizer::execute -- init of " << full_type() << " done\n";
  }
  if( run() ) { 
    dc_trace( TRACE_ERROR ) { 
      cerr << "ga_optimizer::execute -- error initing " << full_type() << "\n";
    }
    cleanup();
    return true;
  }
  dc_trace( TRACE_FEW ) {
    cout << "ga_optimizer::execute -- run of " << full_type() << " done\n";
  }
  if( finish() ) { 
    dc_trace( TRACE_ERROR ) { 
      cerr << "ga_optimizer::execute -- error initing " << full_type() << "\n";
    }
    cleanup();
    return true;
  }
  dc_trace( TRACE_FEW ) {
    cout << "ga_optimizer::execute -- finish of " << full_type() << " done\n";
  }
  if( output() ) { 
    dc_trace( TRACE_ERROR ) { 
      cerr << "ga_optimizer::execute -- error initing " << full_type() << "\n";
    }
    cleanup();
    return true;
  }
  dc_trace( TRACE_FEW ) {
    cout << "ga_optimizer::execute -- execute of " << full_type() << " done\n";
  }
  cleanup();
  return false;
}

void ga_optimizer::clear_inputs( void ) {
  inputs.clear();
  //  dc_arg *a;
  //  forall( a, input_args ) {
  //    delete( a );
  //  }
  //  input_args.clear();
}

void ga_optimizer::clear_output_clauses( void ) {
  dc_func *o;
  forall( o, outputs ) {
    delete( o );
  }
  outputs.clear();
}

void ga_optimizer::child_init( dc_label *child ) {
  dc_component::child_init( child ); 
  set_child_df( child );
}

/* to set status of children */
void set_child_a( dc_label *child ) {
  switch( child->sub_type() ) {
  case Component_t : case Modification_t : case GA_t :
    ( ( dc_component * )child )->set_dormant( false );
    ( ( dc_component * )child )->set_frozen( false );
    break;
  case Element_t :
    ( ( dc_element * )child )->set_dormant( false );
    ( ( dc_element * )child )->set_frozen( false );
    break;
  default :;
  }
}

void set_child_f( dc_label *child ) {
  switch( child->sub_type() ) {
  case Component_t : case Modification_t : case GA_t :
    ( ( dc_component * )child )->set_dormant( false );
    ( ( dc_component * )child )->set_frozen( true );
    break;
  case Element_t :
    ( ( dc_element * )child )->set_dormant( false );
    ( ( dc_element * )child )->set_frozen( true );
    break;
  default :;
  }
}

void set_child_df( dc_label *child ) {
  switch( child->sub_type() ) {
  case Component_t : case Modification_t : case GA_t :
    ( ( dc_component * )child )->set_dormant( true );
    ( ( dc_component * )child )->set_frozen( true );
    break;
  case Element_t :
    ( ( dc_element * )child )->set_dormant( true );
    ( ( dc_element * )child )->set_frozen( true );
    break;
  default :;
  }
}
