/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>

#include "appenv.h"
#include "errors.h"
#include "fileops.h"
#include "general.h"
#include "gimprc.h"
#include "menus.h"
#include "plug_in.h"


#define ERROR  0
#define DONE   1
#define OK     2


typedef struct _ParseFunc ParseFunc;

struct _ParseFunc
{
  char *name;
  int (*func) (void);
};

typedef struct _UnknownToken UnknownToken;

struct _UnknownToken
{
  char *token;
  char *value;
};

/*  global gimprc variables  */
char *    plug_in_path = NULL;
char *    temp_path = NULL;
char *    swap_path = NULL;
char *    brush_path = NULL;
char *    default_brush = NULL;
char *    pattern_path = NULL;
char *    default_pattern = NULL;
char *    palette_path = NULL;
char *    default_palette = NULL;
char *    gradient_path = NULL;
char *    default_gradient = NULL;
int       tile_cache_size = 4194304;  /* 4 MB */
int       marching_speed = 150;   /* 150 ms */
double    gamma_val = 1.0;
int       transparency_type = 1;  /* Mid-Tone Checks */
int       transparency_size = 1;  /* Medium sized */
int       levels_of_undo = 1;     /* 1 level of undo default */
int       color_cube_shades[4] = {6, 7, 4, 24};
int       install_cmap = 0;
int       cycled_marching_ants = 0;
int       default_threshold = 15;
int       stingy_memory_use = 0;
int       allow_resize_windows = 0;
int       no_cursor_updating = 0;
int       preview_size = 64;
int       show_rulers = TRUE;
int       ruler_units = GTK_PIXELS;
int       auto_save = TRUE;
int       cubic_interpolation = FALSE;
int       toolbox_x = 0, toolbox_y = 0;
int       progress_x = 170, progress_y = 5;
int       info_x = 165, info_y = 0;
int       color_select_x = 140, color_select_y = 120;
int       tool_options_x = 0, tool_options_y = 345;


static int get_next_token (void);
static int peek_next_token (void);
static int parse_statement (void);
static int parse_position (int *x, int *y);

static int parse_temp_path (void);
static int parse_swap_path (void);
static int parse_brush_path (void);
static int parse_pattern_path (void);
static int parse_plug_in_path (void);
static int parse_palette_path (void);
static int parse_gradient_path (void);
static int parse_path (char **path);
static int parse_default_brush (void);
static int parse_default_pattern (void);
static int parse_default_palette (void);
static int parse_default_gradient (void);
static int parse_gamma_correction (void);
static int parse_color_cube (void);
static int parse_tile_cache_size (void);
static int parse_marching_ants_speed (void);
static int parse_undo_levels (void);
static int parse_transparency_type (void);
static int parse_transparency_size (void);
static int parse_install_colormap (void);
static int parse_colormap_cycling (void);
static int parse_default_threshold (void);
static int parse_stingy_memory_use (void);
static int parse_allow_resize_windows (void);
static int parse_no_cursor_updating (void);
static int parse_preview_size (void);
static int parse_dont_show_rulers (void);
static int parse_ruler_units (void);
static int parse_dont_auto_save (void);
static int parse_cubic_interpolation (void);
static int parse_toolbox_position (void);
static int parse_progress_position (void);
static int parse_info_position (void);
static int parse_color_select_position (void);
static int parse_tool_options_position (void);
static int parse_plug_in (void);
static int parse_plug_in_def (void);
static int parse_proc_def (PlugInProcDef **proc_def);
static int parse_proc_arg (ProcArg *arg);
static int parse_menu_path (void);
static int parse_unknown (char *token_sym);

static char* transform_path (char *path, int destroy);

static char* gimprc_find_token (char *token);
static void gimprc_set_token (char *token, char *value);
static Argument * gimprc_query (Argument *args);

static void add_gimp_directory_token (char *gimp_dir);

static ParseInfo parse_info;

static GList *unknown_tokens = NULL;

static int cur_token;
static int next_token;
static int done;

#define MAX_GIMPDIR_LEN 500

char *
gimp_directory ()
{
  static char gimp_dir[MAX_GIMPDIR_LEN + 1] = "";
  char *env_gimp_dir;
  char *env_home_dir;
  size_t len_env_home_dir;

  if ('\000' != gimp_dir[0])
    return gimp_dir;

  env_gimp_dir = getenv ("GIMP_DIRECTORY");
  env_home_dir = getenv ("HOME");
  len_env_home_dir = strlen (env_home_dir);

  if (NULL != env_gimp_dir)
    {
      if ('/' == env_gimp_dir[0])
	  strncpy (gimp_dir, env_gimp_dir, MAX_GIMPDIR_LEN);
      else
	{
	  if (NULL != env_home_dir)
	    {
	      strncpy (gimp_dir, env_home_dir, MAX_GIMPDIR_LEN);
	      gimp_dir[len_env_home_dir] = '/';
	    }
	  else
	    g_warning ("No home directory.");

	  strncpy (&gimp_dir[len_env_home_dir+1],
		   env_gimp_dir,
		   MAX_GIMPDIR_LEN - len_env_home_dir - 1);
	}
    }
  else
    {
      if (NULL != env_home_dir)
	{
	  strncpy (gimp_dir, env_home_dir, MAX_GIMPDIR_LEN);
	  gimp_dir[len_env_home_dir] = '/';
	}
      else
	g_warning ("No home directory.");

      strncpy (&gimp_dir[len_env_home_dir+1],
	       GIMPDIR,
	       MAX_GIMPDIR_LEN - len_env_home_dir - 1);
    }

  gimp_dir[MAX_GIMPDIR_LEN] = '\000';
  return gimp_dir;
}

void
parse_gimprc ()
{
  char libfilename[512];
  char filename[512];
  char *gimp_dir;

  parse_info.buffer = g_new (char, 4096);
  parse_info.tokenbuf = parse_info.buffer + 2048;
  parse_info.buffer_size = 2048;
  parse_info.tokenbuf_size = 2048;

  gimp_dir = gimp_directory ();
  add_gimp_directory_token (gimp_dir);

  sprintf (libfilename, "%s/gimprc", DATADIR);
  parse_gimprc_file (libfilename);

  sprintf (filename, "%s/gimprc", gimp_dir);
  if (strcmp (filename, libfilename) != 0)
    parse_gimprc_file (filename);
}

void
parse_gimprc_file (char *filename)
{
  static char *home_dir = NULL;
  int status;
  char rfilename[512];

  if (filename[0] != '/')
    {
      if (!home_dir)
	home_dir = g_strdup (getenv ("HOME"));
      sprintf (rfilename, "%s/%s", home_dir, filename);
      filename = rfilename;
    }

  parse_info.fp = fopen (filename, "rt");
  if (!parse_info.fp)
    return;

  g_print ("parsing \"%s\"\n", filename);

  cur_token = -1;
  next_token = -1;

  parse_info.position = -1;
  parse_info.linenum = 1;
  parse_info.charnum = 1;
  parse_info.inc_linenum = FALSE;
  parse_info.inc_charnum = FALSE;

  done = FALSE;
  while ((status = parse_statement ()) == OK)
    ;

  fclose (parse_info.fp);

  if (status == ERROR)
    {
      g_print ("error parsing: \"%s\"\n", filename);
      g_print ("  at line %d column %d\n", parse_info.linenum, parse_info.charnum);
      g_print ("  unexpected token: %s\n", token_sym);
    }
}

static int
parse_position (int *x, int *y)
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    return ERROR;
  token = get_next_token ();

  *x = token_num;

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    return ERROR;
  token = get_next_token ();

  *y = token_num;

  return OK;
}

static int
get_next_token ()
{
  if (next_token != -1)
    {
      cur_token = next_token;
      next_token = -1;
    }
  else
    {
      cur_token = get_token (&parse_info);
    }

  return cur_token;
}

static int
peek_next_token ()
{
  if (next_token == -1)
    next_token = get_token (&parse_info);

  return next_token;
}

static int
parse_statement ()
{
  static ParseFunc funcs[] =
  {
    { "temp-path", parse_temp_path },
    { "swap-path", parse_swap_path },
    { "brush-path", parse_brush_path },
    { "pattern-path", parse_pattern_path },
    { "plug-in-path", parse_plug_in_path },
    { "palette-path", parse_palette_path },
    { "gradient-path", parse_gradient_path },
    { "default-brush", parse_default_brush },
    { "default-pattern", parse_default_pattern },
    { "default-palette", parse_default_palette },
    { "default-gradient", parse_default_gradient },
    { "gamma-correction", parse_gamma_correction },
    { "color-cube", parse_color_cube },
    { "tile-cache-size", parse_tile_cache_size },
    { "marching-ants-speed", parse_marching_ants_speed },
    { "undo-levels", parse_undo_levels },
    { "transparency-type", parse_transparency_type },
    { "transparency-size", parse_transparency_size },
    { "install-colormap", parse_install_colormap },
    { "colormap-cycling", parse_colormap_cycling },
    { "default-threshold", parse_default_threshold },
    { "stingy-memory-use", parse_stingy_memory_use },
    { "allow-resize-windows", parse_allow_resize_windows },
    { "no-cursor-updating", parse_no_cursor_updating },
    { "preview-size", parse_preview_size },
    { "dont-show-rulers", parse_dont_show_rulers },
    { "ruler-units", parse_ruler_units },
    { "done-auto-save", parse_dont_auto_save },
    { "cubic-interpolation", parse_cubic_interpolation },
    { "toolbox-position", parse_toolbox_position },
    { "progress-position", parse_progress_position },
    { "info-position", parse_info_position },
    { "color-select-position", parse_color_select_position },
    { "tool-options-position", parse_tool_options_position },
    { "plug-in", parse_plug_in },
    { "plug-in-def", parse_plug_in_def },
    { "menu-path", parse_menu_path },
  };
  static int nfuncs = sizeof (funcs) / sizeof (funcs[0]);
  int token;
  int i;

  token = peek_next_token ();
  if (!token)
    return DONE;
  if (token != TOKEN_LEFT_PAREN)
    return ERROR;
  token = get_next_token ();

  token = peek_next_token ();
  if (!token || (token != TOKEN_SYMBOL))
    return ERROR;
  token = get_next_token ();

  for (i = 0; i < nfuncs; i++)
    if (strcmp (funcs[i].name, token_sym) == 0)
      return (* funcs[i].func) ();

  return parse_unknown (token_sym);
}

static int
parse_temp_path ()
{
  return parse_path (&temp_path);
}

static int
parse_swap_path ()
{
  return parse_path (&swap_path);
}

static int
parse_brush_path ()
{
  return parse_path (&brush_path);
}

static int
parse_pattern_path ()
{
  return parse_path (&pattern_path);
}

static int
parse_plug_in_path ()
{
  return parse_path (&plug_in_path);
}

static int
parse_palette_path ()
{
  return parse_path (&palette_path);
}

static int
parse_gradient_path ()
{
  return parse_path (&gradient_path);
}

static int
parse_path (char **path)
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    return ERROR;
  token = get_next_token ();

  if (*path)
    g_free (*path);
  *path = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    {
      g_free (*path);
      *path = NULL;
      return ERROR;
    }
  token = get_next_token ();

  *path = transform_path (*path, TRUE);

  return OK;
}

static int
parse_default_brush ()
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    return ERROR;
  token = get_next_token ();

  if (default_brush)
    g_free (default_brush);
  default_brush = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    {
      g_free (default_brush);
      default_brush = NULL;
      return ERROR;
    }
  token = get_next_token ();

  return OK;
}

static int
parse_default_pattern ()
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    return ERROR;
  token = get_next_token ();

  if (default_pattern)
    g_free (default_pattern);
  default_pattern = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    {
      g_free (default_pattern);
      default_pattern = NULL;
      return ERROR;
    }
  token = get_next_token ();

  return OK;
}

static int
parse_default_palette ()
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    return ERROR;
  token = get_next_token ();

  if (default_palette)
    g_free (default_palette);
  default_palette = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    {
      g_free (default_palette);
      default_palette = NULL;
      return ERROR;
    }
  token = get_next_token ();

  return OK;
}

static int
parse_default_gradient ()
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    return ERROR;
  token = get_next_token ();

  if (default_gradient)
    g_free (default_gradient);
  default_gradient = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    {
      g_free (default_gradient);
      default_gradient = NULL;
      return ERROR;
    }
  token = get_next_token ();

  return OK;
}

static int
parse_gamma_correction ()
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    return ERROR;
  token = get_next_token ();

  gamma_val = token_num;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_color_cube ()
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    return ERROR;
  token = get_next_token ();

  color_cube_shades[0] = token_num;

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    return ERROR;
  token = get_next_token ();

  color_cube_shades[1] = token_num;

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    return ERROR;
  token = get_next_token ();

  color_cube_shades[2] = token_num;

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    return ERROR;
  token = get_next_token ();

  color_cube_shades[3] = token_num;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_tile_cache_size ()
{
  int mult;
  int suffix;
  int token;

  token = peek_next_token ();
  if (!token || ((token != TOKEN_NUMBER) &&
		 (token != TOKEN_SYMBOL)))
    return ERROR;
  token = get_next_token ();

  if (token == TOKEN_NUMBER)
    {
      tile_cache_size = token_num * 1024;
    }
  else
    {
      tile_cache_size = atoi (token_sym);

      suffix = token_sym[strlen (token_sym) - 1];
      if ((suffix == 'm') || (suffix == 'M'))
	mult = 1024 * 1024;
      else if ((suffix == 'k') || (suffix == 'K'))
	mult = 1024;
      else if ((suffix == 'b') || (suffix == 'B'))
	mult = 1;
      else
	return FALSE;

      tile_cache_size *= mult;
    }

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_marching_ants_speed ()
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    return ERROR;
  token = get_next_token ();

  marching_speed = token_num;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_undo_levels ()
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    return ERROR;
  token = get_next_token ();

  levels_of_undo = token_num;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_transparency_type ()
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    return ERROR;
  token = get_next_token ();

  transparency_type = token_num;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_transparency_size ()
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    return ERROR;
  token = get_next_token ();

  transparency_size = token_num;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_install_colormap ()
{
  int token;

  install_cmap = TRUE;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_colormap_cycling ()
{
  int token;

  cycled_marching_ants = TRUE;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_default_threshold ()
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    return ERROR;
  token = get_next_token ();

  default_threshold = token_num;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_stingy_memory_use ()
{
  int token;

  stingy_memory_use = TRUE;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_allow_resize_windows ()
{
  int token;

  allow_resize_windows = TRUE;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_no_cursor_updating ()
{
  int token;

  no_cursor_updating = TRUE;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_preview_size ()
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_SYMBOL && token != TOKEN_NUMBER))
    return ERROR;
  token = get_next_token ();

  if (token == TOKEN_SYMBOL)
    {
      if (strcmp (token_sym, "none") == 0)
	preview_size = 0;
      else if (strcmp (token_sym, "small") == 0)
	preview_size = 32;
      else if (strcmp (token_sym, "medium") == 0)
	preview_size = 64;
      else if (strcmp (token_sym, "large") == 0)
	preview_size = 128;
      else
	preview_size = 0;
    }
  else if (token == TOKEN_NUMBER)
    preview_size = token_num;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_dont_show_rulers ()
{
  int token;

  show_rulers = FALSE;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_ruler_units ()
{
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_SYMBOL))
    return ERROR;
  token = get_next_token ();

  if (strcmp (token_sym, "pixels") == 0)
    ruler_units = GTK_PIXELS;
  else if (strcmp (token_sym, "inches") == 0)
    ruler_units = GTK_INCHES;
  else if (strcmp (token_sym, "centimeters") == 0)
    ruler_units = GTK_CENTIMETERS;
  else
    ruler_units = GTK_PIXELS;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_dont_auto_save ()
{
  int token;

  auto_save = FALSE;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_cubic_interpolation ()
{
  int token;

  cubic_interpolation = TRUE;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_toolbox_position ()
{
  int token;
  int val;

  val = parse_position (&toolbox_x, &toolbox_y);
  if (!val)
    return val;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_progress_position ()
{
  int token;
  int val;

  val = parse_position (&progress_x, &progress_y);
  if (!val)
    return val;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_info_position ()
{
  int token;
  int val;

  val = parse_position (&info_x, &info_y);
  if (!val)
    return val;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_color_select_position ()
{
  int token;
  int val;

  val = parse_position (&color_select_x, &color_select_y);
  if (!val)
    return val;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_tool_options_position ()
{
  int token;
  int val;

  val = parse_position (&tool_options_x, &tool_options_y);
  if (!val)
    return val;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    return ERROR;
  token = get_next_token ();

  return OK;
}

static int
parse_plug_in ()
{
  char *name;
  char *menu_path;
  char *accelerator;
  int token;

  name = NULL;
  menu_path = NULL;
  accelerator = NULL;

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    return ERROR;
  token = get_next_token ();

  name = g_strdup (token_str);

  token = peek_next_token ();
  if (token == TOKEN_STRING)
    {
      menu_path = g_strdup (token_str);
      token = get_next_token ();
    }

  token = peek_next_token ();
  if (token == TOKEN_STRING)
    {
      accelerator = g_strdup (token_str);
      token = get_next_token ();
    }

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    {
      g_free (name);
      g_free (menu_path);
      g_free (accelerator);
      return ERROR;
    }
  token = get_next_token ();

  if (name && menu_path)
    plug_in_add (name, menu_path, accelerator);

  return OK;
}

static int
parse_plug_in_def ()
{
  PlugInDef *plug_in_def;
  PlugInProcDef *proc_def;
  GSList *tmp_list;
  int token;

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    return ERROR;
  token = get_next_token ();

  plug_in_def = g_new (PlugInDef, 1);
  plug_in_def->prog = g_strdup (token_str);
  plug_in_def->proc_defs = NULL;
  plug_in_def->mtime = 0;
  plug_in_def->query = FALSE;

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    goto error;
  token = get_next_token ();

  plug_in_def->mtime = token_int;

  while (parse_proc_def (&proc_def))
    {
      proc_def->prog = g_strdup (plug_in_def->prog);
      plug_in_def->proc_defs = g_slist_append (plug_in_def->proc_defs, proc_def);
    }

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    goto error;
  token = get_next_token ();

  plug_in_def_add (plug_in_def);

  return OK;

error:
  g_warning ("error parsing pluginrc");
  tmp_list = plug_in_def->proc_defs;
  while (tmp_list)
    {
      g_free (tmp_list->data);
      tmp_list = tmp_list->next;
    }
  g_slist_free (plug_in_def->proc_defs);
  g_free (plug_in_def->prog);
  g_free (plug_in_def);

  return ERROR;
}

static int
parse_proc_def (PlugInProcDef **proc_def)
{
  PlugInProcDef *pd;
  int token;
  int i;

  token = peek_next_token ();
  if (!token || (token != TOKEN_LEFT_PAREN))
    return ERROR;
  token = get_next_token ();

  token = peek_next_token ();
  if (!token || (token != TOKEN_SYMBOL) ||
      (strcmp ("proc-def", token_sym) != 0))
    return ERROR;
  token = get_next_token ();

  pd = g_new0 (PlugInProcDef, 1);

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    goto error;
  token = get_next_token ();

  pd->db_info.name = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    goto error;
  token = get_next_token ();

  pd->db_info.proc_type = token_int;

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    goto error;
  token = get_next_token ();

  pd->db_info.blurb = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    goto error;
  token = get_next_token ();

  pd->db_info.help = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    goto error;
  token = get_next_token ();

  pd->db_info.author = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    goto error;
  token = get_next_token ();

  pd->db_info.copyright = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    goto error;
  token = get_next_token ();

  pd->db_info.date = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    goto error;
  token = get_next_token ();

  if (token_str[0] != '\0')
    pd->menu_path = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    goto error;
  token = get_next_token ();

  if (token_str[0] != '\0')
    pd->extensions = g_strdup (token_str);
  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    goto error;
  token = get_next_token ();
  if (token_str[0] != '\0')
    pd->prefixes = g_strdup (token_str);
  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    goto error;
  token = get_next_token ();
  if (token_str[0] != '\0')
    pd->magics = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    goto error;
  token = get_next_token ();

  if (token_str[0] != '\0')
    {
      pd->image_types = g_strdup (token_str);
      pd->image_types_val = plug_in_image_types_parse (token_str);
    }

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    goto error;
  token = get_next_token ();

  pd->db_info.num_args = token_int;
  pd->db_info.args = g_new0 (ProcArg, pd->db_info.num_args);

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    goto error;
  token = get_next_token ();

  pd->db_info.num_values = token_int;
  pd->db_info.values = NULL;
  if (pd->db_info.num_values > 0)
    pd->db_info.values = g_new (ProcArg, pd->db_info.num_values);

  for (i = 0; i < pd->db_info.num_args; i++)
    if (!parse_proc_arg (&pd->db_info.args[i]))
      goto error;

  for (i = 0; i < pd->db_info.num_values; i++)
    if (!parse_proc_arg (&pd->db_info.values[i]))
      goto error;

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    goto error;
  token = get_next_token ();

  *proc_def = pd;
  return OK;

error:
  g_free (pd->db_info.name);
  g_free (pd->db_info.blurb);
  g_free (pd->db_info.help);
  g_free (pd->db_info.author);
  g_free (pd->db_info.copyright);
  g_free (pd->db_info.date);
  g_free (pd->menu_path);
  g_free (pd->extensions);
  g_free (pd->prefixes);
  g_free (pd->magics);
  g_free (pd->image_types);

  for (i = 0; i < pd->db_info.num_args; i++)
    {
      g_free (pd->db_info.args[i].name);
      g_free (pd->db_info.args[i].description);
    }

  for (i = 0; i < pd->db_info.num_values; i++)
    {
      g_free (pd->db_info.values[i].name);
      g_free (pd->db_info.values[i].description);
    }

  g_free (pd->db_info.args);
  g_free (pd->db_info.values);
  g_free (pd);
  return ERROR;
}

static int
parse_proc_arg (ProcArg *arg)
{
  int token;

  arg->arg_type = -1;
  arg->name = NULL;
  arg->description = NULL;

  token = peek_next_token ();
  if (!token || (token != TOKEN_LEFT_PAREN))
    return ERROR;
  token = get_next_token ();

  token = peek_next_token ();
  if (!token || (token != TOKEN_SYMBOL) ||
      (strcmp ("proc-arg", token_sym) != 0))
    return ERROR;
  token = get_next_token ();

  token = peek_next_token ();
  if (!token || (token != TOKEN_NUMBER))
    return ERROR;
  token = get_next_token ();

  arg->arg_type = token_int;

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    return ERROR;
  token = get_next_token ();

  arg->name = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    goto error;
  token = get_next_token ();

  arg->description = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    goto error;
  token = get_next_token ();

  return OK;

error:
  g_free (arg->name);
  g_free (arg->description);

  return ERROR;
}

static int
parse_menu_path ()
{
  char *menu_path;
  char *accelerator;
  int token;

  menu_path = NULL;
  accelerator = NULL;

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    goto error;
  token = get_next_token ();

  menu_path = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    goto error;
  token = get_next_token ();

  accelerator = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    goto error;
  token = get_next_token ();

  menus_add_path (menu_path, accelerator);

  return OK;

error:
  g_free (menu_path);
  g_free (accelerator);

  return ERROR;
}

static char*
transform_path (char *path,
		int   destroy)
{
  int length;
  char *new_path;
  char *home;
  char *token;
  char *tmp;
  char *tmp2;

  home = getenv ("HOME");
  length = 0;

  tmp = path;
  while (*tmp)
    {
      if (*tmp == '~')
	{
	  length += strlen (home);
	  tmp += 1;
	}
      else if (*tmp == '$')
	{
	  tmp += 1;
	  if (!*tmp || (*tmp != '{'))
	    return path;
	  tmp += 1;

	  token = tmp;
	  while (*tmp && (*tmp != '}'))
	    tmp += 1;

	  if (!*tmp)
	    return path;

	  *tmp = '\0';

	  tmp2 = gimprc_find_token (token);
	  tmp2 = transform_path (tmp2, FALSE);
	  gimprc_set_token (token, tmp2);

	  length += strlen (tmp2);

	  *tmp = '}';
	  tmp += 1;
	}
      else
	{
	  length += 1;
	  tmp += 1;
	}
    }

  if (length == strlen (path))
    return path;

  new_path = g_new (char, length + 1);

  tmp = path;
  tmp2 = new_path;

  while (*tmp)
    {
      if (*tmp == '~')
	{
	  *tmp2 = '\0';
	  strcat (tmp2, home);
	  tmp2 += strlen (home);
	  tmp += 1;
	}
      else if (*tmp == '$')
	{
	  tmp += 1;
	  if (!*tmp || (*tmp != '{'))
	    {
	      g_free (new_path);
	      return path;
	    }
	  tmp += 1;

	  token = tmp;
	  while (*tmp && (*tmp != '}'))
	    tmp += 1;

	  if (!*tmp)
	    {
	      g_free (new_path);
	      return path;
	    }

	  *tmp = '\0';
	  token = gimprc_find_token (token);
	  *tmp = '}';

	  *tmp2 = '\0';
	  strcat (tmp2, token);
	  tmp2 += strlen (token);
	  tmp += 1;
	}
      else
	{
	  *tmp2++ = *tmp++;
	}
    }

  *tmp2 = '\0';

  if (destroy)
    g_free (path);

  return new_path;
}

static int
parse_unknown (char *token_sym)
{
  int token;
  UnknownToken *ut, *tmp;
  GList *list;

  ut = g_new (UnknownToken, 1);
  ut->token = g_strdup (token_sym);

  token = peek_next_token ();
  if (!token || (token != TOKEN_STRING))
    {
      g_free (ut->token);
      g_free (ut);
      return ERROR;
    }
  token = get_next_token ();

  ut->value = g_strdup (token_str);

  token = peek_next_token ();
  if (!token || (token != TOKEN_RIGHT_PAREN))
    {
      g_free (ut->token);
      g_free (ut->value);
      g_free (ut);
      return ERROR;
    }
  token = get_next_token ();

  /*  search through the list of unknown tokens and replace an existing entry  */
  list = unknown_tokens;
  while (list)
    {
      tmp = (UnknownToken *) list->data;
      list = list->next;

      if (strcmp (tmp->token, ut->token) == 0)
	{
	  unknown_tokens = g_list_remove (unknown_tokens, tmp);
	  g_free (tmp->token);
	  g_free (tmp->value);
	  g_free (tmp);
	}
    }

  ut->value = transform_path (ut->value, TRUE);
  unknown_tokens = g_list_append (unknown_tokens, ut);

  return OK;
}

static void
add_gimp_directory_token (char *gimp_dir)
{
  UnknownToken *ut;

  /*
    The token holds data from a static buffer which is initialized
    once.  There should be no need to change an already-existing
    value.
  */
  if (NULL != gimprc_find_token ("gimp_dir"))
    return;

  ut = g_new (UnknownToken, 1);
  ut->token = g_strdup ("gimp_dir");
  ut->value = g_strdup (gimp_dir);

  /* Similarly, transforming the path should be silly. */
  unknown_tokens = g_list_append (unknown_tokens, ut);
}

static char*
gimprc_find_token (char *token)
{
  GList *list;
  UnknownToken *ut;

  list = unknown_tokens;
  while (list)
    {
      ut = (UnknownToken *) list->data;
      if (strcmp (ut->token, token) == 0)
	return ut->value;
      list = list->next;
    }

  return NULL;
}

static void
gimprc_set_token (char *token,
		  char *value)
{
  GList *list;
  UnknownToken *ut;

  list = unknown_tokens;
  while (list)
    {
      ut = (UnknownToken *) list->data;
      if (strcmp (ut->token, token) == 0)
	{
	  if (ut->value != value)
	    {
	      if (ut->value)
		g_free (ut->value);
	      ut->value = value;
	    }
	  break;
	}
      list = list->next;
    }
}

/******************/
/*  GIMPRC_QUERY  */

static Argument *
gimprc_query (Argument *args)
{
  Argument *return_args;
  int success = TRUE;
  char *token;
  char *value;

  token = (char *) args[0].value.pdb_pointer;

  success = ((value = gimprc_find_token (token)) != NULL);
  return_args = procedural_db_return_args (&gimprc_query_proc, success);

  if (success)
    return_args[1].value.pdb_pointer = g_strdup (value);

  return return_args;
}

static ProcArg gimprc_query_args[] =
{
  { PDB_STRING,
    "token",
    "the token to query for"
  }
};

static ProcArg gimprc_query_out_args[] =
{
  { PDB_STRING,
    "value",
    "the value associated with the queried token"
  }
};

ProcRecord gimprc_query_proc =
{
  "gimp_gimprc_query",
  "Queries the gimprc file parser for information on a specified token",
  "This procedure is used to locate additional information contained in the gimprc file considered extraneous to the operation of the GIMP.  Plug-ins that need configuration information can expect it will be stored in the user's gimprc file and can use this procedure to retrieve it.  This query procedure will return the value associated with the specified token.  This corresponds _only_ to entries with the format: (<token> <value>).  The value must be a string.  Entries not corresponding to this format will cause warnings to be issued on gimprc parsing and will not be queryable.",
  "Spencer Kimball & Peter Mattis",
  "Spencer Kimball & Peter Mattis",
  "1997",
  PDB_INTERNAL,

  /*  Input arguments  */
  1,
  gimprc_query_args,

  /*  Output arguments  */
  1,
  gimprc_query_out_args,

  /*  Exec method  */
  { { gimprc_query } },
};
