/*
 * 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").
 *
 * October 1993
 *
 */
#include "lib/config.h"
#include "lib/assert.h"
#include <ctype.h>
#include <signal.h>
#include <sys/ioctl.h>
#include "lib/output.h"
#include "lib/fuzzkana.h"
#include "lib/loadfile.h"
#include "lib/romaji2kana.h"
#include "lib/jregex.h"
#include "lib/strsave.h"
#include "lib/replace.h"
#include "lib/xmalloc.h"
#include "lib/input.h"
#include "lookup.h"

struct lookup lookup;
int UseNoMemIndex = 0;

const char *expand_filename_tilde(const char *filename)
{
    extern const char *getenv(const char *);
    String *home = (String*)getenv("HOME");
    if (home == 0 || filename[0] != '~' || filename[1] != '/')
	return filename;
    else
    {
	static const char *new = 0;
	if (new)
	    free(new);
	/*
	 * Note: in calculating the length to malloc, we realize that we'll
	 * need an extra byte for the final null, but since we won't be
	 * using the '~' in FILENAME, it all works out.
	 */
	new = xmalloc(str_len(home) + str_len(filename));
	str_cpy(new, home);
	str_cat(new, &filename[1]);
	return new;
    }
}

#ifdef LOG_FILE_SUPPORT
 String *current_log_file = 0;
#endif

#ifdef SERVER_CONFIG
static unsigned int serv_tcp_port = SERV_TCP_PORT; /* default port */
static int verbose_server = 0;
#endif

#ifndef SERVER_CONFIG
#define DEFAULT_PROMPT "%C(%0 command)%!C(search [%n])> "

static int in_command;

# ifdef NOREADLINE
#  include <stdio.h>
# else
#  include "lib/jreadline.h"
# endif

/*
 * Getline: get a line of user input.
 */
static __inline__ string *
getline(String *prompt)
{
#ifdef NOREADLINE
    static string linebuf[200];
    String *line;
    unsigned len;

    output(prompt);
    flush_output();
    line = fgets(linebuf, sizeof(linebuf), stdin);
    if (line == 0)
	return 0;
    len = str_len(linebuf);
    if (linebuf[len-1] == '\n')
	ilnebuf[len-1] = 0;
    return linebuf;
#else /* NOREADLINE */
    static string *lastline = 0, *line = 0;
 #ifdef LOG_FILE_SUPPORT
    int log_fd=0;
 #endif
    if (line)
	free(line);  /* free the previously read line */

    jreadline_auto_romaji = lookup.default_slot->default_flag.autokana;
    apply_regex_abort = 0;

 #ifdef LOG_FILE_SUPPORT
    if (current_log_file != 0) {
	log_fd = set_extra_output_file(JUST_CHECKING_OUTPUT_FILE);
	flush_output();
	set_extra_output_file(NO_OUTPUT_FILE); /* turn logging off */
    }
 #endif /* LOG_FILE_SUPPORT */

    output_pager_reset_more();
    line = readline(prompt); /* Get a line from the user. */
    output_pager_reset_more();

 #ifdef LOG_FILE_SUPPORT
    if (current_log_file != 0) {
	int normal_fd = set_normal_output_file(log_fd);
	outputf("%s%s\n", jreadline_last_prompt, line);
	flush_output();
	set_normal_output_file(normal_fd);
	set_extra_output_file(log_fd);
    }
 #endif /* LOG_FILE_SUPPORT */

    /* If the line has any text in it, save it on the history. */
    if (line && *line && !(lastline && str_eql(lastline, line)))
    {
	if (lastline)
	    free(lastline);
	lastline = strsave(line);
	add_history(line);
    }
    return line;
#endif /* NOREADLINE */
}

/* If we get a kill or something, tell the apply_regex routine to abort */
static int
sighandler(int sig)
{
    if (apply_regex_abort++ > 2)
	exit(1); /* exit immediately if three aborts in a row. */
    return 0;
}

static void
romaji_converter(string *start_of_line, String *bufend,
		 string **dot_p, String **eol_p, int force)
{
    string *dot = *dot_p;
    string *slwp = start_of_line; /*Start of Line Without Prefixes*/
    int eat_leading_slash = !force; /* if forcing, don't eat slash */
    static const char *allowed = 0;

    /*
     * This works with the main input processor... both must know the
     * form of an input line to at least some extent.
     *
     * Skip past any line prefixes. If the first character is then
     * cmdstart_char, we're on a command line, so we'll just return.
     */
    if (slwp[0] == '+')
	slwp++;
    if (slwp[0] == '!')
    {
	unsigned char c;
	while (c = (++slwp)[0], slwp < dot && isascii(c) && isalpha(c))
	    ;
	if (c == '!')
	    slwp++;
    }
    if (slwp >= dot)
	return;

    if (!force)
    {
	/*
	 * If we're on a multiple-regex line (with || or |!|) figure
	 * the start of the regex we're on.
	 */
	string *this_regex = slwp;
	string *ptr;

	/* line "begins" with cmdstart_char no conversion */
	if (this_regex[0] == lookup.cmdstart_char)
	    return;

	for (ptr = dot - 1; ptr > &slwp[3]; ptr--)
	{
	    if (ptr[-1] != '|')
		continue;
	    if (ptr[-2] == '|' && ptr[-3] != '\\')
	    {
		this_regex = ptr;
		break;
	    }
	    if (ptr[-2] == '!' && ptr[-3] == '|' &&
		(&ptr[-4] >= start_of_line) && ptr[-4] != '\\')
	    {
		this_regex = ptr;
		break;
	    }
	}

	/*
	 * If first character of regex is '=', don't do any
	 * automatic conversion.
	 */
	if (this_regex[0] == '=')
	    return;

	if (this_regex[0] == '[' || this_regex[0] == '/')
	{
	    allowed = std_romaji_allowed_nonletters("-^'*?+.[]<>\\$ \t");

	    if (this_regex[0] == '[')
	    {
		this_regex[0] = '/';
		std_romaji_converter(start_of_line,bufend,dot_p,eol_p,force,0);
		this_regex[0] = '[';
		goto done;
	    }

	    /* else this_regex[0] == '/' */
	    while (--dot > this_regex)
		if (*dot == '/')
		    break;
	    if (dot == this_regex)
		eat_leading_slash = 0;
	}
    }

    std_romaji_converter(start_of_line, bufend, dot_p, eol_p,
			 force, eat_leading_slash);
  done:
    if (allowed)
	std_romaji_allowed_nonletters(allowed);
}

static int
check_commandline_status(string *line, string **p_dot, string **p_eol)
{
    int old_in_command = in_command;
    String *eol = *p_eol;

    if (line[0] == '+')
	line++;

    if (line[0] == '!')
    {
	unsigned char c;
	while (c = (++line)[0], line < eol && isascii(c) && isalpha(c))
	    ;
	if (c == '!')
	    line++;
    }
    if (line >= eol)
	in_command = 0;
    else
	in_command = (line[0] == lookup.cmdstart_char);

    if (in_command != old_in_command)
    {
	(void)jreadline_mod_prompt(gen_prompt(lookup.prompt_format, 0));
	return 1;
    }
    return 0;
}


#define PROMPT_OK                         0
#define PROMPT_UNMATCHED_OPEN            -1
#define PROMPT_UNMATCHED_CLOSE           -2
#define PROMPT_UNEXPECTED_EOL            -3
#define PROMPT_EXPECTED_PAREN_OR_QUOTE   -4
#define PROMPT_OVERFLOW                  -5

static String *prompt_errors[] =
{
   (String *)"ok",
   (String *)"unmatched open paren",
   (String *)"unmatched close paren",
   (String *)"unexpected end-of-string",
   (String *)"expected open paren or quote",
   (String *)"prompt too large"
};

/*
 * Given a pointer into a string just beyond an open paren,
 * and a pointer to the end of the string, return the number of
 * characters to the matching close paren. If the return value is
 * negative, it's one of the PROMPT_* errors.
 */
static int skip_parens(String *template, String *tend)
{
    String *orig = template;
    unsigned char c;
    int level;

    for (level = 0; template < tend; template++)
    {
	if (c = *template, c == '\\')
	    template++;
	else if (c == '(')
	    level++;
	else if (c == ')') {
	    if (level == 0)
		return template - orig;
	    if (level == 0)
		return PROMPT_UNMATCHED_CLOSE;
	    --level;
	}
    }
    return PROMPT_UNMATCHED_OPEN;
}

static string *do_prompt_buf;
static int do_prompt_len;
static int do_prompt_use_command;

static int do_create_prompt(String *template, String *tend)
{
    unsigned char c;
    String *orig_buf = do_prompt_buf;

    while (template < tend)
    {
	int not;
	String *temp;
	
	/* deal with non-special and escaped characters */
	if (c = *template, c != '%')
	{
	    if (c == '\\')
	    {
		if (++template >= tend)
		    return PROMPT_UNEXPECTED_EOL;
		c = *template;
	    }
	    if (do_prompt_len-- == 0)
		return PROMPT_OVERFLOW;
	    *do_prompt_buf++ = c;
	    ++template;
	    continue;
	}

	temp = template;
	c = *++template;

	not = 0;
	while (c == '!')
	{
	    not = !not;
	    c = *++template;
	}

	if (template >= tend)
	    return PROMPT_UNEXPECTED_EOL;

	switch(c)
	{
	    int len, doit; /* "may be used uninitialized" warning OK here */
	    unsigned char temp_buf[3];

	  do_flag:
	    kibishii_assert(doit == 0 || doit == 1);
	    /* expect an open paren */
	    if (template[1] == '\'' || template[1] == '"')
	    {
		unsigned char match = template[1];
		temp = (template += 2);
		while (template < tend && *template != match)
		    template++;
		if (template++ >= tend)
		    return PROMPT_UNEXPECTED_EOL;
		if (doit) {
		    len = template - temp - 1;
		    goto dump_temp_with_len;
		}
	    } else if (template[1] != '(') /* ) */
		return PROMPT_EXPECTED_PAREN_OR_QUOTE;
	    else if (len = skip_parens(&template[2], tend), len < 0)
		return len;
	    else
	    {
		if (doit)
		{
		    int retval =
			do_create_prompt(&template[2], &template[2+len]);
		    if (retval < 0)
			return retval;
		}
		template += 2 + len +1;
	    }
	    break;
	    
	  dump_temp:
	    len = str_len(temp);
	  dump_temp_with_len:
	    kibishii_assert(len < 1234); /* some random sanity check */
	    if (do_prompt_len -= len, do_prompt_len < 0)
		return PROMPT_OVERFLOW;
	    while (len-- > 0)
		*do_prompt_buf++ = *temp++;
	    break;

	  default:
	    len = ++template - temp;
	    goto dump_temp_with_len;

	  case '#':
	    ++template;
	    temp_buf[0] = '0' + slot_num(lookup.default_slot);
	    temp = temp_buf;
	    len = 1;
	    goto dump_temp_with_len;
	       
	  case 'n':
	    ++template;
	    if (COMBO(lookup.default_slot)) {
		kibishii_assert(lookup.default_slot->combo.name);
		temp = lookup.default_slot->combo.name;
	    } else {
		kibishii_assert(lookup.default_slot->file->short_filename);
		temp = (String *)lookup.default_slot->file->short_filename;
	    }
	    goto dump_temp;

	  case 'N': /*... */
	    ++template;
	    if (COMBO(lookup.default_slot)) {
		kibishii_assert(lookup.default_slot->combo.name);
		temp = lookup.default_slot->combo.name;
	    } else {
		kibishii_assert(lookup.default_slot->file->v->filename);
		temp = (String *)lookup.default_slot->file->v->filename;
	    }
	    goto dump_temp;

	  case 'w':
	    doit = lookup.default_slot->default_flag.word != not;
	    goto do_flag;

	  case 'W':
	    doit = lookup.default_slot->default_flag.glob != not;
	    goto do_flag;

	  case 'd':
	    doit = lookup.default_slot->default_flag.display != not;
	    goto do_flag;

	  case 'f':
	    doit = lookup.default_slot->default_flag.fuzz != not;
	    goto do_flag;

	  case 'F':
	    doit = (lookup.default_slot->default_flag.filter != not &&
		    (COMBO(lookup.default_slot) ||
		     lookup.default_slot->filter_spec.pattern));
	    goto do_flag;

	  case 'M':
	    doit = (lookup.default_slot->default_flag.modify != not &&
		    (COMBO(lookup.default_slot) ||
		     lookup.default_slot->modify_spec.pattern));
	    goto do_flag;

	  case 'C':
	    doit = (in_command != not);
	    do_prompt_use_command = 1;
	    goto do_flag;

	  case 'c':
	    doit = lookup.default_slot->default_flag.fold != not;
	    goto do_flag;

	  case 'S':
	    ++template;
	    temp = &lookup.cmdstart_char;
	    len = 1;
	    goto dump_temp_with_len;

	  case '0':
	    ++template;
	    temp = (String *)lookup.prog_short;
	    goto dump_temp;

	  case 'l': /* is logging */
	    #ifdef LOG_FILE_SUPPORT
	      doit = !!(current_log_file) != not;
	    #else
	      doit = 0;
	    #endif
	    goto do_flag;

	  case 'L': /* logging file name, if any */
	    ++template;
	    #ifdef LOG_FILE_SUPPORT
		if (current_log_file) {
		    temp = current_log_file;
		    goto dump_temp;
		}
	    #endif
	    break;
	}
    }
    return do_prompt_buf - orig_buf;
}

String *gen_prompt(String *template, int showerror)
{
    static unsigned char buffer[100];
    int i;

    do_prompt_use_command = 0;
    do_prompt_buf = buffer;
    do_prompt_len = sizeof(buffer);
    i = do_create_prompt(template, template + str_len(template));
    if (i < 0)
    {
	if (showerror)
	    warn("(prompt spec error: %s)\n",
		 (unsigned)-i > array_elements(prompt_errors) ?
		 (String*)"???" : prompt_errors[-i]);
	jreadline_access = 0;
	return template;
    }
    buffer[i] = '\0';
    jreadline_access = do_prompt_use_command ? check_commandline_status: 0;
    return buffer;
}
#endif /* if not SERVER_CONFIG */

static char **temp_memory = 0; /* used in temp_malloc(), et. al. */

/*
 * Allocate temporary memory that will be freed when free_temp_memory
 * is later called. Don't use for allocating double floating point
 * values (due to possible alignment problems)
 */
static void *temp_malloc(unsigned size)
{
    char **mem = xmalloc(size + /*enough for hidden link*/ sizeof(mem));
    *(char ***)mem = temp_memory; /* Link the chain to the new memory */
    temp_memory = mem;		  /* and the new memory to the chain. */
    return &mem[1];               /* Return the requested non-hidden memory */
}

/*
 * Free whatever memory has been allocated via temp_malloc.
 */
static void free_temp_memory(void)
{
    while (temp_memory)
    {
	char *tmp = (char *)temp_memory;
	temp_memory = (char **)*temp_memory;
	free(tmp);
    }
}

/*
 * return true if OK, zero on error.
 */
static __inline__ int
prepare_line(int print_only, string *line, unsigned len, int not)
{
    int convert_from_romaji = 0;
    
    if (lookup.patterns >= MAX_PATS_ON_ONE_LINE)
    {
	outputf("maximum of %d regexes per line\n", MAX_PATS_ON_ONE_LINE);
	return /*BAD*/0;
    }

    /* if pattern begins with '=', skip that character */
    if (line[0] == '=') {
	line++;
	len--;
    } else if (line[0] == '[') {
	line[0] = '<';
	if (line[len-1] == ']')
	    line[len-1] = '>';
	convert_from_romaji = 1;
    } else if (line[0] == '/') {
	convert_from_romaji = 1;
	line++;
	len--;
    }

    if (line[0] == '\0')
    {
	output("¢ãno pattern!¢ä\n");
	return /*BAD*/0;
    }

    if (lookup.slot->current_flag.glob)
    {
	unsigned char *new = temp_malloc(len * 2);
	unsigned char *dest = new, c;
	int is_word = lookup.slot->current_flag.word || line[0] == '<'
	    || line[0] == '[';

	while (c = *line++, c) {
	    if (c == '.' || c == '+') {
		*dest++ = '\\';
		*dest++ = c;
	    } else if (c == '*') {
		/*
		 * If in word mode or pattern begins with < or [,
		 * a wildcard '*' will become "\S*". Otherwise, ".*"
		 */
		if (!is_word) {
		    *dest++ = '.';
		} else {
		    *dest++ = '\\';
		    *dest++ = 'S';
		}
		*dest++ = '*';

		/*
		 * Just in case there were multiple *** in a row, eat them.
		 */
		while (*line == '*')
		    line++;

	    } else if (c == '?') {
		/* similar to '*' above, will become "." or "\S" */
		if (!is_word) {
		    *dest++ = '.';
		} else {
		    *dest++ = '\\';
		    *dest++ = 'S';
		}
	    } else {
		*dest++ = c;
	    }
	}
	*dest = '\0';
	line = new;
    }
	

    if (convert_from_romaji)
    {
	struct romaji2kana_info info;
	string *kana;
	int error = romaji2kana(line, line + len, 0, 0, &info);
	
	if (error < 0)
	{
	    outputf("¢ãromaji2kana returns %d¢ä\n", error);
	    return /*BAD*/0;
	}

	if (info.modified)
	{
	    /* get memory and do conversion */
	    kana = temp_malloc(info.k_buf_used);
	    error = romaji2kana(line, line+len, kana , info.k_buf_used, 0);
	    if (error)
	    {
		kibishii_assert(error > 0); /* < 0 case taken care of above */
		outputf("¢ãbad kana conversion %d" quote(%s) "¢ä\n",
			error, kana);
		return /*BAD*/0;
	    }
	    line = kana;
	}

	if (lookup.slot->current_flag.fuzz)
	{
	    /*
	     * No fuzz if *, +, or ? is used to modify a non-ASCII
	     */
	    unsigned const char *ptr = &line[1];
	    while (*ptr) {
		if (ptr[-1] >= 0x80 && (*ptr=='*' || *ptr=='+' || *ptr=='?'))
		    break;
		ptr++;
	    }
	    if (!*ptr) {
		unsigned buflen = fuzzkana(line, 0, 0, FUZZ_ALL);
		string *Fuzz;
		kibishii_assert(buflen != 0);
		fuzzkana(line, Fuzz = temp_malloc(buflen), buflen, FUZZ_ALL);
		line = Fuzz;
	    }
	}
    }

    if (print_only)
    {
	outputf("%s%s" quote(%s) "\n", lookup.patterns ? "and" : "a match is",
		not ? " not" : "", line);
    }
    else
    {
	lookup.search[lookup.patterns].pattern = line;
	lookup.search[lookup.patterns].not = not;
    }
    lookup.patterns++;
    return /* OK */1;
}


static __volatile__ void usage(void)
{
    warn("usage: %s [FLAGS] [FILES...]\n", lookup.prog);
    output(
        "  -help                :: report this list.\n"
	"  -version             :: show program version string and exit.\n"
     #ifdef SERVER_CONFIG
        "  -verbose             :: have server report verbosely to stdout.\n"
     #else
        "  -verbose             :: turn index-creation verbosity on\n"
     #endif
	"  -debug               :: turn on general debugging flag\n"
        "  -writeindex          :: create indices for FILES and exit.\n"
        "  -percent #           :: parameter for created indices.\n"
	"  -jis | -euc | -sjis  :: select encoding.\n"
     #ifndef SERVER_CONFIG
	"  -norc                :: (this is the default for a server)\n"
     #else
	"  -norc                :: don't read '~/.lookup'.\n"
     #endif
	"  -rc FILE             :: use FILE as the rc file.\n"
        "  -noindex             :: don't load FILE's precomputed index\n"
	"  -cmddebug            :: turn on command debugging flag\n"
	"  -regexdebug          :: turn on regex debugging flag\n"
     #ifdef SERVER_CONFIG
        "  -port #              :: use given port number.\n"
     #endif
        );
     #ifdef SERVER_CONFIG
         outputf("Default port number is %d.\n", serv_tcp_port);
     #endif
    exit(1);
}

#ifndef SERVER_CONFIG
#if OUTPUT_PAGER || !NOREADLINE  /* if we need the window size */
 /*
  * Taken from GNU emacs source -- Define the 4.3 names in terms of the
  * Sun names if the latter exist and the former do not.
  */
 #ifdef TIOCGSIZE
 # ifndef TIOCGWINSZ
 #  define TIOCGWINSZ TIOCGSIZE
 #  define winsize ttysize
 #  define ws_row ts_lines
 #  define ws_col ts_cols
 #endif
 #endif

 #ifdef TIOCGWINSZ
   #define GET_WINDOW_SIZE get_window_size_bsd
   int GET_WINDOW_SIZE(/*unused*/int signum)
   {
      struct winsize w;

      if (ioctl(2, TIOCGWINSZ, &w) == 0 && w.ws_row > 0)
      {
	  #if OUTPUT_PAGER
	  (void)output_pager_columns(w.ws_col);
	  (void)output_pager_lines(w.ws_row < 5 ? w.ws_row : w.ws_row - 2);
	  #endif
	  #if !NOREADLINE
	  (void)set_jreadline_width(w.ws_col);
	  #endif
      }

      #ifdef SIGWINCH
	  signal(SIGWINCH, GET_WINDOW_SIZE);
      #endif
      return 0;
   }
 #endif /* ifdef TIOCGWINSZ */

#endif /* OUTPUT_PAGER || !NOREADLINE */
#endif /* SERVER_CONFIG */

int slot_num(struct slot_info *s)
{
    unsigned i;
    for (i = 0; i < lookup.slots; i++)
	if (lookup.slot_info[i] == s)
	    return (int)i;
    kibishii_assert(0);
    return -1;
}

int exit_program_now = 0;

void process_input_line(string *input, int forced_search)
{
    int left_side_is_not = 0;
    int print_only = 0;
    int len;

    kibishii_assert(!exit_program_now);
    kibishii_assert(input && input[0]);

    if (input[0] == '?' && input[1] == '\0')	/* special request for help */
    {
	outputf("Command indicator is" quote(%c) "; use" quote(%chelp)
		"for help.\n", lookup.cmdstart_char, lookup.cmdstart_char);
	return;
    }

    /*
     * If the line starts with '+', then we'll only print the pattern
     * we would otherwise search for.
     */
    if (input[0] == '+')
    {
	input++;
	print_only = 1;
    }

    if (len = str_len(input), len == 0)
	return;
    
    lookup.slot = lookup.default_slot; /* default file if no ,# */
    
    /* ending with ",<digit>" means "apply command to <digit>th file" */
    if (len>2 && input[len-2]==',' && input[len-1]>='0' && input[len-1]<='9')
    {
	int value = input[len-1] - '0';
	if (value >= lookup.slots)
	{
	    outputf("Nothing loaded to slot #%d.\n", value);
	    return;
	}
	len -= 2;
	lookup.slot = lookup.slot_info[value];
	kibishii_assert(lookup.slot != 0);
    } else {
	/* allow an embedded ",1||" type thing */
	int i;
	for (i = 2; i < len; i++) {
	    if (input[i] == ',' &&
		input[i+1] >= '0' && input[i+1] <= '9' &&
		input[i+2] == '|' &&
	       (input[i+3] == '|' || (input[i+3] == '!' && input[i+4] == '|')))
	    {
		int value = input[i+1] - '0';
		if (value >= lookup.slots)
		    continue; /* quietly ignore ones that don't match */
		lookup.slot = lookup.slot_info[value];
		len -= 2;
		MOVE_MEMORY(&input[i+2], &input[i], len-i+1/*+1 for null*/);
		break;
	    }
        }
    }

    lookup.slot->current_flag = lookup.slot->default_flag;

    /*
     * If the line starts with a '!', read search modifiers.
     * Stop after the first non-letter. Eat a following '!' if there.
     */
    if (input[0] == '!')
    {
	unsigned char c;
	int count = 0;
	int error = 0;

	while (len--, c = (++input)[0], !error && isascii(c)&&isalpha(c))
	{
	    count++;
	    switch(c)
	    {
	      default:
		outputf("unknown !-code " quote(%c) "; use !? for list.\n",c);
		error = 1;
		break;
	      case 'w':
		lookup.slot->current_flag.word ^= 1;
		break;
	      case 'W':
		lookup.slot->current_flag.glob ^= 1;
		break;
	      case 'M':
		if (COMBO(lookup.slot) || lookup.slot->modify_spec.pattern)
		    lookup.slot->current_flag.modify ^= 1;
		break;
	      case 'F':
		if (COMBO(lookup.slot) || lookup.slot->filter_spec.pattern)
		    lookup.slot->current_flag.filter ^= 1;
		break;
	      case 't':
		if (COMBO(lookup.slot) || lookup.slot->tag_string)
		    lookup.slot->current_flag.tag ^= 1;
		break;
	      case 'c':
		lookup.slot->current_flag.fold ^= 1;
		break;
	      case 'd':
		lookup.slot->current_flag.display ^= 1;
		break;
	      case 'f':
		lookup.slot->current_flag.fuzz ^= 1;
		break;
	      case 'r': /* special "raw" (no fuzz) */
		lookup.slot->current_flag.fuzz = 0;
		break;
	      case 'h':
		lookup.slot->current_flag.highlight ^= 1;
		break;
	    }
	}

	if (c == '?')
	{
	    outputf("¨®¨¬¨¸¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¸¨¬¨¯\n"
		    "¨­ F¨¢toggle filtration           ¨¢%s¨­\n"
		    "¨­ M¨¢toggle modification         ¨¢%s¨­\n"
		    "¨­ w¨¢toggle word-preference mode ¨¢%s¨­\n",
		    lookup.slot->default_flag.filter ? "§·" : "  ",
		    lookup.slot->default_flag.modify ? "§·" : "  ",
		    lookup.slot->default_flag.word   ? "§·" : "  ");

	    outputf("¨­ c¨¢toggle case folding         ¨¢%s¨­\n"
		    "¨­ f¨¢toggle fuzzification        ¨¢%s¨­\n"
		    "¨­ r¨¢raw (force fuzzification off)   ¨­\n"
		    "¨­ W¨¢toggle wildcard-pattern mode¨¢%s¨­\n",
		    lookup.slot->default_flag.fold      ? "§·" : "  ",
		    lookup.slot->default_flag.fuzz      ? "§·" : "  ",
		    lookup.slot->default_flag.glob      ? "§·" : "  ");

	    outputf(
		    "¨­ h¨¢toggle match highlighting   ¨¢%s¨­\n"
		    "¨­ t¨¢toggle tagging              ¨¢%s¨­\n"
		    "¨­ d¨¢toggle displaying           ¨¢%s¨­\n"
		    "¨±¨¬¨º¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨¬¨º¨¬¨°\n",
		    lookup.slot->default_flag.highlight ? "§·" : "  ",
		    lookup.slot->default_flag.display   ? "§·" : "  ",
		    lookup.slot->default_flag.tag       ? "§·" : "  ");
	    return;
	}
	if (error)
	    return;
	
	if (count == 0)
	{
	    /* was simply a "!!", so do default "!", which is "!f" */
	    lookup.slot->current_flag.fuzz ^= 1;
	}

	if (c == '!') /* eat a trailing '!' if there */
	{
	    input++;
	    len--;
	}
    }

    kibishii_assert(len >= 0);
    if (len == 0)
	return;

    /*
     * beginning with cmdstart_char makes it a command, unless we were
     * told that it's a search regardless
     */
    if (!forced_search && input[0] == lookup.cmdstart_char)
    {
	/* skip the beginning-of-command character */
	while (*input == lookup.cmdstart_char) {
	    input++;
	    len--;
	}

	/* also skip spaces */
	while (*input == ' ') {
	    input++;
	    len--;
	}
	    
	if (len && parse_command(input,len,CMD_GENERAL,0) == COMMAND_NOT_FOUND)
	    outputf("unknown command" quote(%s) "\n", input);
	return;
    }

    input[len] = '\0'; /* ensure a null-terminated line */
    lookup.patterns = 0;
    left_side_is_not = 0;

    while (input)
    {
	string *ptr = input;
	int right_side_is_not = 0;
	string *nextstart = 0;

	kibishii_assert(*input != 0);

	while (ptr[0] != '\0')
	{
	    if (ptr[0] == '\\' && ptr[1] != 0)
		ptr += 2;
	    else if (ptr[0] == '|' && ptr[1] == '|') {
		nextstart = &ptr[2];
		ptr[0] = '\0';
		break;
	    } else if (ptr[0] == '|' && ptr[1] == '!' && ptr[2] == '|') {
		nextstart = &ptr[3];
		right_side_is_not = 1;
		ptr[0] = '\0';
		break;
	    } else {
		ptr++;
	    }
	}

	if (input == ptr)
	{
	    outputf("empty regex clause before" quote(%s) ".\n", input);
	    return;
	}

	if (nextstart && *nextstart == 0)
	{
	    outputf("expected regex after final" quote(|%s)
		    ", aborting\n", &ptr[1]);
	    return;
	}

	/* have a line to deal with */
	if (!prepare_line(print_only, input, ptr-input, left_side_is_not))
	    return;

	input = nextstart;
	left_side_is_not = right_side_is_not;
    }

    if (lookup.patterns == 0)
    {
	output("no patterns!\n");
	return;
    }

    if (print_only)
	return;

    apply_regex();
    free_temp_memory();

    if (!apply_regex_abort && lookup.slot->current_flag.display)
    {
	if (!lookup.count.printed && lookup.count.nonword)
	{
	    output("¢ãno whole words found, non-words shown here¢ä\n");
	    cmd_show();
	    lookup.count.printed += lookup.list.used;
	    if (lookup.count.filtered)
	    {
		outputf("¢ãadditionally, %ld ", lookup.count.filtered);
		if (!COMBO(lookup.slot) && lookup.slot->filter_spec.name != 0)
		    outputf(quote(%s), lookup.slot->filter_spec.name);
		output("filtered and discarded¢ä\n");
	    }
	    return;
	}
	if (lookup.list.used == 1 &&
	    (lookup.count.filtered + lookup.count.nonword) == 1)
	{
	    cmd_show();	    /* aw, just show */
	    lookup.count.printed += lookup.list.used;
	    return;
	}
    }

    if (lookup.count.filtered || lookup.count.nonword)
    {
	if (lookup.flag.debug)
	    outputf("[%ld checked; %ld matched; %ld filtered; "
		    "%ld nonword; %ld printed]\n",
		    lookup.count.checked, lookup.count.matched,
		    lookup.count.filtered, lookup.count.nonword,
		    lookup.count.printed);

	output("¢ãelided: ");
	if (lookup.count.filtered)
	{
	    if (COMBO(lookup.slot) || lookup.slot->filter_spec.name == 0)
		outputf("%ld filtered", lookup.count.filtered);
	    else
		outputf("%ld" quote(%s) "filtered", lookup.count.filtered,
			lookup.slot->filter_spec.name);
	}

	if (lookup.count.nonword)
	    outputf("%s%ld nonword",
		    lookup.count.filtered ? "; " : "", lookup.count.nonword);

	if (!lookup.list.used)
	    outputf("¢ä\n");
	else
	{
	    outputf(" (use" quote(%cshow) "to display", lookup.cmdstart_char);
	    if (lookup.list.overflow)
		outputf(" first %d", lookup.list.used);
	    outputf(")¢ä\n");
	}
    }
}


/*
 * Given a filename and some flags to describe how, load.
 * Returns the slot number, or a negative number on error;
 */
int load_file(const char *filename, unsigned flag)
{
    const char *action = "load";
    struct fileinfo *fileinfo = 0;
    int i;

    for (i = 0; i < lookup.slots; i++)
	if (!COMBO(lookup.slot_info[i]) &&
	    str_eql(filename, lookup.slot_info[i]->file->v->filename))
	{
	    fileinfo = lookup.slot_info[i]->file;
	    action = "link";
	    break;
	}

    if (fileinfo == 0)
    {
	if (lookup.slots >= MAX_LOADED_FILES)
	{
	    outputf("%scan't load [%s] (too many loaded files, max is %d)\n",
		    lookup.where, filename, MAX_LOADED_FILES);
	    return /*ERROR*/-1;
	}

	if (UseNoMemIndex)
	    flag |= LOADFILE_NO_MEM_INDEX;

	if (fileinfo = loadfile(filename, lookup.percent, flag), fileinfo == 0)
	    return /*ERROR*/-1;
	if (flag & LOADFILE_WRITEINDEX)
	    return 0; /* no need to save to slot if just writing and exiting */
	if (lookup.flag.verbose)
	    outputf("loaded \"%s\".\n", filename);
    }

    lookup.slot_info[lookup.slots] = xmalloc(sizeof(struct slot_info));
    bzero(lookup.slot_info[lookup.slots], sizeof(struct slot_info));
    lookup.slot_info[lookup.slots]->file = fileinfo;
    lookup.slot_info[lookup.slots]->default_flag = lookup.flag;
    lookup.slot_info[lookup.slots]->default_flag.filter = 0; /* just 2 b sure*/
    lookup.slot_info[lookup.slots]->default_flag.modify = 0; /* just 2 b sure*/
    if (lookup.flag.verbose)
	outputf("\r%sed \"%s\" to slot %d\n", action, filename, lookup.slots);
    return lookup.slots++;
}

static __inline__ void startup(int argc, const char *argv[])
{
    unsigned cmd_line_loadfile_flags = 0;
    const char *rc_file_name = 0;
    String *encoding = 0;
    unsigned read_rc_skip_mask = 0;

 #ifndef SERVER_CONFIG
    int read_rc = 1;
 #else /* SERVER_CONFIG */
    int read_rc = 0;
 #endif /* SERVER_CONFIG */

    if (argv[0] == 0)
	lookup.prog = lookup.prog_short = "lookup";
    else {
	const char *ptr;
	lookup.prog = argv[0];
	for (ptr = lookup.prog_short = lookup.prog; *ptr; ptr++)
	    if (*ptr == '/')
		lookup.prog_short = ptr+1;
    }

    lookup.where = (String *)"commandline: ";

    while (++argv, --argc > 0&& argv[0][0] == '-')
    {
	if (str_eql(*argv, "-help"))
	    usage();
	else if (str_eql(*argv, "-verbose"))
	{
	    cmd_line_loadfile_flags |= INDEX_REPORT_STATS;
            #ifdef SERVER_CONFIG
	        verbose_server = 1; /* additionally, report this stuff */
            #endif
	}
	else if (str_eql(*argv, "-debug"))
	    lookup.flag.debug = 1;
	else if (str_eql(*argv, "-v") || str_eql(*argv, "-version")) {
	    (void)cmd_version();
	    exit(0);
	}
	else if (str_eql(*argv, "-writeindex") ||
		   str_eql(*argv, "-write")) {
	    cmd_line_loadfile_flags |= LOADFILE_WRITEINDEX;
	    lookup.flag.verbose = 1;
	}
	else if (str_eql(*argv, "-rc")) {
	    if (--argc <= 0) {
		warn("%s: expected filename arg to %s\n",lookup.prog, argv[0]);
		usage();
	    }
	    rc_file_name = (++argv)[0];
	    read_rc = 1;
	}
	else if (str_eql(*argv, "-percent")) {
	    if (--argc > 0)
		lookup.percent = atoi((++argv)[0]);
	    else {
		warn("%s: expected arg to -percent\n", lookup.prog);
		usage();
	    }
	}
	else if (str_eql(*argv, "-jis"))
	    encoding = (String*)"encode jis";
	else if (str_eql(*argv, "-euc"))
	    encoding = (String*)"encode euc";
	else if (str_eql(*argv, "-sjis"))
	    encoding = (String*)"encode sjis";
	else if (str_eql(*argv, "-noindex"))
	    cmd_line_loadfile_flags |= LOADFILE_READINDEX;
	else if (str_eql(*argv, "-regexdebug"))
	    lookup.flag.regex_debug = 1;
	else if (str_eql(*argv, "-cmddebug"))
	    lookup.flag.cmd_debug = 1;
	else if (str_eql(*argv, "-nomem"))
	    UseNoMemIndex = 1;
	else if (str_eql(*argv, "-norc"))
	    read_rc = 0;
      #ifdef SERVER_CONFIG
	else if (str_eql(*argv, "-port")) {
	    if (--argc > 0)
		serv_tcp_port = atoi((++argv)[0]);
	    else {
		warn("%s: expected arg to -port\n", lookup.prog);
		usage();
	    }
	}
      #endif /* SERVER_CONFIG */
	else {
	    warn("%s: unknown flag \"%s\".\n", lookup.prog, *argv);
	    usage();
	}
    }

    if (encoding)
    {
	quick_command(encoding);
	read_rc_skip_mask = CMD_ENCODING_RELATED;
    }

    /* any remaining command-line arguments are files to load */
    if (argc)
    {
	/*
	 * Set default load flags if none had been set.
	 */
       	if ((cmd_line_loadfile_flags &
	     (LOADFILE_WRITEINDEX|LOADFILE_READINDEX)) == 0)
	{
	    cmd_line_loadfile_flags |= LOADFILE_READifPRESENT;
	}

	while (argc-- > 0)
	    if (load_file(argv++[0], cmd_line_loadfile_flags) < 0)
		die("exiting");
    }

    /* if just writing the index, exit now */
    if (cmd_line_loadfile_flags & LOADFILE_WRITEINDEX)
	exit(0);

    /*
     * Some more defaults for interactive use.
     */
    cmd_list_size((String*)"100");
    lookup.flag.verbose = 1;

    /*
     * If we haven't been told not to load the startup file
     * "~/.lookup", load it.  However, if we've loaded any files via
     * the command line, we'll not load any files or set any filters,
     * etc., from the startup file.
     */
    if (read_rc)
    {
	int i;
	int file_must_be_found = 1;
	if (rc_file_name == 0)
	{
	    rc_file_name = expand_filename_tilde("~/.lookup");
	    if (rc_file_name[0] == '~')
		rc_file_name += 2; /* skip the "~/" that didn't get expanded */
	    file_must_be_found = 0;
	}
	if (lookup.slots != 0)
	    read_rc_skip_mask |= CMD_LOAD_RELATED;

	i = read_commands_from_file(rc_file_name, CMD_GENERAL|CMD_FILE_ONLY,
				    read_rc_skip_mask);
	if (file_must_be_found && i == FILE_NOT_FOUND)
	    die("%s: startup file \"%s\" not found.\n",
		lookup.prog, rc_file_name);
	if (i != FILE_READ_OK && i != FILE_NOT_FOUND)
	    die("aborting");
    }

    /* if none loaded anywhere, nothing to do....  */
    if (lookup.slots == 0)
	die("%s: no files specified\n", lookup.prog);

  #ifndef SERVER_CONFIG
    /* set default prompt if none had been set in startup file */
    if (lookup.prompt_format == 0)
	lookup.prompt_format = strsave((String*)DEFAULT_PROMPT);
  #endif /* SERVER_CONFIG */

    /* set default slot if non had ben set in startup file */
    if (lookup.default_slot == 0)
	lookup.default_slot = lookup.slot_info[0];

    kibishii_assert(lookup.default_slot != 0);
    kibishii_assert(slot_num(lookup.default_slot) < MAX_LOADED_FILES);

  #ifndef SERVER_CONFIG
    (void)set_romaji_converter(romaji_converter);
    signal(SIGINT, sighandler);
  #endif /* SERVER_CONFIG */

    lookup.where = (String *)"";
}


#ifdef SERVER_CONFIG
/*
 * Experimental server.
 *
 * Compiling 'lookup' (and its library) with SERVER_CONFIG defined will
 * result in a server which will listen on a socket (at port SERV_TCP_PORT)
 * for command lines (which would normally come from the user via the
 * readline interface) and spit back the results to the socket.
 *
 * The idea is that some commonly-accessed takes-time-to-load file would be
 * loaded via a server-configured lookup, and then a simple client program
 * would access it when needed.
 *
 * I grafted this in (with, as it turns out, pretty minimal fudging) to for
 * use by my World Wide Web Japanese/English/Japanese dictionary server as
 * of October 94, at:
 *	http://www.wg.omron.co.jp/cgi-bin/j-e
 *
 * Others, however, might find it useful for other purposes.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#define heinous_death(SYSCALL)                                               \
{                                                                            \
    perror(SYSCALL);                                                         \
    die("%s: heinous death at %s line %d\n", lookup.prog,__FILE__,__LINE__); \
}

static void service_socket(void)
{
    struct sockaddr_in cli_addr, serv_addr;
    struct protoent *proto;
    int socket_fd;

    if (proto = getprotobyname("tcp"), proto == 0)
	heinous_death("getprotobyname");
    if (socket_fd = socket(AF_INET, SOCK_STREAM, proto->p_proto), socket_fd<0)
        heinous_death("socket");

    bzero((void*)&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(serv_tcp_port);

    if (bind(socket_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
	heinous_death("bind");

    if (listen(socket_fd, 5) < 0)
	heinous_death("listen");

    if (verbose_server)
	printf("server waiting...\n");

    for (;;)
    {
        int clilen, newsockfd, i, old_output = NO_OUTPUT_FILE;
	int continuous = 0, first = 1;
	string buffer[500];

        clilen = sizeof(cli_addr);
        newsockfd = accept(socket_fd, (struct sockaddr *)&cli_addr, &clilen);
        if (newsockfd < 0)
	    heinous_death("accept");
        while (i = read(newsockfd, buffer, sizeof(buffer)), i > 0)
	{
	    buffer[i] = '\0'; /* ensure capped off */
	    if (str_eql(buffer, "-- -exit- --")) {
		output("--exit--\n");
		exit(1);
	    }

	    if (first) {
		old_output = set_normal_output_file(newsockfd);
		first = 0;
		if (str_eql(buffer, "--continuous--")) {
		    continuous = 1;
		    output("--ok--\n");
		    flush_output();
		    continue;
		}
	    }
	    if (continuous && str_eql(buffer, "--bye--")) {
		output("--bye--\n");
		break;
	    }
	    process_input_line(buffer, 0);

	    /* Report to the server's TTY */
	    if (verbose_server)
	    {
		printf("%s: %ld checked; %ld matched; %ld filtered; "
		       "%ld nonword; %ld printed\n", buffer,
		       lookup.count.checked, lookup.count.matched,
		       lookup.count.filtered, lookup.count.nonword,
		       lookup.count.printed);
	    }

	    /* if not continuous, always exit after the first line */
	    if (!continuous)
		break;
	    output("--done--\n"); /* tell that no more output coming */
	    flush_output();
        }
	flush_output();
	if (old_output != NO_OUTPUT_FILE) {
	    set_normal_output_file(old_output);
	}
	close(newsockfd);
    }
}
#endif /* SERVER_CONFIG */

int main(int argc, const char *argv[])
{
    #ifdef GET_WINDOW_SIZE
    GET_WINDOW_SIZE(0);
    #endif
       
    /* set some basic defaults */
    lookup.flag.fuzz = 1;
    lookup.flag.fold = 1;
    lookup.flag.display = 1;
    lookup.flag.autokana = 1;
    lookup.cmdstart_char = ' ';
  #ifdef HAVE_SPINNER
    lookup.spinner.interval = 200;
  #endif
    lookup.max_lines_to_print = 100;
    lookup.percent = 100;

    startup(argc, argv);

    kibishii_assert(lookup.default_slot);

 #ifdef SERVER_CONFIG
    {
	extern void service_socket(void);
	extern int std_romaji_toggled_force;
	lookup.flag.verbose = 0;
	std_romaji_toggled_force = 0;
	cmd_output_encoding((String *)"euc", 0, 0, 0);
	service_socket();
    }
 #else /* SERVER_CONFIG */
    do
    {
	static int abort_count = 0;
	String *prompt_format = lookup.default_slot->prompt_format ?
	    lookup.default_slot->prompt_format : lookup.prompt_format;
	string *input;

	in_command = 0;
	kibishii_assert(prompt_format);
	input = getline(gen_prompt(prompt_format, 1));

	/* if they're pounding on the break key, let them eventually leave */
	if (apply_regex_abort)
	{
	    if (abort_count++ == 2)
		output("  ¢ª  ¢¨one more to abort¢¨  ¢«\n");
	    else if (abort_count >= 3)
	    {
		output("  ¢¨abort¢¨\n");
		break;
	    }
	    continue;
	}
	abort_count = 0;

	if (input == 0)
	    break;
	else if (input[0] != '\0')
	    process_input_line(input, 0);
    } while (!exit_program_now);

    #ifdef LOG_FILE_SUPPORT
	/*
	 * If there's a log open, close it verbosely.
	 */
	if (output_fd_valid(set_extra_output_file(JUST_CHECKING_OUTPUT_FILE)))
	{
	    lookup.flag.verbose = 1;
	    cmd_log(1,0,0);
	}
    #endif /* LOG_FILE_SUPPORT */
  #endif /* SERVER_CONFIG */
    return 0;
}
