// $Id$
// Global flags for VSL evaluation (and the old VSE editor)

// Copyright (C) 1995 Technische Universitaet Braunschweig, Germany.
// Copyright (C) 2003 Free Software Foundation, Inc.
// Written by Andreas Zeller <zeller@gnu.org>.
// 
// This file is part of DDD.
// 
// DDD 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 3 of the License, or (at your option) any later version.
// 
// DDD 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 DDD -- see the file COPYING.
// If not, see <http://www.gnu.org/licenses/>.
// 
// DDD is the data display debugger.
// For details, see the DDD World-Wide-Web page, 
// `http://www.gnu.org/software/ddd/',
// or send a mail to the DDD developers <ddd@gnu.org>.

char VSEFlags_rcsid[] = 
    "$Id$";

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sstream>
#include <ctype.h>
#include <X11/Intrinsic.h>

#include "assert.h"
#include "strclass.h"
#include "VSEFlags.h"
#include "VSLLib.h"
#include "config.h"
#include "home.h"

#ifndef VSL_INCLUDE_PATH
#define VSL_INCLUDE_PATH "/usr/local/lib/vsl-include"
#endif

#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif

#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif


// Defaults

// help
static bool help                         = false;

// internal optimize options (only to be set)
bool VSEFlags::optimize_resolveDefs      = true;
bool VSEFlags::optimize_resolveSynonyms  = true;
bool VSEFlags::optimize_foldOps          = true;
bool VSEFlags::optimize_foldConsts       = true;
bool VSEFlags::optimize_inlineFuncs      = true;
bool VSEFlags::optimize_cleanup          = true;
int VSEFlags::max_optimize_loops         = 2;

// external optimize options
bool VSEFlags::incremental_eval          = true;
bool VSEFlags::optimize_globals          = true;

// assertion options
bool VSEFlags::assert_library_ok         = false;
bool VSEFlags::assert_document_ok        = false;

// dump to file options
bool VSEFlags::dump_tree                 = false;
bool VSEFlags::dump_library              = false;
bool VSEFlags::dump_picture              = false;
bool VSEFlags::dump_document             = false;
bool VSEFlags::dump_while_optimizing     = false;
bool VSEFlags::dump_last                 = false;

// show options
bool VSEFlags::show_optimize             = false;
bool VSEFlags::show_match_nodes          = false;
bool VSEFlags::show_match_boxes          = false;
bool VSEFlags::show_match_defs           = false;
bool VSEFlags::show_tiny_eval            = false;
bool VSEFlags::show_large_eval           = false;
bool VSEFlags::show_huge_eval            = false;
bool VSEFlags::show_balance              = false;
bool VSEFlags::show_flags                = false;
bool VSEFlags::show_backtrace            = true;
bool VSEFlags::show_ids                  = false;
bool VSEFlags::show_vars                 = false;
bool VSEFlags::show_draw                 = false;

// info options
bool VSEFlags::include_list_info         = false;
bool VSEFlags::include_size_info         = false;
bool VSEFlags::include_font_info         = false;
bool VSEFlags::include_const_info        = true;
bool VSEFlags::include_tag_info          = false;
bool VSEFlags::include_id_info           = false;
bool VSEFlags::include_select_info       = false;

// time options
bool VSEFlags::show_eval_time            = false;
bool VSEFlags::show_optimizing_time      = false;
bool VSEFlags::show_display_time         = false;

// eval options
bool VSEFlags::suppress_eval             = false;
bool VSEFlags::issue_nooptionals         = false;

// arguments
const char *VSEFlags::library_file             = "";
const char *VSEFlags::document_file            = "";
const char *VSEFlags::startup_file             = "";

// mode options
bool VSEFlags::bag_mode                  = false;
bool VSEFlags::concurrent_mode           = false;
bool VSEFlags::verbose                   = false;
bool VSEFlags::what                      = false;
bool VSEFlags::load_open_mode            = true;

// other settings
int VSEFlags::max_eval_nesting           = 500;
int VSEFlags::max_info_nesting           = -1;
int VSEFlags::max_pattern_variables      = 256;
int VSEFlags::loops                      = 1;
const char *VSEFlags::include_search_path      = "";


// Table options <-> names
VSEOptionTableEntry VSEFlags::optionTable[] = {

// Help options
{ TITLE,    "",                     "Help",
    0 },
{ BOOLEAN,  "help",                 "Show VSL options",
    &help },
{ BOOLEAN,  "?",                    "Show VSL options",
    &help },

// Settings
{ TITLE,    "",                     "Settings",
    0 },
{ BOOLEAN,  "verbose",              "Enter verbose mode",
    &verbose },
{ BOOLEAN,  "bag",                  "Enter bag mode",
    &bag_mode },
{ BOOLEAN,  "open",                 "Open documents when loaded",
    &load_open_mode },
{ BOOLEAN,  "concurrent",           "Allow concurrent editing",
    &concurrent_mode },
{ BOOLEAN,  "what",                 "Display source file versions",
    &what },
{ STRING,   "library",              "Load vsl library",
    &library_file },
{ STRING,   "document",             "Load document",
    &document_file },
{ STRING,   "startup",              "Load startup file",
    &startup_file },
{ STRING,   "I",                    "Set #include search path",
    &include_search_path },
{ STRING,   "include",              "Set #include search path",
    &include_search_path },

// Optimize options
{ TITLE,    "",                     "Optimizing",
    0 },
{ BOOLEAN,  "optimize-inline-functions",    "Perform inlineFuncs optimization",
    &optimize_inlineFuncs },
{ BOOLEAN,  "optimize-fold-ops",            "Perform foldOps optimization",
    &optimize_foldOps },
{ BOOLEAN,  "optimize-fold-consts",         "Perform foldConsts optimization",
    &optimize_foldConsts },
{ BOOLEAN,  "optimize-resolve-defs",        "Perform resolveDefs optimization",
    &optimize_resolveDefs },
{ BOOLEAN,  "optimize-resolve-synonyms",    "Perform resolveSynonyms optimization",
    &optimize_resolveSynonyms },
{ BOOLEAN,  "optimize-cleanup",             "Perform cleanup between optimization stages",
    &optimize_cleanup },
{ BOOLEAN,  "optimize-incremental-eval",    "Perform incremental evaluation",
    &incremental_eval },
{ BOOLEAN,  "optimize-",                    "Perform all optimizations",
    0 },
{ INT,      "max-optimize-loops",           "Set maximum #optimize loops",
    &max_optimize_loops },

// Debugging Options
{ TITLE,    "",                     "Debugging",
    0 },
{ BOOLEAN,  "debug-assert-document-ok",  "Assert document::OK()",
    &assert_document_ok },
{ BOOLEAN,  "debug-assert-library-ok",   "Assert library::OK()",
    &assert_library_ok },
{ BOOLEAN,  "debug-show-tree",           "Show library tree",
    &dump_tree },
{ BOOLEAN,  "debug-show-library",        "Show library (in VSL)",
    &dump_library },
{ BOOLEAN,  "debug-show-last",           "Show only last library def",
    &dump_last },
{ BOOLEAN,  "debug-show-document",       "Show document (in VSL)",
    &dump_document },
{ BOOLEAN,  "debug-show-picture",        "Show picture (in VSL)",
    &dump_picture },
{ BOOLEAN,  "debug-const-info",          "Include const info",
    &include_const_info },
{ BOOLEAN,  "debug-list-info",           "Include list info",
    &include_list_info },
{ BOOLEAN,  "debug-box-fonts-info",      "Include box font info",
    &include_font_info },
{ BOOLEAN,  "debug-box-id-info",         "Include box id info",
    &include_id_info },
{ BOOLEAN,  "debug-box-tag-info",        "Include box tag info",
    &include_tag_info },
{ BOOLEAN,  "debug-box-size-info",       "Include box size info",
    &include_size_info },
{ BOOLEAN,  "debug-doc-select-info",     "Include document select state info",
    &include_select_info },
{ BOOLEAN,  "debug-show-balance",        "Show balance",
    &show_balance },
{ BOOLEAN,  "debug-show-eval-huge",      "Show huge evaluation (documents)",
    &show_huge_eval },
{ BOOLEAN,  "debug-show-eval-large",     "Show large evaluation (defs)",
    &show_large_eval },
{ BOOLEAN,  "debug-show-eval-tiny",      "Show tiny evaluation (nodes)",
    &show_tiny_eval },
{ BOOLEAN,  "debug-show-backtrace",      "Show backtrace on errors",
    &show_backtrace },
{ BOOLEAN,  "debug-show-ids",            "Show id creation and deletion",
    &show_ids },
{ BOOLEAN,  "debug-show-vars",           "Show variable declaration and use",
    &show_vars },
{ BOOLEAN,  "debug-show-draw",           "Show drawing",
    &show_draw },
{ BOOLEAN,  "debug-show-flags",          "Show flag processing",
    &show_flags },
{ BOOLEAN,  "debug-show-optimize",       "Show optimization",
    &show_optimize },
{ BOOLEAN,  "debug-show-match-nodes",    "Show pattern matching (nodes)",
    &show_match_nodes },
{ BOOLEAN,  "debug-show-match-boxes",    "Show pattern matching (boxes)",
    &show_match_boxes },
{ BOOLEAN,  "debug-show-match-defs",     "Show pattern matching (defs)",
    &show_match_defs },
{ BOOLEAN,  "debug-time-display",        "Show display time",
    &show_display_time },
{ BOOLEAN,  "debug-time-evaluation",     "Show evaluation time",
    &show_eval_time },
{ BOOLEAN,  "debug-time-optimizing",     "Show optimizing time",
    &show_optimizing_time },
{ BOOLEAN,  "debug-optimize-globals",    "Perform foldConsts on global defs",
    &optimize_globals },
{ BOOLEAN,  "debug-suppress-eval",       "Skip evaluation and picture",
    &suppress_eval },
{ BOOLEAN,  "debug-issue-nooptionals",   "Generate code for nooptionals",
    &issue_nooptionals },

// Sizes
{ TITLE,    "",                     "Sizes",
    0 },
{ INT,      "eval-nesting",        "Set max eval nesting",
    &max_eval_nesting },
{ INT,      "info-nesting",        "Set max info nesting",
    &max_info_nesting },
{ INT,      "pattern-variables",   "Set max #variables in pattern",
    &max_pattern_variables },
{ INT,      "eval-loops",          "Set #evaluation loops",
    &loops },

// Last Option
{ LAST,     "",                     "",
    0 }
};

// Read options; show help and exit if required
void VSEFlags::parse(int& argc, const char**& argv, const char *args)
{
    // Get default settings
    getDefaults(true);

    // Parse arguments
    if (_parse(argc, argv, false))
    {
	std::cerr << argv[0] << ": usage: " << argv[0] << " [options] "
	     << args << "\n\n" << explain();

	exit(EXIT_FAILURE);
    }
}

bool VSEFlags::parse_vsl(int& argc, const char**& argv)
{
    // Get default settings
    getDefaults(false);

    return _parse(argc, argv, true);
}
    

// Read options; return true if help is required
bool VSEFlags::_parse(int& argc, const char**& argv, bool vsl_prefix_required)
{
    int arg = 1;

    while (arg < argc)
    {
	int skip = 0;

	if (argv[arg] == 0)
	    break;                      // No more args

	if (argv[arg][0] != '-')
	    break;			// No option

	if (argv[arg][1] == '\0')
	    break;		        // No option ("-")

	if (argv[arg][1] == '-' && argv[arg][2] == '\0')
	    break;                      // End of options ("--")

	string opt = argv[arg];         // Full option

	// Remove "no-" prefix
	bool no = false;
	if (opt.contains("no-"))
	{
	    opt.del("no-");
	    no = true;
	}

	// Build option name after leading `-'
	string optName = opt;
	while (optName[0] == '-')
	    optName = optName.after('-');

	// If `vsl-' prefix is required, ignore all other options
	if (vsl_prefix_required)
	    if (optName.index("vsl") != 0)
		break;

	// `vsl-' prefix is optional
	if (optName.index("vsl-") == 0)
	    optName = optName.after("vsl-");

	for (int nentry = 0; optionTable[nentry].type != LAST; nentry++)
	{
	    VSEOptionTableEntry entry = optionTable[nentry];
	    string entryName = entry.name;

	    if (entryName.index(optName) == 0)
	    {   
		// entryName starts with optName: process option

		switch (entry.type)
		{
		    case TITLE:
			break;

		    case BOOLEAN:
		    {
			bool *boolptr = (bool *)entry.flag;
			if (boolptr)
			    *boolptr = !no;
			skip = 1;
			break;
		    }

		    case INT:
		    {
			if (arg + 1 < argc)
			{
			    int *intptr = (int *)entry.flag;
			    if (intptr)
				*intptr = atoi(argv[arg + 1]);
			    skip = 2;
			}
			break;
		    }

		    case STRING:
		    {
			if (arg + 1 < argc)
			{
			    const char **stringptr = (const char **)entry.flag;
			    if (stringptr)
				*stringptr = argv[arg + 1];
			    skip = 2;
			}
			break;
		    }

		    default:
			// This can't happen
			assert(2 + 2 == 5);
			break;
		}

		if (show_flags)
		{
		    // Show processed option
		    std::cout << "processed flag: " << argv[arg] << " => -";

		    if (no && entry.type == BOOLEAN)
		    {
			if (entryName.contains("debug-"))
			    std::cout << entryName.through("debug-") << "no-" 
				 << entryName.after("debug-");
			else if (entryName.contains("optimize-"))
			    std::cout << entryName.through("optimize-") << "no-" 
				 << entryName.after("optimize-");
			else
			    std::cout << "no-" << entryName;
		    }
		    else
			std::cout << entryName;

		    for (int i = 1; i < skip; i++)
			std::cout << " " << argv[arg + i];
		    std::cout << "\n";
		}
	    }
	}

	if (skip > 0)
	{
	    // Remove option from argument list
	    for (int a = arg; a < argc; a++)
		argv[a] = argv[a + skip];
	    argc -= skip;
	}
	else
	{
	    // Process next argument; keep this one
	    arg++;
	}
    }

    return help;
}


// Read default options from .vserc, ~/.vserc, /usr/lib/vse/vse-options
void VSEFlags::getDefaults(bool warn)
{

    string filename = ".vserc";
    FILE *fp = fopen(filename.chars(), "r");
    if (fp == 0)
    {
	string home = gethome();
	filename.prepend(home + "/");
	fp = fopen(filename.chars(), "r");

	if (fp == 0)
	{
	    static string path = 
		string(VSL_INCLUDE_PATH) +
		":/usr/local/vse"
		":/usr/vse"
		":/usr/local/lib/vse"
		":/usr/lib/vse"
		":/usr/local/lib"
		":/usr/lib";

	    char buffer[BUFSIZ];
	    strcpy(buffer, path.chars());

	    for (char *p = strtok(buffer, ":"); 
		 p != 0; 
		 p = strtok(0, ":"))
	    {
		filename = string(p) + "/vserc";

		fp = fopen(filename.chars(), "r");
		if (fp != 0)
		    break;
	    }

	    if (warn)
		std::cerr << "Warning: could not find .vserc, ~/.vserc or vserc\n";
	    return;
	}
    }

    static const char *argv[3];
    static char buf1[BUFSIZ];
    static char buf2[BUFSIZ];

    int nargs;
    do {
	nargs = fscanf(fp, "%s%*[ \t]%s%*[ \t]%*[\n]", buf1, buf2);

	if (nargs > 0 && buf1[0] == '-')
	{
	    argv[0] = "dummy";
	    argv[1] = "";
	    argv[2] = "";
	    if (nargs >= 1)
	    {
		argv[1] = (String)XtNewString(buf1);
		if (nargs >= 2)
		    argv[2] = (String)XtNewString(buf2);
	    }
	    int myargc = nargs + 1;
	    const char **myargv = argv;

	    _parse(myargc, myargv, false);

	    for (int i = 1; i < myargc; i++)
		if (myargv[i][0] == '-')
		    std::cerr << filename << ": unknown option " << myargv[i] 
			 << "\n";
	}
    } while (nargs != EOF);

    if (fclose(fp) == EOF)
	perror(filename.chars());

    return;
}


// Explain options (and report default values)
string VSEFlags::explain(bool vsl_prefix_required)
{
    help = false;   // Always report '-help' (we'd get `-no-help' otherwise)

    string s = "Options include:\n";
    if (vsl_prefix_required)
	s = "List of VSL options:\n";

    for (int nentry = 0; optionTable[nentry].type != LAST; nentry++)
    {
	VSEOptionTableEntry entry = optionTable[nentry];

	if (entry.type == TITLE)
	{
	    s += '\n';
	    s += entry.usage;
	    s += '\n';
	    continue;
	}

	string optName = entry.name;
	string optArg  = "";
	string usage   = entry.usage;
	string deflt   = "";

	if (vsl_prefix_required)
	    optName.prepend("vsl-");

	switch (entry.type) {
	    case BOOLEAN:
	    {
		bool *boolptr = (bool *)entry.flag;
		if (boolptr)
		{
		    if (*boolptr)
		    {
			if (optName.contains("debug-"))
			    optName = optName.through("debug-") + "no-" 
				 + optName.after("debug-");
			else if (optName.contains("optimize-"))
			    optName = optName.through("optimize-") + "no-" 
				 + optName.after("optimize-");
			else if (optName.contains("vsl-"))
			    optName = optName.through("vsl-") + "no-" 
				 + optName.after("vsl-");
			else
			    optName = "no-" + optName;

			usage[0] = tolower(usage[0]);
			usage.prepend("Don't ");
		    }
		}
		break;
	    }

	    case INT:
	    {
		int *intptr = (int *)entry.flag;

		optArg = " [num]";
		usage += " to 'num'";

		if (intptr)
		{
		    std::ostringstream os;
		    os << " (default: " << *intptr << ")";
		    deflt = string(os);
		}
		break;
	    }

	    case STRING:
	    {
		char **stringptr = (char **)entry.flag;

		optArg = " [name]";
		usage += " 'name'";

		if (stringptr)
		    deflt = string(" (default: '") + *stringptr + "')";
		break;
	    }
	
	    default:
		// This can't happen.
		assert(1 + 1 == 3);
		::abort();
		break;
	}

	optName = "--" + optName + optArg + ":";
	while (optName.length() < 36)
	    optName += " ";
	s += optName;
	usage += deflt;
	s += usage;
	s += "\n";
    }

    s += "\n";
    s += (vsl_prefix_required ? "VSL options" : "Options");
    s += " may be abbreviated, which causes all matching options"
	 " to be set.\n";

    return s;
}


// Return optimization mode
unsigned VSEFlags::optimize_mode()
{
    unsigned mode = 0;
    
    if (optimize_resolveDefs)
	mode |= ResolveDefs;
    if (optimize_resolveSynonyms)
	mode |= ResolveSynonyms;
    if (optimize_foldOps)
	mode |= FoldOps;
    if (optimize_foldConsts)
	mode |= FoldConsts;
    if (optimize_inlineFuncs)
	mode |= InlineFuncs;
    if (optimize_cleanup)
	mode |= Cleanup;

    mode |= max_optimize_loops;

    return mode;
}
