/* xpenguins_theme.c - provides the themability of xpenguins
 * -copyright-
#-# 
#-# Copyright (C) 1999-2001  Robin Hogan, 2021 Willem Vermin
#-# 
#-# This program is free software; you can redistribute it and/or modify
#-# it under the terms of the GNU General Public License as published by
#-# the Free Software Foundation; either version 2 of the License, or
#-# (at your option) any later version.
#-# 
#-# 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 <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <glob.h>
#include <X11/xpm.h>

#include "xpenguins.h"
#include "debug.h"
#include "utils.h"

char *xpenguins_directory = _(XPENGUINS_SYSTEM_DIRECTORY);
int xpenguins_verbose = 1;

#define MAX_STRING_LENGTH 513

/* Print a warning message */
static void xpenguins_out_of_memory(int line)
{
   fprintf(stderr, _("%s: Out of memory at line %d\n"), __FILE__, line);
}

#define xpenguins_out_of_memory() xpenguins_out_of_memory(__LINE__)

/* Initialise an XPenguinsTheme structure ready to be filled
 * with data */
static int xpenguins_theme_init(XPenguinsTheme *theme)
{
   int j;
   if (!(theme->data = (ToonData **)malloc(PENGUIN_NGENERA * sizeof(ToonData *)))) {
      return 1;
   }
   if (!(theme->data[0] = (ToonData *)malloc(PENGUIN_NTYPES * sizeof(ToonData)))) {
      return 1;
   }
   if (!(theme->name = (char **)malloc(PENGUIN_NGENERA * sizeof(char *)))) {
      return 1;
   }
   theme->name[0] = NULL;
   if (!(theme->number = (int *)malloc(PENGUIN_NGENERA * sizeof(int)))) {
      return 1;
   }
   theme->number[0]    = 1;
   theme->ngenera      = 1;
   theme->delay        = 60; /* milliseconds */
   theme->_nallocated  = PENGUIN_NGENERA;

   for (j = 0; j < PENGUIN_NTYPES; ++j) {
      ToonData *data = theme->data[0] + j;
      data->exists = 0;
      data->image = NULL;
      data->filename = NULL;
   }
   return 0;
}

/* Grow a theme ready to accept another genus. */
static int xpenguins_theme_grow(XPenguinsTheme *theme)
{
   int j;
   if (theme->_nallocated == theme->ngenera) {
      if (!(theme->data = (ToonData **)realloc(theme->data, (theme->ngenera + PENGUIN_NGENERA) * sizeof(ToonData *)))) {
	 return 1;
      }
      if (!(theme->name = (char **)realloc(theme->name, (theme->ngenera + PENGUIN_NGENERA) * sizeof(char *)))) {
	 return 1;
      }
      if (!(theme->number = (int *)realloc(theme->number, (theme->ngenera + PENGUIN_NGENERA) * sizeof(int)))) {
	 return 1;
      }
      theme->_nallocated += PENGUIN_NGENERA;
   }

   if (!(theme->data[theme->ngenera] = (ToonData *)malloc(PENGUIN_NTYPES * sizeof(ToonData)))) {
      return 1;
   }
   theme->name[theme->ngenera] = NULL;
   theme->number[theme->ngenera] = 1;

   for (j = 0; j < PENGUIN_NTYPES; ++j) {
      ToonData *data = theme->data[theme->ngenera] + j;
      data->exists   = 0;
      data->image    = NULL;
      data->filename = NULL;
   }
   ++(theme->ngenera);
   return 0;
}

/* Set the data directory to use to find themes */
void xpenguins_set_directory(char *directory)
{
   xpenguins_directory = directory;
   return;
}     

/* Remove underscores from a theme name */
char *xpenguins_remove_underscores(char *name)
{
   char *ch = name;
   while (*ch != '\0') {
      if (*ch == '_') {
	 *ch = ' ';
      }
      ++ch;
   }
   return name;
}

/* Return a NULL-terminated list of names of apparently valid themes -
 * basically the directory names from either the user or the system
 * theme directories that contain a file called "config" are
 * returned. Underscores in the directory names are converted into
 * spaces, but directory names that already contain spaces are
 * rejected. This is because the install program seems to choke on
 * directory names containing spaces, but theme names containing
 * underscores are ugly. The list should be freed with
 * xpenguins_free_list(). If nthemes is not NULL then it is used to
 * return the number of themes that were found */
char **xpenguins_list_themes(int *nthemes)
{
   char *home        = getenv("HOME");
   char *home_root   = _(XPENGUINS_USER_DIRECTORY);
   char *themes      = _(XPENGUINS_THEME_DIRECTORY);
   char *config      = _(XPENGUINS_CONFIG);
   char *config_path = NULL;
   char *list        = NULL;
   char **list_array = NULL;
   int list_len      = 0;
   int string_length = 0;
   int sys_glob_mode = 0;
   char *tmp_path;
   int i;
   glob_t globbuf;
   int n = 0;

   if (home != NULL) {
      /* First add themes from the users home directory */
      string_length = strlen(home) + strlen(home_root)
	 + strlen(themes) + 2 + strlen(config) + 1;
      config_path = (char*)malloc(string_length*sizeof(char));
      if (!config_path) {
	 xpenguins_out_of_memory();
	 return NULL;
      }
      snprintf(config_path, string_length, "%s%s%s/*%s",
	    home, home_root, themes, config);
      if (glob(config_path, 0, NULL, &globbuf) == GLOB_NOSPACE) {
	 free(config_path);
	 xpenguins_out_of_memory();
	 return NULL;
      }
      sys_glob_mode=GLOB_APPEND;
   }
   {
      P("config_path: %s %ld\n",config_path,(long int)globbuf.gl_pathc);
      size_t i;
      for (i=0; i<globbuf.gl_pathc; i++)
	 printf("%ld: %s\n",(long int)i,globbuf.gl_pathv[i]);
   }

   /* Then add system themes */
   string_length = strlen(xpenguins_directory) + strlen(themes) + 2
      + strlen(config) + 1;
   tmp_path = (char *)realloc(config_path, string_length*sizeof(char));
   if (!tmp_path) {
      globfree(&globbuf);
      free(config_path);
      xpenguins_out_of_memory();
      return NULL;
   }
   else {
      config_path = tmp_path;
   }
   snprintf(config_path, string_length, "%s%s/*%s",
	 xpenguins_directory, themes, config);
   if (glob(config_path, sys_glob_mode, NULL, &globbuf) == GLOB_NOSPACE) {
      free(config_path);
      xpenguins_out_of_memory();
      return NULL;
   }
   free(config_path);

   /* Now add just the sub directory name to a list */
   for (i = 0; i < (int)globbuf.gl_pathc; ++i) 
   {
      char *file_path = globbuf.gl_pathv[i];
      P("file_path: %s\n",file_path);
      int file_path_len = strlen(file_path);
      char *dir_name = file_path + file_path_len - strlen(config);
      P("dir_name %s\n",dir_name);
      int dir_name_len = 0;
      int valid_name = 1;

      while (dir_name > file_path && dir_name[-1] != '/') 
      {
	 --dir_name;
	 ++dir_name_len;
	 /* We convert all underscores in the directory name 
	  * to spaces, but actual spaces in the directory
	  * name are not allowed. */
	 if (*dir_name == ' ') {
	    valid_name = 0;
	    break;
	 }
	 else if (*dir_name == '_') {
	    *dir_name = ' ';
	 }
      }
      P("dir_name %s\n",dir_name);

      /* Check that theme with this name does not exist - a user may
       * wish to override a system theme, but we don't want duplication
       * in the list that is returned. */
      if (list && valid_name) {
	 char *listp = list;
	 char *namep = dir_name;
	 int  possible = 1;
	 do {
	    if (*listp == '/' || *listp == '\0') {
	       if (possible && *namep == '/') {
		  /* Found a duplicate name */
		  valid_name = 0;
		  break;
	       }
	       else {
		  namep = dir_name;
		  possible = 1;
	       }
	    }
	    else if (possible) {
	       if (*listp != *namep) {
		  possible = 0;
	       }
	       else {
		  ++namep;
	       }
	    }
	 }
	 while (*(listp++) != '\0');
      }

      if (dir_name_len && valid_name) {
	 /* Add theme name to list */
	 /*  wwvv deletion of original code

	     char *tmp_list;
	     if (list) {
	     list[list_len-1] = '/';
	     }
	     list_len += (dir_name_len + 1);
	     tmp_list = (char *)realloc(list, list_len);
	     if (!tmp_list) {
	     if (list) {
	     free(list);
	     }
	     xpenguins_out_of_memory();
	     globfree(&globbuf);
	     return NULL;
	     }
	     else {
	     list = tmp_list;
	     }

	     strncpy(list + list_len - dir_name_len - 1, dir_name, dir_name_len);
	     list[list_len - 1] = '\0';
	     P("list: %s %s %d\n",list,dir_name,dir_name_len);
	     */
	 ++n;
	 // wwvv added code
	 list_array      = (char **) realloc(list_array,sizeof(char*)*(n+1));
	 list_array[n-1] = strndup(dir_name,dir_name_len);
	 list_array[n]   = NULL;
	 // /wwvv
      }
   }
   globfree(&globbuf);

   // wwvv added code
   if (nthemes) 
      *nthemes = n;

   return list_array;
   // /wwvv

   // next code is not used any more:

   if (list_len) {
      /* Allocate the array of pointers to point to each theme name */
      char *ch = list;
      int j = 1;
      list_array = (char **) malloc((n+1) * sizeof(char *));
      if (!list_array) {
	 if (list) {
	    free(list);
	 }
	 xpenguins_out_of_memory();
	 return NULL;
      }
      list_array[0] = list;
      while (j < n) {
	 if (*ch == '/') {
	    *ch = '\0';
	    list_array[j++] = ++ch;
	 }
	 else {
	    ++ch;
	 }
      }
      list_array[j] = NULL;
      if (nthemes) {
	 *nthemes = n;
      }
      return list_array;
   }

   return NULL;
}

/* Generic function for freeing character lists that are allocated for
 * various purposes. */
void xpenguins_free_list(char **list)
{
   if (list) 
   {
      char **t;
      for (t = list; *t; t++)
	 free(*t);
      free(list);
   }
}

void xpenguins_print_list(char **list)
{
   if (list && !*list)
      printf("empty\n");
   if (list)
   {
      char **t;
      for (t = list; *t; t++)
	 printf("%s\n",*t);
   }
   else
      printf("nil\n");
}

/* Return the full path of the specified theme. Spaces in the theme
 * name are converted into underscores. */
char *xpenguins_theme_directory(char *name)
{
   char  *home          = getenv("HOME");
   char  *home_root     = _(XPENGUINS_USER_DIRECTORY);
   char  *themes        = _(XPENGUINS_THEME_DIRECTORY);
   char  *config        = _(XPENGUINS_CONFIG);
   char  *config_path   = NULL;
   int    string_length = 0;
   char  *tmp_path;
   char  *ch;
   int    root_length;
   struct stat stat_buf;

   if(home!=NULL) 
   {
      /* First look in $HOME/.xpenguins/themes for config */
      root_length = strlen(home) + strlen(home_root) + strlen(themes) + 1;
      string_length = root_length + strlen(name) + strlen(config) + 1;
      config_path = (char *)malloc(string_length*sizeof(char));
      if (!config_path) {
	 xpenguins_out_of_memory();
	 return NULL;
      }
      snprintf(config_path, string_length, "%s%s%s/%s%s",
	    home, home_root, themes, name, config);
      /* Convert spaces to underscores */
      ch = config_path + root_length;
      while (*ch != '/') {
	 if (*ch == ' ') {
	    *ch = '_';
	 }
	 ++ch;
      }
      /* See if theme exists */
      if (stat(config_path, &stat_buf) == 0) {
	 config_path[string_length-strlen(config)-1] = '\0';
	 return config_path;
      }
      /* Theme not found in users theme directory... */
   }

   /* Now look in [xpenguins_directory]/themes for config */
   root_length = strlen(xpenguins_directory) + strlen(themes) + 1;
   string_length = root_length + strlen(name) + strlen(config) + 1;
   tmp_path = (char *)realloc(config_path, string_length*sizeof(char));
   if (!tmp_path) {
      xpenguins_out_of_memory();
      free(config_path);
      return NULL;
   }
   else {
      config_path = tmp_path;
   }
   snprintf(config_path, string_length, "%s%s/%s%s",
	 xpenguins_directory, themes, name, config);
   /* Convert spaces to underscores */
   ch = config_path + root_length;
   while (*ch !='/') {
      if (*ch == ' ') {
	 *ch = '_';
      }
      ++ch;
   }
   /* Look for theme */
   if (stat(config_path, &stat_buf) == 0) {
      config_path[string_length-strlen(config)-1] = '\0';
      return config_path;
   }
   /* Theme not found */
   return NULL;
}

/* Copy select properties from one ToonData structure to another */
static void xpenguins_copy_properties(ToonData *src, ToonData *dest)
{
   dest->nframes           = src->nframes;
   dest->ndirections       = src->ndirections;
   dest->width             = src->width;
   dest->height            = src->height;
   dest->acceleration      = src->acceleration;
   dest->speed             = src->speed;
   dest->terminal_velocity = src->terminal_velocity;
   dest->conf              = src->conf;
   dest->loop              = src->loop;
   dest->master            = src->master;
   return;
}

/* Append the theme named "name" to the theme structure "theme". To
 * initialise the theme, set theme->ngenera to 0 before calling this
 * function. On error, a static error message is returned, on success
 * NULL (i.e. 0) is returned. */
static char *xpenguins_append_theme(char *name, XPenguinsTheme *ptheme)
{
   FILE *config;
   char *file_name = xpenguins_theme_directory(name);
   char *file_base, *xpm_file_name, *tmp_name;
   char word[MAX_STRING_LENGTH];
   static char *out_of_memory;
   ToonData def; /* The default */
   ToonData duf; /* If toon type is unknown... */
   ToonData *current = &def;
   int i, j, word_length, tmp_int;
   int max_file_name_length         = 0;
   int genus                        = ptheme->ngenera;
   int first_genus                  = ptheme->ngenera;
   char started                     = 0;

   P("xpenguins_append_theme: name: %s\n",name);
   out_of_memory = _("Out of memory");

   /* Find theme */
   if (!file_name) {
      return _("Theme not found or config file not present");
   }

   /* Open config file */
   max_file_name_length = strlen(file_name) + 1 + MAX_STRING_LENGTH;
   tmp_name = (char *)realloc(file_name, max_file_name_length*sizeof(char));
   if (!tmp_name) {
      free(file_name);
      return out_of_memory;
   }
   else {
      file_name = tmp_name;
   }


   file_base = file_name + strlen(file_name);
   *file_base = '/';
   ++file_base;
   snprintf(file_base, MAX_STRING_LENGTH, "config");
   config = fopen(file_name, "r");
   if (!config) {
      free(file_name);
      return _("Unreadable config file");
   }

   /* Allocate space for theme if this is the first one loaded */
   if (genus == 0) {
      if (xpenguins_theme_init(ptheme)) {
	 return out_of_memory;
      }
   }
   else {
      if (xpenguins_theme_grow(ptheme)) {
	 xpenguins_free_theme(ptheme);
	 return out_of_memory;
      }
   }

   /* Set default values */
   def.nframes           = 1;
   def.ndirections       = 1;
   def.width             = PENGUIN_DEFAULTWIDTH;
   def.height            = PENGUIN_DEFAULTHEIGHT;
   def.acceleration      = 0;
   def.speed             = 4;
   def.terminal_velocity = 0;
   def.conf              = TOON_DEFAULTS;
   def.loop              = 0;
   def.master            = NULL;

   /* Read config file */
#define NEXT_WORD() (word_length = \
      xpenguins_read_word(config, MAX_STRING_LENGTH, word))
#define WORD_IS(string) (strcasecmp(word, string) == 0)
#define CURRENT_TYPE(type) started = 1; xpenguins_copy_properties(&def, \
      current = ptheme->data[genus] + type)
#define GET_INT(dest) if (NEXT_WORD() > 0) { dest = atoi(word); }
#define GET_UINT(dest) \
   if (NEXT_WORD() > 0 && (tmp_int = atoi(word)) >= 0) { dest = tmp_int; }

#define WARNING if(xpenguins_verbose) fprintf

   while(NEXT_WORD() != 0) {
      if (word_length < 0) {
	 WARNING(stderr, _("Warning: %s: \"%s...\" ignored: longer than %d characters\n"),
	       file_name, word, MAX_STRING_LENGTH);
	 continue;
      }
      /* Define a new genus of toon */
      else if (WORD_IS("toon")) {
	 if (NEXT_WORD() > 0) {
	    if (started) {
	       if (xpenguins_theme_grow(ptheme)) {
		  xpenguins_free_theme(ptheme);
		  return out_of_memory;
	       }
	       ++genus;
	    }
	    else {
	       started = 1;
	    }
	    if (!(ptheme->name[genus] = strdup(word))) {
	       xpenguins_free_theme(ptheme);
	       return out_of_memory;
	    }
	 }
	 else if (word_length < 0) {
	    WARNING(stderr, _("Warning: %s: \"%s...\" ignored: longer than %d characters\n"),
		  file_name, word, MAX_STRING_LENGTH);
	 }
	 else {
	    WARNING(stderr, _("Warning: theme config file ended unexpectedly\n"));
	 }
      }
      /* Set preferred frame delay in milliseconds */
      else if (WORD_IS("delay")) { GET_UINT(ptheme->delay); }
      /* Various types of toon */
      else if (WORD_IS("define")) { 
	 if (NEXT_WORD() > 0) {
	    /* Start defining the default properties of the current genus */
	    if (WORD_IS("default")) { current = &def; }
	    /* Now the other types of toon */
	    else if (WORD_IS("walker"))  { CURRENT_TYPE(PENGUIN_WALKER);  }
	    else if (WORD_IS("faller"))  { CURRENT_TYPE(PENGUIN_FALLER);  }
	    else if (WORD_IS("tumbler")) { CURRENT_TYPE(PENGUIN_TUMBLER); }
	    else if (WORD_IS("floater")) { CURRENT_TYPE(PENGUIN_FLOATER); }
	    else if (WORD_IS("climber")) { CURRENT_TYPE(PENGUIN_CLIMBER); }
	    else if (WORD_IS("runner"))  { CURRENT_TYPE(PENGUIN_RUNNER);  }
	    else if (WORD_IS("action0")) { CURRENT_TYPE(PENGUIN_ACTION0); }
	    else if (WORD_IS("action1")) { CURRENT_TYPE(PENGUIN_ACTION1); }
	    else if (WORD_IS("action2")) { CURRENT_TYPE(PENGUIN_ACTION2); }
	    else if (WORD_IS("action3")) { CURRENT_TYPE(PENGUIN_ACTION3); }
	    else if (WORD_IS("action4")) { CURRENT_TYPE(PENGUIN_ACTION4); }
	    else if (WORD_IS("action5")) { CURRENT_TYPE(PENGUIN_ACTION5); }
	    else if (WORD_IS("exit"))  {
	       CURRENT_TYPE(PENGUIN_EXIT);
	       current->conf |= (TOON_NOCYCLE | TOON_INVULNERABLE);
	    }
	    else if (WORD_IS("explosion")) {
	       CURRENT_TYPE(PENGUIN_EXPLOSION);
	       current->conf |= (TOON_NOCYCLE | TOON_INVULNERABLE | TOON_NOBLOCK);
	    }
	    else if (WORD_IS("splatted")) {
	       CURRENT_TYPE(PENGUIN_SPLATTED);
	       current->conf |= (TOON_NOCYCLE | TOON_INVULNERABLE);
	    }
	    else if (WORD_IS("squashed")) {
	       CURRENT_TYPE(PENGUIN_SQUASHED);
	       current->conf |= (TOON_NOCYCLE | TOON_INVULNERABLE | TOON_NOBLOCK);
	    }
	    else if (WORD_IS("zapped")) {
	       CURRENT_TYPE(PENGUIN_ZAPPED);
	       current->conf |= (TOON_NOCYCLE | TOON_INVULNERABLE | TOON_NOBLOCK);
	    }
	    else if (WORD_IS("angel")) {
	       CURRENT_TYPE(PENGUIN_ANGEL);
	       current->conf |= (TOON_INVULNERABLE | TOON_NOBLOCK);
	    }
	    else /* Unknown type! */ {
	       WARNING(stderr, _("Warning: unknown type \"%s\": ignoring\n"), word);
	       current = &duf;
	    }
	 }
	 else {
	    WARNING(stderr, _("Warning: theme config file ended unexpectedly\n"));
	 }
      }
      /* Toon properties */
      else if (WORD_IS("width"))             { GET_UINT(current->width); }
      else if (WORD_IS("height"))            { GET_UINT(current->height); }
      else if (WORD_IS("frames"))            { GET_UINT(current->nframes); }
      else if (WORD_IS("directions"))        { GET_UINT(current->ndirections); }
      else if (WORD_IS("speed"))             { GET_UINT(current->speed); }
      else if (WORD_IS("acceleration"))      { GET_UINT(current->acceleration); }
      else if (WORD_IS("terminal_velocity")) { GET_UINT(current->terminal_velocity); }
      else if (WORD_IS("loop"))              { GET_INT (current->loop); }
      else if (WORD_IS("pixmap")) { 
	 if (NEXT_WORD() > 0) {
	    if (current == &def) {
	       WARNING(stderr, _("Warning: theme config file may not specify a default pixmap\n"));
	    }
	    else if (current == &duf) {
	       /* Don't want to allocate any memory for an unused type */
	       continue;
	    }
	    else {
	       int status;
	       int igenus, itype; /* For scanning for duplicated pixmaps */
	       int new_pixmap = 1;
	       if (word[0] == '/') {
		  xpm_file_name = word;
	       }
	       else {
		  snprintf(file_base, MAX_STRING_LENGTH, "%s", word);
		  xpm_file_name = file_name;
	       }
	       if (current->image) {
		  /* Pixmap is already defined! */
		  WARNING(stderr, _("Warning: resetting pixmap to %s\n"), word);
		  if (!current->master) {
		     /* Free old pixmap if it is not a copy */
		     XpmFree(current->image);
		     current->image = NULL;
		     if (current->filename) {
			free(current->filename);
		     }
		     current->exists = 0;
		  }
	       }

	       /* Check if the file has been used before, but only look in
		  the pixmaps for the current theme... */
	       for (igenus = first_genus; igenus <= genus && new_pixmap; ++igenus) {
		  ToonData *data = ptheme->data[igenus];
		  for (itype = 0; itype < PENGUIN_NTYPES && new_pixmap; ++itype) {
		     if (data[itype].filename && !data[itype].master
			   && data[itype].exists
			   && strcmp(xpm_file_name, data[itype].filename) == 0) {
			current->master = data + itype;
			current->exists = 1;
			current->filename = data[itype].filename;
			current->image = data[itype].image;
			new_pixmap = 0;
		     }
		  }
	       }

	       if (new_pixmap) {
		  status = XpmReadFileToData(xpm_file_name, &(current->image));
		  switch (status) {
		     case XpmSuccess:
			current->exists = 1;
			current->filename = strdup(xpm_file_name);
			current->master = NULL;
			break;
		     case XpmNoMemory:
			fclose(config);
			free(file_name);
			xpenguins_free_theme(ptheme);
			return out_of_memory;
			break;
		     case XpmOpenFailed:
			WARNING(stderr, _("Warning: could not read %s\n"), xpm_file_name);
			break;
		     case XpmFileInvalid:
			WARNING(stderr, _("Warning: %s is not a valid xpm file\n"), xpm_file_name);
			break;
		  }
	       }
	    }	      
	 }
	 else if (word_length < 0) {
	    WARNING(stderr, _("Warning: %s: \"%s...\" ignored: longer than %d characters\n"),
		  file_name, word, MAX_STRING_LENGTH);
	 }
	 else {
	    WARNING(stderr, _("Warning: theme config file ended unexpectedly\n"));
	 }
      }
      else if (WORD_IS("number")) {
	 GET_UINT(ptheme->number[genus]); 
      }
      else {
	 WARNING(stderr, _("Warning: %s ignored in config file\n"), word);
      }
   }
   fclose(config);
   free(file_name);

   ptheme->ngenera = genus+1;

   /* Now validate our widths, heights etc. with the size of the image */
   for (i = first_genus; i < ptheme->ngenera; ++i) {
      for (j = 0; j < PENGUIN_NTYPES; ++j) {
	 current = ptheme->data[i] + j;
	 if (current->exists) {
	    int imwidth, imheight;
	    int values;
	    values = sscanf(current->image[0], "%d %d", &imwidth, &imheight);
	    if (values < 2) {
	       WARNING(stderr, _("Error reading width and height of %s\n"),
		     current->filename);
	       xpenguins_free_theme(ptheme);
	       return _("Invalid xpm data");
	    }
	    if (imwidth < current->width * current->nframes) {
	       if ((current->nframes = imwidth / current->width) < 1) {
		  xpenguins_free_theme(ptheme);
		  return _("Width of xpm image too small for even a single frame");
	       }
	       else {
		  WARNING(stderr, _("Warning: width of %s is too small to display all the frames\n"),
			current->filename);
	       }
	    }
	    if (imheight < current->height * current->ndirections) {
	       if ((current->ndirections = imheight / current->height) < 1) {
		  xpenguins_free_theme(ptheme);
		  return _("Height of xpm image too small for even a single frame");
	       }
	       else {
		  WARNING(stderr, _("Warning: height of %s is too small to display all the frames\n"),
			current->filename);
	       }
	    }
	 }
      }
      if ((!ptheme->data[i][PENGUIN_WALKER].exists)
	    || (!ptheme->data[i][PENGUIN_FALLER].exists)) {
	 xpenguins_free_theme(ptheme);
	 return _("Theme must contain at least walkers and fallers");
      }
   }

   /* Work out the total number */
   ptheme->total = 0;
   for (i = first_genus; i < ptheme->ngenera; ++i) {
      ptheme->total += ptheme->number[i];
   }
   if (ptheme->total > PENGUIN_MAX) {
      ptheme->total = PENGUIN_MAX;
   }

   penguin_data    = ptheme->data;
   penguin_ngenera = ptheme->ngenera;
   penguin_numbers = ptheme->number;
   return NULL;
}


/* Load the themes in the string array (where the array is NULL
 * terminated, as well as all the strings that are pointed to) */
char *xpenguins_load_themes(char ***names, XPenguinsTheme *ptheme)
{
   ptheme->ngenera = 0;
   char **newnames = NULL;
   int n           = 0;

   P("*names: %p\n",(void*)*names); //xpenguins_print_list(*names);
   char **t;
   for(t = *names; *t; t++) 
   {
      P("xpenguins_load_themes %p %s\n",(void*)t,*t);
      char *error;
      error = xpenguins_append_theme(*t, ptheme);
      if (error) {
	 printf("Theme %s: %s\n",*t,error);
      }
      else
      {
	 n++;
	 newnames = (char**)realloc(newnames,(n+1)*sizeof(char*));
	 newnames[n-1] = strdup(*t);
	 newnames[n]   = NULL;
      }
   }
   P("%d\n",n);
   P("newnames\n"); //xpenguins_print_list(newnames);
   xpenguins_free_list(*names);
   *names = newnames;
   if (n == 0)
      return(_("No valid theme found\n"));
   return NULL;
}


/* Free all the data associated with a theme */
void xpenguins_free_theme(XPenguinsTheme *ptheme)
{
   int i, j;
   for (i = 0; i < ptheme->ngenera; ++i) {
      for (j = 0; j < PENGUIN_NTYPES; ++j) {
	 ToonData *data = ptheme->data[i] + j;
	 if (data->exists && data->image && !data->master) {
	    XpmFree(data->image);
	    data->exists = 0;
	    data->image = NULL;
	    if (data->filename) {
	       free(data->filename);
	       data->filename = NULL;
	    }
	 }
      }
      if (ptheme->name[i]) {
	 free(ptheme->name[i]);
	 ptheme->name[i] = NULL;
      }
      free(ptheme->data[i]);
   }
   free(ptheme->data);
   free(ptheme->name);
   free(ptheme->number);
   ptheme->_nallocated = 0;
   ptheme->ngenera = 0;
   return;
}

/* Print basic theme information to standard error - for debugging
 * purposes */
void xpenguins_describe_theme(XPenguinsTheme *ptheme)
{
   int i, j;
   if (!ptheme) {
      fprintf(stderr, _("No theme!\n"));
      return;
   }
   for (i = 0; i < ptheme->ngenera; ++i) {
      if (ptheme->name[i]) {
	 fprintf(stdout, "%s\n", ptheme->name[i]);
      }
      for (j = 0; j < PENGUIN_NTYPES; ++j) {
	 ToonData *data = ptheme->data[i] + j;
	 if (data->exists) {
	    fprintf(stderr, _("  Toon %d: %s %ux%u %ux%u\n"), i, data->filename,
		  data->nframes, data->ndirections, data->width, data->height);
	 }
      }
   }
}

#define ADD_STRING(index) \
   if (string_length) { \
      if (!(tmp_string = (char *)realloc(list, (list_length + string_length + 1)*sizeof(char)))) { \
	 xpenguins_out_of_memory(); \
	 free(file_name); free(theme_dir); free(list); \
	 return NULL; \
      } \
      else { list = tmp_string; \
	 offsets[index] = list_length; \
	 mystrncpy(list + offsets[index], string, string_length + 1); \
	 list_length += (string_length + 1); \
      }; \
   }

/* Read the "about" file in a theme directory and return a string list
 * of theme properties. This list should be freed with
 * xpenguins_free_list(). */
char **xpenguins_theme_info(char *name)
{
   char word[MAX_STRING_LENGTH];
   char string[MAX_STRING_LENGTH];
   char **info;
   char *list;
   char *theme_dir = xpenguins_theme_directory(name);
   char *file_name;
   char *about = _("/about");
   FILE *about_file;

   int i, file_name_length;
   int word_length, string_length;
   int about_length = strlen(about);
   int theme_dir_length;
   int list_length = 0;
   int offsets[PENGUIN_NABOUTFIELDS];

   /* Determine "about" filename string */
   if (!theme_dir) {
      return NULL;
   }
   theme_dir_length = strlen(theme_dir);
   file_name_length = theme_dir_length + about_length + 1;
   file_name = (char *)malloc(file_name_length*sizeof(char));
   if (!file_name) {
      xpenguins_out_of_memory();
      free(file_name);
      free(theme_dir);
      return NULL;
   }
   snprintf(file_name, file_name_length, "%s%s", theme_dir, about);

   for (i = 0; i < PENGUIN_NABOUTFIELDS; ++i) {
      offsets[i] = 0;
   }

   /* Open "about" file */
   about_file = fopen(file_name, "r");
   if (!about_file) {
      fprintf(stderr, _("Could not read %s\n"), file_name);
      free(file_name);
      free(theme_dir);
      return NULL;
   }

   list = (char *)malloc(1);
   if (!list) {
      xpenguins_out_of_memory();
      free(file_name);
      free(theme_dir);
      return NULL;
   }
   list_length = 1;
   *list = '\0';

   while ((word_length = xpenguins_read_word(about_file,
	       MAX_STRING_LENGTH,
	       word))) {
      char *tmp_string;
      string_length = xpenguins_read_line(about_file,
	    MAX_STRING_LENGTH,
	    string);

      if (WORD_IS("artist") && !offsets[PENGUIN_ARTIST]) {
	 ADD_STRING(PENGUIN_ARTIST);
      }
      else if (WORD_IS("maintainer") && !offsets[PENGUIN_MAINTAINER]) {
	 ADD_STRING(PENGUIN_MAINTAINER);
      }
      else if (WORD_IS("date") && !offsets[PENGUIN_DATE]) {
	 ADD_STRING(PENGUIN_DATE);
      }
      else if (WORD_IS("copyright") && !offsets[PENGUIN_COPYRIGHT]) {
	 ADD_STRING(PENGUIN_COPYRIGHT);
      }
      else if (WORD_IS("license") && !offsets[PENGUIN_LICENSE]) {
	 ADD_STRING(PENGUIN_LICENSE);
      }
      else if (WORD_IS("comment") && !offsets[PENGUIN_COMMENT]) {
	 ADD_STRING(PENGUIN_COMMENT);
      }
      else if (WORD_IS("icon") && !offsets[PENGUIN_ICON]) {
	 if (string_length && string_length < MAX_STRING_LENGTH-1) {
	    if (string[0] == '/') {
	       ADD_STRING(PENGUIN_ICON);
	    }
	    else if (!(tmp_string = (char *)realloc(list, (list_length + theme_dir_length + string_length + 2)*sizeof(char) ))) {
	       xpenguins_out_of_memory();
	       free(file_name);
	       free(theme_dir);
	       free(list);
	       return NULL;
	    } else {
	       list = tmp_string;
	       offsets[PENGUIN_ICON] = list_length;
	       snprintf(list + offsets[PENGUIN_ICON], theme_dir_length + string_length + 2,
		     "%s/%s", theme_dir, string);
	       list_length += (theme_dir_length + string_length + 2);
	    }
	 }
      }
   }

   /* Allocate array of strings */
   info = (char **)malloc(PENGUIN_NABOUTFIELDS * sizeof(char *));
   if (!info) {
      xpenguins_out_of_memory();
      free(file_name);
      free(theme_dir);
      return NULL;
   }

   info[0] = list;
   for (i = 1; i < PENGUIN_NABOUTFIELDS; ++i) {
      info[i] = list + offsets[i];
   }
   free(file_name);
   free(theme_dir);
   return info;
}
