/*  Core Wars.
 *  Copyright (C) 1999  Walter Hofmann
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#define CMD_LINE

#include "config.h"

#ifdef HAVE_ARGP_H

#include <stdio.h>
#include <stdlib.h>
#include <argp.h>
#include <math.h>
#include <string.h>
#include <time.h>

#include "options.h"
#include "main.h"
#include "program.h"
#include "program-cw.h"
#include "program-rc.h"
#include "execute.h"
#include "execute-cw.h"
#include "execute-rc.h"
#include "statistic.h"
#include "pstatistic.h"

const char *argp_program_version = "CoreWars " VERSION;
const char *argp_program_bug_address = "Walter Hofmann <walterh@gmx.de>";

int default_count = 1;
int repeats = 1;
int reproducible = 0;
char *format = "%4r %7s %-25t %a";
int supress_header = 0;
int score_old = 0;
int dump = 0;
int dump_internal = 0;

static error_t parse_opt (int key, char *arg, struct argp_state *state)
{
  char *p;

  switch (key)
    {
    case 'l':
      if (strcmp (arg, "COREWARS")==0)
	LANGUAGE = LANGUAGE_CW;
      else if (strcmp (arg, "REDCODE")==0)
	LANGUAGE = LANGUAGE_RC;
      else
	{
	  printf ("Invalid argument for --language.\n");
	  argp_usage (state);
	}
      break;

    case 's':
      SIZE = atoi (arg);
      if (SIZE<SIZE_MIN || SIZE>SIZE_MAX)
	{
	  printf ("Invalid argument for --memory-size.\n");
	  argp_usage (state);
	}
      DIMENSIONX = (int) sqrt (SIZE);
      DIMENSIONY = (SIZE+DIMENSIONX-1)/DIMENSIONX;
      break;
      
    case 'm':
      MAX_CYCLES = atoi (arg);
      if (MAX_CYCLES<1)
	{
	  printf ("Invalid argument for --max-cycles.\n");
	  argp_usage (state);
	}
      break;
      
    case 't':
      MAX_THREADS = atoi (arg);
      if (MAX_THREADS<1)
	{
	  printf ("Invalid argument for --max-threads.\n");
	  argp_usage (state);
	}
      break;

    case 'e':
      MAX_LENGTH = atoi (arg);
      if (MAX_LENGTH<1)
	{
	  printf ("Invalid argument for --max-length.\n");
	  argp_usage (state);
	}
      break;
      
    case 'z':
      MIN_DISTANCE = strtol (arg, &p, 0);
      if (*arg=='\0' || *p!='\0')
	{
	  printf ("Invalid argument for --min-distance.\n");
	  argp_usage (state);
	}
      break;

    case 'y':
      STOP_EARLY = 0;
      break;
      
    case 'd':
      default_count = atoi (arg);
      if (default_count<1)
	{
	  printf ("Invalid argument for --default_count.\n");
	  argp_usage (state);
	}
      break;
      
    case 'r':
      repeats = atoi (arg);
      if (repeats<1)
	{
	  printf ("Invalid argument for --repeats.\n");
	  argp_usage (state);
	}
      break;
      
    case 'i':
      ALLOW_SELF_MOVE = 0;
      break;

    case 'a':
      SCORE_ALIVE = strtol (arg, &p, 0);
      if (*arg=='\0' || *p!='\0')
	{
	  printf ("Invalid argument for --score-alive.\n");
	  argp_usage (state);
	}
      break;
      
    case 'c':
      SCORE_CELLS = strtol (arg, &p, 0);
      if (*arg=='\0' || *p!='\0')
	{
	  printf ("Invalid argument for --score-cells.\n");
	  argp_usage (state);
	}
      break;
      
    case 'k':
      SCORE_KILLS = strtol (arg, &p, 0);
      if (*arg=='\0' || *p!='\0')
	{
	  printf ("Invalid argument for --score-kills.\n");
	  argp_usage (state);
	}
      break;

    case 'o':
      score_old = 1;
      break;
      
    case 'n':
      SCORE_RANK = 1;
      break;
      
    case 'b':
      SCORE_BEST = 1;
      break;

    case 'x':
      reproducible = 1;
      break;

    case 'f':
      format = arg;
      break;

    case 'h':
      supress_header = 1;
      break;

    case 'p':
      dump = 1;
      break;

    case 'g':
      dump_internal = 1;
      break;

    case ARGP_KEY_ARG:
      *((char***)state->input) = &state->argv[state->next-1];
      state->next = state->argc;
      break;
      
    case ARGP_KEY_END:
      if (state->arg_num < 1)
	argp_usage (state);
      break;
      
    default:
      return ARGP_ERR_UNKNOWN;
    }
  return 0;
}

static struct argp_option options[] = {
  {"language",        'l', "LANGUAGE", 0, "Select language (COREWARS or REDCODE, default COREWARS)" },
  { NULL, 0, 0, 0, "" },
  {"core-size",       's', "SIZE",     0, "Set memory size (cells, 625<=SIZE<=1000000, default 6400)" },
  {"max-cycles",      'm', "CYCLES",   0, "Set maximum number of cycles to execute (default 1000000)" },
  {"max-threads",     't', "THREADS",  0, "Set maximum threads a process may have (default 1000)" },
  {"max-length",      'e', "CELLS",    0, "Set maximum program size (default 200)" },
  {"min-distance",    'z', "CELLS",    0, "Set minimum distance (default 100)" },
  {"dont-stop-early", 'y', NULL,       0, "Don't stop when only one process left" },
  { NULL, 0, 0, 0, "" },
  {"default-copies",  'd', "NUMBER",   0, "Set number of copies to load per default (default 1)" },
  {"repeats",         'r', "NUMBER",   0, "Set number of games to play (default 1)" },
  { NULL, 0, 0, 0, "" },
  {"forbid-self-move",'i', NULL,       0, "Forbid self move instruction (default off)" },
  { NULL, 0, 0, 0, "" },
  {"score-alive",     'a', "SCORE",    0, "Score for every step the process was alive (default 1)" },
  {"score-cells",     'c', "SCORE",    0, "Score for every cell the process owns (default 1)" },
  {"score-kills",     'k', "SCORE",    0, "Score for evey other process the process has killed (default 100)" },
  {"score-old",       'o', NULL,       0, "Old-Style scoring, the only surviving process gets 3 points, a "
                                          "surviving process gets 1 point" },
  { NULL, 0, 0, 0, "" },
  {"use-rank",        'n', NULL,       0, "Use rank in each game to create final score (instead of the game score)" },
  {"use-best",        'b', NULL,       0, "Use score from best copy (instead of summing the scores of all copies)" },
  { NULL, 0, 0, 0, "" },
  {"reproducible",    'x', NULL,       0, "Supress initialisation of the random number generator" },
  {"format",          'f', "FORMAT",   0, "Set the format for the highscore table" },
  {"supress-header",  'h', NULL,       0, "Supress highscore table header" },
  {"dump",            'p', NULL,       0, "Dump the programs and exit" },
  {"dump-internal",   'g', NULL,       0, "Dump the programs using an internal format and exit" },
  { 0 }
};

struct argp argp = 
{
  options: options,
  parser: parse_opt,
  args_doc: "filename[:copies] ...",
  doc: "Starts the CoreWars command line version.\v"
  "The format string for the highscore table can contain the following directives: "
  "%r and %R for the rank (%r prints nothing if two warriors have the same rank), "
  "%s for the score, %t for the title, %a for the author and %f for the filename. "
  "The default format string is '%4r %7s %-25t %a'.",
};

int main(int argc,char *argv[])
{
  char **file_list;
  int i,j,n,m;
  struct program *p;
  char *c;
  int count;
  struct program **table;
  char *s;

  argp_parse (&argp, argc, argv,0 ,0 , &file_list);

  if (!reproducible)
    srandom (time (0));

  for (i = 0; file_list[i]; i++)
    {
      c = strrchr (file_list[i], ':');
      if (c)
	{
	  *c = '\0';
	  c++;
	  count = atoi(c);
	  if (count<=0)
	    {
	      printf("Invalid copies count for %s.\n",file_list[i]);
	      exit(1);
	    }
	}
      else
	count = default_count;
      p = alloc_and_link_program ();
      p->language = LANGUAGE;
      if (LANGUAGE==LANGUAGE_CW)
	p->lang.cw.code = NULL;
      else
	p->lang.rc.obj = NULL;
      p->filename = malloc (strlen (file_list[i])+1);		    
      strcpy (p->filename, file_list[i]);
      if ((LANGUAGE==LANGUAGE_CW && cw_load_program (p)==FAIL) ||
	  (LANGUAGE==LANGUAGE_RC && rc_load_program (p)==FAIL))
	{
	  unlink_and_free_program (p);
	  printf ("Cannot load %s.\n", file_list[i]);
	  exit (1);
	}
      if (p->error)
	{
	  printf ("%s\n", p->error);
	  exit (1);
	}
      if (dump)
	if (p->language==LANGUAGE_CW)
	  cw_dump_program (p);
	else
	  rc_dump_program (p);
      if (dump_internal)
	if (p->language==LANGUAGE_CW)
	  cw_dump_program_internal (p);
	else
	  rc_dump_program_internal (p);
      p->load_count = count;
    }
  if (dump || dump_internal)
    exit (0);
  if (score_old)
    {
      /* Adjust score settings. */
      SCORE_CELLS = SCORE_ALIVE = 0;
      SCORE_KILLS = 1;
    }
  for (i=0; i<repeats; i++)
    {
      if (execute_start_cmd ()==FAIL)
	{
	  printf ("Core size is too small to load all programs.\n");
	  exit (1);
	}
      while (elapsed_time<MAX_CYCLES && thread_count && !(STOP_EARLY && process_alive==1 && process_count>1))
	if (LANGUAGE==LANGUAGE_CW)
	  cw_execute_step ();
	else
	  rc_execute_step ();
      execute_stop_cmd ();
      for (j=0; j<process_count; j++)
	process_array[j].cell_count = 0;
      for (j=0; j<SIZE; j++)
	if (memory[j].owner)
	  memory[j].owner->cell_count++;	
      if (score_old)
	{ 
	  /* We need do adjust some things to make this work. */
	  if (thread_count==0)
	    {
	      /* No process lived till the end of the game. We put the ones 
		 that lived the longest time back to life. */
	      m = 0;
	      for (j=1; j<process_count; j++)
		if (process_array[j].death_time>process_array[m].death_time)
		  m = j;
	      for (j=0; j<process_count; j++)
		if (process_array[j].death_time==process_array[m].death_time)
		  {
		    process_array[j].thread_count = 1;
		    thread_count++;
		  }
	    }
	  /* We abuse kill_count to hold the score. pstatistic_add will get 
	     it from there. */
	  for (j=0; j<process_count; j++)
	    if (process_array[j].thread_count)
	      if (process_array[j].thread_count==thread_count)
		process_array[j].kill_count = 3;
	      else
		process_array[j].kill_count = 1;
	    else
	      process_array[j].kill_count = 0;
	}
      pstatistic_add ();
    }
  p = program_list_root;
  n = 0;
  while (p)
    {
      n++;
      p = p->next;
    }
  table = malloc (sizeof (struct program *)*n);
  p = program_list_root;
  i = 0;
  while (p)
    {
      table[i++] = p;
      p = p->next;
    }
  qsort (table, n, sizeof (struct program *), &program_compare);
  if (!supress_header)
    {
      for (s=format; *s; s++)
	{
	  if (*s=='%')
	    {
	      count = strtol(++s, &s, 0);
	      switch (*s)
		{
		case 'r':
		case 'R':
		  printf("%*s",count,"Rank");
		  break;
		case 's':
		  printf("%*s",count,"Score");
		  break;
		case 't':
		  printf("%*s",count,"Title");
		  break;
		case 'a':
		  printf("%*s",count,"Author");
		  break;
		case 'f':
		  printf("%*s",count,"Filename");
		  break;
		case '%':
		  putchar('%');
		  break;
		}
	    }
	  else
	    putchar(*s);
	}
      printf("\n");      
    }
  for (i=0; i<n; i++)
    {
      for (s=format; *s; s++)
	{
	  if (*s=='%')
	    {
	      count = strtol(++s, &s, 0);
	      switch (*s)
		{
		case 'r':
		  if (i==0 || table[i]->tournament_score!=table[i-1]->tournament_score)
		    printf("%*d",count,i+1);
		  else
		    printf("%*s",count,"");
		  break;
		case 'R':
		  printf("%*d",count,i+1);
		  break;
		case 's':
		  printf("%*d",count,table[i]->tournament_score);
		  break;
		case 't':
		  printf("%*s",count,table[i]->title);
		  break;
		case 'a':
		  printf("%*s",count,table[i]->author);
		  break;
		case 'f':
		  printf("%*s",count,table[i]->filename);
		  break;
		case '%':
		  putchar('%');
		  break;
		}
	    }
	  else
	    putchar(*s);
	}
      printf("\n");      
    }
  return 0;
}

#endif
