/*----------------------------------------------------------------------------*
 | This file is part of DEU (Doom Editing Utilities), created by the DEU team:|
 | Raphael Quinet, Brendon Wyber, Ted Vessenes and others.  See README.1ST or |
 | the "about" dialog box for full credits.                                   |
 |                                                                            |
 | DEU is an open project: if you think that you can contribute, please join  |
 | the DEU team.  You will be credited for any code (or ideas) included in    |
 | the next version of the program.                                           |
 |                                                                            |
 | If you want to make any modifications and re-distribute them on your own,  |
 | you must follow the conditions of the DEU license.  Read the file LICENSE  |
 | in this directory or README.1ST in the top directory.  If do not have a    |
 | copy of these files, you can request them from any member of the DEU team, |
 | or by mail: Raphael Quinet, Rue des Martyrs 9, B-4550 Nandrin (Belgium).   |
 |                                                                            |
 | This program comes with absolutely no warranty.  Use it at your own risks! |
 *----------------------------------------------------------------------------*

 D_CONFIG.C - Load and save configuration file (DEU.INI), command-line options

*/

/* the includes */
#include "deu.h"
#include "d_misc.h"
#include "d_main.h"
#ifdef DEU_UNIX
#include <sys/types.h>
#include <sys/time.h>
#else
#include <time.h>
#endif
#include "d_config.h"

/* structure containing all config variables */
struct _Config Config;

/* possible types for an option */
enum option_type
{
  OPT_BOOLEAN,        /* boolean (Bool) */
  OPT_INTEGER,        /* integer number (Int16) */
  OPT_STRING,         /* character string (char *) */
  OPT_STRINGACC,      /* character string, but store in a list (char **) */
  OPT_STRINGLIST,     /* list of character strings (char **) */
  OPT_END             /* end of the options description */
};

/* description of the options for the command line and configuration file */
struct
{
  char             *short_name;     /* abbreviated command line argument */
  char             *long_name;      /* command line arg. or keyword */
  enum option_type  opt_type;       /* type of this option */
  char             *msg_if_true;    /* message printed if option is true */
  char             *msg_if_false;   /* message printed if option is false */
  void             *data_ptr;       /* pointer to the data */
} options[] =
{
/*  short & long names   type            message if true/changed       message if false              where to store the value */
  { NULL, "verbose",     OPT_BOOLEAN,    "Verbose mode ON",            "Verbose mode OFF",           &(Config.verbose)        },
  { "d",  "debug",       OPT_BOOLEAN,    "Debug mode ON",              "Debug mode OFF",             &(Config.debug)          },
  { "gd", "gdebug",      OPT_BOOLEAN,    "Graphical Debug mode ON",    "Graphical Debug mode OFF",   &(Config.gDebug)         },

  { NULL, "config",      OPT_STRING,     "Config file",                NULL,                         &(Config.cfgFile)        },
  { NULL, "reminder2",   OPT_BOOLEAN,    NULL,                         NULL,                         &(Config.reminder),      },

  { "w",  "main",        OPT_STRING,     "Main WAD file",              NULL,                         &(Config.mainWad)        },
  { NULL, "file",        OPT_STRINGLIST, "Patch WAD files",            NULL,                         &(Config.patchWads)      },
  { "pw", "pwad",        OPT_STRINGACC,  "Patch WAD file",             NULL,                         &(Config.patchWads)      },
  { "wd", "waddir",      OPT_STRING,     "Directory for WAD files",    NULL,                         &(Config.wadDir)         },
  { "ld", "libdir",      OPT_STRING,     "Dir. for library of shapes", NULL,                         &(Config.libDir)         },

#ifdef __TURBOC__
  { NULL, "bgi",         OPT_STRING,     "BGI driver",                 "Using default VGA driver",   &(Config.BGIDriver)      },
#else
  { NULL, "bgi",         OPT_STRING,     "BGI driver (ignored)",       "(ignored)",                  &(Config.BGIDriver)      },
#endif
  { "v",  "video",       OPT_INTEGER,    "Video mode",                 NULL,                         &(Config.videoMode)      },
  { "g",  "gamma",       OPT_INTEGER,    "Gamma correction level",     NULL,                         &(Config.gamma),         },
  { "fw", "forcewhite",  OPT_BOOLEAN,    "Force color #15 = white",    "Use normal WAD palette",     &(Config.forceWhite)     },
  { "fc", "fakecursor",  OPT_BOOLEAN,    "Fake cursor ON",             "Fake cursor OFF",            &(Config.fakeCursor)     },
/*  { "cc", "cirruscursor",OPT_BOOLEAN,    "Cirrus hardware cursor ON",  "Cirrus hardware cursor OFF", &(Config.CirrusCursor)   }, */
  { "cc", "cirruscursor",OPT_BOOLEAN,    "(ignored)",                  "(ignored)",                  &(Config.CirrusCursor)   },

  { "q",  "quiet",       OPT_BOOLEAN,    "Quiet mode ON",              "Quiet mode OFF",             &(Config.quiet)          },
  { "qq", "quieter",     OPT_BOOLEAN,    "Quieter mode ON",            "Quieter mode OFF",           &(Config.quieter)        },
  { "e",  "expert",      OPT_BOOLEAN,    "Expert mode ON",             "Expert mode OFF",            &(Config.expert)         },
  { "u",  "undo",        OPT_BOOLEAN,    "Undo ON",                    "Undo OFF",                   &(Config.undo)           },

  { "sb", "swapbuttons", OPT_BOOLEAN,    "Mouse buttons swapped",      "Mouse buttons restored",     &(Config.swapButtons)    },
  { NULL, "mousedelay",  OPT_INTEGER,    "Mouse Repeat Delay",         NULL,                         &(Config.mouseRepeatDelay) },
  { NULL, "mouserate",   OPT_INTEGER,    "Mouse Repeat Rate",          NULL,                         &(Config.mouseRepeatRate) },

  { "z",  "zoom",        OPT_INTEGER,    "Initial zoom factor",        NULL,                         &(Config.initialScale)   },
  { "c",  "color2",      OPT_BOOLEAN,    "Alternate Things color set", "Normal Things color set",    &(Config.colour2)        },
  { "i",  "infobar",     OPT_BOOLEAN,    "Info bar shown",             "Info bar hidden",            &(Config.infoShown)      },
  { "a",  "addselbox",   OPT_BOOLEAN,    "Additive selection box",     "Select objects in box only", &(Config.additiveSelBox) },
  { "s0", "select0",     OPT_BOOLEAN,    "Select object 0 by default", "No default selection",       &(Config.select0),       },
  { NULL, "movethings",  OPT_BOOLEAN,    "Moving Things with Sectors", "Things not affected by move",&(Config.moveThings)     },

  { NULL, "walltexture", OPT_STRING,     "Default wall texture",       NULL,                         &(Config.wallTexture)    },
  { NULL, "lowertexture",OPT_STRING,     "Default lower wall texture", NULL,                         &(Config.lowerTexture)   },
  { NULL, "uppertexture",OPT_STRING,     "Default upper wall texture", NULL,                         &(Config.upperTexture)   },
  { NULL, "doortexture", OPT_STRING,     "Default door face texture",  NULL,                         &(Config.doorFaceTexture) },
  { NULL, "dtraktexture",OPT_STRING,     "Default door track texture", NULL,                         &(Config.doorTrakTexture) },
  { NULL, "lifttexture", OPT_STRING,     "Default lift face texture",  NULL,                         &(Config.liftFaceTexture) },
  { NULL, "steptexture", OPT_STRING,     "Default step face texture",  NULL,                         &(Config.stepFaceTexture) },
  { NULL, "switchtexture", OPT_STRING,   "Default switch texture",     NULL,                         &(Config.switchTexture)  },
  { NULL, "floortexture",OPT_STRING,     "Default floor texture",      NULL,                         &(Config.floorTexture)   },
  { NULL, "ceiltexture", OPT_STRING,     "Default ceiling texture",    NULL,                         &(Config.ceilingTexture) },
  { NULL, "floorheight", OPT_INTEGER,    "Default floor height",       NULL,                         &(Config.floorHeight)    },
  { NULL, "ceilheight",  OPT_INTEGER,    "Default ceiling height",     NULL,                         &(Config.ceilingHeight)  },
  { NULL, "lightlevel",  OPT_INTEGER,    "Default light level",        NULL,                         &(Config.lightLevel)     },

  { NULL, "edit",        OPT_STRING,     "Starting the editor on",     NULL,                         &(Config.autoEdit)       },
  { NULL, "quit",        OPT_BOOLEAN,    NULL,                         NULL,                         &(Config.autoQuit)       },
  { NULL, "random",      OPT_BOOLEAN,    "Randomiser ON",              "Randomiser OFF",             &(Config.autoRandom)     },

  { NULL, "bestfit",     OPT_BOOLEAN,    "Choose best orientation",    "Fixed orientation",          &(Config.printBestFit)   },
  { NULL, "portrait",    OPT_BOOLEAN,    "Printing in Portrait mode",  "Printing in Landcape mode",  &(Config.printPortrait)  },
  { NULL, "graysecrets", OPT_BOOLEAN,    "Printing secrets in gray",   "Printing secrets as normal", &(Config.printGraySecrets) },
  { NULL, "thickimpass", OPT_BOOLEAN,    "Printing thicker impass.",   "Printing impass. as normal", &(Config.printThickImpass) },
  { NULL, "printthings", OPT_BOOLEAN,    "Printing LineDefs & Things", "Printing LineDefs only",     &(Config.printThings)    },

  { "rn", "rebuildnodes", OPT_BOOLEAN,   "Rebuild Nodes",              "Do not rebuild nodes",       &(Config.RebuildNodes)   },
  { "rj", "rebuildreject", OPT_BOOLEAN,  "Rebuild Reject",             "Do not rebuild reject data", &(Config.RebuildReject)  },
  { "rb", "rebuildblockmap", OPT_BOOLEAN,"Rebuild Blockmap",           "Do not rebuild blockmap data", &(Config.RebuildBlockmap) },
  { "sf", "splitfactor", OPT_INTEGER,    "Split factor",               NULL,                         &(Config.BSP_splitfactor) },
  { "sl", "slopefactor", OPT_INTEGER,    "Slope factor",               NULL,                         &(Config.BSP_slopefactor) },
  { "sp", "speedfactor", OPT_INTEGER,    "Speed factor",               NULL,                         &(Config.BSP_speedfactor) },
  { "ro", "rejectoption",OPT_INTEGER,    "Reject option",              NULL,                         &(Config.RejectOption)   },
  { NULL, NULL,          OPT_END,        NULL,                         NULL,                         NULL                     }
};



/*
   Set default values for the options.
*/

void InitDefaultOptions(int argc, char *argv[])
{
  int i;

  if (argc > 0 && !stricmp(argv[0], "+verbose"))
    Config.verbose = FALSE;
  else
    Config.verbose = TRUE;
  Config.debug = FALSE;
  Config.gDebug = FALSE;

  Config.cfgFile = DEU_CONFIG_FILE;
  /* quick and dirty check for a "-config" option */
  for (i = 0; i < argc - 1; i++)
    if (!stricmp(argv[i], "-config"))
      {
        Config.cfgFile = argv[i + 1];
        break;
      }
  Config.reminder = TRUE;

  Config.mainWad = "doom.wad";
  if (Exists("heretic.wad"))
    Config.mainWad = "heretic.wad";
  else if (Exists("doom2.wad"))
    Config.mainWad = "doom2.wad";
  else if (Exists("doom1.wad"))
    Config.mainWad = "doom1.wad";
  Config.patchWads = NULL;
  Config.wadDir = NULL;
#ifdef DEU_DOS
  Config.libDir = ".\\shapes";
#else
  Config.libDir = "./shapes";
#endif

  Config.BGIDriver = NULL;
  Config.videoMode = 2;
  Config.gamma = 0;
  Config.forceWhite = FALSE;
  Config.fakeCursor = FALSE;
  Config.CirrusCursor = FALSE;

  Config.swapButtons = FALSE;
  Config.mouseRepeatDelay = 500;
  Config.mouseRepeatRate = 10;

  Config.quiet = FALSE;
  Config.quieter = FALSE;
  Config.expert = FALSE;
  Config.undo = TRUE;

  Config.initialScale = 8;
  Config.colour2 = FALSE;
  Config.infoShown = TRUE;
  Config.additiveSelBox = FALSE;
  Config.select0 = TRUE;
  Config.moveThings = TRUE;

  Config.wallTexture     = "GRAY4";
  Config.upperTexture    = "ICKWALL2";
  Config.lowerTexture    = "GRAY1";
  Config.doorFaceTexture = "BIGDOOR2";
  Config.doorTrakTexture = "DOORTRAK";
  Config.liftFaceTexture = "SHAWN2";
  Config.stepFaceTexture = "STEP1";
  Config.switchTexture   = "SW1GRAY";
  Config.floorTexture    = "FLOOR0_3";
  Config.ceilingTexture  = "FLAT18";
  Config.floorHeight     = 0;
  Config.ceilingHeight   = 128;
  Config.lightLevel      = 180;

  Config.autoEdit   = NULL;
  Config.autoQuit   = FALSE;
  Config.autoRandom = FALSE;

  Config.printBestFit     = TRUE;
  Config.printPortrait    = TRUE; /* ignored if Config.printBestFit = TRUE */
  Config.printGraySecrets = TRUE;
  Config.printThickImpass = TRUE;
  Config.printThings      = TRUE;

  Config.RebuildNodes    = TRUE;
  Config.RebuildReject   = TRUE;
  Config.RebuildBlockmap = TRUE;
  Config.BSP_splitfactor = SPLITFACTOR_DEF;
  Config.BSP_slopefactor = SLOPEFACTOR_DEF;
  Config.BSP_speedfactor = SPEEDFACTOR_DEF;
  Config.RejectOption    = REJECT_NORMAL;
}



/*
   Append a string to a null-terminated list of strings.
*/

void AppendItemToList(char ***list, char *item)
{
  int i;

  i = 0;
  if (*list != NULL)
    {
      /* count the number of elements in the list (last = null) */
      while ((*list)[i] != NULL)
        i++;
      /* expand the list */
      *list = (char **)ResizeMemory(*list, (i + 2) * sizeof(char **));
    }
  else
    {
      /* create a new list */
      *list = (char **)GetMemory(2 * sizeof(char **));
    }
  /* append the new element */
  (*list)[i] = item;
  (*list)[i + 1] = NULL;
}



/*
   Change the value of one option.
*/

void ChangeOption(char *option, char *value)
{
  int optnum;

  for (optnum = 0; options[optnum].opt_type != OPT_END; optnum++)
    {
      if (!stricmp(option, options[optnum].long_name))
        {
          switch (options[optnum].opt_type)
            {
            case OPT_BOOLEAN:
              if (!stricmp(value, "yes") || !stricmp(value, "true") || !stricmp(value, "on") || !stricmp(value, "1"))
                {
                  *((Bool *) (options[optnum].data_ptr)) = TRUE;
                  if (Config.verbose && options[optnum].msg_if_true != NULL)
                    printf("%s.\n", options[optnum].msg_if_true);
                }
              else if (!stricmp(value, "no") || !stricmp(value, "false") || !stricmp(value, "off") || !stricmp(value, "0"))
                {
                  *((Bool *) (options[optnum].data_ptr)) = FALSE;
                  if (Config.verbose && options[optnum].msg_if_false != NULL)
                    printf("%s.\n", options[optnum].msg_if_false);
                }
              else
                ProgError("invalid value for option %s: \"%s\"", option, value);
              break;
            case OPT_INTEGER:
              *((Int16 *) (options[optnum].data_ptr)) = atoi(value);
              if (Config.verbose && options[optnum].msg_if_true != NULL)
                printf("%s: %d.\n", options[optnum].msg_if_true, atoi(value));
              break;
            case OPT_STRING:
              if (value != NULL)
                {
                  *((char **) (options[optnum].data_ptr)) = StrDup(value);
                  if (Config.verbose && options[optnum].msg_if_true != NULL)
                    printf("%s: %s.\n", options[optnum].msg_if_true, value);
                }
              else
                {
                  *((char **) (options[optnum].data_ptr)) = NULL;
                  if (Config.verbose && options[optnum].msg_if_false != NULL)
                    printf("%s.\n", options[optnum].msg_if_false);
                }
              break;
            case OPT_STRINGACC:
              AppendItemToList((char ***) options[optnum].data_ptr, StrDup(value));
              if (Config.verbose && options[optnum].msg_if_true != NULL)
                printf("%s: %s.\n", options[optnum].msg_if_true, value);
              break;
            case OPT_STRINGLIST:
              while (value[0])
                {
                  option = value;
                  while (option[0] && !isspace(option[0]))
                    option++;
                  option[0] = '\0';
                  option++;
                  while (isspace(option[0]))
                    option++;
                  AppendItemToList((char ***) options[optnum].data_ptr, StrDup(value));
                  if (Config.verbose && options[optnum].msg_if_true != NULL)
                    printf("%s: %s.\n", options[optnum].msg_if_true, value);
                  value = option;
                }
              break;
            default:
              ProgError("BUG: unknown option type");
            }
          break;
        }
    }
  if (options[optnum].opt_type == OPT_END)
    printf("Unknown option: \"%s\"\n", option);
}


/*
   Handle command line options.
*/

void ParseCommandLineOptions(int argc, char *argv[])
{
  int optnum;

  LogMessage("[Parsing command line options]\n");
  while (argc > 0)
    {
      if (argv[0][0] != '-' && argv[0][0] != '+')
        ProgError("options must start with '-' or '+'");
      if (!strcmp(argv[0], "-?") || !stricmp(argv[0], "-h") || !stricmp(argv[0], "-help"))
        {
          Usage(stdout);
          exit(0);
        }
      for (optnum = 0; options[optnum].opt_type != OPT_END; optnum++)
        {
          if (((options[optnum].short_name != NULL)
               && !stricmp(&(argv[0][1]), options[optnum].short_name))
              || !stricmp(&(argv[0][1]), options[optnum].long_name))
            {
              switch (options[optnum].opt_type)
                {
                case OPT_BOOLEAN:
                  if (argv[0][0] == '-')
                    {
                      *((Bool *) (options[optnum].data_ptr)) = TRUE;
                      if (Config.verbose && options[optnum].msg_if_true != NULL)
                        printf("%s.\n", options[optnum].msg_if_true);
                    }
                  else
                    {
                      *((Bool *) (options[optnum].data_ptr)) = FALSE;
                      if (Config.verbose && options[optnum].msg_if_false != NULL)
                        printf("%s.\n", options[optnum].msg_if_false);
                    }
                  break;
                case OPT_INTEGER:
                  if (argc <= 1)
                    ProgError("missing argument after \"%s\"", argv[0]);
                  argv++;
                  argc--;
                  *((Int16 *) (options[optnum].data_ptr)) = (Int16) atoi(argv[0]);
                  if (Config.verbose && options[optnum].msg_if_true != NULL)
                    printf("%s: %d.\n", options[optnum].msg_if_true, atoi(argv[0]));
                  break;
                case OPT_STRING:
                  if (argc <= 1)
                    ProgError("missing argument after \"%s\"", argv[0]);
                  argv++;
                  argc--;
                  *((char **) (options[optnum].data_ptr)) = argv[0];
                  if (Config.verbose && options[optnum].msg_if_true != NULL)
                    printf("%s: %s.\n", options[optnum].msg_if_true, argv[0]);
                  break;
                case OPT_STRINGACC:
                  if (argc <= 1)
                    ProgError("missing argument after \"%s\"", argv[0]);
                  argv++;
                  argc--;
                  AppendItemToList((char ***) options[optnum].data_ptr, argv[0]);
                  if (Config.verbose && options[optnum].msg_if_true != NULL)
                    printf("%s: %s.\n", options[optnum].msg_if_true, argv[0]);
                  break;
                case OPT_STRINGLIST:
                  if (argc <= 1)
                    ProgError("missing argument after \"%s\"", argv[0]);
                  while (argc > 1 && argv[1][0] != '-' && argv[1][0] != '+')
                    {
                      argv++;
                      argc--;
                      AppendItemToList((char ***) options[optnum].data_ptr, argv[0]);
                      if (Config.verbose && options[optnum].msg_if_true != NULL)
                        printf("%s: %s.\n", options[optnum].msg_if_true, argv[0]);
                    }
                  break;
                default:
                  ProgError("unknown option type (BUG!)");
                }
              break;
            }
        }
      if (options[optnum].opt_type == OPT_END)
        ProgError("invalid argument: \"%s\"", argv[0]);
      argv++;
      argc--;
    }
}



/*
   Read the configuration file.
*/

void ParseConfigFileOptions(char *filename)
{
  FILE *cfgfile;
  char  line[1024];
  int   linenum;
  char *value;
  char *option;

  LogMessage("[Reading options from %s]\n", filename);
  if ((cfgfile = fopen(filename, "r")) == NULL)
    {
      printf("Warning: Configuration file not found (%s)\n", filename);
      return;
    }
  linenum = 0;
  while (fgets(line, 1024, cfgfile) != NULL)
    {
      linenum++;
      if (line[0] == '#' || strlen(line) < 2)
        continue;
      if (line[strlen(line) - 1] == '\n')
        line[strlen(line) - 1] = '\0';
      /* skip blanks before the option name */
      option = line;
      while (isspace(option[0]))
        option++;
      /* skip the option name */
      value = option;
      while (value[0] && value[0] != '=' && !isspace(value[0]))
        value++;
      if (!value[0])
        ProgError("line %d of %s ends prematurely", linenum, filename);
      if (value[0] == '=')
        {
          /* mark the end of the option name */
          value[0] = '\0';
        }
      else
        {
          /* mark the end of the option name */
          value[0] = '\0';
          value++;
          /* skip blanks after the option name */
          while (isspace(value[0]))
            value++;
          if (value[0] != '=')
            ProgError("line %d of %s has no equal sign", linenum, filename);
        }
      value++;
      /* skip blanks after the equal sign */
      while (isspace(value[0]))
        value++;
      /* change the value of the option */
      ChangeOption(option, value);
      /* nested configuration files */
      if (!stricmp(option, "config"))
        {
          if (!stricmp(value, filename))
            ProgError("%s includes itself at line %d", filename, linenum);
          ParseConfigFileOptions(value);
        }
    }
  fclose(cfgfile);
}


/*
   Get the current value of an option in a character string.
*/

char *GetOptionValue(char *option)
{
  static char numval[10];
  int         optnum;

  for (optnum = 0; options[optnum].opt_type != OPT_END; optnum++)
    {
      if (!stricmp(option, options[optnum].long_name))
        {
          switch (options[optnum].opt_type)
            {
            case OPT_BOOLEAN:
              if (*((Bool *) (options[optnum].data_ptr)) == FALSE)
                return "false";
              else
                return "true";
            case OPT_INTEGER:
              sprintf(numval, "%d", *((Int16 *) (options[optnum].data_ptr)));
              return numval;
            case OPT_STRING:
              return *((char **) (options[optnum].data_ptr));
            case OPT_STRINGACC:
            case OPT_STRINGLIST:
              /* cannot return the value of lists */
              return NULL;
            default:
              ProgError("BUG: unknown option type");
            }
          break;
        }
    }
  if (options[optnum].opt_type == OPT_END)
    printf("Unknown option: \"%s\"\n", option);
  return NULL;
}


/*
   Print the current value of all options.
*/

void PrintOptionValues(FILE *file)
{
  int   optnum;
  char  buf[80];
  int   lines = 4;
  char  key;

  fprintf(file, "DEU Options\n");
  fprintf(file, "===========\n\n");
  fprintf(file, "LONG_NAME_(SHORT)___     VALUE______________\n");
  for (optnum = 0; options[optnum].opt_type != OPT_END; optnum++)
    {
      if (options[optnum].short_name != NULL)
        sprintf(buf, "%s (%s)", options[optnum].long_name,
                options[optnum].short_name);
      else
        sprintf(buf, "%s", options[optnum].long_name);
      fprintf(file, "%-25s", buf);
      switch (options[optnum].opt_type)
        {
        case OPT_BOOLEAN:
          if (*((Bool *) (options[optnum].data_ptr)) == FALSE)
            {
              if (options[optnum].msg_if_false != NULL)
                fprintf(file, "%s\n", options[optnum].msg_if_false);
              else
                fprintf(file, "(false)\n");
            }
          else
            {
              if (options[optnum].msg_if_true != NULL)
                fprintf(file, "%s\n", options[optnum].msg_if_true);
              else
                fprintf(file, "(true)\n");
            }
          break;
        case OPT_INTEGER:
          if (options[optnum].msg_if_true != NULL)
            fprintf(file, "%s: %d\n", options[optnum].msg_if_true,
                   *((Int16 *) (options[optnum].data_ptr)));
          else
            fprintf(file, "(%d)\n", *((Int16 *) (options[optnum].data_ptr)));
          break;
        case OPT_STRING:
          if (*((char **) (options[optnum].data_ptr)) != NULL)
            {
              if (options[optnum].msg_if_true != NULL)
                fprintf(file, "%s: %s\n", options[optnum].msg_if_true,
                       *((char **) (options[optnum].data_ptr)));
              else
                fprintf(file, "(%s)\n", *((char **) (options[optnum].data_ptr)));
            }
          else
            {
              if (options[optnum].msg_if_false != NULL)
                fprintf(file, "%s\n", options[optnum].msg_if_false);
              else
                fprintf(file, "(null)\n");
            }
          break;
        case OPT_STRINGACC:
        case OPT_STRINGLIST:
          /* cannot return the value of lists */
          fprintf(file, "(...)\n");
          break;
        default:
          ProgError("BUG: unknown option type");
        }
      if (file == stdout && lines++ > 21)
        {
          lines = 0;
          printf("[Press Q to quit or any other key to continue]");
          key = getchar();
          printf("\r                                                \r");
          if (key == 'Q' || key == 'q')
            return;
        }
    }
}


/*
   Save one option in the configuration file (with its current value).
*/

Bool SaveConfigFileOption(char *filename, char *option)
{
  char   *value;
  FILE   *cfgfile;
  time_t  tval;
  char   *tstr;

  value = GetOptionValue(option);
  if (value == NULL)
    return FALSE;

  /*! Better way to do this:
      - rename the file (backup copy)
      - create a new file
      - read the old file line by line
      - if the line doesn't contain the option to be changed, copy it to
        the new file
      - if the line contains the option, copy the new value instead
        (or copy the original line as a comment, followed by the new one)
      - close both files
  */

  if ((cfgfile = fopen(filename, "a")) == NULL)
    {
      printf("Warning: Cannot save option to %s\n", filename);
      return FALSE;
    }
  /* write a comment before the option, with a time stamp */
  time(&tval);
  tstr = ctime(&tval);
  tstr[strlen(tstr) - 1] = '\0';
  fprintf(cfgfile, "\n# [Following line added by DEU, %s]\n", tstr);
  /* write the option and its new value */
  fprintf(cfgfile, "%s = %s\n", option, value);
  fclose(cfgfile);
  return TRUE;
}


/* end of file */
