/*****************************************************************************
*     FILE: system.c: This file contains systme routines such as errors and
*                     exits
*
*     Authors: David M. Levine, Philip L. Hallstrom, David M. Noelle
*****************************************************************************/

#include "pgapack.h"

/* Macro used to see if an arg is not following the correct format. */
#define bad_arg(a)    ( ((a)==NULL) || ((*(a)) == '-') )

static char PGAProgram[100];	 /* Holds argv[0] for PGAUsage() call */

/*U****************************************************************************
   PGAError - reports error messages.  Terminates if fatal

   Category: System

   Inputs:
      ctx      - context variable
      msg      - the error message to print
      level    - PGA_WARNING or PGA_FATAL to indicate the error's severity
      datatype - the data type of the following argument
      data     - the address of the data to be written out, cast as a void
                 pointer
      
   Outputs:
      None
      
   Example:
      Allocates memory and assigns the address of the allocated memory to
      the binary string field (ind->chrom) of the individual.

      PGAContext *ctx;
      :
      PGAError(ctx, "Some Non Fatal Error", PGA_WARNING, PGA_INT,
	       (void *) &val);

****************************************************************************U*/
void PGAError( PGAContext *ctx, char *msg,
               int level, int datatype, void *data )
{

     PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGAError", "Entered", 
                   PGA_VOID, NULL );

     switch (datatype)
     {
     case PGA_INT:
	  fprintf(stderr, "%s %d\n", msg, *(int *)    data);
	  break;
     case PGA_DOUBLE:
	  fprintf(stderr, "%s %f\n", msg, *(double *) data);
	  break;
     case PGA_CHAR:
	  fprintf(stderr, "%s %s\n", msg,  (char *)   data);
	  break;
     case PGA_VOID:
	  fprintf(stderr, "%s\n", msg);
	  break;
     };
     if ( level == PGA_FATAL ) {
	  fprintf(stderr, "Fatal error\n");
	  PGAExit(ctx,-1);
     }
}

/*U****************************************************************************
   PGAExit - exit routine, either graceful or due to error

   Category: System

   Inputs:
      ctx      - context variable
      exittype - 0 is graceful, -1 is due to an error
      
   Outputs:
      None
      
   Example:
      PGAContext *ctx;
      :
      PGAExit(ctx,-1);

****************************************************************************U*/
void  PGAExit( PGAContext *ctx, int exittype )
{

    PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGAExit", "Entered", 
                   PGA_VOID, NULL );
    
    exit(exittype);
    return;
}
        

/*U****************************************************************************
   PGAWriteString - write the allele values in a string to a file
   
   Category: Reporting

   Inputs:
      ctx - context variable
      fp  - pointer to file to write the string to
      p   - index of the string to write out
      pop - symbolic constant of the population string p is in

   Outputs:
      None
      
   Example:
      PGAContext *ctx;
      int p;
      :
      PGAWriteString(ctx, stdout, p, PGA_OLDPOP);

****************************************************************************U*/
void PGAWriteString ( PGAContext *ctx, FILE *fp, int p, int pop )
{
     PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGAWriteString", "Entered", 
		   PGA_VOID, NULL );
     PGADebugPrint( ctx, PGADEBUG_PRINTVAR,"PGAWriteString", "p   = ", 
                   PGA_INT, (void *) &p );
     PGADebugPrint( ctx, PGADEBUG_PRINTVAR,"PGAWriteString", "pop = ", 
                   PGA_INT, (void *) &pop );
     
     (*ctx->ops.WriteString)( ctx, fp, p, pop);
     fprintf(fp,"\n");

     PGADebugPrint( ctx, PGADEBUG_EXIT, "PGAWriteString", "Exit", 
                   PGA_VOID, NULL );
}

      
/*U****************************************************************************
   PGARandomFlip - flip's a biased coin and returns PGATRUE if the coin is
   a "winner."  Otherwise, returns PGAFALSE.

   Category: Random

   Inputs:
      ctx - context variable
      p   - biased probability (.5 is a fair coin)

   Outputs:
      PGATRUE or PGAFALSE
      
   Example:
      To return PGATRUE approximately seventy percent of the time, use
      
      PGAContext *ctx;
      int p;
      :
      PGARandomFlip(ctx, 0.7)

****************************************************************************U*/
int PGARandomFlip ( PGAContext *ctx, double p )
{
    if ( PGARandom01(ctx, 0) < p )
        return(PGATRUE);
     else
        return(PGAFALSE);
}


/*U****************************************************************************
   PGARandomInterval - returns a uniform random number on the specified
   interval

   Category: Random

   Inputs:
      ctx - context variable
      start - starting (integer) value of the interval
      end   - ending   (integer) value of the interval

   Outputs:
      
   Example:
      To generate a value uniformly random from the interval [0,99] use
      
      PGAContext *ctx;
      :
      PGARandomInterval(ctx, 0, 99);

****************************************************************************U*/
int PGARandomInterval( PGAContext *ctx, int start, int end)
{
    return(ceil( (double)(end-start+1) * PGARandom01(ctx, 0) )+start-1);
}


/*****************************************************************************
*  This is a C language implementation of the universal random number        *
*  generator proposed by G. Marsaglia and A. Zaman and translated from       *
*  F. James' version.                                                        *
*                                                                            *
*  F. James                                                                  *
*  A review of pseudorandom number generators                                *
*  Computer Physics Communication                                            *
*  60 (1990) 329-344                                                         *
*                                                                            *
*  G. Marsaglia, A. Zaman, W. Tseng                                          *
*  Stat Prob. Letter                                                         *
*  9 (1990) 35.                                                              *
*                                                                            *
*  G. Marsaglia, A. Zaman                                                    *
*  FSU-SCRI-87-50                                                            *
*                                                                            *
*  This algorithm is a combination of a lagged Fibonacci and arithmetic      *
*  sequence (F. James) generator with period of 2^144.  It provides 32-bit   *
*  floating point numbers in the range from zero to one.  It is claimed to   *
*  be portable and provides bit-identical results on all machines with at    *
*  least 24-bit mantissas.                                                   *
*                                                                            *
*  PGARandom01 should be initialized with a 32-bit integer seed such that    *
*  0 <= seed <= 900,000,000.  Each of these 900,000,000 values gives rise    *
*  to an independent sequence of ~ 10^30.                                    *
*                                                                            *
*  warning on use of static storage class on thread shared memory machines   *
*****************************************************************************/
/*U****************************************************************************
   PGARandom01 - generates a uniform random number on the interval (0,1)
   If the second argument is 0 it returns the next random number in the
   sequence.  Otherwise, the second argument is used as a new seed for the
   population

   Category: Random

   Inputs:
      ctx - context variable
      newseed - either 0 to get the next random number, or nonzero
                to reseed
   Outputs:
      A random number on the interval (0,1)
      
   Example:
      To get the next random number use
      
      PGAContext *ctx;
      double r;
      :
      r = PGARandom01(ctx,0);

****************************************************************************U*/
double PGARandom01( PGAContext *ctx, int newseed )
{

    /* initialization variables */
    int ij, kl, i, j, k, l, m, ii, jj;
    float s, t;

    /* random number variables */
    static int seed=1;      /* default seed if none specified */
    static int i96, j96;
    static float u[97], uni, c, cd, cm;

    /* initialization */    
/*     printf("i96 = %d\tj96 = %d\n", i96, j96); */
    
    if ( newseed != 0 ) {

	seed = newseed % 900000000;
        ij   = seed / 30082;
        kl   = seed - 30082 * ij;
        i    = ( (ij/177) % 177 ) + 2;
        j    = (  ij      % 177 ) + 2;
        k    = ( (kl/169) % 178 ) + 1;
        l    = (  kl      % 169 );
        
        for ( ii=0; ii<97; ii++ ) {

            s = 0.0;
            t = 0.5;

            for ( jj=0; jj<24; jj++ ) {

                m = ( ((i*j) % 179) * k ) % 179;
                i = j;
                j = k;
                k = m;
                l = ( (53*l) + 1 ) % 169;
                if ( ( (l*m) % 64 ) >= 32 )
                    s += t;
                t *= .5;
            }
            
            u[ii] = s;
        }
        
        c   = 362436.  /16777216.;
        cd  = 7654321. /16777216.;
        cm  = 16777213./16777216.;
        i96 = 96;
        j96 = 32;            
    }

    /* random number generation */
    uni = u[i96] - u[j96]; 
    if ( uni < 0. ) uni += 1.0;
    u[i96] = uni;
    i96--;
    if ( i96 < 0  ) i96 = 96;
    j96--;
    if ( j96 < 0  ) j96 = 96;
    c   -= cd;
    if ( c   < 0. ) c += cm;
    uni -= c;
    if ( uni < 0. ) uni += 1.0;

    return( (double) uni);
}

/*U****************************************************************************
   PGARandomUniform - returns a uniform random number on the interval
                      [start,end]

   Category: Random

   Inputs:
      ctx - context variable
      start - starting (double) value of the interval
      end   - ending   (double) value of the interval

   Outputs:
      A random number on the interval [start,end]
      
   Example:
      To generate a uniform random number on the interval [-0.5, 1.5] use
      
      PGAContext *ctx;
      double r;
      :
      r = PGARandomUniform(ctx, -0.5, 1.5);

****************************************************************************U*/
double PGARandomUniform( PGAContext *ctx, double start, double end)
{
     double val, r;

     r = PGARandom01(ctx, 0);
     val = (end-start) * r + start;
     return val;
}


/*U****************************************************************************
   PGARandomGaussian - returns an approximation to a Gaussian random number 

   Category: Random

   Inputs:
       mean  - the mean of the Gaussian distribution
       sigma - the standard deviation of the Gaussian distribution

   Outputs:
      A random number selected from a Gaussian distribution with given
      mean and standard deviation
      
   Example:
      To generate a Gaussian random number with mean 0.0 and standard
      deviation 1.0 use
      
      PGAContext *ctx;
      :
      r = PGARandomGaussian(ctx, 0.0, 1.0);

****************************************************************************U*/
double PGARandomGaussian( PGAContext *ctx, double mean, double sigma)
{
    int i;
    double sum = 0.;

    for (i=0;i<12; i++)
        sum += PGARandom01(ctx, 0);
    return ( (sum-6.0) * sigma + mean );
}

/*U****************************************************************************
  PGARound - This routine takes a double and rounds it into an int using 0.5 as
  the cutoff value.

  Inputs:
     ctx - context variable
     x - the number to be rounded

  Outputs:
     The rounded number.

  Example:
     PGAContext *ctx;
     int y;
     y = PGARound(ctx, -78.6);

****************************************************************************U*/
int PGARound(PGAContext *ctx, double x)
{
	double ipart, frac;

	frac = modf(x, &ipart);
	if (frac <= -0.5)
		ipart--;
	else if (frac >= 0.5)
		ipart++;

	return ((int)ipart);
}

/*I****************************************************************************
   PGAReadCmdLine - Code that looks at the arguments, recognizes any that
   are for PGAPack, uses the arguments, and removes them from the command
   line args.

   Inputs:
      ctx  - context variable
      argc - address of the count of the number of command line argumen
      argv - array of command line arguments

   Outputs:
      None
      
   Example:
      PGAContext *ctx;
      int *argc, char **argv;
      :
      PGAReadCmdLine(ctx, argc, argv);

****************************************************************************I*/
void PGAReadCmdLine( PGAContext *ctx, int *argc, char **argv )
{
     int c;
     char *s, **a;

     PGADebugPrint (ctx, PGADEBUG_ENTERED, "PGAReadCmdLine", "Entered",
		    PGA_VOID, NULL); 

     PGADebugPrint (ctx, PGADEBUG_PRINTVAR, "PGAReadCmdLine", "argc = ",
		    PGA_INT, (void *) argc);

     /* Put name of called program (according to the args) into PGAProgram */
     s = (char *)  strrchr(*argv, '/');
     if (s)
	  strcpy(PGAProgram, s + 1);
     else
	  strcpy(PGAProgram, *argv);

     /* Set all command line flags (except procgroup) to their defaults */
   
     /* Move to last argument, so that we can go backwards. */
     a = &argv[*argc - 1];

     /*
      * Loop backwards through arguments, catching the ones that start with
      * '-'.  Backwards is more efficient when you are stripping things out.
      */
     for (c = (*argc); c > 1; c--, a--)
     {
	  if (**a != '-')
	       continue;
	  
	  if ( !strcmp(*a, "-pgadbg") || !strcmp(*a, "-pgadebug") )
	  {
	       if bad_arg(a[1])
		    PGAUsage(ctx);
	       PGAParseDebugArg( ctx, a[1] );
	       PGAStripArgs(a, argc, &c, 2);
	       continue;
	  }
	  
	  if ( !strcmp(*a, "-pgaversion") )
	  {
	       PGAStripArgs(a, argc, &c, 1);
	       PGAPrintVersion( ctx );
	       continue;
	  }

	  if (!strcmp(*a, "-pgahelp") )
	  {
	       if (a[1] == NULL)
		    PGAUsage(ctx);
	       else
		    if (!strcmp(a[1], "debug"))
			 PGAPrintDebugOptions(ctx);
		    else
			 fprintf(stderr, "Invalid option following"
				 "-pgahelp.\n");
	  }
     }
     PGASetSupportingDebugFlags(ctx);
     PGADebugPrint (ctx, PGADEBUG_EXIT, "PGAReadCmdLine", "Exit",
		    PGA_VOID, NULL);
}

/*I****************************************************************************
   PGAParseDebugArg - routine to parse debug command line options

   Inputs:
      ctx - context variable
      st  - debug command line options

   Outputs:
      None
      
   Example:
      PGAContext *ctx;
      :
      PGAParseDebugArg(ctx, );

****************************************************************************I*/
void PGAParseDebugArg(PGAContext *ctx, char * st)
{
     int num2index = 0,
     num1index = 0,
     index = 0,
     num1 = 0,
     num2 = 0,
     x = 0;
     char length = strlen(st),
     range = 0,
     num1ch[4],
     num2ch[4];

     PGADebugPrint (ctx, PGADEBUG_ENTERED, "PGAParseDebugArg", "Entered",
		    PGA_VOID, NULL);
     PGADebugPrint (ctx, PGADEBUG_PRINTVAR, "PGAParseDebugArg", "st = ",
		    PGA_CHAR, (void *) st);

     length--;
     for(index=0; index <= length; index++)
     {
	  if (!isdigit(st[index]) && st[index] != ',' && st[index] != '-')
	       PGAError(ctx, "PGASetDebugLevel: Invalid Debug Value:",
			PGA_FATAL, PGA_CHAR, (void *) st);
	  if (st[index] == '-')
	  {
	       range = 1;
	       num1ch[num1index] = '\0';
	       num1 = atoi(num1ch);
	       if (num1 < 0 || num1 > PGADEBUG_NUMFLAGS)
		    PGAError(ctx,
			     "PGASetDebugLevel: Lower Limit Out of Range:",
			     PGA_FATAL, PGA_INT, (void *) &num1);
	       num1index = 0;
	  }
	  else
	  {
	       if (isdigit(st[index]))
		    if (range)
			 num2ch[num2index++] = st[index];
		    else
			 num1ch[num1index++] = st[index];
	       if (st[index] == ',' || index == length)
	       {
		    if (range)
		    {
			 num2ch[num2index] = '\0';
			 num2 = atoi(num2ch);
			 if (num2 < 0 || num2 > PGADEBUG_NUMFLAGS)
			      PGAError(ctx,
				       "PGASetDebugLevel: Upper Limit Out of"
				       " Range:",
				       PGA_FATAL, PGA_INT, (void *) &num2);
			 if (num1 <= num2)
			 {
			      for (x = num1; x <= num2; x++)
			      {
				   if (x == 212)
					printf("%s %s\n", num1ch, num2ch);
				   
				   PGASetDebugLevel(ctx, x);
			      }
			      
			 }
			 else
			      PGAError(ctx,
				       "PGASetDebugLevel: Lower Limit Exceeds"
				       "Upper:", PGA_FATAL, PGA_INT,
				       (void *) &num1);
			 num2index = 0;
			 range = 0;
		    }
		    else
		    {
			 num1ch[num1index] = '\0';
			 num1 = atoi(num1ch);
			 if (num1 < 0 || num1 > PGADEBUG_NUMFLAGS) 
			      PGAError(ctx, "PGASetDebugLevel: Debug Number"
				       "Out of Range:", PGA_FATAL, PGA_INT,
				       (void *) &num1);
			 if (num1 == 212)
			      printf("%s\n", num1ch);
			 
			 PGASetDebugLevel(ctx, num1);
			 num1index = 0;
		    }
	       }
	  }
     }
     PGADebugPrint (ctx, PGADEBUG_EXIT, "PGAParseDebugArg", "Exit",
		    PGA_VOID, NULL);
}

/*I****************************************************************************
   PGAStripArgs - code to strip arguments out of command list

   Inputs:
      argc - address of the count of the number of command line argumen
      argv - array of command line arguments
      c    -
      num  - 

   Outputs:
      None
      
   Example:
      int *argc, char **argv, c, num
      :
      PGAStripArgs(argv, argc, &c, num);

****************************************************************************I*/
void PGAStripArgs(char **argv, int *argc, int *c, int num)
{
    char **a;
    int i;

    /* Strip out the argument. */
    for (a = argv, i = (*c); i <= *argc; i++, a++)
	*a = (*(a + num));
    (*argc) -= num;
}


/*I****************************************************************************
   PGAUsage - code to print list of available parameters

   Inputs:
      ctx - context variable

   Outputs:
     list of available parametersNone 
      
   Example:
      PGAContext ctx;
      :
      PGAUsage(ctx);

****************************************************************************I*/
void PGAUsage( PGAContext *ctx )
{
    PGAPrintVersion( ctx );
    printf("PGAPack usage: %s [pga options]\n", PGAProgram);
    printf("Valid PGAPack options:\n");
    printf("\t-pgahelp            \tget this message\n");
    printf("\t-pgahelp debug      \tlist of debug options\n");
    printf("\t-pgadbg <option>    \tset debug option\n");
    printf("\t-pgadebug <option>  \tset debug option\n");
    printf("\t-pgaversion         \tprint current PGAPack version number\n");
    printf("\n");
    exit(-1);

}


/*I****************************************************************************
   PGAPrintVersion - print PGAPack version number

   Inputs:
      ctx - context variable

   Outputs:
      PGAPack version number
      
   Example:
      PGAContext ctx;
      :
      PGAPrintVersion(ctx);

****************************************************************************I*/
void PGAPrintVersion( PGAContext *ctx )
{
        printf("\n");
        printf( "PGAPack version 0.1\n");
        printf("\n");
}


/*I****************************************************************************
   PGAPrintDebugOptions - prints the list of available debug options

   Inputs:
      ctx - context variable

   Outputs:
      list of available debug options
      
   Example:
      PGAContext ctx;
      :
      PGAPrintDebugOptions(ctx);

****************************************************************************I*/
void PGAPrintDebugOptions(PGAContext *ctx)
{
     PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGAPrintDebugOptions", "Entered", 
                   PGA_VOID, NULL );

     fprintf(stderr, "\n");
     fprintf(stderr, "  0                         Print ALL debugging messages"
	     "\n");
     fprintf(stderr, "\n");
     fprintf(stderr, " 11   PGADEBUG_ENTERED      Trace entering a high-level"
	     " routine\n");
     fprintf(stderr, " 12   PGADEBUG_EXIT         Trace exiting  a high-level"
	     " routine\n");
     fprintf(stderr, " 14   PGADEBUG_MALLOC       Trace memory allocations\n");
     fprintf(stderr, "\n");
     fprintf(stderr, " 20   PGADEBUG_PRINTVAR     Trace variable prints\n");
     fprintf(stderr, " 25   PGADEBUG_INDIVIDUAL   Trace info whenever update"
	     " an individual\n");
     fprintf(stderr, "\n");
     fprintf(stderr, " 31   PGADEBUG_ENTEREDHIGH  Trace entering any"
	     " routine\n");
     fprintf(stderr, " 32   PGADEBUG_EXITHIGH     Trace exiting  any"
	     " routine\n");
     fprintf(stderr, "\n");
     fprintf(stderr, "\n");
     fprintf(stderr, "\n");
     fprintf(stderr, " 51                         Trace high-level    routines"
	     "\n");
     fprintf(stderr, " 52                         Trace parallel      routines"
	     "\n");
     fprintf(stderr, " 53                         Trace BINARY        routines"
	     "\n");
     fprintf(stderr, " 54                         Trace INTEGER       routines"
	     "\n");
     fprintf(stderr, " 55                         Trace REAL         routines"
	     "\n");
     fprintf(stderr, " 56                         Trace random number routines"
	     "\n");
     fprintf(stderr, " 61                         PGACreate\n");
     fprintf(stderr, " 62                         PGASetUp\n");
     fprintf(stderr, " 70                         PGARun\n");
     fprintf(stderr, " 71                         PGARunMutateAndCross\n");
     fprintf(stderr, " 72                         PGARunMutateOrCross\n");
     fprintf(stderr, " 73                         PGASelect\n");
     fprintf(stderr, " 74                         PGACopyIndividual\n");
     fprintf(stderr, " 75                         PGACrossover\n");
     fprintf(stderr, " 76                         PGAMutate\n");
     fprintf(stderr, " 77                         PGADuplicate\n");
     fprintf(stderr, " 78                         PGAEvaluate\n");
     fprintf(stderr, " 79                         PGAFitness\n");
     fprintf(stderr, " 80                         PGAHammingDistance\n");
     fprintf(stderr, " 82                         PGASortPop\n");
     fprintf(stderr, " 83                         PGAGetSortPop\n");
     fprintf(stderr, " 84                         PGAUpdateGeneration\n");
     fprintf(stderr, " 85                         PGADone\n");
     fprintf(stderr, " 90                         PGAParallelSetUp\n");
     fprintf(stderr, " 91                         PGAExchange\n");
     PGAExit(ctx, 0);
     PGADebugPrint( ctx, PGADEBUG_EXIT, "PGAPrintDebugOptions", "Exit", 
                   PGA_VOID, NULL );
}
