/* XRACER (C) 1999-2000 Richard W.M. Jones <rich@annexia.org> and other AUTHORS
 *
 * 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.
 *
 * $Id: craft.c,v 1.4 2000/01/07 08:44:41 rich Exp $
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#if HAVE_DLFCN_H
#include <dlfcn.h>
#endif

#include "xracer.h"
#include "xracer-log.h"
#include "xracer-craft.h"

/* The internal list of craft. There is one list for each level. */
struct xrCraft *craft_list[4];

static void load_all_craft (void);

void
xrCraftInit ()
{
  int level;

  /* Load all craft available. */
  load_all_craft ();

  /* Check that at least one craft was loaded. */
  for (level = 0; level < 4; ++level)
    if (craft_list[level] != 0)
      break;

  if (level == 4) xrLogFatal ("no craft could be loaded");
}

/* This function looks in the craft directory for files matching
 * the pattern ``craft/libcraft*.so'' and loads each one (if possible).
 */
static void
load_all_craft ()
{
#if defined(HAVE_OPENDIR) && defined(HAVE_READDIR) && defined(HAVE_CLOSEDIR)
  DIR *d;
  struct dirent *ent;

  /* Get the list of craft files. */
  d = opendir ("craft");
  if (d == NULL) { xrLogPerror ("open directory: craft"); exit (1); }

  while ((ent = readdir (d)) != NULL)
    {
      const char *name = ent->d_name;
      int len = strlen (name);

      /* xrLog (LOG_DEBUG, "name = %s (len = %d)", name, len); */

      if (strncmp (name, "libcraft", 8) == 0 &&
	  strcmp (name + len - 3, ".so") == 0)
	{
	  char shortname[1024];

	  strncpy (shortname, name + 8, len - 11);
	  shortname[len-11] = '\0';

	  /* Try loading it. Don't worry about the return value. */
	  xrCraftLoadByName (shortname);
	}
    }

  closedir (d);
#else
#error "require working opendir(3)/readdir(3)/closedir(3) API"
#endif
}

/* Load a craft by name. */
int
xrCraftLoadByName (const char *name)
{
#if HAVE_DL_API

  char filename[1024];
  void *lib;
  void *craft_struct;
  struct xrCraft *craft;

  /* Try to construct the name of the shared library containing this craft. */
  snprintf (filename, sizeof filename, "craft/libcraft%s.so", name);

  /* See if we can open this track. Make sure we resolve all link-time
   * errors now, and make sure that symbols from the track don't pollute
   * the namespace.
   */
  lib = dlopen (filename, RTLD_NOW);

  if (lib == NULL)
    {
      xrLog (LOG_ERROR, "dlopen: %s: %s", filename, dlerror ());
      return -1;
    }

  /* The shared library contains one symbol of interest: ``craft'' */
  craft_struct = dlsym (lib, "craft");

  /* This is OK. This symbol should never actually be NULL. */
  if (craft_struct == NULL)
    {
      xrLog (LOG_ERROR, "dlsym: craft: %s", dlerror ());
      dlclose (lib);
      return -1;
    }

  craft = (struct xrCraft *) craft_struct;
  craft->lib = lib;

  xrLog (LOG_DEBUG, "loaded craft: %s %s (level %d)",
	 craft->name, craft->model, craft->level);

  xrLogAssert (0 <= craft->level && craft->level <= 3);

  /* Push it on to the right list. */
  craft->next = craft_list[craft->level];
  craft_list[craft->level] = craft;

  return 0;
#else
#error "require working dlopen(3)/dlsym(3)/dlclose(3) API"
#endif
}

/* Get the default craft. */
const struct xrCraft *
xrCraftGetDefault ()
{
  int level;

  for (level = 0; level < 4; ++level)
    if (craft_list[level] != 0)
      return craft_list[level];

  xrLogFatal ("no craft found");
}

/* Get the next craft in sequence. */
const struct xrCraft *
xrCraftGetNext (const struct xrCraft *craft)
{
  int level;

  if (craft->next) return craft->next;

  level = craft->level;

  for (;;)
    {
      level++;
      if (level >= 4) level = 0;
      if (craft_list[level] != 0)
	return craft_list[level];
    }
}
