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

#include <stdlib.h>
#include <string.h>

#ifndef CMD_LINE
#include <gtk/gtk.h>
#endif

#include "main.h"
#include "main-gui.h"
#include "execute.h"
#include "execute-cw.h"
#include "execute-rc.h"
#include "tournament.h"
#include "statistic.h"
#include "pstatistic.h"
#include "select.h"

#ifndef CMD_LINE
GdkGC* execute_gc;
GdkColor black;
GdkColormap *execute_colormap;
#endif

int running = FALSE;
struct cell memory[SIZE_MAX];
struct process *process_array = NULL;
int process_count = 0;
int process_alive;
gint execute_timeout, statistic_timeout;
int elapsed_time;
int thread_count;
struct process *marked_process = NULL;
int cleanup_needed = FALSE;
int paused;

struct {
  int contains_code;
  int marked;
  struct process *owner;
} old_memory[SIZE_MAX];

#ifndef CMD_LINE
void execute_init ()
{
  execute_gc = gdk_gc_new (main_drawing_area->window);
  execute_colormap = gdk_window_get_colormap (main_drawing_area->window);
  black.red = 0;
  black.green = 0;
  black.blue = 0;
  gdk_color_alloc (execute_colormap, &black);      
  gdk_gc_set_background (execute_gc, &black);
}

void mark (int i, int force)
{
  int marked;
  int mx, my, x, y;
  int t, b, l, r;
  GdkPoint points[5];

  marked = marked_process && memory[i].owner==marked_process;
  if (!force && old_memory[i].contains_code==memory[i].contains_code && 
      old_memory[i].owner==memory[i].owner && old_memory[i].marked==marked)
    return;

  mx = i % DIMENSIONX;
  my = i / DIMENSIONX;
  x = 6*mx;
  y = 6*my;

  if (force || old_memory[i].marked!=marked)
    if (marked)
      gdk_draw_rectangle (main_map, GTK_WIDGET (main_drawing_area)->style->white_gc, FALSE, x, y, 6, 6);
    else
      {
	gdk_draw_rectangle (main_map, GTK_WIDGET (main_drawing_area)->style->dark_gc[0], FALSE, x, y, 6, 6);
	if (marked_process)
	  {
	    t = my>0 && old_memory[i-DIMENSIONX].marked;
	    b = my<DIMENSIONY-1 && i+DIMENSIONX<SIZE && old_memory[i+DIMENSIONX].marked;
	    l = mx>0 && old_memory[i-1].marked;
	    r = mx<DIMENSIONX && i+1<SIZE && old_memory[i+1].marked;
	    if (t)
	      gdk_draw_line (main_map, GTK_WIDGET (main_drawing_area)->style->white_gc, x, y, x+6, y);
	    if (b)
	      gdk_draw_line (main_map, GTK_WIDGET (main_drawing_area)->style->white_gc, x, y+6, x+6, y+6);
	    if (l)
	      gdk_draw_line (main_map, GTK_WIDGET (main_drawing_area)->style->white_gc, x, y, x, y+6);
	    if (r)
	      gdk_draw_line (main_map, GTK_WIDGET (main_drawing_area)->style->white_gc, x+6, y, x+6, y+6);
	    if (!t && !l && mx>0 && my>0 && old_memory[i-DIMENSIONX-1].marked)
	      gdk_draw_point (main_map, GTK_WIDGET (main_drawing_area)->style->white_gc, x, y);
	    if (!t && !r && mx<DIMENSIONX-1 && my>0 && old_memory[i-DIMENSIONX+1].marked)
	      gdk_draw_point (main_map, GTK_WIDGET (main_drawing_area)->style->white_gc, x+6, y);
	    if (!b && !l && mx>0 && my<DIMENSIONY-1 && i+DIMENSIONX-1<SIZE && old_memory[i+DIMENSIONX-1].marked)
	      gdk_draw_point (main_map, GTK_WIDGET (main_drawing_area)->style->white_gc, x, y+6);
	    if (!b && !r && mx<DIMENSIONX-1 && my<DIMENSIONY-1 && i+DIMENSIONX+i<SIZE && old_memory[i+DIMENSIONX+1].marked)
	      gdk_draw_point (main_map, GTK_WIDGET (main_drawing_area)->style->white_gc, x+6, y+6);
	  }
      }

  if (force || old_memory[i].contains_code!=memory[i].contains_code || old_memory[i].owner!=memory[i].owner)
    if (memory[i].owner)
      { 
	if (memory[i].contains_code)
	  {
	    gdk_gc_set_foreground (execute_gc, &(memory[i].owner->color));
	    gdk_draw_rectangle (main_map, execute_gc, TRUE, x+1, y+1, 5, 5); 
	    gdk_gc_set_foreground (execute_gc, &black);
	    points[0].x = x+3; points[0].y = y+2;
	    points[1].x = x+2; points[1].y = y+3;
	    points[2].x = x+3; points[2].y = y+3;
	    points[3].x = x+4; points[3].y = y+3;
	    points[4].x = x+3; points[4].y = y+4;
	    gdk_draw_points (main_map, execute_gc, points, 5);
	  }
	else
	  {
	    gdk_gc_set_foreground (execute_gc, &black);
	    gdk_draw_rectangle (main_map, execute_gc, TRUE, x+1, y+1, 5, 5); 
	    gdk_gc_set_foreground (execute_gc, &(memory[i].owner->color));
	    gdk_draw_rectangle (main_map, execute_gc, TRUE, x+2, y+2, 3, 3); 
	  }
      }
    else
      {
	gdk_gc_set_foreground (execute_gc, &black);
	gdk_draw_rectangle (main_map, execute_gc, TRUE, x+1, y+1, 5, 5); 
      }

  old_memory[i].contains_code = memory[i].contains_code;
  old_memory[i].owner = memory[i].owner;
  old_memory[i].marked = marked;
}

void mark_active (int i, struct process* active)
{
  gdk_gc_set_foreground (execute_gc, &(active->color));
  gdk_draw_rectangle (main_map, execute_gc, TRUE, 6*(i % DIMENSIONX)+1, 6*(i / DIMENSIONX)+1, 5, 5); 
}

void mark_inactive (int i)
{
  mark (i, TRUE);
}

void execute_mark (struct process *p)
{
  int i;
  struct process *old;

  if (marked_process!=p)
    {
      old = marked_process;
      marked_process = p;
      for (i=0; i<SIZE; i++)
	if (memory[i].owner==old || memory[i].owner==marked_process)
	  mark (i, FALSE);
    }
}

gint execute_step_handler (gpointer data)
{
  int i;

  if (!paused)
    for (i=0; running && i<=(EXECUTE_UPDATE/process_alive); i++)
      if (LANGUAGE==LANGUAGE_CW)
	cw_execute_step ();
      else
	rc_execute_step ();
  return TRUE;
}

gint statistic_handler (gpointer data)
{
  if (!paused)
    statistic_update (TRUE);
  return TRUE;
}
#endif

void execute_cleanup ()
{
  int i;
  struct process *p;
  struct thread *t, *nextt;

  if (cleanup_needed)
    {
#ifndef CMD_LINE
      statistic_clear ();
#endif
      marked_process = NULL;
      for (i=0; i<process_count; i++)
	{
	  p = process_array+i;
#ifndef CMD_LINE
	  gdk_pixmap_unref (p->bullet);
#endif
	  if (p->thread)
	    {
	      t = p->thread;
	      t->prev->next = NULL;
	      while (t)
		{
		  nextt = t->next;
		  free (t);
		  t = nextt;
		}
	    }
	}
      free (process_array);
      /* Don't needlessly touch unused virtual memory */
      for (i=0; i<SIZE; i++)
	{
	  memory[i].owner = NULL;
	  memory[i].contains_code = FALSE;
	  if (LANGUAGE==LANGUAGE_CW)
	    memory[i].content.data = 0;
	  else
	    {
	      memory[i].content.command.command = RC_CMD_DAT|RC_MOD_F|RC_AM_A_DIRECT|RC_AM_B_DIRECT;
	      memory[i].content.command.a.number = 0;
	      memory[i].content.command.b.number = 0;
	    }
	  mark (i, TRUE);
	}
      process_array = NULL;
      process_count = 0;
      /*
	#ifndef CMD_LINE
	gdk_gc_unref (execute_gc);
	#endif
      */
      cleanup_needed = FALSE;
    }
}

void execute_stop_cmd ()
{
  running = FALSE;
  cleanup_needed = TRUE;
}

#ifndef CMD_LINE
/* Stop Programs */ 
void execute_stop ()
{
  if (running)
    {
      gtk_widget_set (GTK_WIDGET (menu_file_start), "sensitive", TRUE, NULL);
      gtk_widget_set (GTK_WIDGET (menu_file_pause), "sensitive", FALSE, NULL);
      gtk_widget_set (GTK_WIDGET (menu_file_continue), "sensitive", FALSE, NULL);
      gtk_widget_set (GTK_WIDGET (menu_file_step), "sensitive", FALSE, NULL);
      gtk_widget_set (GTK_WIDGET (menu_file_stop), "sensitive", FALSE, NULL);
      gtk_timeout_remove (execute_timeout);
      gtk_timeout_remove (statistic_timeout);

      execute_stop_cmd ();

      pstatistic_add ();
      statistic_update (FALSE);
      options_update ();
      select_update ();
      tournament_update ();
    }
}

/* Start Programs */ 
int execute_start ()
{
  GtkWidget **dialog;
  GtkWidget *label;
  GtkWidget *button;

  if (!running)
    {
      if (cleanup_needed)
	execute_cleanup ();
      gtk_widget_set (GTK_WIDGET (menu_file_start), "sensitive", FALSE, NULL);
      gtk_widget_set (GTK_WIDGET (menu_file_pause), "sensitive", TRUE, NULL);
      gtk_widget_set (GTK_WIDGET (menu_file_stop), "sensitive", !tournament_running, NULL);

      if (execute_start_cmd ()==FAIL)
	{
	  gtk_widget_set (GTK_WIDGET (menu_file_start), "sensitive", TRUE, NULL);
	  gtk_widget_set (GTK_WIDGET (menu_file_pause), "sensitive", FALSE, NULL);
	  gtk_widget_set (GTK_WIDGET (menu_file_stop), "sensitive", FALSE, NULL);

	  dialog = malloc (sizeof (GtkWidget *));
	  *dialog = gtk_dialog_new ();
	  gtk_window_set_title (GTK_WINDOW (*dialog), "Cannot start simulation");
	  label = gtk_label_new (" The core size is too small to load \n"
				 " the given number of warriors with \n"
				 " the given minimum distance. ");
	  gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
	  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (*dialog)->vbox),
			      label, TRUE, TRUE, 0);
	  gtk_widget_show (label);
	  button = gtk_button_new_with_label( "Ok" );
	  gtk_signal_connect_object (GTK_OBJECT (button), "clicked", 
				     GTK_SIGNAL_FUNC (gtk_widget_destroy),
				     GTK_OBJECT (*dialog)); 
	  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (*dialog)->action_area),
			      button, TRUE, TRUE, 0);
	  gtk_widget_show (button);
	  gtk_window_set_position (GTK_WINDOW (*dialog), GTK_WIN_POS_CENTER);
	  gtk_widget_show (*dialog);
      	  
	  return FAIL;
	}

      execute_timeout = gtk_timeout_add (EXECUTE_DELAY, execute_step_handler, NULL);
      statistic_timeout = gtk_timeout_add (STATISTIC_DELAY, statistic_handler, NULL);
      statistic_update (FALSE);
      options_update ();
      select_update ();
      tournament_update ();
    }
  return OK;
}

/* Pause Programs */ 
void execute_pause ()
{
  paused = TRUE;
  gtk_widget_set (GTK_WIDGET (menu_file_pause), "sensitive", FALSE, NULL);
  gtk_widget_set (GTK_WIDGET (menu_file_continue), "sensitive", TRUE, NULL);
  gtk_widget_set (GTK_WIDGET (menu_file_step), "sensitive", TRUE, NULL);
  statistic_update (FALSE);
}

/* Continue Programs */ 
void execute_continue ()
{
  paused = FALSE;
  gtk_widget_set (GTK_WIDGET (menu_file_pause), "sensitive", TRUE, NULL);
  gtk_widget_set (GTK_WIDGET (menu_file_continue), "sensitive", FALSE, NULL);
  gtk_widget_set (GTK_WIDGET (menu_file_step), "sensitive", FALSE, NULL);
}

void create_process (struct program *p, struct process *pr, int position)
{
  do {
    pr->color.red = random() % 65535;
    pr->color.green = random() % 65535;
    pr->color.blue = random() % 65535;
  } while (pr->color.red+pr->color.green+pr->color.blue<100000);
  gdk_color_alloc (execute_colormap, &(pr->color));

  pr->bullet = gdk_pixmap_new (main_drawing_area->window, STATISTIC_BULLET_SIZE,
			       STATISTIC_BULLET_SIZE, -1);
  gdk_gc_set_foreground (execute_gc, &(pr->color));
  gdk_draw_rectangle (pr->bullet, execute_gc, TRUE, 0, 0, 
		      STATISTIC_BULLET_SIZE, STATISTIC_BULLET_SIZE);

  if (LANGUAGE==LANGUAGE_CW)
    cw_create_process_cmd (p, pr, position);
  else
    rc_create_process_cmd (p, pr, position);
}
#endif

struct entry {
  int weight;
  struct program *p;
} ;

int entry_compare(const void *a, const void *b)
{
  return ((struct entry *) a)->weight-((struct entry *) b)->weight;
}

int execute_start_cmd ()
{
  struct program *p;
  int i, j;
  int total_size;
  int space;  
  struct entry *table;
  int position;

  if (cleanup_needed)
    execute_cleanup ();
  
  for (i=0; i<SIZE; i++)
    {
      memory[i].owner = NULL;
      memory[i].contains_code = FALSE;
      if (LANGUAGE==LANGUAGE_CW)
	memory[i].content.data = 0;
      else
	{
	  memory[i].content.command.command = RC_CMD_DAT|RC_MOD_F|RC_AM_A_DIRECT|RC_AM_B_DIRECT;
	  memory[i].content.command.a.number = 0;
	  memory[i].content.command.b.number = 0;
	}
      mark (i, TRUE);
    }
  thread_count = 0;
  process_count = 0;
  total_size = 0;
  p = program_list_root;
  while (p)
    {
      if (!p->error && p->language==LANGUAGE)
	{
	  process_count += p->load_count;
	  if (p->language==LANGUAGE_CW)
	    total_size += p->load_count*p->lang.cw.opcode_number;
	  else
	    total_size += p->load_count*p->lang.rc.size;
	}
      p = p->next;
    }
  space = SIZE-total_size-process_count*MIN_DISTANCE;
  if (space<0)
    {
      process_count = 0;
      return FAIL;
    }
  process_alive = process_count;
  table = malloc (sizeof (struct entry)*process_count);
  p = program_list_root;
  j = 0;
  while (p)
    {
      if (!p->error && p->language==LANGUAGE)
	for (i=0; i<p->load_count; i++)
	  {
	    table[j].weight = random();
	    table[j++].p = p;
	  }
      p = p->next;
    }
  qsort (table, process_count, sizeof (struct entry), &entry_compare);
  for (j=0; j<process_count; j++)
    table[j].weight = MIN_DISTANCE;
  j = process_count-1;
  while (space)
    if (random()%(space+j)<j)
      j--;
    else
      {
	table[j].weight++;
	space--;
      }
  process_array = malloc (sizeof (struct process)*process_count);
  position = random() % SIZE;
  for (j=0; j<process_count; j++)
    {
#ifndef CMD_LINE
      create_process (table[j].p, process_array+j, position);
#else
      if (LANGUAGE==LANGUAGE_CW)
	cw_create_process_cmd (table[j].p, process_array+j, position);
      else
	rc_create_process_cmd (table[j].p, process_array+j, position);
#endif
      if (LANGUAGE==LANGUAGE_CW)
	position += table[j].p->lang.cw.opcode_number;
      else
	position += table[j].p->lang.rc.size;
      position = (position+table[j].weight) % SIZE;
    }
  elapsed_time = 0;
  running = TRUE;
  paused = FALSE;
  return OK;
}
