/*****
* settings.cc
* Andy Hammerlindl 2004/05/10
*
* Declares a list of global variables that act as settings in the system.
*****/

#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <cerrno>
#include <sys/stat.h>
#include <cfloat>
#include <clocale>
#include <algorithm>

#if defined(_WIN32)
#include <Windows.h>
#include <io.h>
#define isatty _isatty
#else
#include <unistd.h>
#endif

#include "common.h"

#if HAVE_GNU_GETOPT_H
#include <getopt.h>
#else
#include "getopt.h"
#endif

#include "util.h"
#include "settings.h"
#include "interact.h"
#include "locate.h"
#include "lexical.h"
#include "record.h"
#include "env.h"
#include "item.h"
#include "refaccess.h"
#include "pipestream.h"
#include "array.h"

#include "glrender.h"

#ifdef HAVE_LIBCURSES
extern "C" {

#ifdef HAVE_NCURSES_CURSES_H
#define USE_SETUPTERM
#include <ncurses/curses.h>
#include <ncurses/term.h>
#elif HAVE_NCURSES_H
#define USE_SETUPTERM
#include <ncurses.h>
#include <term.h>
#elif HAVE_CURSES_H
#include <curses.h>

#if defined(HAVE_TERM_H)
#define USE_SETUPTERM
#include <term.h>
#endif

#endif
}
#endif

// Workaround broken curses.h files:
#ifdef clear
#undef clear
#endif
// Workaround broken header file on i386-solaris with g++ 3.4.3.
#ifdef erase
#undef erase
#endif

using vm::item;

using trans::itemRefAccess;
using trans::refAccess;
using trans::varEntry;
using vm::array;

void runFile(const string& filename);

namespace settings {

using camp::pair;

#ifdef HAVE_LIBGLM
const bool havegl=true;
#else
const bool havegl=false;
#endif

#if !defined(_WIN32)
mode_t mask;
#endif

string systemDir=ASYMPTOTE_SYSDIR;
string defaultPSdriver="ps2write";
string defaultEPSdriver="eps2write";
string defaultPNGdriver="png16malpha"; // pngalpha has issues at high resolutions
string defaultAsyGL="https://vectorgraphics.github.io/asymptote/base/webgl/asygl-"+
 string(AsyGLVersion)+".js";

#if !defined(_WIN32)

bool msdos=false;
string HOME="HOME";
string docdir=ASYMPTOTE_DOCDIR;
const char pathSeparator=':';
#ifdef __APPLE__
string defaultPSViewer="open";
string defaultPDFViewer="open";
string defaultHTMLViewer="open";
#else
string defaultPSViewer="evince";
string defaultPDFViewer="evince";
string defaultHTMLViewer="google-chrome";
#endif
string defaultGhostscript="gs";
string defaultGhostscriptLibrary="";
string defaultDisplay="display";
string defaultAnimate="magick";
void queryRegistry() {}
const string dirsep="/";

#else

bool msdos=true;
string HOME="USERPROFILE";
string docdir="c:\\Program Files\\Asymptote";
const char pathSeparator=';';
string defaultPSViewer;
//string defaultPDFViewer="AcroRd32.exe";
string defaultPDFViewer;
string defaultHTMLViewer;
string defaultGhostscript;
string defaultGhostscriptLibrary;
string defaultDisplay;
//string defaultAnimate="magick";
string defaultAnimate="";
const string dirsep="\\";

/**
* Use key to look up an entry in the MSWindows registry,
* @param baseRegLocation base location for a key
* @param key Key to look up, respecting wild cards. Note that wildcards
* only support single-level glob. Recursive globs are not supported.
* @param value Value to look up
* @remark Wildcards can only be in keys, not in the final value
* @return Entry value, or nullopt if not found
*/
optional<string>
getEntry(HKEY const& baseRegLocation, string const& key, string const& value)
{
 string path= key;
 if (key.find('\\') == 0) {
   path= path.substr(1);// strip the prefix separator
 }

 size_t const star= path.find('*');
 if (star == string::npos) {
   // absolute path, can return right away
   DWORD dataSize= 0;
   if (RegGetValueA(
               baseRegLocation, path.c_str(), value.c_str(), RRF_RT_REG_SZ,
               nullptr, nullptr, &dataSize
       ) != ERROR_SUCCESS) {
     return nullopt;
   }

   mem::vector<BYTE> outputBuffer(dataSize);

   if (RegGetValueA(
               baseRegLocation, path.c_str(), value.c_str(), RRF_RT_REG_SZ,
               nullptr, outputBuffer.data(), &dataSize
       ) != ERROR_SUCCESS) {
     return nullopt;
   }

   return make_optional<string>(
           reinterpret_cast<char const*>(outputBuffer.data())
   );
 }

 // has a glob, search until we find one
 string const prefix= path.substr(0, star);
 string const pathSuffix= path.substr(star + 1);

 // open the key in prefix

 camp::w32::RegKeyWrapper directoryWithPrefix;
 if (RegOpenKeyExA(
             baseRegLocation, prefix.c_str(), 0, KEY_READ,
             directoryWithPrefix.put()
     ) != ERROR_SUCCESS) {
   return nullopt;// prefix path does not exist, or some other error
 }

 DWORD numSubKeys= 0;
 DWORD longestSubkeySize= 0;

 // querying # of subkeys + their longest path length
 if (RegQueryInfoKeyA(
             directoryWithPrefix.getKey(), nullptr, nullptr, nullptr,
             &numSubKeys, &longestSubkeySize, nullptr, nullptr, nullptr,
             nullptr, nullptr, nullptr
     ) != ERROR_SUCCESS) {
   return nullopt;
 }

 mem::vector<CHAR> subkeyBuffer(longestSubkeySize + 1);

 for (DWORD i= 0; i < numSubKeys; ++i) {
   DWORD cchValue= longestSubkeySize + 1;

   // get subkey's name
   if (RegEnumKeyExA(
               directoryWithPrefix.getKey(), i, subkeyBuffer.data(), &cchValue,
               nullptr, nullptr, nullptr, nullptr
       ) != ERROR_SUCCESS) {
     continue;
   }

   // open the subkey
   camp::w32::RegKeyWrapper searchKey;
   if (RegOpenKeyExA(
               directoryWithPrefix.getKey(), subkeyBuffer.data(), 0, KEY_READ,
               searchKey.put()
       ) != ERROR_SUCCESS) {
     continue;
   }

   // do a recursive search starting at the opened key
   if (auto retResult= getEntry(searchKey.getKey(), pathSuffix, value);
       retResult.has_value()) {
     return retResult;
   }
 }
 return nullopt;
}

// Use key to look up an entry in the MSWindows registry, respecting wild cards
string getEntry(const string& key, const string& value)
{
 for (HKEY const keyToSearch : {HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER}) {
   camp::w32::RegKeyWrapper baseRegKey;
   if (RegOpenKeyExA(keyToSearch, "SOFTWARE", 0, KEY_READ, baseRegKey.put()) !=
       ERROR_SUCCESS) {
     baseRegKey.release();
     continue;
   }
   optional<string> entry= getEntry(baseRegKey.getKey(), key, value);

   if (entry.has_value()) {
     return entry.value();
   }
 }

 return "";
}

void queryRegistry()
{
 defaultGhostscriptLibrary= getEntry(R"(GPL Ghostscript\*)", "GS_DLL");
 if (defaultGhostscriptLibrary.empty())
   defaultGhostscriptLibrary= getEntry(R"(AFPL Ghostscript\*)", "GS_DLL");

 string gslib= stripDir(defaultGhostscriptLibrary);
 defaultGhostscript=
         stripFile(defaultGhostscriptLibrary) +
         ((gslib.empty() || gslib.substr(5, 2) == "32") ? "gswin32c.exe"
                                                        : "gswin64c.exe");

 string const s= getEntry(
         R"(Microsoft\Windows\CurrentVersion\App Paths\Asymptote)", "Path"
 );
 if (!s.empty()) {
   docdir= s;
 }
 // An empty systemDir indicates a TeXLive build
 if (!systemDir.empty() && !docdir.empty())
   systemDir= docdir;
}

#endif

// The name of the program (as called).  Used when displaying help info.
char *argv0;

Int verbose;
bool debug;
bool xasy;
bool keys;

bool quiet=false;

// Conserve memory at the expense of speed.
bool compact;

// Colorspace conversion flags (stored in global variables for efficiency).
bool gray;
bool bw;
bool rgb;
bool cmyk;

// Disable system calls.
bool safe=true;
// Enable reading from other directories
bool globalRead=true;
// Enable writing to (or changing to) other directories
bool globalWrite=false;

bool globalwrite() {return globalWrite || !safe;}
bool globalread() {return globalRead || !safe;}

const string suffix="asy";
const string guisuffix="gui";
const string standardprefix="out";

string initdir;
string historyname;

// Local versions of the argument list.
int argCount = 0;
char **argList = 0;

typedef ::option c_option;

types::dummyRecord *settingsModule;

types::record *getSettingsModule() {
 return settingsModule;
}

void noWarn(const string& s)
{
 array *Warn=getSetting<array *>("suppress");
 size_t size=checkArray(Warn);
 if(s.empty()) return;
 for(size_t i=0; i < size; i++)
   if(vm::read<string>(Warn,i) == s) return;
 Warn->push(s);
}

void Warn(const string& s)
{
 array *Warn=getSetting<array *>("suppress");
 size_t size=checkArray(Warn);
 for(size_t i=0; i < size; i++)
   if(vm::read<string>(Warn,i) == s)
     (*Warn).erase((*Warn).begin()+i,(*Warn).begin()+i+1);
}

bool warn(const string& s)
{
 if(debug) return true;
 array *Warn=getSetting<array *>("suppress");
 size_t size=checkArray(Warn);
 for(size_t i=0; i < size; i++)
   if(vm::read<string>(Warn,i) == s) return false;
 return true;
}

// The dictionaries of long options and short options.
struct option;
typedef mem::map<const string, option *> optionsMap_t;
optionsMap_t optionsMap;

typedef mem::map<const char, option *> codeMap_t;
codeMap_t codeMap;

struct option : public gc {
 string name;
 char code;      // Command line option, i.e. 'V' for -V.
 bool argument;  // If it takes an argument on the command line.  This is set
                 // based on whether argname is empty.
 string argname; // The argument name for printing the description.
 string desc; // One line description of what the option does.
 bool cmdlineonly; // If it is only available on the command line.
 string Default; // A string containing an optional default value.

 option(string name, char code, string argname, string desc,
        bool cmdlineonly=false, string Default="")
   : name(name), code(code), argument(!argname.empty()), argname(argname),
     desc(desc), cmdlineonly(cmdlineonly), Default(Default) {}

 virtual ~option() {}

 // Builds this option's contribution to the optstring argument of get_opt().
 virtual string optstring() {
   if (code) {
     string base;
     base.push_back(code);
     if(argument) base.push_back(':');
     return base;
   }
   else return "";
 }

 // Sets the contribution to the longopt array.
 virtual void longopt(c_option &o) {
   o.name=name.c_str();
   o.has_arg=argument ? 1 : 0;
   o.flag=0;
   o.val=0;
 }

 // Add to the dictionaries of options.
 virtual void add() {
   optionsMap[name]=this;
   if (code)
     codeMap[code]=this;
 }

 // Set the option from the command-line argument.  Return true if the option
 // was correctly parsed.
 virtual bool getOption() = 0;

 void error(string msg) {
   cerr << endl << argv0 << ": ";
   if (code)
     cerr << "-" << code << " ";
   cerr << "(-" << name << ") " << msg << endl;
 }

 // The "-f,-outformat format" part of the option.
 virtual string describeStart() {
   ostringstream ss;
   if (code)
     ss << "-" << code << ",";
   ss << "-" << name;
   if (argument)
     ss << " " << argname;
   return ss.str();
 }

 // Outputs description of the command for the -help option.
 virtual void describe(char option) {
   // Don't show the option if it has no desciption.
   if(!hide() && ((option == 'h') ^ env())) {
     const unsigned WIDTH=22;
     string start=describeStart();
     cerr << std::left << std::setw(WIDTH) << start;
     if (start.size() >= WIDTH) {
       cerr << endl;
       cerr << std::left << std::setw(WIDTH) << "";
     }
     cerr << " " << desc;
     if(cmdlineonly) cerr << "; command-line only";
     if(Default != "") {
       if(!desc.empty()) cerr << " ";
       cerr << Default;
     }
     cerr << endl;
   }
 }

 virtual void reset() {
 }

 virtual bool env() {return false;}
 virtual bool hide() {return false;}
};

const string noarg;

struct setting : public option {
 types::ty *t;

private:
 trans::permission perm;
 bool added;

 // Flag the setting as secure, so that it can only be set on the command-line,
 // though it can still be read in Asymptote code.
 void secure() {
   assert(!added);
   perm = trans::RESTRICTED;
 }

public:
 setting(string name, char code, string argname, string desc,
         types::ty *t, string Default)
   : option(name, code, argname, desc, false,Default),
     t(t), perm(trans::PUBLIC), added(false) {}

 void reset() = 0;

 virtual trans::access *buildAccess() = 0;

 // Add to the dictionaries of options and to the settings module.
 virtual void add() {
   assert(!added);

   option::add();
   settingsModule->add(name, t, buildAccess(), perm);

   added=true;
 }

 friend void addSecureSetting(setting *s) {
   s->secure();
   s->add();
 }
};

struct itemSetting : public setting {
 item defaultValue;
 item value;

 itemSetting(string name, char code,
             string argname, string desc,
             types::ty *t, item defaultValue, string Default="")
   : setting(name, code, argname, desc, t, Default),
     defaultValue(defaultValue) {reset();}

 void reset() {
   value=defaultValue;
 }

 trans::access *buildAccess() {
   return new itemRefAccess(&(value));
 }
};

item& Setting(string name) {
 itemSetting *s=dynamic_cast<itemSetting *>(optionsMap[name]);
 if(!s) {
   cerr << "Cannot find setting named '" << name << "'" << endl;
   exit(-1);
 }
 return s->value;
}

struct boolSetting : public itemSetting {
 boolSetting(string name, char code, string desc,
             bool defaultValue=false)
   : itemSetting(name, code, noarg, desc,
                 types::primBoolean(), (item)defaultValue,
                 defaultValue ? "[true]" : "[false]") {}

 bool getOption() {
   value=(item)true;
   return true;
 }

 option *negation(string name) {
   struct negOption : public option {
     boolSetting &base;

     bool hide() {return true;}

     negOption(boolSetting &base, string name)
       : option(name, 0, noarg, ""), base(base) {}

     bool getOption() {
       base.value=(item)false;
       return true;
     }
   };
   return new negOption(*this, name);
 }

 void add() {
   setting::add();
   negation("no"+name)->add();
   if (code) {
     string nocode="no"; nocode.push_back(code);
     negation(nocode)->add();
   }
 }

 // Set several related boolean options at once.  Used for view and trap which
 // have batch and interactive settings.
 struct multiOption : public option {
   typedef mem::list<boolSetting *> setlist;
   setlist set;
   multiOption(string name, char code, string desc)
     : option(name, code, noarg, desc, true) {}

   void add(boolSetting *s) {
     set.push_back(s);
   }

   void setValue(bool value) {
     for (setlist::iterator s=set.begin(); s!=set.end(); ++s)
       (*s)->value=(item)value;
   }

   bool getOption() {
     setValue(true);
     return true;
   }

   option *negation(string name) {
     struct negOption : public option {
       multiOption &base;

       bool hide() {return true;}

       negOption(multiOption &base, string name)
         : option(name, 0, noarg, ""), base(base) {}

       bool getOption() {
         base.setValue(false);
         return true;
       }
     };
     return new negOption(*this, name);
   }

   void add() {
     option::add();
     negation("no"+name)->add();
     if (code) {
       string nocode="no"; nocode.push_back(code);
       negation(nocode)->add();
     }

     for (multiOption::setlist::iterator s=set.begin(); s!=set.end(); ++s)
       (*s)->add();
   }
 };
};

typedef boolSetting::multiOption multiOption;

struct argumentSetting : public itemSetting {
 argumentSetting(string name, char code,
                 string argname, string desc,
                 types::ty *t, item defaultValue)
   : itemSetting(name, code, argname, desc, t, defaultValue)
 {
   assert(!argname.empty());
 }
};

struct stringSetting : public argumentSetting {
 stringSetting(string name, char code,
               string argname, string desc,
               string defaultValue="")
   : argumentSetting(name, code, argname, desc == "" ? "["+defaultValue+"]" :
                     desc+(defaultValue.empty() ? "" : " ["+defaultValue+"]"),
                     types::primString(), (item)defaultValue) {}

 bool getOption() {
   value=(item)(string)optarg;
   return true;
 }
};

struct userSetting : public argumentSetting {
 userSetting(string name, char code,
             string argname, string desc,
             string defaultValue="")
   : argumentSetting(name, code, argname, desc,
                     types::primString(), (item)defaultValue) {}

 bool getOption() {
   string s=vm::get<string>(value)+string(optarg);
   s.push_back(';');
   value=(item) s;
   return true;
 }
};

struct warnSetting : public option {
 warnSetting(string name, char code,
             string argname, string desc)
   : option(name, code, argname, desc, true) {}

 bool getOption() {
   Warn(string(optarg));
   return true;
 }

 option *negation(string name) {
   struct negOption : public option {
     warnSetting &base;

     bool hide() {return true;}

     negOption(warnSetting &base, string name, string argname)
       : option(name, 0, argname, ""), base(base) {}

     bool getOption() {
       noWarn(string(optarg));
       return true;
     }
   };
   return new negOption(*this, name, argname);
 }

 void add() {
   option::add();
   negation("no"+name)->add();
   if (code) {
     string nocode="no"; nocode.push_back(code);
     negation(nocode)->add();
   }
 }
};

string GetEnv(string s, string Default) {
 transform(s.begin(), s.end(), s.begin(), toupper);
 string t=Getenv(("ASYMPTOTE_"+s).c_str(),msdos);
 return t.empty() ? Default : t;
}

struct envSetting : public stringSetting {
 envSetting(string name, string Default)
   : stringSetting(name, 0, " ", "", GetEnv(name,Default)) {}
 bool env() {return true;}
};

template<class T>
struct dataSetting : public argumentSetting {
 string text;
 dataSetting(const char *text, string name, char code,
             string argname, string desc, types::ty *type,
             T defaultValue)
   : argumentSetting(name, code, argname, desc,
                     type, (item)defaultValue), text(text) {}

 bool getOption() {
   try {
     value=(item)lexical::cast<T>(optarg);
   } catch (lexical::bad_cast&) {
     error("option requires " + text + " as an argument");
     return false;
   }
   return true;
 }
};

template<class T>
string description(string desc, T defaultValue)
{
 return desc.empty() ? "" : desc+" ["+String(defaultValue)+"]";
}

struct IntSetting : public dataSetting<Int> {
 IntSetting(string name, char code,
            string argname, string desc, Int defaultValue=0)
   : dataSetting<Int>("an int", name, code, argname,
                      description(desc,defaultValue),
                      types::primInt(), defaultValue) {}
};

struct realSetting : public dataSetting<double> {
 realSetting(string name, char code,
             string argname, string desc, double defaultValue=0.0)
   : dataSetting<double>("a real", name, code, argname,
                         description(desc,defaultValue),
                         types::primReal(), defaultValue) {}
};

struct pairSetting : public dataSetting<pair> {
 pairSetting(string name, char code,
             string argname, string desc, pair defaultValue=0.0)
   : dataSetting<pair>("a pair", name, code, argname,
                       description(desc,defaultValue),
                       types::primPair(), defaultValue) {}
};

// For setting the alignment of a figure on the page.
struct alignSetting : public argumentSetting {
 alignSetting(string name, char code,
              string argname, string desc,
              string defaultValue)
   : argumentSetting(name, code, argname, description(desc,defaultValue),
                     types::primString(), (item)defaultValue) {}

 bool getOption() {
   string str=optarg;
   if(str == "C" || str == "T" || str == "B" || str == "Z") {
     value=str;
     return true;
   }
   error("invalid argument for option");
   return false;
 }
};

struct stringArraySetting : public itemSetting {
 stringArraySetting(string name, array *defaultValue)
   : itemSetting(name, 0, "", "",
                 types::stringArray(), (item) defaultValue) {}
 bool hide() {return true;}

 bool getOption() {return true;}
};

struct engineSetting : public argumentSetting {
 engineSetting(string name, char code,
               string argname, string desc,
               string defaultValue)
   : argumentSetting(name, code, argname, description(desc,defaultValue),
                     types::primString(), (item)defaultValue) {}

 bool getOption() {
   string str=optarg;

   if(str == "latex" || str == "pdflatex" || str == "xelatex" ||
      str == "tex" || str == "pdftex" || str == "luatex" ||
      str == "lualatex" || str == "context" || str == "none") {
     value=str;
     return true;
   }
   error("invalid argument for option");
   return false;
 }
};

template<class T>
string stringCast(T x)
{
 ostringstream buf;
 buf.precision(DBL_DIG);
 buf.setf(std::ios::boolalpha);
 buf << x;
 return string(buf.str());
}

template <class T>
struct refSetting : public setting {
 T *ref;
 T defaultValue;
 string text;

 refSetting(string name, char code, string argname,
            string desc, types::ty *t, T *ref, T defaultValue,
            const char *text="")
   : setting(name, code, argname, desc, t, stringCast(defaultValue)),
     ref(ref), defaultValue(defaultValue), text(text) {
   reset();
 }

 virtual bool getOption() {
   try {
     *ref=lexical::cast<T>(optarg);
   } catch (lexical::bad_cast&) {
     error("option requires " + text + " as an argument");
     return false;
   }
   return true;
 }

 virtual void reset() {
   *ref=defaultValue;
 }

 trans::access *buildAccess() {
   return new refAccess<T>(ref);
 }
};

struct boolrefSetting : public refSetting<bool> {
 boolrefSetting(string name, char code, string desc, bool *ref,
                bool Default=false)
   : refSetting<bool>(name, code, noarg, desc,
                      types::primBoolean(), ref, Default) {}
 virtual bool getOption() {
   *ref=true;
   return true;
 }

 virtual option *negation(string name) {
   struct negOption : public option {
     boolrefSetting &base;

     bool hide() {return true;}

     negOption(boolrefSetting &base, string name)
       : option(name, 0, noarg, ""), base(base) {}

     bool getOption() {
       *(base.ref)=false;
       return true;
     }
   };
   return new negOption(*this, name);
 }

 void add() {
   setting::add();
   negation("no"+name)->add();
   if (code) {
     string nocode="no"; nocode.push_back(code);
     negation(nocode)->add();
   }
 }
};

struct compactSetting : public boolrefSetting {
 compactSetting(string name, char code, string desc, bool *ref,
                bool Default=false)
   : boolrefSetting(name,code,desc,ref,Default) {}
 bool getOption() {
   mem::compact(1);
   return boolrefSetting::getOption();
 }

 option *negation(string name) {
   mem::compact(0);
   return boolrefSetting::negation(name);
 }
};

struct incrementSetting : public refSetting<Int> {
 incrementSetting(string name, char code, string desc, Int *ref)
   : refSetting<Int>(name, code, noarg, desc,
                     types::primInt(), ref, 0) {}

 bool getOption() {
   // Increment the value.
   ++(*ref);
   return true;
 }

 option *negation(string name) {
   struct negOption : public option {
     incrementSetting &base;

     bool hide() {return true;}

     negOption(incrementSetting &base, string name)
       : option(name, 0, noarg, ""), base(base) {}

     bool getOption() {
       if(*base.ref) --(*base.ref);
       return true;
     }
   };
   return new negOption(*this, name);
 }

 void add() {
   setting::add();
   negation("no"+name)->add();
   if (code) {
     string nocode="no"; nocode.push_back(code);
     negation(nocode)->add();
   }
 }
};

struct incrementOption : public option {
 Int *ref;
 Int level;

 incrementOption(string name, char code, string desc, Int *ref,
                 Int level=1)
   : option(name, code, noarg, desc, true), ref(ref), level(level) {}

 bool hide() {return true;}

 bool getOption() {
   // Increment the value.
   (*ref) += level;
   return true;
 }
};

void addOption(option *o) {
 o->add();
}

void version()
{
 cerr << PACKAGE_NAME << " version " << REVISION
      << " [(C) 2004 Andy Hammerlindl, John C. Bowman, Tom Prince]"
      << endl;
}

void usage(const char *program)
{
 version();
 cerr << "\t\t\t" << "https://asymptote.sourceforge.io/"
      << endl
      << "Usage: " << program << " [options] [file ...]"
      << endl;
}

void reportSyntax() {
 cerr << endl;
 usage(argv0);
 cerr << endl << "Type '" << argv0
      << " -h' for a description of options." << endl;
 exit(1);
}

void displayOptions(char code)
{
 cerr << endl;
 if(code == 'h')
   cerr << "Options (negate boolean options by replacing - with -no): "
        << endl << endl;
 else
   cerr << "Environment settings: "
        << endl << endl;
 for (optionsMap_t::iterator opt=optionsMap.begin();
      opt!=optionsMap.end();
      ++opt)
   opt->second->describe(code);
}

struct helpOption : public option {
 helpOption(string name, char code, string desc)
   : option(name, code, noarg, desc, true) {}

 bool getOption() {
   usage(argv0);
   displayOptions(code);
   cerr << endl;
   exit(0);

   // Unreachable code.
   return true;
 }
};

struct versionOption : public option {
 versionOption(string name, char code, string desc)
   : option(name, code, noarg, desc, true) {}

 bool disabled;

 const void feature(const char *s, bool enabled) {
   if(enabled ^ disabled)
     cerr << s << endl;
 }

 void features(bool enabled) {
   disabled=!enabled;
   cerr << endl << (disabled ? "DIS" : "EN") << "ABLED OPTIONS:" << endl;

   bool glm=false;
   bool gl=false;
   bool ssbo=false;
   bool gsl=false;
   bool fftw3=false;
   bool eigen=false;
   bool xdr=false;
   bool curl=false;
   bool lsp=false;
   bool readline=false;
   bool editline=false;
   bool sigsegv=false;
   bool usegc=false;
   bool usethreads=false;

#if HAVE_LIBGLM
   glm=true;
#endif

#ifdef HAVE_GL
   gl=true;
#endif

#ifdef HAVE_SSBO
   ssbo=true;
#endif

#ifdef HAVE_LIBGSL
   gsl=true;
#endif

#ifdef HAVE_LIBFFTW3
   fftw3=true;
#endif

#ifdef HAVE_EIGEN_DENSE
   eigen=true;
#endif

#ifdef HAVE_LIBTIRPC
   xdr=true;
#endif

#ifdef HAVE_LIBCURL
   curl=true;
#endif

#ifdef HAVE_LSP
   lsp=true;
#endif

#ifdef HAVE_LIBCURSES
#ifdef HAVE_LIBREADLINE
   readline=true;
#else
#ifdef HAVE_LIBEDIT
   editline=true;
#endif
#endif
#endif

#ifdef HAVE_LIBSIGSEGV
   sigsegv=true;
#endif

#ifdef USEGC
   usegc=true;
#endif

#ifdef HAVE_PTHREAD
   usethreads=true;
#endif

   feature("V3D      3D vector graphics output",glm && xdr);
   feature("WebGL    3D HTML rendering",glm);
#ifdef HAVE_LIBOSMESA
   feature("OpenGL   3D OSMesa offscreen rendering",gl);
#else
   feature("OpenGL   3D OpenGL rendering",gl);
#endif
   feature("SSBO     GLSL shader storage buffer objects",ssbo);
   feature("GSL      GNU Scientific Library (special functions)",gsl);
   feature("FFTW3    Fast Fourier transforms",fftw3);
   feature("Eigen    Eigenvalue library",eigen);
   feature("XDR      External Data Representation (portable binary file format for V3D)",xdr);
   feature("CURL     URL support",curl);
   feature("LSP      Language Server Protocol",lsp);
   feature("Readline Interactive history and editing",readline);
   if(!readline)
     feature("Editline interactive editing (Readline is unavailable)",editline);
   feature("Sigsegv  Distinguish stack overflows from segmentation faults",
           sigsegv);
   feature("GC       Boehm garbage collector",usegc);
   feature("threads  Render OpenGL in separate thread",usethreads);
 }

 bool getOption() {
   version();
   features(1);
   features(0);
   exit(0);

   // Unreachable code.
   return true;
 }
};

struct divisorOption : public option {
 divisorOption(string name, char code, string argname, string desc)
   : option(name, code, argname, desc) {}

 bool getOption() {
   try {
#ifdef USEGC
     Int n=lexical::cast<Int>(optarg);
     if(n > 0) GC_set_free_space_divisor((GC_word) n);
#endif
   } catch (lexical::bad_cast&) {
     error("option requires an int as an argument");
     return false;
   }
   return true;
 }
};

// For security reasons, these options aren't fields of the settings module.
struct stringOption : public option {
 char **variable;
 stringOption(string name, char code, string argname,
              string desc, char **variable)
   : option(name, code, argname, desc, true), variable(variable) {}

 bool getOption() {
   *variable=optarg;
   return true;
 }
};

string build_optstring() {
 string s;
 for (codeMap_t::iterator p=codeMap.begin(); p !=codeMap.end(); ++p)
   s +=p->second->optstring();

 return s;
}

c_option *build_longopts() {
 size_t n=optionsMap.size();

 c_option *longopts=new(UseGC) c_option[n+1];
 Int i=0;
 for (optionsMap_t::iterator p=optionsMap.begin();
      p !=optionsMap.end();
      ++p, ++i)
   p->second->longopt(longopts[i]);

 longopts[n].name=NULL;
 longopts[n].has_arg=0;
 longopts[n].flag=NULL;
 longopts[n].val=0;

 return longopts;
}

void resetOptions()
{
 for(optionsMap_t::iterator opt=optionsMap.begin(); opt != optionsMap.end();
     ++opt)
   if(opt->first != "config" && opt->first != "dir" && opt->first != "sysdir")
     opt->second->reset();
}

void getOptions(int argc, char *argv[])
{
 bool syntax=false;
 optind=0;

 string optstring=build_optstring();
 //cerr << "optstring: " << optstring << endl;
 c_option *longopts=build_longopts();
 int long_index = 0;

 errno=0;
 for(;;) {
   int c = getopt_long_only(argc,argv,
                            optstring.c_str(), longopts, &long_index);
   if (c == -1)
     break;

   if (c == 0) {
     const char *name=longopts[long_index].name;
     //cerr << "long option: " << name << endl;
     if (!optionsMap[name]->getOption())
       syntax=true;
   }
   else if (codeMap.find((char)c) != codeMap.end()) {
     //cerr << "char option: " << (char)c << endl;
     if (!codeMap[(char)c]->getOption())
       syntax=true;
   }
   else {
     syntax=true;
   }

   errno=0;
 }

 if (syntax)
   reportSyntax();
}

#ifdef USEGC
void no_GCwarn(char *, GC_word)
{
}
#endif

array* stringArray(const char **s)
{
 size_t count=0;
 while(s[count])
   ++count;
 array *a=new array(count);
 for(size_t i=0; i < count; ++i)
   (*a)[i]=string(s[i]);
 return a;
}

void initSettings() {
 static bool initialize=true;
 if(initialize) {
#if defined(_WIN32)
   queryRegistry();
#endif
   initialize=false;
 }

 settingsModule=new types::dummyRecord(symbol::literalTrans("settings"));

// Default mouse bindings

// LEFT: rotate
// SHIFT LEFT: zoom
// CTRL LEFT: shift
// ALT LEFT: pan
 const char *leftbutton[]={"rotate","zoom","shift","pan",NULL};

// MIDDLE:
 const char *middlebutton[]={NULL};

// RIGHT: zoom
// SHIFT RIGHT: rotateX
// CTRL RIGHT: rotateY
// ALT RIGHT: rotateZ
 const char *rightbutton[]={"zoom","rotateX","rotateY","rotateZ",NULL};

// WHEEL_UP: zoomin
 const char *wheelup[]={"zoomin",NULL};

// WHEEL_DOWN: zoomout
 const char *wheeldown[]={"zoomout",NULL};

 addOption(new stringArraySetting("leftbutton", stringArray(leftbutton)));
 addOption(new stringArraySetting("middlebutton", stringArray(middlebutton)));
 addOption(new stringArraySetting("rightbutton", stringArray(rightbutton)));
 addOption(new stringArraySetting("wheelup", stringArray(wheelup)));
 addOption(new stringArraySetting("wheeldown", stringArray(wheeldown)));
 addOption(new stringArraySetting("suppress", new array));

 addOption(new warnSetting("warn", 0, "str", "Enable warning"));

 multiOption *view=new multiOption("View", 'V', "View output");
 view->add(new boolSetting("batchView", 0, "View output in batch mode",
                           msdos));
 view->add(new boolSetting("multipleView", 0,
                           "View output from multiple batch-mode files",
                           false));
 view->add(new boolSetting("interactiveView", 0,
                           "View output in interactive mode", true));
 addOption(view);
 addOption(new stringSetting("outformat", 'f', "format",
                             "Convert each output file to specified format",
                             ""));
 addOption(new boolSetting("svgemulation", 0,
                           "Emulate unimplemented SVG shading", true));
 addOption(new boolSetting("prc", 0,
                           "Embed 3D PRC graphics in PDF output", false));
 addOption(new boolSetting("v3d", 0,
                           "Embed 3D V3D graphics in PDF output", false));
 addOption(new boolSetting("toolbar", 0,
                           "Show 3D toolbar in PDF output", true));
 addOption(new boolSetting("axes3", 0,
                           "Show 3D axes in PDF output", true));
 addOption(new boolSetting("ibl", 0,
                           "Enable environment map image-based lighting", false));
 addOption(new stringSetting("image", 0,"str","Environment image name","snowyField"));
 addOption(new stringSetting("imageDir", 0,"str","Environment image library directory","ibl"));
 addOption(new stringSetting("imageURL", 0,"str","Environment image library URL","https://vectorgraphics.gitlab.io/asymptote/ibl"));
 addOption(new realSetting("render", 0, "n",
                           "Render 3D graphics using n pixels per bp (-1=auto)",
                           havegl ? -1.0 : 0.0));
 addOption(new realSetting("devicepixelratio", 0, "n", "Ratio of physical to logical pixels", 1.0));
 addOption(new IntSetting("antialias", 0, "n",
                          "Antialiasing width for rasterized output", 2));
 addOption(new IntSetting("multisample", 0, "n",
                          "Multisampling width for screen images", 4));
 addOption(new boolSetting("twosided", 0,
                           "Use two-sided 3D lighting model for rendering",
                           true));
 addOption(new boolSetting("GPUindexing", 0,
                           "Compute indexing partial sums on GPU", true));
 addOption(new boolSetting("GPUinterlock", 0,
                           "Use fragment shader interlock", true));
 addOption(new boolSetting("GPUcompress", 0,
                           "Compress GPU transparent fragment counts",
                           false));
 addOption(new IntSetting("GPUlocalSize", 0, "n",
                          "Compute shader local size", 256));
 addOption(new IntSetting("GPUblockSize", 0, "n",
                          "Compute shader block size", 8));

 addOption(new pairSetting("position", 0, "pair",
                           "Initial 3D rendering screen position"));
 addOption(new pairSetting("maxviewport", 0, "pair",
                           "Maximum viewport size",pair(0,0)));
 addOption(new pairSetting("viewportmargin", 0, "pair",
                           "Horizontal and vertical 3D viewport margin",
                           pair(0.5,0.5)));
 addOption(new boolSetting("webgl2", 0,
                           "Use webgl2 if available", false));
 addOption(new boolSetting("absolute", 0,
                           "Use absolute WebGL dimensions", false));
 addOption(new pairSetting("maxtile", 0, "pair",
                           "Maximum rendering tile size",pair(1024,768)));
 addOption(new boolSetting("iconify", 0,
                           "Iconify rendering window", false));
 addOption(new boolSetting("thick", 0,
                           "Render thick 3D lines", true));
 addOption(new boolSetting("thin", 0,
                           "Render thin 3D lines", true));
 addOption(new boolSetting("autobillboard", 0,
                           "3D labels always face viewer by default", true));
 addOption(new boolSetting("threads", 0,
                           "Use POSIX threads for 3D rendering", true));
 addOption(new boolSetting("fitscreen", 0,
                           "Fit rendered image to screen", true));
 addOption(new boolSetting("interactiveWrite", 0,
                           "Write expressions entered at the prompt to stdout",
                           true));
 addOption(new helpOption("help", 'h', "Show summary of options"));
 addOption(new helpOption("environment", 'e', "Show summary of environment settings"));
 addOption(new versionOption("version", 0, "Show version"));

 addOption(new pairSetting("offset", 'O', "pair", "PostScript offset"));
 addOption(new pairSetting("aligndir", 0, "pair",
                           "Directional page alignment (overrides align)"));
 addOption(new alignSetting("align", 'a', "C|B|T|Z",
                            "Center, Bottom, Top, or Zero page alignment",
                            "C"));

 addOption(new boolrefSetting("debug", 'd', "Enable debugging messages and traceback",&debug));
 addOption(new incrementSetting("verbose", 'v',
                                "Increase verbosity level (can specify multiple times)", &verbose));
 // Resolve ambiguity with --version
 addOption(new incrementOption("vv", 0,"", &verbose,2));
 addOption(new incrementOption("novv", 0,"", &verbose,-2));

 addOption(new boolSetting("keep", 'k', "Keep intermediate files"));
 addOption(new boolSetting("keepaux", 0,
                           "Keep intermediate LaTeX .aux files"));
 addOption(new engineSetting("tex", 0, "engine",
                             "latex|pdflatex|xelatex|lualatex|tex|pdftex|luatex|context|none",
                             "latex"));

 addOption(new boolSetting("twice", 0,
                           "Run LaTeX twice (to resolve references)"));
 addOption(new boolSetting("inlinetex", 0, "Generate inline TeX code"));
 addOption(new boolSetting("embed", 0, "Embed rendered preview image", true));
 addOption(new boolSetting("auto3D", 0, "Automatically activate 3D scene",
                           true));
 addOption(new boolSetting("autoplay", 0, "Autoplay 3D animations", false));
 addOption(new boolSetting("loop", 0, "Loop 3D animations", false));
 addOption(new boolSetting("interrupt", 0, "", false));
 addOption(new boolSetting("animating", 0, "", false));
 addOption(new boolSetting("reverse", 0, "reverse 3D animations", false));

 addOption(new boolSetting("inlineimage", 0,
                           "Generate inline embedded image"));
 addOption(new boolSetting("compress", 0,
                           "Compress images in PDF output", true));
 addOption(new boolSetting("parseonly", 'p', "Parse file"));
 addOption(new boolSetting("translate", 's',
                           "Show translated virtual machine code"));
 addOption(new boolSetting("tabcompletion", 0,
                           "Interactive prompt auto-completion", true));
 addOption(new realSetting("prerender", 0, "resolution",
                           "Prerender V3D objects (0 implies vector output)", 0));
 addOption(new boolSetting("lossy", 0,
                           "Use single precision for V3D reals", false));
 addOption(new boolSetting("listvariables", 'l',
                           "List available global functions and variables"));
 addOption(new boolSetting("where", 0,
                           "Show where listed variables are declared"));

 multiOption *mask=new multiOption("mask", 'm',
                                   "Mask fpu exceptions");
 mask->add(new boolSetting("batchMask", 0,
                           "Mask fpu exceptions in batch mode", false));
 mask->add(new boolSetting("interactiveMask", 0,
                           "Mask fpu exceptions in interactive mode", true));
 addOption(mask);

 addOption(new boolrefSetting("bw", 0,
                              "Convert all colors to black and white",&bw));
 addOption(new boolrefSetting("gray", 0, "Convert all colors to grayscale",
                              &gray));
 addOption(new boolrefSetting("rgb", 0, "Convert cmyk colors to rgb",&rgb));
 addOption(new boolrefSetting("cmyk", 0, "Convert rgb colors to cmyk",&cmyk));

 addSecureSetting(new boolrefSetting("safe", 0, "Disable system call",
                                     &safe, true));
 addSecureSetting(new boolrefSetting("globalwrite", 0,
                                     "Allow write to other directory",
                                     &globalWrite, false));
 addSecureSetting(new boolrefSetting("globalread", 0,
                                     "Allow read from other directory",
                                     &globalRead, true));
 addSecureSetting(new stringSetting("outname", 'o', "name",
                                    "Alternative output directory/file prefix"));
 addOption(new stringOption("cd", 0, "directory", "Set current directory",
                            &startpath));

 addOption(new compactSetting("compact", 0,
                              "Conserve memory at the expense of speed",
                              &compact));
 addOption(new divisorOption("divisor", 0, "n",
                             "Garbage collect using purge(divisor=n) [2]"));

 addOption(new stringSetting("prompt", 0,"str","Prompt","> "));
 addOption(new stringSetting("prompt2", 0,"str",
                             "Continuation prompt for multiline input ",
                             ".."));
 addOption(new boolSetting("multiline", 0,
                           "Input code over multiple lines at the prompt"));
 addOption(new boolrefSetting("xasy", 0,
                           "Interactive mode for xasy",&xasy));
 addOption(new boolrefSetting("keys", 0,
                           "Generate WebGL keys",&keys));

 addOption(new boolSetting("lsp", 0, "Interactive mode for the Language Server Protocol"));
 addOption(new envSetting("lspport", ""));
 addOption(new envSetting("lsphost", "127.0.0.1"));

 addOption(new boolSetting("wsl", 0, "Run asy under the Windows Subsystem for Linux"));

 addOption(new boolSetting("wait", 0,
                           "Wait for child processes to finish before exiting"));
 addOption(new IntSetting("inpipe", 0, "n","Input pipe",-1));
 addOption(new IntSetting("outpipe", 0, "n","Output pipe",-1));
 addOption(new boolSetting("exitonEOF", 0, "Exit interactive mode on EOF",
                           true));

 addOption(new boolSetting("quiet", 'q',
                           "Suppress welcome text and noninteractive stdout"));
 addOption(new boolSetting("localhistory", 0,
                           "Use a local interactive history file"));
 addOption(new IntSetting("historylines", 0, "n",
                          "Retain n lines of history",1000));
 addOption(new IntSetting("scroll", 0, "n",
                          "Scroll standard output n lines at a time",0));
 addOption(new IntSetting("level", 0, "n", "Postscript level",3));
 addOption(new boolSetting("autoplain", 0,
                           "Enable automatic importing of plain",
                           true));
 addOption(new boolSetting("autorotate", 0,
                           "Enable automatic PDF page rotation",
                           false));
 addOption(new boolSetting("offline", 0,
                           "Produce offline html files",false));
 addOption(new boolSetting("pdfreload", 0,
                           "Automatically reload document in pdfviewer",
                           false));
 addOption(new IntSetting("pdfreloaddelay", 0, "usec",
                          "Delay before attempting initial pdf reload"
                          ,750000));
 addOption(new stringSetting("autoimport", 0, "str",
                             "Module to automatically import"));
 addOption(new userSetting("command", 'c', "str",
                           "Command to autoexecute"));
 addOption(new userSetting("user", 'u', "str",
                           "General purpose user string"));

 addOption(new realSetting("zoomfactor", 0, "factor", "Zoom step factor",
                           1.05));
 addOption(new realSetting("zoomPinchFactor", 0, "n",
                           "WebGL zoom pinch sensitivity", 10));
 addOption(new realSetting("zoomPinchCap", 0, "limit",
                           "WebGL maximum zoom pinch", 100));
 addOption(new realSetting("zoomstep", 0, "step", "Mouse motion zoom step",
                           0.1));
 addOption(new realSetting("shiftHoldDistance", 0, "n",
                           "WebGL touch screen distance limit for shift mode",
                           20));
 addOption(new realSetting("shiftWaitTime", 0, "ms",
                           "WebGL touch screen shift mode delay",
                           200));
 addOption(new realSetting("vibrateTime", 0, "ms",
                           "WebGL shift mode vibrate duration",
                           25));
 addOption(new realSetting("spinstep", 0, "deg/s", "Spin speed",
                           60.0));
 addOption(new realSetting("framerate", 0, "frames/s", "Animation speed",
                           30.0));
 addOption(new realSetting("resizestep", 0, "step", "Resize step", 1.2));
 addOption(new IntSetting("digits", 0, "n",
                          "Default output file precision", 7));

 addOption(new realSetting("paperwidth", 0, "bp", "Default page width"));
 addOption(new realSetting("paperheight", 0, "bp", "Default page height"));

 addOption(new stringSetting("dvipsOptions", 0, "str", ""));
 addOption(new stringSetting("dvisvgmOptions", 0, "str", "", "--optimize"));
 addOption(new boolSetting("dvisvgmMultipleFiles", 0,
                           "dvisvgm supports multiple files", true));
 addOption(new stringSetting("convertOptions", 0, "str", ""));
 addOption(new stringSetting("gsOptions", 0, "str", ""));
 addOption(new stringSetting("htmlviewerOptions", 0, "str", ""));
 addOption(new stringSetting("psviewerOptions", 0, "str", ""));
 addOption(new stringSetting("pdfviewerOptions", 0, "str", ""));
 addOption(new stringSetting("pdfreloadOptions", 0, "str", ""));
 addOption(new stringSetting("glOptions", 0, "str", ""));
 addOption(new stringSetting("hyperrefOptions", 0, "str",
                             "","setpagesize=false,unicode,pdfborder=0 0 0"));

 addOption(new envSetting("config","config."+suffix));
 addOption(new envSetting("htmlviewer", defaultHTMLViewer));
 addOption(new envSetting("pdfviewer", defaultPDFViewer));
 addOption(new envSetting("psviewer", defaultPSViewer));
 addOption(new envSetting("gs", defaultGhostscript));
 addOption(new envSetting("libgs", defaultGhostscriptLibrary));
 addOption(new envSetting("epsdriver", defaultEPSdriver));
 addOption(new envSetting("psdriver", defaultPSdriver));
 addOption(new envSetting("pngdriver", defaultPNGdriver));
 addOption(new envSetting("asygl", defaultAsyGL));
 addOption(new envSetting("texpath", ""));
 addOption(new envSetting("texcommand", ""));
 addOption(new envSetting("dvips", "dvips"));
 addOption(new envSetting("dvisvgm", "dvisvgm"));
 addOption(new envSetting("convert", "magick"));
 addOption(new envSetting("display", defaultDisplay));
 addOption(new envSetting("animate", defaultAnimate));
 addOption(new envSetting("papertype", "letter"));
 addOption(new envSetting("dir", ""));
 addOption(new envSetting("sysdir", systemDir));
 addOption(new envSetting("textcommand","groff"));
 addOption(new envSetting("textcommandOptions","-e -P -b16"));
 addOption(new envSetting("textextension", "roff"));
 addOption(new envSetting("textoutformat", "ps"));
 addOption(new envSetting("textprologue", ".EQ\ndelim $$\n.EN"));
 addOption(new envSetting("textinitialfont", ".fam T\n.ps 12"));
 addOption(new envSetting("textepilogue", ".bp"));
}

// Access the arguments once options have been parsed.
int numArgs() { return argCount; }
char *getArg(int n) { return argList[n]; }

void setInteractive()
{
 if(xasy && getSetting<Int>("outpipe") < 0) {
   cerr << "Missing outpipe." << endl;
   exit(-1);
 }

 bool lspmode=getSetting<bool>("lsp");

 if(numArgs() == 0 && !getSetting<bool>("listvariables") &&
    getSetting<string>("command").empty() &&
    (isatty(STDIN_FILENO) || xasy || lspmode))
   interact::interactive=true;

 if(getSetting<bool>("localhistory"))
   historyname=string(getPath())+dirsep+"."+suffix+"_history";
 else {
#if defined(_WIN32)
   bool mkdirResult = CreateDirectoryA(initdir.c_str(), nullptr);
   bool mkdirSuccess = mkdirResult || GetLastError() == ERROR_ALREADY_EXISTS;
#else
   int mkdirResult = mkdir(initdir.c_str(),0777);
   bool mkdirSuccess = mkdirResult == 0 || errno == EEXIST;
#endif
   if(!mkdirSuccess)
     cerr << "failed to create directory "+initdir+"." << endl;
   historyname=initdir+"/history";
 }
 if(!quiet && verbose > 1)
   cerr << "Using history " << historyname << endl;
}

bool view()
{
 if (interact::interactive)
   return getSetting<bool>("interactiveView");
 else
   return getSetting<bool>("batchView") &&
     (numArgs() == 1 || getSetting<bool>("multipleView"));
}

bool trap()
{
 if (interact::interactive)
   return !getSetting<bool>("interactiveMask");
 else
   return !getSetting<bool>("batchMask");
}

string outname()
{
 string name=getSetting<string>("outname");
 if(name.empty() && interact::interactive) return standardprefix;
 if(msdos) backslashToSlash(name);
 return name;
}

string lookup(const string& symbol)
{
 string s;
 mem::vector<string> cmd;
 string kpsewhich="kpsewhich";
 string fullname=stripFile(argv0)+kpsewhich;
 std::ifstream exists(fullname.c_str());
 if(!exists) fullname=kpsewhich;
 cmd.push_back(fullname);
 cmd.push_back("--var-value="+symbol);
 iopipestream pipe(cmd);
 pipe >> s;
 size_t n=s.find('\r');
 if(n != string::npos)
   s.erase(n,1);
 n=s.find('\n');
 if(n != string::npos)
   s.erase(n,1);
 return s;
}

void initDir() {
 if(getSetting<string>("sysdir").empty()) {
   string s=lookup("TEXMFMAIN");
   if(s.size() > 1) {
     string texmf=s+dirsep;
     docdir=texmf+"doc"+dirsep+"asymptote";
     Setting("sysdir")=texmf+"asymptote";
     s=lookup("ASYMPTOTE_HOME");
     if(s.size() > 1)
       initdir=s;
   }
 }

 if(initdir.empty())
   initdir=Getenv("ASYMPTOTE_HOME",msdos);

 if(initdir.empty())
   initdir=Getenv(HOME.c_str(),msdos)+dirsep+"."+suffix;

#if defined(_WIN32)
 DWORD dirAttrib = GetFileAttributesA(initdir.c_str());
 bool dirExists = dirAttrib != INVALID_FILE_ATTRIBUTES && ((dirAttrib & FILE_ATTRIBUTE_DIRECTORY) != 0);
#else
 bool dirExists = access(initdir.c_str(),F_OK) == 0;
#endif

 if(dirExists) {
   if(!quiet && verbose > 1)
     cerr << "Using configuration directory " << initdir << endl;
 }
}

void setPath() {
 searchPath.clear();
 searchPath.push_back(".");
 string asydir=getSetting<string>("dir");
 if(asydir != "") {
   size_t p,i=0;
   while((p=asydir.find(pathSeparator,i)) < string::npos) {
     if(p > i) searchPath.push_back(asydir.substr(i,p-i));
     i=p+1;
   }
   if(i < asydir.length()) searchPath.push_back(asydir.substr(i));
 }
#if defined(_WIN32)
 DWORD dirAttrib = GetFileAttributesA(initdir.c_str());
 bool dirExists = dirAttrib != INVALID_FILE_ATTRIBUTES && ((dirAttrib & FILE_ATTRIBUTE_DIRECTORY) != 0);
#else
 bool dirExists = access(initdir.c_str(),F_OK) == 0;
#endif

 if(dirExists)
   searchPath.push_back(initdir);
 string sysdir=getSetting<string>("sysdir");
 if(sysdir != "")
   searchPath.push_back(sysdir);
 searchPath.push_back(docdir+"/examples");
}

void SetPageDimensions() {
 string paperType=getSetting<string>("papertype");

 if(paperType.empty() &&
    getSetting<double>("paperwidth") != 0.0 &&
    getSetting<double>("paperheight") != 0.0) return;

 if(paperType == "letter") {
   Setting("paperwidth")=8.5*inches;
   Setting("paperheight")=11.0*inches;
 } else {
   Setting("paperwidth")=21.0*cm;
   Setting("paperheight")=29.7*cm;

   if(paperType != "a4") {
     cerr << "Unknown paper size \'" << paperType << "\'; assuming a4."
          << endl;
     Setting("papertype")=string("a4");
   }
 }
}

bool xe(const string& texengine)
{
 return texengine == "xelatex";
}

bool lua(const string& texengine)
{
 return texengine == "luatex" || texengine == "lualatex";
}

bool context(const string& texengine)
{
 return texengine == "context";
}

bool pdf(const string& texengine)
{
 return texengine == "pdflatex" || texengine == "pdftex" || xe(texengine) ||
   lua(texengine) || context(texengine);
}

bool latex(const string& texengine)
{
 return texengine == "latex" || texengine == "pdflatex" ||
   texengine == "xelatex" || texengine == "lualatex";
}

string nativeformat()
{
 return pdf(getSetting<string>("tex")) ? "pdf" : "eps";
}

string defaultformat()
{
 string format=getSetting<string>("outformat");
 return (format.empty()) ? nativeformat() : format;
}

// TeX special command to set up currentmatrix for typesetting labels.
const char *beginlabel(const string& texengine)
{
 if(pdf(texengine))
   return xe(texengine) ? "\\special{pdf:literal q #5 0 0 cm}" :
     "\\special{pdf:q #5 0 0 cm}";
 else
   return "\\special{ps:gsave currentpoint currentpoint translate [#5 0 0] "
     "concat neg exch neg exch translate}";
}

// TeX special command to restore currentmatrix after typesetting labels.
const char *endlabel(const string& texengine)
{
 if(pdf(texengine))
   return xe(texengine) ? "\\special{pdf:literal Q}" : "\\special{pdf:Q}";
 else
   return "\\special{ps:currentpoint grestore moveto}";
}

// TeX macro to typeset raw postscript code
const char *rawpostscript(const string& texengine)
{
 if(pdf(texengine))
   return "\\def\\ASYraw#1{#1}";
 else
   return "\\def\\ASYraw#1{\n"
     "currentpoint currentpoint translate matrix currentmatrix\n"
     "100 12 div -100 12 div scale\n"
     "#1\n"
     "setmatrix neg exch neg exch translate}";
}

// TeX macro to begin picture
const char *beginpicture(const string& texengine)
{
 if(latex(texengine))
   return "\\begin{picture}";
 if(context(texengine))
   return "";
 else
   return "\\picture";
}

// TeX macro to end picture
const char *endpicture(const string& texengine)
{
 if(latex(texengine))
   return "\\end{picture}%";
 else if(context(texengine))
   return "%";
 else
   return "\\endpicture%";
}

// TeX macro to begin new page.
const char *newpage(const string& texengine)
{
 if(latex(texengine))
   return "\\newpage";
 else if(context(texengine))
   return "}\\page\\hbox{%";
 else
   return "\\eject";
}

// Begin TeX special command.
const char *beginspecial(const string& texengine)
{
 if(pdf(texengine))
   return xe(texengine) ? "\\special{pdf:literal " : "\\special{pdf:";
 else
   return "\\special{ps:";
}

// End TeX special command.
const char *endspecial()
{
 return "}%";
}

string texcommand()
{
 string command=getSetting<string>("texcommand");
 return command.empty() ? getSetting<string>("tex") : command;
}

string texprogram()
{
 string path=getSetting<string>("texpath");
 string engine=texcommand();
 return path.empty() ? engine : (string) (path+"/"+engine);
}

Int getScroll()
{
 Int scroll=settings::getSetting<Int>("scroll");
 if(scroll < 0) {
#ifdef HAVE_LIBCURSES
   static char *terminal=NULL;
   if(!terminal)
     terminal=getenv("TERM");
   if(terminal) {
#if defined(USE_SETUPTERM)
     int error=setupterm(terminal,1,&error);
     if(error == 0) scroll=lines > 2 ? lines-1 : 1;
     else
#endif
       scroll=0;
   } else scroll=0;
#else
   scroll=0;
#endif
 }
 return scroll;
}

void doConfig(string file)
{
 bool autoplain=getSetting<bool>("autoplain");
 bool listvariables=getSetting<bool>("listvariables");
 if(autoplain) Setting("autoplain")=false; // Turn off for speed.
 if(listvariables) Setting("listvariables")=false;

 runFile(file);

 if(autoplain) Setting("autoplain")=true;
 if(listvariables) Setting("listvariables")=true;
}

void setOptions(int argc, char *argv[])
{
 argv0=argv[0];

 cout.precision(DBL_DIG);

 // Build settings module.
 initSettings();

 // Read command-line options initially to obtain config, dir, sysdir,
 // verbose, and quiet.
 getOptions(argc,argv);

 quiet=getSetting<bool>("quiet");

 // Make configuration and history directory
 initDir();

 Int Verbose=verbose;
 string sysdir=getSetting<string>("sysdir");

 resetOptions();

 // Read user configuration file.
 setPath();
 string filename=getSetting<string>("config");
 if(!filename.empty()) {
   string file=locateFile(filename);
   if(!file.empty()) {
     if(!quiet && Verbose > 1)
       cerr << "Loading " << filename << " from " << file << endl;
     doConfig(file);
   }
 }

 // Read command-line options again to override configuration file defaults.
 getOptions(argc,argv);

 if(getSetting<Int>("outpipe") == 2) // Redirect cerr to cout
   std::cerr.rdbuf(std::cout.rdbuf());

 Setting("sysdir")=sysdir;

 if(docdir.empty())
   docdir=getSetting<string>("dir");

#ifdef USEGC
 if(verbose == 0 && !debug) GC_set_warn_proc(no_GCwarn);
#endif

 if(setlocale (LC_ALL, "") == NULL && debug)
   perror("setlocale");

 // Set variables for the file arguments.
 argCount = argc - optind;
 argList = argv + optind;

 // Recompute search path.
 setPath();

 if(getSetting<double>("paperwidth") != 0.0 &&
    getSetting<double>("paperheight") != 0.0)
   Setting("papertype")=string("");

 SetPageDimensions();

 setInteractive();
}

}