/*  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>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

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

#include "main.h"
#include "main-gui.h"
#include "parser-cw.h"
#include "program.h"
#include "program-cw.h"
#include "program-rc.h"
#include "options.h"
#include "select.h"
#include "execute.h"

struct program *program_list_root;

/* Unloads error and code from struct program. */
void unload_program (struct program *p)
{
  if (p->error)
    {
      free (p->error);
      p->error = NULL;
    }
  if (p->title)
    {
      free (p->title);
      p->title = NULL;
    }
  if (p->author)
    {
      free (p->author);
      p->author = NULL;
    }
  if (p->language==LANGUAGE_CW)
    cw_free_program (p);
  else if (p->language==LANGUAGE_RC)
    rc_free_program (p);
}

/* Frees struct program. Unloads error and code if neccessary. Frees
   other allocated fields. Assumes filename is allocated. */
void unlink_and_free_program (struct program *p)
{
  unload_program (p);
  free (p->filename);
  if (p->prev)
    p->prev->next = p->next;
  if (p->next)
    p->next->prev = p->prev;
  if (p==program_list_root)
    program_list_root = p->next;
  free (p);
}

/* Allocates struct program and links it in doubly linked list.
   Initialises error and code to NULL. Returns pointer to struct 
   program. */
struct program *alloc_and_link_program ()
{
  struct program *p;

  p = malloc (sizeof (struct program));
  p->next = program_list_root;
  p->prev = NULL;
  if (program_list_root)
    program_list_root->prev = p;
  program_list_root = p;
  p->error = NULL;
  p->title = NULL;
  p->author = NULL;
  p->language = LANGUAGE_NONE;
  p->load_count = STANDARD_LOAD_COUNT;
  p->tournament_count = 0;
  p->tournament_score = 0;
  p->tournament_games = 0;
  return p;
}

#ifndef CMD_LINE

/* Checks each loaded program for deletions and changes in the 
   source code. Reloads if neccessary. Old entries are removed. 
   Must only be called when not running! */
void recheck_program_list ()
{
  struct program *p = program_list_root, *next;
  struct stat buf;
  bool remove;

  execute_cleanup ();
  while (p)
    {
      remove = TRUE;
      if (!stat (p->filename, &buf))
	if (S_ISREG (buf.st_mode))
	  {
	    if (p->inode==buf.st_ino && p->device==buf.st_dev && p->mtime>=buf.st_mtime)
	      remove = FALSE;
	    else
	      {
		if ((p->language==LANGUAGE_CW && cw_load_program (p)==OK) ||
		    (p->language==LANGUAGE_RC && rc_load_program (p)==OK))
		  {
		    p->inode = buf.st_ino;
		    p->device = buf.st_dev;
		    p->mtime = buf.st_mtime;
		    remove = FALSE;
		  }
	      }    
	  }
      next = p->next;
      if (remove)
	unlink_and_free_program (p);
      p = next;
    }
  select_update_list ();
}

int add_program_directory(const char *directory)
{
  DIR *dir;
  struct dirent *entry;
  int d_len, fn_len;
  char filename[1024];
  struct stat buf;
  struct program *p;

  d_len = strlen (directory);
  dir = opendir (directory);
  if (!dir)
    {
      perror ("opendir");
      return FAIL;
    }
  while ((entry = readdir (dir))!=NULL)
    {
      fn_len = strlen (entry->d_name);
      if ((fn_len>CW_PROGRAM_FILE_EXTENSION_LENGTH && 
	   strcmp (entry->d_name+fn_len-CW_PROGRAM_FILE_EXTENSION_LENGTH, CW_PROGRAM_FILE_EXTENSION)==0) || 
	  (fn_len>RC_PROGRAM_FILE_EXTENSION_LENGTH && 
	   strcmp (entry->d_name+fn_len-RC_PROGRAM_FILE_EXTENSION_LENGTH, RC_PROGRAM_FILE_EXTENSION)==0))
	{
	  strcpy (filename, directory);
	  strcat (filename, "/");
	  strcat (filename, entry->d_name);
	  if (stat (filename, &buf))
	    perror ("stat");
	  else
	    if (S_ISREG (buf.st_mode))
	      {
		p = program_list_root;
		while (p)
		  {
		    if (p->inode==buf.st_ino && p->device==buf.st_dev)
		      break;
		    p = p->next;
		  }
		if (!p)
		  {
		    p = alloc_and_link_program ();
		    p->filename = malloc (fn_len+1+d_len+1);		    
		    strcpy (p->filename, filename);
		    p->device = buf.st_dev;
		    p->inode = buf.st_ino;
		    p->mtime = buf.st_mtime;
		    if (strcmp (entry->d_name+fn_len-CW_PROGRAM_FILE_EXTENSION_LENGTH, CW_PROGRAM_FILE_EXTENSION)==0)
		      {
			p->language = LANGUAGE_CW;
			p->lang.cw.code = NULL;
		      }
		    else
		      {
			p->language = LANGUAGE_RC;
			p->lang.rc.obj = NULL;
		      }
		    if ((p->language==LANGUAGE_CW && cw_load_program (p)==FAIL) ||
			(p->language==LANGUAGE_RC && rc_load_program (p)==FAIL))
		      unlink_and_free_program (p);
		  }
	      }
	}
    }
  closedir (dir);
  select_update_list ();
  return OK;
}

void program_update ()
{
  struct program *p, *p2;

  p = program_list_root;
  while (p)
    {
      p2 = p->next;
      unload_program (p);
      if ((p->language==LANGUAGE_RC && rc_load_program (p)==FAIL) ||
	  (p->language==LANGUAGE_CW && cw_load_program (p)==FAIL))
	unlink_and_free_program (p);
      p = p2;
    }
}

#endif
