#!/usr/local/bin/perl
$gnuplot=1;


# Ask about function file to optimize
# Compute many things from it, like number of parameters in the genome
# and returntype of same.

# open file, where generated program is going to end
open( OUTFILE, ">gags.cc" ); 


#start printing the headers, include files, and whatnots
&ADDHEADER;

#Include gnuplot-wrapper class header, but only if needed
#the variable definition has been appended by the install procedure
print OUTFILE "#include \"gstream.hpp\"\n" if ( $gnuplot == 1 ); 
				# Include gnuplot-wrapper 

#look for the .ga function name, or query for it
$funcName= &FUNCNAME;


# Now start to parse the function, trying to compute
#    1. The number of genes in the chromosome
#    2. The dimension of the input vectors, and if there is an input file
# Every (processed) function line will be kept inside the $funcLines array, 
# some thing must be prepended to it before writing it.
#------------------------------------------------------------------------

$forRef = $fitnessRef = $funcRef = $sampleRef = 0;
				# Flags; tell if the subject has been ref'ed

open( FUNC, "<$funcName");
$bigstNumFun=$bigstNumTra = 0;	# biggest index, for function and input file
$idxFun = 0;

while (<FUNC>) {

    
    if ( /#define\s(\w*)\s*(\d*)/ ) {
	# def'ed constants will be kept inside an associative array
	$define{$1} = $2;

    }elsif ( /\sfor[( ]*(\w*)\s=\s\d*[ ;]*\1([ =<]*)(\w*)/) {
	# This if looks for indices, and their limits.
	# The regular expression is a model for the beginning of a "for"
	# loop
	print "$1 $2 $3\n";
	$idxName = $1;
	if ( $3 =~ /\d/ ) {	# only numeric
	    $idxLimit = $3;
	}else {			# Must be a #def'ed constant
	    $idxLimit = $define{$3};
	}
	#$2 is the comparator
	$idxLimits{ $idxName } = ($2 eq "<")? $idxLimit + 1:$idxLimit;	
	$forRef = 1;
	
    }elsif (/\s*(\w*)\s*fitness/) {
	# look for return type; saved in $returnType variable
	# regular expression is a model for <word> fitness <whatever>
	# Don't know how it will work without double or float.	
	$returnType = $1;	# Parenthesized expression
        print "We're gonna have trouble with this $returnType" 
            if ( ($returnType !~ "float") && ($returnType !~ "double") );
	$fitnessRef = 1;

    }elsif ( /__sample\[\w*\]\[\s*(\w*)\s*\]/ ) {
	$fooIndex = ($&=~/\d/) ? $&:( $idxLimits{$&} - 1);
	$bigstNumTra = ( $fooIndex > $bigstNumTra )? $fooIndex: $bigstNumTra;
	$sampleRef = 1;		# Sample is referenced

    }

    if  ( /__gen\[\s*(\w*)\s*\]/ ) {
	$fooIndex = ($&=~/\d/) ? $&:( $idxLimits{$&} - 1);
	$bigstNumFun = ( $fooIndex > $bigstNumFun )? $fooIndex: $bigstNumFun;
	$funcRef = 1;		# Function is referenced
    }
	
    #substitute by "real names"
    s/FILESIZE/sample.getSize\(\)/g;
    s/__gen/myGen/g;
    s/__sample/sample/g;

    #stick it inside the array
    $funcLines[$idxFun++] =  $_;
}

#print warnings and fatal errors
    die "There is no reference to the chromosome...check the .ga file\n" 
    if $funcRef == 0;

    die "Fitness function does not seem to be defined\
There must be a <returnType> fitness ( ) definition at the beginning\
of the file\n" if $fitnessRef == 0;	

    print "No reference found to a training file\n" if $sampleRef == 0;

# Print everything to the generated program; first the include, if needed
    print OUTFILE "#include \"ezsample.hpp\"\n" if $sampleRef;
    print OUTFILE "\n";
    $fitnessLine =  "$returnType fitness ( genSGAv<float> & myGen ";
    $fitnessLine .= ", ezSample< float > & sample " if $sampleRef;
    $fitnessLine .=  ") {\n";	# header finished, and customized

    for ( $i = 0; $i < $idxFun; $i ++ ) {
	print OUTFILE $funcLines[ $i ] unless $funcLines[$i] =~ /fitness/;
	print OUTFILE $fitnessLine if $funcLines[$i] =~ /fitness/;
    }
	
    print OUTFILE "\n\n";

#define constant number of loci in a genome
$bigstNumFun++;				#convert last index to size
print OUTFILE "main( int argc,  char ** argv ) {\n";
print OUTFILE "const unsigned genSize = $bigstNumFun;\n";
print OUTFILE "\t$returnType rangeStart[genSize], rangeEnd[genSize];\n";


#now start to stuff things into the configuration file
#first get the config file name
($funcBase,  $foo )=split(/\./, $funcName);
open (CONFIG, ">$funcBase.ini");

if ($sampleRef) {
    $bigstNumTra++;			#convert last index to size
    print "Training file name? ";
    $trngFileName = <stdin>;
    chop  $trngFileName;
    print CONFIG "inputFile $trngFileName\n";
}


#other genome parameters
&INPDEFAULT( $numBytes, 2, "Number of bytes per parameter" );
print CONFIG "locusSize $numBytes\n";

print "Parameter range for each parameter [-1, 1] -- there are $bigstNumFun parameters \n";
for( $i = 0; $i < $bigstNumFun; $i ++ ) {
    &INPDEFAULT( $range, "-1, 1", "Parameter #$i ");
    ($rgStart, $rgEnd)=split(/,/, $range);
    $bigRgStart .= $rgStart.(($i != $bigstNumFun -1)?" ":" ");
    $bigRgEnd .= $rgEnd.(($i != $bigstNumFun -1)?" ":" " );
}

print CONFIG "startRange $bigRgStart\n";
print CONFIG "endRange $bigRgEnd\n";

&ADDSCAN;			# Adds some variable definition and file scanning

&INPDEFAULT( $mutRate, 0.01, "Mutation rate" );
print CONFIG "mutRate $mutRate\n";
&INPDEFAULT( $popSize, 50, "Population size" );
print CONFIG "popSize $popSize\n";

#Mode of reproduction
&INPDEFAULT( $repMode, "ELITE", "Reproduction mode (ELITE, ROULWHEEL, TOURNAMENT)" );
print CONFIG "repType $repMode " ;
if ( $repMode eq "ELITE" ) {	# ask about percentage
    &INPDEFAULT( $repArg, "0.2", "Percentage of altered population" );
}elsif ( $repMode =~ "TOUR" ) { # ask about size of tournament
    &INPDEFAULT( $repArg, "7", "Size of the tournament" );
} else {
    $repArg = "";
}
print CONFIG "$repArg\n";

print OUTFILE "\tezSample<$returnType> sample( inputFile, genSize );\n" if $sampleRef == 1;
print OUTFILE "\tpopSGAr<$returnType, genSGAv<$returnType> > myPop(popSize, mutRate, genSize, locusSize,rangeStart,rangeEnd);\n";

if ($gnuplot ) {
    &INPDEFAULT( $gnuStr, "", "Gnuplot initialization string" );
    print OUTFILE "\tgstream gout(\"$gnuStr\");\n";
}

&INPDEFAULT( $generations, "50", "Number of Generations" );
print CONFIG "Generations $generations\n";

&ADDLOOP;			# prints the beginning of the GA loop

#output info interval, and output file
&INPDEFAULT( $interval, "1", "Output information every ... steps" );
&INPDEFAULT( $outInfo, "FV", "Save\n\tF - fitness\n\tV - best Vector\n\tor both" );
&INPDEFAULT( $outFileN, "STDOUT", "Output file name" );

print OUTFILE "\tif ( g % $interval == 0 ) {\n" if $interval > 1;

if ( $outFileN ne "STDOUT" ) {
    print OUTFILE "\tfstream fout(\"$outFileN\", ios::app| ios::out);\n";
    $outFileS = "fout";
}
else {
    $outFileS = "cout";
}

print OUTFILE "\t\t$outFileS << myPop.bestIndiv() << endl;\n" if $outInfo =~/V/;;
print OUTFILE "\t\t$outFileS << myPop.bestFitness() << endl;\n" if $outInfo =~/F/;
print OUTFILE "\t\tgout << myPop.bestFitness();\n";

print OUTFILE "\t\tfout.close();\n" if $outFileN ne "STDOUT";
print OUTFILE "\t}\n" if $interval > 1;

&ADDEND;			# Add fixed part at the end of the program

close OUTFILE;			# close program file
close CONFIG;			# and config file

#And try to run it
$incFlag ="  -I$ENV{'GAGSINC'}" if defined $ENV{'GAGSINC'};
$libFlag ="  -L$ENV{'GAGSLIB'}" if defined $ENV{'GAGSLIB'};

if (`g++ gags.cc -o gags  $incFlag $libFlag -lGAGS -lm -g` != 0) {
    die "Problems with compilation...\n";
}

&INPDEFAULT( $run, "Y", "Wanna run" );
if ($run=~/[Yy]/) {
    system "gags $funcBase.ini";
}
    





##########################################################################
#                      SUBROUTINES                                       #
# all subroutines have been included here to avoid having too many       #
# environment variables around.                                          #
##########################################################################


##########################################################################
sub ADDHEADER {			# one of the many "append" functions
				# just prints out those lines
				# Uses global variable OUTFILE

    print OUTFILE<<EOC;
// -------------------------------------gags.cc-------------------------------
// Program generated automagically by gags.pl
// gags.pl (c) JJ Merelo, 1994

#include "gensgav.hpp"
#include "popsgar.hpp"
#include "scanfile.hpp"
EOC

}

sub ADDLOOP {			# Add the beginning of the GA loop.
				# Uses global variable OUTFILE.
				# and $sampleRef; 
    $sample = "";
    $sample = ", sample " if $sampleRef == 1;
    print OUTFILE<<EOC;
cout << "Starting genetic algorithm";
for ( unsigned g= 0; g < generations ; g++ ) {
 
    // Evaluate population fitness
    for ( unsigned p = 0; p < myPop.getPopSize(); p ++ ) 
        myPop.setFitness( p, fitness( *(myPop[p])$sample ));

    // now really apply GA

	switch ( thisRepMode ) {
	    case ELITE:
	    myPop.newGeneration( thisRepMode, fArgRepMode );
	    break;
	    case TOURNAMENT:
	    case ROULWHEEL:	// argument is not used in ROULWHEEL
		myPop.newGeneration( thisRepMode, uArgRepMode );
	    break;
	}
EOC

}


##########################################################################
sub ADDSCAN {			# adds lines to output file
				# Uses global variable OUTFILE
    print OUTFILE <<EOC;

        char inputFile[40];
	unsigned popSize, generations, locusSize;
	double mutRate;
	enum reprodMode thisRepMode;
	unsigned uArgRepMode;
	float fArgRepMode;	// both arguments for reproduction mode
	
	if ( argc > 1 )
	    scanFile(rangeStart, rangeEnd,  argv[1], inputFile, 
		     popSize,  mutRate,  generations, locusSize, 
		     thisRepMode, uArgRepMode, fArgRepMode, 
		     genSize );
	else {
	    cout << "Usage: " << argv[0] << " <inifile>.ini \n";
	    exit(1);
	}
        
        mysrand();

EOC

}


##########################################################################
sub FUNCNAME {			# Looks for function name. If there is 
				# only one .ga file, takes it; if not,
				# queries the user

    $count = 0;			    #compute number of function files
    while (<*.ga>) {
	$funcfoo = $_;
	$count++;
    }
    
    if ( ($count  == 0) || ($count > 1) ) {
	$funcfoo = "";
	askFunc: {
	    print "Function file [*.ga]? ";
	    $funcfoo = <stdin>;
	    chop $funcfoo;
	    if ( -e $funcfoo ) {
		last askFunc;
	    } else {
		print "File $funcName does not exist\n";
	    }
	};
	return $funcfoo;
    } else {
	return $funcfoo;
    }

}				

##########################################################################
sub INPDEFAULT {    # for input with a default value... must use the original variables;
		    # call is by reference
		    # 0 - variable to assign
		    # 1 - default value
		    # 2 - message
    print $_[2]."? [".$_[1]."] ";
    $_[0] = <stdin>;
    if ($_[0]!~/\w/) {
	$_[0] = $_[1];
    }
    else {
	chop $_[0];
    }
}


###################################################################
sub ADDEND {			# Adds the end of the program
				# Uses global variable OUTFILE
    print OUTFILE <<EOC;

    }
        
        return 0;
}
EOC

}




