/*
 * Jeffrey Friedl
 * Omron Corporation			左丞伕件﹋堪﹌
 * Nagaokakyoshi, Japan			◣617贏痊詁票酗執儈
 *
 * jfriedl@nff.ncl.omron.co.jp
 *
 * This work is placed under the terms of the GNU General Purpose License
 * (the "GNU Copyleft").
 *
 * December 1993.
 */
#include <stdio.h>
#include <ctype.h>

#include "lib/system.h"
#if defined(_HAVE_SYS_FCNTL_H_)
# include <sys/fcntl.h>
#elif defined(_HAVE_FCNTL_H_)
# include <fcntl.h>
#endif

#include "lib/config.h"
#include "lib/assert.h"
#include "lib/jreadline.h"
#include "lib/jregex.h"
#include "lib/loadfile.h"
#include "lib/output.h"
#include "lib/replace.h"
#include "lib/strsave.h"
#include "lib/xmalloc.h"
#include "lib/euc.h"
#include "lookup.h"
#include "eval.h"


/*
 * The input string is "0", "1", "on", or "off".
 * Return 0 for "0" or "off", 1 otherwise.
 */
static unsigned interpret_boolean(String *str)
{
    if (str[0] == '0' || str[1] == 'f' || str[1] == 'F')
	return 0;
    if (str[0] == '1' || str[1] == 'n' || str[1] == 'N')
	return 1;
    die("<don't know what to do with [%s]>\n", str);
    return 0; /* in case assert isn't on */
}

/*
 * Returned by each command.
 */
#define COMMAND_RUNS_OK			0
#define COMMAND_HAS_ERROR		1

#define SIMPLE_GLOBAL_FLAG(VAR, DESC)                                        \
    static int cmd_set_default_ ## VAR ## _flag(String *bool)                \
    {                                                                        \
	String *when = (String*)"is";                                        \
	if (bool)                                                            \
	{                                                                    \
	    lookup.flag.VAR = interpret_boolean(bool);                       \
	    if (!lookup.flag.verbose)                                        \
		return COMMAND_RUNS_OK;                                      \
	    when = (String*)"now";                                           \
	}                                                                    \
	outputf("%s %s %s.\n", DESC, when, lookup.flag.VAR ? "on" : "off");  \
	return COMMAND_RUNS_OK;                                              \
    }

#define SIMPLE_LOCAL_FLAG(VAR, DESC)                                         \
    static int cmd_set_local_ ## VAR ## _flag(String *bool)                  \
    {                                                                        \
	String *when = (String*)"is";                                        \
	kibishii_assert(lookup.slot != 0);                                   \
	if (bool) {                                                          \
	    lookup.slot->default_flag.VAR = interpret_boolean(bool);         \
	    if (!lookup.flag.verbose)                                        \
		return COMMAND_RUNS_OK;                                      \
	    when = (String*)"now";                                           \
	}                                                                    \
	outputf("slot #%d's %s %s %s.\n", slot_num(lookup.slot),             \
		DESC, when, (lookup.slot->default_flag.VAR) ? "on" : "off"); \
	return COMMAND_RUNS_OK;                                              \
    }

#define GLOBAL_AND_LOCAL_FLAG(VAR, DESC) \
    SIMPLE_GLOBAL_FLAG(VAR, DESC)        \
    SIMPLE_LOCAL_FLAG(VAR, DESC)

SIMPLE_GLOBAL_FLAG(debug,       "general debugging")
SIMPLE_GLOBAL_FLAG(regex_debug, "regex debugging")
SIMPLE_GLOBAL_FLAG(cmd_debug,   "command debugging")

GLOBAL_AND_LOCAL_FLAG(word,      "word preference mode")
GLOBAL_AND_LOCAL_FLAG(glob,      "wildcard-pattern mode")
GLOBAL_AND_LOCAL_FLAG(fold,      "case folding")
GLOBAL_AND_LOCAL_FLAG(display,   "matching-line display")
GLOBAL_AND_LOCAL_FLAG(fuzz,      "fuzzification")
GLOBAL_AND_LOCAL_FLAG(highlight, "highlighting")
GLOBAL_AND_LOCAL_FLAG(autokana,  "auto-kana")


/*
 * Used to turn verbosity on or off, or report its value.
 * Specialized version of command_flag() so that we don't say "verbosity on"
 * when we turn it on.
 */
static int set_verbose_flag(String *bool)
{
    if (bool)
    {
	lookup.flag.verbose = interpret_boolean(bool);
	return COMMAND_RUNS_OK;
    }
    outputf("verbosity is %s.\n", lookup.flag.verbose ? "on" : "off");
    return COMMAND_RUNS_OK;
}

/*
 * Given a filename, load it.
 */
static int cmd_load(const char *filename)
{
    int slotnum =
	load_file(expand_filename_tilde(filename),
		  LOADFILE_READifPRESENT);

    if (slotnum >= 0)
    {
	lookup.slot = lookup.slot_info[slotnum];
	return COMMAND_RUNS_OK;
    }
    return COMMAND_HAS_ERROR;
}

int cmd_log(int closelog, int append, String *File)
{
  #ifndef LOG_FILE_SUPPORT
    outputf("%s: log file support not compiled in\n", lookup.where);
  #else
    int fd = set_extra_output_file(JUST_CHECKING_OUTPUT_FILE);

    kibishii_assert((!closelog && !append && !File) || /* no args ok */
		    (closelog && !append && !File) ||  /* or just close ok */
		    !closelog);                        /* or not close ok */
    kibishii_assert(!append || File); /* APPEND only allowed with FILE */
    kibishii_assert(!output_fd_valid(fd) == !current_log_file);

    if (closelog)
    {
	/* close any current log */
	if (!output_fd_valid(fd))
	{
	    if (lookup.flag.verbose)
		outputf("%sno log file to close\n", lookup.where);
	} else {
	    flush_output();
	    close(fd);
	    set_extra_output_file(NO_OUTPUT_FILE);
	    if (lookup.flag.verbose)
		outputf("%swrote log file" quote(%s) "\n",
			lookup.where, current_log_file);
	    free(current_log_file);
	    current_log_file = 0;
	}
	return COMMAND_RUNS_OK; /* close-failure should not stop cmd loads */
    }

    if (File)
    {
	/* open a new file */
	const char *file = (const char *)File;
	int flags = O_WRONLY|O_CREAT | (append ? O_APPEND : O_TRUNC);

	if (output_fd_valid(fd))
	{
	    outputf("%salready logging to" quote(%s) "\n",
		    lookup.where, current_log_file);
	    return COMMAND_HAS_ERROR;
	}
	file = expand_filename_tilde(file);
	if (fd = open(file, flags, 0644), fd <0)
	{
	    outputf("%sbad open of" quote(%s) ": %n", lookup.where, file);
	    return COMMAND_HAS_ERROR;
	}
	if (lookup.flag.verbose)
	    outputf("%soutput now %sing to" quote(%s) "\n",
		    lookup.where, append ? "append" : "logg", file);
	flush_output();

	/*
	 * If we're appending, we'll add a line to the log, but not to
	 * the normal output. So we'll leave the log unset, set the normal
	 * output temporarily to the log file, print, then put the normal
	 * output back.
	 */
	if (append)
	{
	    int normal_fd = set_normal_output_file(fd);
	    output("\n---------------------------------------------------\n");
	    flush_output();
	    set_normal_output_file(normal_fd);
	}
	set_extra_output_file(fd); /* set the log */
	current_log_file = (const unsigned char *)strsave((String *)file);
	return COMMAND_RUNS_OK;
    }

    /* just asking about the current status */
    if (!output_fd_valid(fd))
	output("output not currently logged.\n");
    else
	outputf("output currently logged to" quote(%s) "\n", current_log_file);
 #endif /* LOG_FILE_SUPPORT */
    return COMMAND_RUNS_OK;
}

/*
 * Set the prompt, or show the current one.
 */
static int cmd_set_prompt(int local, String *new_prompt)
{
    kibishii_assert(!local || lookup.slot);

    if (new_prompt == 0)
    {
	if (!local)
	    outputf("prompt format is" quote(%s) "\n", lookup.prompt_format);
	else if (lookup.slot->prompt_format)
	    outputf("local prompt format for selected slot is" quote(%s) "\n",
		    lookup.slot->prompt_format);
	else
	    outputf("no local prompt for selected slot.\n");
    }
    else
    {
	if (local)
	{
	    if (lookup.slot->prompt_format)
		free(lookup.slot->prompt_format);
	    lookup.slot->prompt_format = strsave(new_prompt);
	}
	else
	{
	    if (lookup.prompt_format)
		free(lookup.prompt_format);
	    lookup.prompt_format = strsave(new_prompt);
	}
    }
    return COMMAND_RUNS_OK;
}

/*
 * Clear the screen.
 */
static int cmd_clear(void)
{
    static enum { unchecked, yes, no} is_xterm = unchecked;

    if (is_xterm == unchecked)
    {
	extern const char *getenv(const char *);
	String *term = (String *)getenv("TERM");
	if (term && (strNcmp(term, "kterm", 5) == 0 ||
		     strNcmp(term, "xterm", 5) == 0 ||
		     strNcmp(term, "vt100", 5) == 0))
	{
	    is_xterm = yes;
	} else {
	    is_xterm = no;
	}
    }

    if (is_xterm == yes)
    {
	(void)output_pager_transparent(1);
	output("\033[H\33[2J");
	(void)output_pager_transparent(0);
	output_pager_reset_more();
	flush_output();
    } else {
	flush_output();
	system("clear");
    }

    return COMMAND_RUNS_OK;
}


static int cmd_toggle_filter(String *bool)
{
    const char *when = "";

    kibishii_assert(lookup.slot);

    if (!COMBO(lookup.slot) && lookup.slot->filter_spec.pattern == 0) {
	outputf("%sno filter installed.\n", lookup.where);
	return bool ? COMMAND_HAS_ERROR : COMMAND_RUNS_OK;
    }

    if (bool)
    {
	lookup.slot->default_flag.filter = interpret_boolean(bool);
	if (!lookup.flag.verbose)
	    return COMMAND_RUNS_OK;
	when = "now ";
    }

    output("filter ");

    if (COMBO(lookup.slot))
    {
	outputf("flag is %s for combo slot #%d.\n",
		lookup.slot->default_flag.filter ? "on" : "off",
		slot_num(lookup.slot));
    } else {
	if (lookup.slot->filter_spec.name)
	    outputf("(" quote(%s) ") ", lookup.slot->filter_spec.name);
	outputf("(%s%sabled): %s" quote(%s) "\n", when,
		lookup.slot->default_flag.filter ? "en" : "dis",
		lookup.slot->filter_spec.negative ? "!" : "",
		lookup.slot->filter_spec.pattern);
	if (lookup.slot->default_flag.filter) {
	    if (lookup.list.size == 0)
		outputf("%snote: no filtered lines will be saved (change with "
			"\"set filter list\").\n", lookup.where);
	    else
		outputf("(first %d filtered lines will be saved each time).\n",
			lookup.list.size);
	}
    }
    return COMMAND_RUNS_OK;
}



/* set the filter for a file */
static int cmd_filter(String *filter_regex, String *filter_name,
		      int is_negative, int is_case_insensitive)
{
    unsigned flags = REGCOMP_SAVE_MATCHED_PAREN_INFO;

    kibishii_assert(lookup.slot);

    if (COMBO(lookup.slot))
    {
	outputf("%sslot is a combo; no filter allowed.\n", lookup.where);
	return COMMAND_HAS_ERROR;
    }

    if (is_case_insensitive)
	flags |= REGCOMP_IGNORE_CASE;

    if (lookup.slot->filter_spec.name)
    {
	free(lookup.slot->filter_spec.name);
	lookup.slot->filter_spec.name = 0;
    }
    if (lookup.slot->filter_spec.pattern)
    {
	free(lookup.slot->filter_spec.pattern);
	lookup.slot->filter_spec.pattern = 0;
	regfree(&lookup.slot->filter_spec.regex);
    }

    if (regcomp(&lookup.slot->filter_spec.regex, filter_regex, flags)
	!= REGCOMP_SUCCESS)
    {
	outputf("%s%s.\n", lookup.where,
		(string*)regcomp_error_report());
	return COMMAND_HAS_ERROR;
    }
    lookup.slot->filter_spec.name = filter_name ? strsave(filter_name) : 0;
    lookup.slot->filter_spec.pattern = strsave(filter_regex);
    lookup.slot->filter_spec.negative = is_negative;
    lookup.slot->default_flag.filter = 1;
    return COMMAND_RUNS_OK;
}

/* set the size of the filter, etc, list */
int cmd_list_size(String *spec)
{
    const char *when = "is";

    if (spec)
    {
	int length = atoi(spec);
	if (length != lookup.list.size) /* no work if already that size */
	{
	    void *mem = 0;
	    if (length)
	    {
		mem = xmalloc(length * sizeof(lookup.list.array[0]));
		if (mem == 0)
		{
		    outputf("%scan't allocate memory for filter "
			    "list of length %d.\n", lookup.where, length);
		    return COMMAND_HAS_ERROR;
		}
	    }

	    if (lookup.list.array)
		free(lookup.list.array);
	    lookup.list.array = mem;
	    lookup.list.size = length;
	    lookup.list.used = 0;
	}

	if (!lookup.flag.verbose)
	    return COMMAND_RUNS_OK;
	when = "now";
    }
    outputf("maximum filter list size %s: %d.\n", when, lookup.list.size);
    if (lookup.list.used)
	outputf("current lines held: %d.\n", lookup.list.used);
    return COMMAND_RUNS_OK;
}

static int cmd_set_limit(String *spec)
{
    const char *when = "is";

    if (spec)
    {
	int value = atoi(spec);

	if (value < 0)
	{
	    outputf("%slimit of %d makes no sense.\n",
		    lookup.where, value);
	    return COMMAND_HAS_ERROR;
	}

	lookup.max_lines_to_print = value;
	if (!lookup.flag.verbose)
	    return COMMAND_RUNS_OK;
	when = "now";
    }
    if (lookup.max_lines_to_print == 0)
	outputf("line-per-search limit %s disabled.\n", when);
    else
	outputf("line-per-search limit %s %ld.\n",
		when, lookup.max_lines_to_print);

    return COMMAND_RUNS_OK;
}

static int cmd_do_search(String *search)
{
    string *copy;
    int clear_slot_when_done = 0;

    if (lookup.slot == 0)
    {
	if (lookup.default_slot)
	{
	    lookup.slot = lookup.default_slot;
	    clear_slot_when_done = 0;
	} else {
	    outputf("%sno files loaded.\n", lookup.where);
	    return COMMAND_HAS_ERROR;
	}
    }

    copy = strsave(search);
    (void)process_input_line(copy, 1);
    free(copy);
    if (clear_slot_when_done)
	lookup.slot = 0;
    return COMMAND_RUNS_OK;
}

static int cmd_if(String *expr, String *command)
{
    int value = eval(expr);
    if (eval_error_val != EVAL_OK)
    {
	outputf("ERROR: %s\nAT-----", expr);
	while (expr++ < eval_error_loc)
	    outchar('-');
	outputf("^\n%sexpression: [%s]\n", lookup.where,
		eval_errstr[eval_error_val]);
	return COMMAND_HAS_ERROR;
    }
    if (value) {
	int length = str_len(command)+1;
	string *writable = xmalloc(length);
	MOVE_MEMORY(command, writable, length);
	parse_command(writable, str_len(writable), CMD_GENERAL|CMD_FILE_ONLY, 0);
	free(writable);
    }
    return COMMAND_RUNS_OK;
}

static int cmd_msg(String *msg)
{
  #ifdef HAVE_SPINNER
    /* output an initial space if the first char is a tab, so that it
       will be sure to erase any spinner */
    if (msg[0] == '\t' && lookup.spinner.interval)
    {
      #ifndef LOG_FILE_SUPPORT
	outchar(' ');
      #else
	/*
	 * But we don't want to output the space to the log file,
	 * (if there is one), so we have to be careful about that.
	 */
	int log_fd = set_extra_output_file(JUST_CHECKING_OUTPUT_FILE);
	if (current_log_file == 0)
	    outchar(' ');
	else {
	    flush_output();
	    set_extra_output_file(NO_OUTPUT_FILE); /* turn logging off */
	    outchar(' ');
	    flush_output();
	    set_extra_output_file(log_fd);
	}
      #endif /* LOG_FILE_SUPPORT */
    }
  #endif /* HAVE_SPINNER */
    output((const char *)msg);
    outchar('\n');
    return COMMAND_RUNS_OK;
}

static int cmd_exit(void)
{
    exit_program_now = 1;
    return COMMAND_RUNS_OK;
}

static int cmd_tag(String *boolean, String *tag)
{
    const char *when = "is";

    kibishii_assert(lookup.slot);

    if (tag)
    {
	if (COMBO(lookup.slot))	{
	    outputf("%sMay not set a tag string for a combo slot.\n",
		    lookup.where);
	    return COMMAND_HAS_ERROR;
	} else {
	    if (lookup.slot->tag_string)
		free(lookup.slot->tag_string);
	    lookup.slot->tag_string = strsave(tag);
	    when = "now";
	}
    }

    if (boolean)
    {
	if (!COMBO(lookup.slot) && lookup.slot->tag_string == 0)
	{
	    outputf("%sno tag string to toggle.\n", lookup.where);
	    return COMMAND_RUNS_OK; /* make a non-fatal error in cmd files */
	}
	lookup.slot->default_flag.tag = interpret_boolean(boolean);
	when = "now";
    }

    if ((tag || boolean) && !lookup.flag.verbose)
	return COMMAND_RUNS_OK;


    if (COMBO(lookup.slot))
	outputf("tag mode for combo #%d %s %s.\n", slot_num(lookup.slot),
		when, lookup.slot->default_flag.tag ? "on" : "off");
    else if (lookup.slot->tag_string == 0)
	outputf("there is no tag for file #%d.\n", slot_num(lookup.slot));
    else
	outputf("tag for file #%d %s" quote(%s) "(%s).\n",
		slot_num(lookup.slot), when, lookup.slot->tag_string,
		lookup.slot->default_flag.tag ? "enabled" : "disabled");
    return COMMAND_RUNS_OK;
}

/* set the spinner value */
static int cmd_set_spinner(String *spec)
{
  #ifdef HAVE_SPINNER
    const char *when = "is";
    if (spec)
    {
	lookup.spinner.interval = atoi(spec);
	if (!lookup.flag.verbose)
	    return COMMAND_RUNS_OK;
	when = "now";
    }
    if (lookup.spinner.interval == 0)
	outputf("spinner %s disabled.\n", when);
    else
	outputf("spinner %s set to: %d.\n", when, lookup.spinner.interval);
  #else  /* HAVE_SPINNER */
    outputf("no spinner support.\n");
  #endif /* HAVE_SPINNER */
    return COMMAND_RUNS_OK;
}


int cmd_show(void)
{
    int i;
    if (lookup.list.size == 0)
	output("the save list is disabled (its size is zero).\n");
    else if (lookup.list.used == 0)
	output("there are currently no saved lines.\n");
    else
    {
	unsigned old_regexec_flags = regexec_setflags(lookup.flag.regex_debug
						      ? REGEXEC_DEBUG : 0);
	for (i = 0; i < lookup.list.used; i++)
	{
	    const struct slot_info *slot = lookup.list.array[i].slot;
	    String *line;
	    unsigned len;
	    line = VirtPos2Str(slot->file->v, lookup.list.array[i].line, &len);
	    output_line(slot, line, len);

	}
	regexec_setflags(old_regexec_flags);	
    }
    return COMMAND_RUNS_OK;
}

static void add_combo_slot(struct slot_info *parent,
			   unsigned parent_slot_num,
			   unsigned new_slot_num)
{
    unsigned i;

    kibishii_assert(parent_slot_num == lookup.slots ||
		    parent_slot_num == slot_num(parent));
    kibishii_assert(new_slot_num < lookup.slots);
    kibishii_assert(!COMBO(lookup.slot_info[new_slot_num]));

    for (i = 0; i < parent->combo.entries; i++)
	if (parent->combo.entry[i] == new_slot_num)
	{
	    if (lookup.flag.verbose)
		outputf(" (%d)", new_slot_num);
	    return;
	}


    /*
     * I don't see how the following could be possible if we get this
     * far, so I make it a kibishii assert.
     */
    kibishii_assert(parent->combo.entries < MAX_LOADED_FILES);

    parent->combo.entry[parent->combo.entries++] = new_slot_num;
    if (lookup.flag.verbose)
	outputf(" %d", new_slot_num);
}

static int cmd_combine(String *name, int num, String *new_nums)
{
    int parent_slot_num;
    struct slot_info *slot;
    unsigned new_num_list[MAX_LOADED_FILES];
    unsigned new_num_next = 0;
    unsigned new_num_seen[MAX_LOADED_FILES];
    const char *what;
    int i;

    kibishii_assert(new_nums != 0);

    for (i = 0; i < MAX_LOADED_FILES; i++)
	new_num_seen[i] = 0;

    /* check new num list to make sure all nums are valid */
    do
    {
	int new_num = 0;
	/* skip to first digit (past a possible '#' or whitespace, etc.) */
	while (*new_nums && !isdigit(*new_nums))
	    new_nums++;
	kibishii_assert(isdigit(*new_nums));
	while (isdigit(*new_nums))
	    new_num = new_num * 10 + (*new_nums++ - '0');

	if (new_num >= lookup.slots)
	{
	    outputf("%sno such slot #%d\n", lookup.where, new_num);
	    return COMMAND_HAS_ERROR;
	}
	if (!new_num_seen[new_num])
	{
	    kibishii_assert(new_num_next < MAX_LOADED_FILES);
	    new_num_seen[new_num] = 0;
	    new_num_list[new_num_next++] = new_num;
	}
	/* skip along until end of line or another number */
	while (*new_nums && !isdigit(*new_nums))
	    new_nums++;
    } while (*new_nums);

    if (num >= 0)
    {
	/* use previous combo */
	what = "adding to";
	if (num > lookup.slots || !COMBO(lookup.slot_info[num]))
	{
	    outputf("%sno previous combo in slot %d!\n", lookup.where, num);
	    return COMMAND_HAS_ERROR;
	}
	slot = lookup.slot_info[num];
	lookup.slot = slot; /* needed during startup */
	parent_slot_num = slot_num(slot);
    }
    else
    {
	/* need a new slot */
	what = "creating";
	if (lookup.slots >= MAX_LOADED_FILES)
	{
	    outputf("%scan't combine (too many loaded slots, max is %d)\n",
		    lookup.where, MAX_LOADED_FILES);
	    return COMMAND_HAS_ERROR;
	}
	slot = xmalloc(sizeof(struct slot_info));
	bzero(slot, sizeof(struct slot_info));
#ifdef KIBISHII_DEBUG
	{
	    char *ptr = (char*)&slot->onefile_or_combo;
	    char *end = ((char*)slot) + sizeof(struct slot_info);
	    while (ptr < end)
		*ptr++ = 0x7f;  /* fill with some garbage */
	    slot->combo.name = 0;   /* but leave these null */
	    slot->combo.entries = 0;/* but leave these null */
	}
#endif
	if (!name)
	    name = (String *)"combo";
	parent_slot_num = lookup.slots;
	lookup.slot_info[lookup.slots++] = slot; /* install new slot */
	slot->default_flag.word      = 1;
	slot->default_flag.fuzz      = 1;
	slot->default_flag.fold      = 1;
	slot->default_flag.highlight = 1;
	slot->default_flag.filter    = 1;
	slot->default_flag.modify    = 1;
	slot->default_flag.autokana  = lookup.flag.autokana;
	slot->default_flag.tag       = 1;
	slot->default_flag.display   = 1;
	lookup.slot = slot; /* needed during startup */
    }

    kibishii_assert(COMBO(slot));

    if (lookup.flag.verbose)
	outputf("%s combo slot #%d", what, parent_slot_num);

    if (name)
    {
	if (slot->combo.name)
	    free(slot->combo.name);
	slot->combo.name = strsave(name);
	outputf("(%s)", name);
    }
    outchar(':');
    

    /* add new numbs */
    for (i = 0; i < new_num_next; i++)
    {
	int new_num = new_num_list[i];
	if (!COMBO(lookup.slot_info[new_num]))
	    add_combo_slot(slot, parent_slot_num, new_num);
	else
	{
	    /* bring in entries from that slot */
	    unsigned j;
	    for (j = 0; j < lookup.slot_info[new_num]->combo.entries; j++)
	    {
		int new_new_num = lookup.slot_info[new_num]->combo.entry[j];
		kibishii_assert(!COMBO(lookup.slot_info[new_new_num]));
		if (!new_num_seen[new_new_num])
		    add_combo_slot(slot, parent_slot_num, new_new_num);
	    }
	}
    }
    if (lookup.flag.verbose)
	outchar('\n');
    return COMMAND_RUNS_OK;
}

static int cmd_toggle_modify(String *bool)
{
    const char *when = "";

    kibishii_assert(lookup.slot);

    if (!COMBO(lookup.slot) && lookup.slot->modify_spec.pattern == 0)
    {
	outputf("%sno modification installed.\n", lookup.where);
	return bool ? COMMAND_HAS_ERROR : COMMAND_RUNS_OK;
    }

    if (bool)
    {
	lookup.slot->default_flag.modify = interpret_boolean(bool);
	if (!lookup.flag.verbose)
	    return COMMAND_RUNS_OK;
	when = "now ";
    }
    output("modify ");
    if (COMBO(lookup.slot))
    {
	outputf("flag is %s for combo slot #%d.\n",
		lookup.slot->default_flag.modify ? "on" : "off",
		slot_num(lookup.slot));
    } else {
	outputf("(%s%sabled):" quote(%s) "with ", when,
		lookup.slot->default_flag.modify ? "en" : "dis",
		lookup.slot->modify_spec.pattern);
	if (lookup.slot->modify_spec.replacement[0] == 0)
	    output("nothing\n");
	else
	    outputf(quote(%s) "\n", lookup.slot->modify_spec.replacement);
    }
    return COMMAND_RUNS_OK;
}

static int cmd_modify(String *pattern, String *replacement,
		      int is_case_insensitive, int is_global)
{
    int i;

    kibishii_assert(lookup.slot);

    if (COMBO(lookup.slot))
    {
	outputf("%sslot is a combo; no modify allowed.\n", lookup.where);
	return COMMAND_HAS_ERROR;
    }

    assert(pattern);
    assert(replacement);

    if (lookup.slots == 0)
    {
	outputf("%sno loaded file for modification.\n", lookup.where);
	return COMMAND_HAS_ERROR;
    }

    if (lookup.slot->modify_spec.pattern)
    {
	free(lookup.slot->modify_spec.pattern);
	regfree(&lookup.slot->modify_spec.regex);
	free(lookup.slot->modify_spec.replacement);
    }
    lookup.slot->modify_spec.pattern = lookup.slot->modify_spec.replacement = 0;

    i = regcomp(&lookup.slot->modify_spec.regex, pattern,
		REGCOMP_SAVE_MATCHED_PAREN_INFO |
		    (is_case_insensitive ? REGCOMP_IGNORE_CASE : 0));

    if (i != REGCOMP_SUCCESS)
    {
	outputf("%s%s.\n", lookup.where,
		(string *)regcomp_error_report());
	return COMMAND_HAS_ERROR;
    }
    lookup.slot->modify_spec.pattern = strsave(pattern);
    lookup.slot->modify_spec.replacement = strsave(replacement);
    lookup.slot->modify_spec.global = is_global;
    lookup.slot->default_flag.modify = 1;
    return COMMAND_RUNS_OK;
}

/*
 * At most one of NUMBER, NAME, or USE_DEFAULT will be non-null.
 */
static int cmd_select(String *number, const char *name, int use_default)
{
    const char *when = "is";
    struct slot_info *selected = 0;

    if (name)
    {
	int i;
	name = expand_filename_tilde(name);
	for (i = 0; i < lookup.slots && !selected; i++)
	    if ((lookup.slot_info[i]->file &&
		 (str_eql(lookup.slot_info[i]->file->short_filename, name)||
		  str_eql(lookup.slot_info[i]->file->v->filename, name))) ||
		(COMBO(lookup.slot_info[i]) &&
		 str_eql(lookup.slot_info[i]->combo.name, name)))
	    {
		selected = lookup.slot_info[i];
		break;
	    }

	if (selected == 0)
	{
	    outputf("%sno" quote(%s) "slot loaded.\n", lookup.where, name);
	    return COMMAND_HAS_ERROR;
	}
    } else if (number) {
	unsigned value = atoi(number);
	if (value >= lookup.slots)
	{
	    outputf("%sno such slot #%d loaded.\n",
		    lookup.where, value);
	    return COMMAND_HAS_ERROR;
	}
	selected = lookup.slot_info[value];
    } else if (use_default) {
	if (lookup.default_slot == 0)
	{
	    outputf("%sno default file to use\n", lookup.where);
	    return COMMAND_HAS_ERROR;
	}
	selected = lookup.default_slot;
    }

    if (selected)
    {
	lookup.slot = selected; /* needed during startup */
	if (lookup.default_slot == selected)
	    when = "already";
	else
	{
	    lookup.default_slot = selected;
	    when = "now";
	}
	if (!lookup.flag.verbose)
	    return COMMAND_RUNS_OK;
    }

    if (lookup.slot == 0)
    {
	outputf("%sno file %s.\n", lookup.where,
		lookup.default_slot ? "selected" : "loaded");
	return COMMAND_HAS_ERROR;
    }

    outputf("default slot %s #%d (%s).\n", when,
	    slot_num(lookup.default_slot),
	    COMBO(lookup.default_slot) ?
	          (const char *)lookup.default_slot->combo.name :
	          (const char *)lookup.default_slot->file->v->filename);
    return COMMAND_RUNS_OK;
}

static int cmd_list_files(int help)
{
    #define ALTTOP "車足足足足足足足足足足足足足足"
    #define TOP    "足迅足足足足足邪足足迅足足足迅"
    #define BOTTOM "迂足巡足足足足足那足足巡足足足巡"
    static char LINE[]   = "足足足足足足足足足足足足足足足足足足足足足足足足"
	"足足足足足足足足足足足足足足足足足足足足足足足足足足足足";
    #define min(A,B) ((A)<(B)?(A):(B))
    int i;
    int extra = output_pager_columns(0) ?
	output_pager_columns(0) - sizeof(TOP) : 0;
    
    if (lookup.slots == 0)
    {
	outputf("%sno files loaded\n", lookup.where);
	return COMMAND_HAS_ERROR;
    }

    if (extra <= 0)
	extra = 0;
    else
    {
	extra = min(extra, sizeof(LINE)-1);
	extra &= ~1; /* round down to an even number of bytes */
    }

    if (!help) {
	outputf(ALTTOP "%.*s\n", extra, LINE);
        outputf("身%s's \"Lookup\", %s (%s).\n迆",
		author_name, version_string,version_date);
    } else {
        output(		 
	  "      +-------------F: has filter; #: but disabled (!F!, filter)\n"
	  "      |+------------M: has modify; %: but disabled (!M!, modify)\n"
	  "      ||+-----------w: word-preference mode (!w!, word)\n"
	  "      |||+----------c: case folding (!c!, fold)\n"
	  "      ||||+---------f: fuzz mode (!f!, fuzz)\n"
	  "      |||||+--------h: highlight mode (!h!, highlight)\n"
	  "      ||||||+-------t: has tag; @:but disabled (!T!, tag)\n"
	  "      |||||||+------d: will display (!d!, display)\n"
	  "      ||||||||  +---a: automatic kana conversion (=, autokana)\n"
	  "      ||||||||  |+--P: slot has local prompt (local prompt)\n"
	  "      ||||||||  ||+-I: file loaded with precomputed-index\n"
	  "      FMwcfhtd  aPI\n"
	  "車"
          );
    }

    outputf(TOP "%.*s\n", extra, LINE);
    for (i = 0; i < lookup.slots; i++)
    {
	char buf1[12], buf2[10];

	buf1[0] = lookup.slot_info[i]->default_flag.filter    ? 'F' :
	          (!COMBO(lookup.slot_info[i]) &&
		   lookup.slot_info[i]->filter_spec.pattern)  ? '#' : ' ';

	buf1[1] = lookup.slot_info[i]->default_flag.modify    ? 'M' :
	          (!COMBO(lookup.slot_info[i]) &&
		   lookup.slot_info[i]->modify_spec.pattern)  ? '%' : ' ';

	buf1[2] = lookup.slot_info[i]->default_flag.word      ? 'w' : ' ';
	buf1[3] = lookup.slot_info[i]->default_flag.fold      ? 'c' : ' ';
	buf1[4] = lookup.slot_info[i]->default_flag.fuzz      ? 'f' : ' ';
	buf1[5] = lookup.slot_info[i]->default_flag.glob      ? 'W' : ' ';
	buf1[6] = lookup.slot_info[i]->default_flag.highlight ? 'h' : ' ';
	buf1[7] = lookup.slot_info[i]->default_flag.tag       ? 't' :
		  (!COMBO(lookup.slot_info[i]) &&
		   lookup.slot_info[i]->tag_string)           ? '@' : ' ';
	buf1[8] = lookup.slot_info[i]->default_flag.display   ? 'd' : ' ';
	buf1[9] = ' ';
	buf1[10] = '\0';

	buf2[0] = lookup.slot_info[i]->default_flag.autokana  ? 'a' : ' ';
	buf2[1] = lookup.slot_info[i]->prompt_format          ? 'P' : ' ';
	buf2[2] =(!COMBO(lookup.slot_info[i]) &&
		  lookup.slot_info[i]->file->indexfile) ?
	    (IsMemIndex(lookup.slot_info[i]->file->index) ? 'i' : 'I') : ' ';

	buf2[3] = ' ';

	if (buf2[3] == ':')
	    buf2[3] = ' ';

	buf2[4] = '\0';

	outputf("身%c%d身%s芍%s身",
		lookup.slot_info[i] == lookup.default_slot  ? '*' : ' ', i,
		buf1, buf2);
	if (!COMBO(lookup.slot_info[i]))
	    outputf("%5dk身%s\n",
		    (lookup.slot_info[i]->file->v->length+1023)/1024,
		    lookup.slot_info[i]->file->v->filename);
	else
	{
	    unsigned int j;
	    outputf(" combo身%s (", lookup.slot_info[i]->combo.name);
	    for (j = 0; j < lookup.slot_info[i]->combo.entries; j++)
		outputf("%s#%d", j ? ", " : "",
			lookup.slot_info[i]->combo.entry[j]);
	    output(")\n");
	}
    }
    outputf(BOTTOM "%.*s\n\n", extra, LINE);
    return COMMAND_RUNS_OK;
}

static int cmd_source(const char *filename)
{
    filename = expand_filename_tilde(filename);
    switch (read_commands_from_file(filename, CMD_GENERAL|CMD_FILE_ONLY, 0))
    {
      case FILE_READ_OK:
	return COMMAND_RUNS_OK;
      case FILE_NOT_FOUND:
	outputf("%sfile" quote(%s) "not found.\n", lookup.where, filename);
	break;
    }
    return COMMAND_HAS_ERROR;
}

static int cmd_stats(void)
{
    outputf("%ld line%s checked, %ld matched",
	    lookup.count.checked, lookup.count.checked == 1 ? "" : "s",
	    lookup.count.matched);

    if (lookup.count.matched)
    {
	if (lookup.count.filtered)
	    outputf(", %ld filtered", lookup.count.filtered);
	if (lookup.count.nonword)
	    outputf(", %ld non-word", lookup.count.nonword);
	outputf(", %ld printed", lookup.count.printed);
    }
    output(".\n");
    return COMMAND_RUNS_OK;
}

#define X208kuten2euc_byte1(R, C)  ((R) + 160)
#define X208kuten2euc_byte2(R, C)  ((C) + 160)
#define X208kuten2euc(R, C)  X208kuten2euc_byte1(R, C),\
                             X208kuten2euc_byte2(R, C)

static int describe(unsigned char c1,  unsigned char c2)
{
    unsigned char k1, k2, j1, j2;
    unsigned char C1 = c1 | 0x80;
    unsigned char C2 = c2 | 0x80;
    c1 &= 0x7f;
    c2 &= 0x7f;

    outputf(quote(%c%c) "as  EUC  is 0x%02x%02x (%3d %3d; \\%03o \\%03o)\n",
	   C1, C2, C1, C2, C1, C2, C1, C2);

    outputf("      as  JIS  is 0x%02x%02x (%3d %3d; \\%03o \\%03o \"%c%c\")\n",
	   c1, c2, c1, c2, c1, c2, c1 ,c2);

    k1 = c1 - 32;
    k2 = c2 - 32;
    outputf("      as KUTEN is   %02d%02d ( 0x%02x%02x; \\%03o \\%03o)\n",
	   k1, k2, k1, k2, k1, k2);

    j1 = ((c1 + 1) >> 1U) + (c1 < 95 ? 112 : 176);
    j2 = c2 + ((c1 & 1) ? (c2 > 95 ? 32 : 31) : 126);

    outputf("      as S-JIS is 0x%02x%02x (%3d %3d; \\%03o \\%03o)\n",
	   j1, j2, j1, j2, j1, j2);
    return COMMAND_RUNS_OK;
}

static int
cmd_describe_ascii(unsigned base, String *text)
{
    unsigned num = 0;
    unsigned char c;

    while (c = *text++, c)
    {
	if (c >= 'a' && c <= 'f')
	    num = num * base + c - 'a' + 10;
	else if (c >= 'A' && c <= 'F')
	    num = num * base + c - 'A' + 10;
        else
	    num = num * base + c - '0';
    }

    outputf("ASCII \\%03o %d 0x%02x", num, num, num);
    if (isascii(num) && isprint(num))
	outputf(": \"%c\"", num);
    outputf("\n");
    return COMMAND_RUNS_OK;
}

/*
 * CHARACTER points to either a single ascii byte to describe, or
 * a string of double-byte characters.
 */
static int
cmd_describe_raw(String *text)
{
    while (*text)
    {
	unsigned c1, c2;

	switch (EUC_CHAR_LENGTH(*text))
	{
	  default:
	    {
		int len = EUC_CHAR_LENGTH(*text);
		outputf("%d-byte character: ", len);
		while (len--)
		    outputf("0x%02x", *text++);
		continue;
	    }

	  case 2: /* "normal" EUC */
	    c1 = text[0];
	    c2 = text[1];
	    text += 2;
	    break;

	  case 1: /* ascii */
	    c1 = text[0];
	    text += 1;

	    /*
	     * attempt to convert to wide.
	     */
	    if (c1 >= 'A' && c1 <= 'Z') {
		c2 = X208kuten2euc_byte2(3,33) + (c1 - 'A');
		c1 = X208kuten2euc_byte1(3,33);
	    } else if (c1 >= 'a' && c1 <= 'z') {
		c2 = X208kuten2euc_byte2(3,65)+ (c1 - 'a');
		c1 = X208kuten2euc_byte1(3,65);
	    } else if (c1 >= '0' && c1 <= '9') {
		c2 = X208kuten2euc_byte2(3,16)+ (c1 - '0');
		c1 = X208kuten2euc_byte1(3,16);
	    } else if (c1 >= ' ' && c1 <= '/') {
		unsigned char byte2[] = {
		    X208kuten2euc_byte2(1, 1), /* space */
		    X208kuten2euc_byte2(1,10), /*   !   */
		    X208kuten2euc_byte2(1,41), /*   "   */
		    X208kuten2euc_byte2(1,84), /*   #   */
		    X208kuten2euc_byte2(1,80), /*   $   */
		    X208kuten2euc_byte2(1,83), /*   %   */
		    X208kuten2euc_byte2(1,85), /*   &   */
		    X208kuten2euc_byte2(1,39), /*   '   */
		    X208kuten2euc_byte2(1,42), /*   (   */
		    X208kuten2euc_byte2(1,43), /*   )   */
		    X208kuten2euc_byte2(1,86), /*   *   */
		    X208kuten2euc_byte2(1,60), /*   +   */
		    X208kuten2euc_byte2(1, 4), /*   ,   */
		    X208kuten2euc_byte2(1,61), /*   -   */
		    X208kuten2euc_byte2(1,03), /*   .   */
		    X208kuten2euc_byte2(1,31), /*   /   */
		};

		c2 = byte2[c1 - ' '];
		c1 = X208kuten2euc_byte1(1, 0);
	    } else if (c1 >= ':' && c1 <= '@') {
		unsigned char byte2[] = {
		    X208kuten2euc_byte2(1, 7), /*   :   */
		    X208kuten2euc_byte2(1, 8), /*   ;   */
		    X208kuten2euc_byte2(1,67), /*   <   */
		    X208kuten2euc_byte2(1,65), /*   =   */
		    X208kuten2euc_byte2(1,68), /*   >   */
		    X208kuten2euc_byte2(1, 9), /*   ?   */
		    X208kuten2euc_byte2(1,87), /*   @   */
		};
		c2 = byte2[c1 - ':'];
		c1 = X208kuten2euc_byte1(1, 0);
	    } else if (c1 >= '[' && c1 <= '`') {
		unsigned char byte2[] = {
		    X208kuten2euc_byte2(1,46), /*  [   */
		    X208kuten2euc_byte2(1,79), /*  \   */
		    X208kuten2euc_byte2(1,47), /*  ]   */
		    X208kuten2euc_byte2(1,16), /*  ^   */
		    X208kuten2euc_byte2(1,18), /*  _   */
		    X208kuten2euc_byte2(1,38), /*  `   */
		};
		c2 = byte2[c1 - '['];
		c1 = X208kuten2euc_byte1(1, 0);
	    } else if (c1 >= '{' && c1 <= '~') {
		unsigned char byte2[] = {
		    X208kuten2euc_byte2(1,48), /*  {   */
		    X208kuten2euc_byte2(1,35), /*  |   */
		    X208kuten2euc_byte2(1,49), /*  }   */
		    X208kuten2euc_byte2(1,33), /*  ~   */
		};
		c2 = byte2[c1 - '{'];
		c1 = X208kuten2euc_byte1(1, 0);
	    } else {
		outputf("ascii character %d\n", c1);
		continue;
	    }

	    break;
	}
	describe(c1, c2);
    }
    return COMMAND_RUNS_OK;
}

/*
 * CODE is a four-digit kuten to describe.
 */
static int
cmd_describe_kuten(String *code)
{
    /* kuten will appear as "####". Interpret as decimal. */
    unsigned c1 = (code[0]-'0') * 10 + (code[1]-'0');
    unsigned c2 = (code[2]-'0') * 10 + (code[3]-'0');
    if (c1 > 85 || c2 > 94 || (c1 >= 9 && c1 <= 15)) {
	warn("invalid KUTEN value %02d%02d\n", c1, c2);
	return COMMAND_HAS_ERROR;
    }
    c1 = (c1 | 0x80) + 32;
    c2 = (c2 | 0x80) + 32;
    return describe(c1, c2);
}

/*
 * Either CODE or ASCII will tell which character to describe...
 * if it's CODE, it'll be a pointer to a four-(hex)-digit string to be
 * taken as a JIS number. If it's ASCII, it'll be a string of 4, 8, 12, etc.
 * characters that are taken to be the ascii that JIS appears as, without
 * the escape characters to make the ascii be interpreted as JIS.
 *
 * In either case of CODE or ASCII, one the TYPE might be non-null and
 * be a pointer to "jis", "sjis", or "euc" to force the interpretation
 * to a different encoding.
 */
static int
cmd_describe_encoding(String *type, String *code)
{
    unsigned c1, c2;

    #define hexval(c) (c >= 'a' ? 10 + c - 'a' :\
		       c >= 'A' ? 10 + c - 'A' : c - '0')
    c1 = (hexval(code[0])<<4)|hexval(code[1]);
    c2 = (hexval(code[2])<<4)|hexval(code[3]);

    if (!type)
       type = (String *)"JIS"; /* default type is JIS */

    switch(type[0])
    {
      default:
	soft_assert(0);
	break;
      case '\0': /* no type? it's JIS */
      case 'j': /* jis */
      case 'J': /* jis */
	if (code && (c1 < 31 || c1 > 126 || c2 < 32 || c2 > 126)) {
	    type = (String *)"JIS";
	  invalid:
	    warn("invalid %s value 0x%02x%02x\n", type, c1, c2);
	    return COMMAND_HAS_ERROR;
	}
	/* we'll just use c1 & c2 as-is */
	break;

      case 'e': /* euc */
      case 'E': /* euc */
	if (code && (c1 < 161 || c1 > 254 || c2 < 161 || c2 > 254)) {
	    type = (String *)"EUC";
	    goto invalid;
	}
	/* we can use c1 and c2 as-is */
	break;

      case 's': /* sjis */
      case 'S': /* sjis */
	if (code && (c1 < 129 || c1 > 239 || (c1 > 159 && c1 < 224) ||
		     c2 < 64 || c2 > 252 || (c2 > 126 && c2 < 128))) {
	    type = (String *)"SJIS";
	    goto invalid;
	}
	c1 = (((c1 - (c1<160 ? 112:176))<<1) - (c2<159));
	c2 = (c2 - (c2<159 ? (c2>127?32:31) : 126));
        break;
    }
    describe(c1, c2);

    return COMMAND_RUNS_OK;
}

static int
cmd_describe_jis_string(String *str)
{
    unsigned c1, c2;

    while (*str)
    {
	c1 = *(str++);
	c2 = *(str++);

	if (c1 < 31 || c1 > 126 || c2 < 32 || c2 > 126) {
	    warn("invalid JIS value 0x%02x%02x\n", c1, c2);
	    return COMMAND_HAS_ERROR;
	}
        describe(c1, c2);
    }

    return COMMAND_RUNS_OK;
}

#ifdef USE_LOCAL_OUTPUT
static void show_output_style_spec(void)
{
    unsigned output_style = select_output_style(INQUIRE_ONLY);

    switch(output_style & _BASIC_OUTPUT_TYPE) {
      default: soft_assert(0); break;
      case SJIS_OUTPUT: output("sjis"); break;
      case EUC_OUTPUT: output("euc"); break;

      case JIS_OUTPUT:
	switch(output_style & _JIS_KANJI_STYLE)
	{
	  default: soft_assert(0); break;
	  case JIS_1978_OUTPUT: output("jis78"); break;
	  case JIS_1983_OUTPUT: output("jis83"); break;
	  case JIS_1990_OUTPUT: output("jis90"); break;
	}
	switch(output_style & _JIS_ENGLISH_STYLE)
	{
	  default: soft_assert(0); break;
	  case JIS_ROMAN: output("-roman"); break;
	  case JIS_ASCII: output("-ascii"); break;
	}
	break;
    }

    switch (output_style & _0212_1990)
    {
      default: soft_assert(0); break;
      case SUPPORT_0212_1990: outputf("-212"); break;
      case NO_0212_1990: outputf("-no212"); break;
    }

    switch (output_style & _KATAKANA)
    {
      default: soft_assert(0); break;
      case PASS_HW_KATANANA: output("-hwk"); break;
      case ELIDE_HW_KATAKANA: output("-nohwk"); break;
      case FOLD_HW_KATAKANA_TO_FULL: output("-foldhwk"); break;
    }

    switch(output_style & _NONDISPLAYABLE) {
      default: soft_assert(0); break;
      case OUTPUT_NONDISPLAYABLE: output("-disp"); break;
      case ELIDE_NONDISPLAYABLE: output("-nodisp"); break;
      case SHOW_NONDISPLAYABLE_CODES: output("-code"); break;
      case MARK_NONDISPLAYABLE: output("-mark");
    }
}
#endif /* USE_LOCAL_OUTPUT */

int cmd_output_encoding(String *main_style, String *jis_year,
			String *jis_english, String *other)
{
#ifndef USE_LOCAL_OUTPUT
    output("<outputstyle support not compiled in>");
#else
    const char *when = "is";

    if (main_style)
    {
	if (main_style[0]=='e' || main_style[0]=='E')
	    (void)select_output_style(EUC_OUTPUT);
	else if (main_style[0]=='s' || main_style[0]=='S')
	    (void)select_output_style(SJIS_OUTPUT);
	else if (main_style[0]=='j' || main_style[0]=='j')
	{
	    if (!jis_year)
		(void)select_output_style(JIS_OUTPUT);
	    else if (jis_year[0] == '7')
		(void)select_output_style(JIS_1978_OUTPUT);
	    else if (jis_year[0] == '8')
		(void)select_output_style(JIS_1983_OUTPUT);
	    else if (jis_year[0] == '9')
		(void)select_output_style(JIS_1990_OUTPUT);
	    else {
		assert(0);
	    }
	    if (jis_english)
	    {
		if (jis_english[0] == 'a' || jis_english[0] == 'A')
		    (void)select_output_style(JIS_ASCII);
		else if (jis_english[0] == 'r' || jis_english[0] == 'R')
		    (void)select_output_style(JIS_ROMAN);
		else {
		    assert(0);
		}
	    }
	} else {
	    assert(0);
	}
    }

    if (other)
    {
	unsigned char c;
	while (c = *other, c && !isalnum(c))
	    other++;
	do
	{
	    String *start = other;
	    unsigned len;

	    while (c = *other, c && isalnum(c))
		other++;
	    len = other - start;
	    if (!strNcmp(start, "", len)) {
	    } else if (!strNcmp(start, "212", len)) {
		(void)select_output_style(SUPPORT_0212_1990);
	    } else if (!strNcmp(start, "no212", len)) {
		(void)select_output_style(NO_0212_1990);
	    } else if (!strNcmp(start, "hwk", len)) {
		(void)select_output_style(PASS_HW_KATANANA);
	    } else if (!strNcmp(start, "nohwk", len)) {
		(void)select_output_style(ELIDE_HW_KATAKANA);
	    } else if (!strNcmp(start, "foldhwk", len)) {
		(void)select_output_style(FOLD_HW_KATAKANA_TO_FULL);
	    } else if (!strNcmp(start, "disp", len)) {
		(void)select_output_style(OUTPUT_NONDISPLAYABLE);
	    } else if (!strNcmp(start, "nodisp", len)) {
		(void)select_output_style(ELIDE_NONDISPLAYABLE);
	    } else if (!strNcmp(start, "code", len)) {
		(void)select_output_style(SHOW_NONDISPLAYABLE_CODES);
	    } else if (!strNcmp(start, "mark", len)) {
		(void)select_output_style(MARK_NONDISPLAYABLE);
	    } else {
		soft_assert(0);
	    }
	    while (c = *other, c && !isalnum(c))
		other++;
	} while (c != 0);
    }

    if (main_style || other)
    {
	if (!lookup.flag.verbose)
	    return COMMAND_RUNS_OK;
	when = "now";
    }
    outputf("Output encoding %s ", when);
    show_output_style();
    output(".\nAn exact specifier string would be ``");
    show_output_style_spec();
    output("''.\n");
#endif /* USE_LOCAL_OUTPUT */
    return COMMAND_RUNS_OK;
}

static int cmd_input_encoding(String *arg)
{
#if !defined(USE_LOCAL_OUTPUT) || defined(SERVER_CONFIG)
    output("<inputstyle support not compiled in>");
#else
    const char *when = "is";
    if (arg) {
	if (arg[0] == 'e' || arg[0] == 'E')
	    jreadline_highbit_input(JREADLINE_EUC);
	else
	    jreadline_highbit_input(JREADLINE_SJIS);
	if (!lookup.flag.verbose)
	    return COMMAND_RUNS_OK;
	when = "now";
    }
    outputf("High-bit input encoding %s %s.\n", when,
	    jreadline_highbit_input(JREADLINE_INQUIRE) == JREADLINE_EUC ?
	    "EUC" : "SJIS");
#endif /* !defined(USE_LOCAL_OUTPUT) || defined(SERVER_CONFIG) */
    return COMMAND_RUNS_OK;
}


static int cmd_encoding(String *arg)
{
#ifndef USE_LOCAL_OUTPUT
    output("<encoding support not compiled in>");
#else
    if (arg)
    {
	if (arg[0] == 'e' || arg[0] == 'E')
	{
	    cmd_input_encoding((String *)"euc");
	    cmd_output_encoding((String *)"euc", 0, 0, 0);
	} else if (arg[0] == 's' || arg[0] == 'S') {
	    cmd_input_encoding((String *)"sjis");
	    cmd_output_encoding((String *)"sjis", 0, 0, 0);
	} else {
	    if (lookup.flag.verbose)
		cmd_input_encoding(0);
	    cmd_output_encoding((String *)"jis", 0, 0, 0);
	}
    } else {
	cmd_input_encoding(0);
	cmd_output_encoding(0, 0, 0, 0);
    }
#endif /* USE_LOCAL_OUTPUT */
    return COMMAND_RUNS_OK;
}

static int cmd_cmdchar(String *arg)
{
    const char *when = "is";
    if (arg)
    {
	lookup.cmdstart_char = *arg;
	if (!lookup.flag.verbose)
	    return COMMAND_RUNS_OK;
	when = "now";
    }
    outputf("command start char %s" quote(%c) ".\n", when,
	    lookup.cmdstart_char);
    return COMMAND_RUNS_OK;
}

int cmd_version(void)
{
    outputf("%s: %s, %s (compiled %s)\n"
     #ifdef SERVER_CONFIG
	    "-- server configuration --\n"
     #endif /* SERVER_CONFIG */
	    ,(string *)lookup.prog,version_string,version_date,compile_date);

    outputf("Author: %s\nComments and questions to: %s\n",
	    author_name, contact_addr);
    return COMMAND_RUNS_OK;
}

static int cmd_pager(String *boolean,
		     String *width_str,
		     String *height_str)
{
#if OUTPUT_PAGER
    const char *when = "is";
    if (boolean)
    {
	(void)output_pager_status(interpret_boolean(boolean));
	if (!lookup.flag.verbose)
	    return COMMAND_RUNS_OK;
	when = "now";
    } else if (height_str) {
	int height = atoi(height_str);
	(void)output_pager_lines(height);

	if (width_str)
	{
	    int width = atoi(width_str);
	    (void)set_jreadline_width(width);
	    (void)output_pager_columns(width);
	}
	if (!lookup.flag.verbose)
	    return COMMAND_RUNS_OK;
	when = "now";
    }
    outputf("pager %s %s (screen registered as %dx%d).\n",
	    when, output_pager_status(-1) ? "on" : "off",
	    output_pager_columns(0), output_pager_lines(0));
#else
    warn("%soutput pager not compiled in, ignoring.\n", lookup.where);
#endif
    return COMMAND_RUNS_OK;
}

/*
 * If string is in the form <....>, such as "<BOLD>", then set highlighting to
 * wrap with <BOLD> and </BOLD>.
 */
static int cmd_set_highlighting_style(String *style)
{
    const char *when = "is";
    if (style)
    {
	if (strcmp((const char*)style, "black") == 0)
	    lookup.flag.hl_style = HL_STYLE_BLACK;
	else if (strcmp((const char*)style, "red") == 0)
	    lookup.flag.hl_style = HL_STYLE_RED;
	else if (strcmp((const char*)style, "green") == 0)
	    lookup.flag.hl_style = HL_STYLE_GREEN;
	else if (strcmp((const char*)style, "yellow") == 0)
	    lookup.flag.hl_style = HL_STYLE_YELLOW;
	else if (strcmp((const char*)style, "blue") == 0)
	    lookup.flag.hl_style = HL_STYLE_BLUE;
	else if (strcmp((const char*)style, "purple") == 0)
	    lookup.flag.hl_style = HL_STYLE_PURPLE;
	else if (strcmp((const char*)style, "cyan") == 0)
	    lookup.flag.hl_style = HL_STYLE_CYAN;
	else if (strcmp((const char*)style, "white") == 0)
	    lookup.flag.hl_style = HL_STYLE_WHITE;
	else if (strcmp((const char*)style, "bold") == 0)
	    lookup.flag.hl_style = HL_STYLE_BOLD;
	else if (strcmp((const char*)style, "blink") == 0)
	    lookup.flag.hl_style = HL_STYLE_FLASH;
	else if (strcmp((const char*)style, "under") == 0)
	    lookup.flag.hl_style = HL_STYLE_UNDERLINE;
	else if (style[0] != '<')
	    lookup.flag.hl_style = HL_STYLE_INVERSE;
	else
	{
	    lookup.flag.hl_style = HL_STYLE_HTML;
	    /* free any previous tag before saving new one */
	    if (lookup.slot->highlight_tag)
		free(lookup.slot->highlight_tag);
	    lookup.slot->highlight_tag = strsave(style);
	}

	if (!lookup.flag.verbose)
	    return COMMAND_RUNS_OK;
	when = "now";
    }

    outputf("highlighting (which is %s for the current slot) %s %s.\n",
	lookup.slot->default_flag.highlight ? "on" : "off", when,
	    (lookup.flag.hl_style == HL_STYLE_HTML)
	        ? (const char *)lookup.slot->highlight_tag
	        : (lookup.flag.hl_style == HL_STYLE_BOLD ? "bold":"inverse"));

    return COMMAND_RUNS_OK;
}




static int cmd_error(const char *error, String *command)
{
    outputf("%s%s to" quote(%s) "command.\n", lookup.where, error, command);
    return COMMAND_HAS_ERROR;
}

/**********************************************************************/
/**********************************************************************/


struct command
{
    unsigned flags;
    String *usage;
    String *help;
    String *pattern;
    int (*function)(void);
    regex_t compiled;
};

#define MAX_PARENS_NEEDED_FOR_COMMANDS 9

static matched_paren_t cmd_paren_info[MAX_PARENS_NEEDED_FOR_COMMANDS];
static string *cmd_paren[MAX_PARENS_NEEDED_FOR_COMMANDS];

static int cmd_help(String *); /* forward */

#include "commands.h"
#define command_count array_elements(command)

static int cmd_help(String *str)
{
    regex_t regex;
    int cmd, count;

    if (str == 0)
    {
	for (cmd = 0; cmd < command_count; cmd++)
	    if (command[cmd].usage && command[cmd].help)
		outputf("%s\n  %s\n", command[cmd].usage, command[cmd].help);
	return COMMAND_RUNS_OK;
    }

    /* list only items that match a particular regex */

    if (regcomp(&regex, str, REGCOMP_IGNORE_CASE) != REGCOMP_SUCCESS)
    {
	outputf("%s%s.\n",lookup.where,
		(string *)regcomp_error_report());
	return COMMAND_HAS_ERROR;
    }
    for (count = cmd = 0; cmd < command_count; cmd++)
    {
	if (command[cmd].usage && command[cmd].help &&
	    (regexec(&regex,command[cmd].usage,str_len(command[cmd].usage)) ||
	     regexec(&regex,command[cmd].help,str_len(command[cmd].help))))
	{
	    if (count++ == 0)
	    {
		int len = output_pager_columns(0);
		while (len -=2, len > 0)
		    output("芋");
		outchar('\n');
	    }
	    outputf("%s\n  %s\n", command[cmd].usage, command[cmd].help);
	}
    }
    regfree(&regex);
    if (count == 0)
	outputf("%snothing appropriate\n", lookup.where);
    return COMMAND_RUNS_OK;
}

/*
 * Given a text line, see if it's a command and if so, execute the command.
 * Return one of the defines from lookup.h.
 * ACCEPT are flags of accepted commands, while SKIP are flags of commands
 * to be ignored.
 */
int parse_command(String *line, unsigned len, unsigned accept, unsigned skip)
{
    int cmd;

    if (lookup.flag.cmd_debug)
	outputf("parsing" quote(%.*s) "accept=%x, skip=%x.\n",
		(int)len, line, accept, skip);

    for (cmd = 0; cmd < command_count; cmd++)
    {
	int matches;
	matched_paren_t *prev_regexec_paren_info;
	unsigned prev_regexec_paren_info_size;

	/* skip commands that we haven't asked for */
	if ((command[cmd].flags & accept) == 0)
	    continue;

	/* if we've never compiled this pattern, do so now */
	if ((command[cmd].flags & _IS_COMPILED_) == 0)
	{
	    int i = regcomp(&command[cmd].compiled, command[cmd].pattern,
			REGCOMP_IGNORE_CASE|REGCOMP_SAVE_MATCHED_PAREN_INFO);
	    if (i != REGCOMP_SUCCESS)
	    {
		outputf("%sbad compile" quote(%s) "at" quote(%s) "%d.\n",
			lookup.where, command[cmd].pattern,
			regcomp_eptr, i);
	    }
	    assert(i == REGCOMP_SUCCESS);
	    if (lookup.flag.cmd_debug)
		outputf("compiled" quote(%s) "\n", command[cmd].pattern);
	    command[cmd].flags |= _IS_COMPILED_;
	}

	/* see if it matches */
	prev_regexec_paren_info = regexec_paren_info;
	prev_regexec_paren_info_size = regexec_paren_info_size;
	regexec_paren_info = cmd_paren_info;
	regexec_paren_info_size = array_elements(cmd_paren_info);

	matches = regexec(&command[cmd].compiled, line, len);

	regexec_paren_info = prev_regexec_paren_info;
	regexec_paren_info_size = prev_regexec_paren_info_size;

	if (matches)
	{
	    int paren, cmd_result;
	    if (lookup.flag.cmd_debug)
		outputf("matches regex" quote(%s) "\n", command[cmd].pattern);

	    if (command[cmd].flags & skip)
	    {
		if (lookup.flag.debug)
		    warn("skipping command [%.*s]\n", (int)len, line);
		return COMMAND_SKIPPED;
	    }

	    if (command[cmd].function == 0)
		return COMMAND_EXECUTED_OK;

	    if (lookup.slot == 0 && (command[cmd].flags & CMD_NEEDS_SLOT))
	    {
		outputf("%sno file %s.\n", lookup.where,
			lookup.default_slot ? "selected" : "loaded");
		return COMMAND_HAS_ERROR;
	    }

	    for (paren = 0; paren < array_elements(cmd_paren_info); paren++)
	    {
		if (paren >= regexec_paren_info_used ||
		    cmd_paren_info[paren].match_start == 0 ||
		    cmd_paren_info[paren].match_end == 0)
			cmd_paren[paren] = 0;
		else
		{
		    String *start = cmd_paren_info[paren].match_start;
		    String *end   = cmd_paren_info[paren].match_end;
		    string *dest = xmalloc(end - start + 1);
		    cmd_paren[paren] = dest;
		    while (start < end)
			    *dest++ = *start++;
		    *dest = '\0';
		}

		if (lookup.flag.cmd_debug)
		{
		    if (cmd_paren[paren])
			outputf("paren \\%d is [%s]\n",
			       paren+1, cmd_paren[paren]);
		    else
			outputf("paren \\%d is empty\n", paren+1);
		}
	    }

	    /* call the function */
	    cmd_result = (command[cmd].function)();

	    for (paren=0; paren < array_elements(cmd_paren_info); paren++)
		if (cmd_paren[paren])
		{
		    free(cmd_paren[paren]);
		    cmd_paren[paren] = 0;
		}

	    if (cmd_result != COMMAND_RUNS_OK)
		return COMMAND_EXECUTED_WITH_ERROR;
	    return COMMAND_EXECUTED_OK;
	}
    }

    return COMMAND_NOT_FOUND;
}

int quick_command(String *str)
{
    int retval, old_verbose = lookup.flag.verbose;
    lookup.flag.verbose = 0;
    retval = parse_command(str, str_len(str), CMD_GENERAL, 0);
    lookup.flag.verbose = old_verbose;
    return retval;
}


int read_commands_from_file(const char *file, unsigned accept, unsigned skip)
{
    FILE *fp;
    #define FILE_INPUT_LINE_LEN (512 * 40)
    unsigned char line[FILE_INPUT_LINE_LEN];
    int len, linenum = 0;
    String *old_where = lookup.where;
    struct slot_info *old_slot;
    int retval = FILE_READ_OK;

    /* open the file, return error if can't do so */
    if (fp = fopen(file, "r"), fp == 0)
	return FILE_NOT_FOUND;

    lookup.where = xmalloc(str_len(file) + 20);

    /*
     * To enforce that while reading a file, the local-flag, etc. commands
     * only work with just-loaded files, we'll start out with "no files
     * loaded (from this command file)" by setting `lookup.slot' to null.
     */
    old_slot = lookup.slot;
    lookup.slot = 0;

    /* for each line... */
    while (fgets((char *)line, sizeof(line), fp))
    {
	int i;
	linenum++;

	sprintf((void*)lookup.where, "\"%s\" line %d: ", file, linenum);

	/* strip traling newline */
	len = str_len(line);
	if (len > 0 && line[len-1] == '\n')
	    line[--len] = '\0';
	else {
	    if (len == FILE_INPUT_LINE_LEN - 1)
	    {
		outputf("%sline too long for internal buffer.\n",
			lookup.where);
		retval = FILE_HAS_UNKNOWN_COMMAND;
		break;
	    }
	}

	i = parse_command(line, len, accept, skip);

	if (i == COMMAND_NOT_FOUND)
	{
	    outputf("%sunknown command:\n   " quote(%s) "\n",
		    lookup.where, line);
	    retval = FILE_HAS_UNKNOWN_COMMAND;
	    break;
	} else	if (i == COMMAND_EXECUTED_WITH_ERROR) {
	    retval = FILE_HAS_BAD_COMMAND;
	    break;
	} else if (exit_program_now) {
	    exit_program_now = 0; /* exit only the source file */
	    break;
	} else if (apply_regex_abort)
	    break;
    }
    fclose(fp);
    free(lookup.where);
    lookup.where = old_where;
    lookup.slot = old_slot;
    return FILE_READ_OK;
}
