/*  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 <gtk/gtk.h>
#include <gdk/gdkprivate.h>
#include <limits.h>

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

#define CELL_MOVE_CODE_EQUAL(a,b) \
  ((b)->command==CMD_MOVE && \
   (a)->operand1->accessmode==(b)->operand1->accessmode && \
   (a)->operand1->operand.value==(b)->operand1->operand.value && \
   (a)->operand2->accessmode==(b)->operand2->accessmode && \
   (a)->operand2->operand.value==(b)->operand2->operand.value)

int is_valid (struct cw_operand *o, int i)
{
  int j;

  switch (o->accessmode)
    {
    case AM_ADDRESS:
    case AM_CONSTANT:
    case AM_VALUE:
      return TRUE;
    case AM_INDIRECT:
      j = (o->operand.value+i+SIZE) % SIZE;
      if (memory[j].contains_code)
	return FALSE;
      return TRUE;
    }
}

inline int get_value (struct cw_operand *o, int i, int *value)
{
  int j;

  switch (o->accessmode)
    {
    case AM_ADDRESS:
      *value = (o->operand.value+i+SIZE) % SIZE;
      return TRUE;
    case AM_CONSTANT:
      *value = o->operand.value;
      return TRUE;
    case AM_VALUE:
      j = (o->operand.value+i+SIZE) % SIZE;
      if (memory[j].contains_code)
	return FALSE;
      *value = memory[j].content.data;
      return TRUE;
    case AM_INDIRECT:
      j = (o->operand.value+i+SIZE) % SIZE;
      if (memory[j].contains_code)
	return FALSE;
      j = ((memory[j].content.data % SIZE)+SIZE) % SIZE;
      if (memory[j].contains_code)
	return FALSE;
      *value = memory[j].content.data;
      return TRUE;
    }
}

inline int get_reference (struct cw_operand *o, int i, struct cell **ref)
{
  int j;

  switch (o->accessmode)
    {
    case AM_ADDRESS:
      return FALSE;
    case AM_CONSTANT:
      return FALSE;
    case AM_VALUE:
      j = (o->operand.value+i+SIZE) % SIZE;
      *ref = memory+j;
      return TRUE;
    case AM_INDIRECT:
      j = (o->operand.value+i+SIZE) % SIZE;
      if (memory[j].contains_code)
	return FALSE;
      j = ((memory[j].content.data % SIZE)+SIZE) % SIZE;
      *ref = memory+j;
      return TRUE;
    }
}

void cw_execute_step ()
{
  struct process *p;
  struct thread *t, *t2;
  int remove;
  struct cw_line *l;
  int value, value2;
  struct cell *dst, *ref, *ref2, *ref3;
  int i;

  for (i=0; i<process_count; i++)
    {
      p = process_array+i;
      if (p->thread)
	{
	  t = p->thread;
	  p->thread = t->next;
	  mark_inactive (t->pc);
	  remove = TRUE;
	  if (memory[t->pc].contains_code && !p->killing)
	    {
	      l = memory[t->pc].content.code;
	      switch (l->command)
		{
		case CMD_MOVE:
		  if (get_reference (l->operand2, t->pc, &dst))
		    if (get_value (l->operand1, t->pc, &value))
		      {
			dst->contains_code = FALSE;
			dst->content.data = value;
			MEMORY_CHANGE_OWNER_POINTER (dst, p);
			mark (dst-memory, FALSE);
			remove = FALSE;
		      }
		    else
		      if (get_reference (l->operand1, t->pc, &ref))
			{
			  if (!ALLOW_SELF_MOVE && l->operand1->accessmode==AM_VALUE && 
			    l->operand2->accessmode==AM_VALUE && ref->contains_code &&
			    CELL_MOVE_CODE_EQUAL(l,ref->content.code))
			    break;
			  MEMORY_CHANGE_OWNER_POINTER (dst, p);
			  *dst = *ref;
			  dst->owner = p; /* owner was overwritten by line above */
			  mark (dst-memory, FALSE);
			  remove = FALSE;
			}
		  break;
		case CMD_ADD:
		  if (get_reference (l->operand2, t->pc, &dst))
		    if (!dst->contains_code)
		      if (get_value (l->operand1, t->pc, &value))
			{
			  dst->content.data += value;
			  MEMORY_CHANGE_OWNER_POINTER (dst, p);
			  mark (dst-memory, FALSE);
			  remove = FALSE;
			}
		  break;
		case CMD_NEG:
		  if (get_reference (l->operand2, t->pc, &dst))
		    if (get_value (l->operand1, t->pc, &value))
		      {
			dst->contains_code = FALSE;
			dst->content.data = -value;
			MEMORY_CHANGE_OWNER_POINTER (dst, p);
			mark (dst-memory, FALSE);
			remove = FALSE;
		      }
		  break;
		case CMD_MUL:
		  if (get_reference (l->operand2, t->pc, &dst))
		    if (!dst->contains_code)
		      if (get_value (l->operand1, t->pc, &value))
			{
			  dst->content.data *= value;
			  MEMORY_CHANGE_OWNER_POINTER (dst, p);
			  mark (dst-memory, FALSE);
			  remove = FALSE;
			}
		  break;
		case CMD_DIV:
		  if (get_reference (l->operand2, t->pc, &dst))
		    if (!dst->contains_code)
		      if (get_value (l->operand1, t->pc, &value))
			if (value)
			  {
			    dst->content.data /= value;
			    MEMORY_CHANGE_OWNER_POINTER (dst, p);
			    mark (dst-memory, FALSE);
			    remove = FALSE;
			  }
		  break;
		case CMD_MOD:
		  if (get_reference (l->operand2, t->pc, &dst))
		    if (!dst->contains_code)
		      if (get_value (l->operand1, t->pc, &value))
			if (value)
			  {
			    dst->content.data %= value;
			    MEMORY_CHANGE_OWNER_POINTER (dst, p);
			    mark (dst-memory, FALSE);
			    remove = FALSE;
			  }
		  break;
		case CMD_AND:
		  if (get_reference (l->operand2, t->pc, &dst))
		    if (!dst->contains_code)
		      if (get_value (l->operand1, t->pc, &value))
			{
			  dst->content.data &= value;
			  MEMORY_CHANGE_OWNER_POINTER (dst, p);
			  mark (dst-memory, FALSE);
			  remove = FALSE;
			}
		  break;
		case CMD_NOT:
		  if (get_reference (l->operand2, t->pc, &dst))
		    if (get_value (l->operand1, t->pc, &value))
		      {
			dst->contains_code = FALSE;
			dst->content.data = !value;
			MEMORY_CHANGE_OWNER_POINTER (dst, p);
			mark (dst-memory, FALSE);
			remove = FALSE;
		      }
		  break;
		case CMD_EQUAL:
		  if (is_valid (l->operand1, t->pc) && is_valid (l->operand2, t->pc))
		    {
		      if (get_value (l->operand1, t->pc, &value) && 
			  get_value (l->operand2, t->pc, &value2) &&
			  value==value2)
			t->pc--;
		      else
			if (get_reference (l->operand1, t->pc, &ref) && 
			    get_reference (l->operand2, t->pc, &ref2) &&
			    ref->contains_code==ref2->contains_code &&
			    ref->content.code==ref2->content.code)
			  t->pc--;
		      t->pc++;
		      remove = FALSE;
		    }
		  break;
		case CMD_LESS:
		  if (get_value (l->operand1, t->pc, &value) && 
		      get_value (l->operand2, t->pc, &value2))
		    {
		      if (value>=value2)
			t->pc++;
		      remove = FALSE;
		    }
		  break;
		case CMD_JUMP:
		  if (get_reference (l->operand1, t->pc, &dst))
		    {
		      t->pc = dst-memory-1;
		      remove = FALSE;
		    }
		  break;
		case CMD_FORK:
		  if (get_reference (l->operand1, t->pc, &dst))
		    if (p->thread_count>=MAX_THREADS)
		      p->killing = TRUE;
		    else
		      {
			t2 = malloc (sizeof (struct thread));
			t2->next = t;
			t2->prev = t->prev;
			t->prev = t2;
			t2->prev->next = t2;
			t2->pc = dst-memory;
			p->thread = t->next;
			p->thread_count++;
			thread_count++;
			remove = FALSE;
		      }
		  break;
		case CMD_INFO:
		  if (get_value (l->operand1, t->pc, &value) &&
		      get_reference (l->operand2, t->pc, &ref))
		    {
		      remove = FALSE;
		      switch (value)
			{
			case 0:
			  value = SIZE;
			  break;
			case 1:
			  value = DIMENSIONX;
			  break;
			case 2:
			  value = random() % SIZE;
			  break;
			case 3:
			  value = random() % DIMENSIONX;
			  break;
			case 4:
			  value = p->thread_count;
			  break;
			case 5:
			  value = elapsed_time;
			  break;
			case 6:
			  value = MAX_THREADS;
			  break;
			default:
			  remove = TRUE;
			}
		      if (!remove)
			{
			  ref->contains_code = FALSE;
			  ref->content.data = value;
			  MEMORY_CHANGE_OWNER_POINTER (ref, p);
			  mark (ref-memory, FALSE);
			}
		    }
		  break;
		case CMD_OWN:
		  if (get_reference (l->operand1, t->pc, &dst))
		    {
		      if (dst->owner!=p)
			t->pc++;
		      remove = FALSE;
		    }
		  break;
		case CMD_LOOP:
		  if (get_reference (l->operand1, t->pc, &dst))
		    if (!dst->contains_code)
		      if (get_reference (l->operand2, t->pc, &ref))
			{
			  dst->content.data--;
			  MEMORY_CHANGE_OWNER_POINTER (dst, p);
			  if (dst->content.data)
			    t->pc = ref-memory-1;
			  remove = FALSE;
			}
		  break;
		case CMD_MOVEI:
		  if (get_value (l->operand1, t->pc, &value))
		    if (get_value (l->operand2, t->pc, &value2))
		      if (get_reference (l->operand1, t->pc, &ref))
			if (get_reference (l->operand2, t->pc, &ref2))
			  {
			    MEMORY_CHANGE_OWNER_POINTER (ref, p);
			    mark (ref-memory, FALSE);
			    MEMORY_CHANGE_OWNER_POINTER (ref2, p);
			    mark (ref2-memory, FALSE);
			    ref3 = memory+(value % SIZE + SIZE) % SIZE;
			    dst = memory+(value2 % SIZE + SIZE) % SIZE;
			    MEMORY_CHANGE_OWNER_POINTER (dst, p);
			    *dst = *ref3;
			    dst->owner = p; /* owner was overwritten by line above */
			    mark (dst-memory, FALSE);
			    ref->content.data++;
			    ref2->content.data++;
			    remove = FALSE;
			  }
		  break;
		}
	    }
	  if (remove)
	    {
	      p->thread_count--;
	      thread_count--;
	      if (t==t->next)
		{
		  if (memory[t->pc].owner && memory[t->pc].owner!=p)
		    memory[t->pc].owner->kill_count++;
		  free (t);
		  p->thread = NULL;
		  p->death_time = elapsed_time;
		  process_alive--;
		}
	      else
		{
		  t->prev->next = t->next;
		  t->next->prev = t->prev;
		  free (t);
		}
	    }
	  else
	    {
	      t->pc = (t->pc+1) % SIZE;
	      mark_active (t->pc, p);
	    }
	}
    }
  elapsed_time++;
#ifndef CMD_LINE
  if (elapsed_time==MAX_CYCLES || !thread_count || (STOP_EARLY && process_alive==1 && process_count>1))
    if (tournament_running)
      tournament_finished_game ();
    else
      execute_stop();
#endif
}

void cw_create_process_cmd (struct program *p, struct process *pr, int position)
{
  int i,j;

  pr->program = p;

  pr->thread = malloc (sizeof (struct thread));
  pr->thread->next = pr->thread;
  pr->thread->prev = pr->thread;
  pr->cell_count = 0;
  pr->kill_count = 0;
  i = position;
  for (j=0; j<p->lang.cw.opcode_number; j++, i = (i+1) % SIZE)
    {
      MEMORY_CHANGE_OWNER_INDEX (i, pr);
      if ((p->lang.cw.code[j])->command==CMD_DATA)
	{
	  memory[i].contains_code = FALSE;
	  if ((p->lang.cw.code[j])->operand1->accessmode==AM_CONSTANT)
	    memory[i].content.data = (p->lang.cw.code[j])->operand1->operand.value;
	  else
	    memory[i].content.data = (p->lang.cw.code[j])->operand1->operand.value+i;
	}
      else
	{
	  memory[i].contains_code = TRUE;
	  memory[i].content.code = p->lang.cw.code[j];
	}
      mark (i, FALSE);
    }
  i = (i-j+SIZE) % SIZE;
  while (!memory[i].contains_code && memory[i].owner==pr)
    i = (i+1) % SIZE;
  pr->thread->pc = i;
  pr->thread_count = 1;
  pr->death_time = INT_MAX;
  pr->killing = FALSE;
  mark_active (i, pr);
  thread_count++;
}
