/*
# Copyright (c) 2001 Toshiki Fujisawa <xbible@fujisawa.gr.jp>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * xbible: BIBLE CODE EXTRACT PROGRAM
 *
 *  Development and maintainance by:
 *      Toshiki Fujisawa <xbible@fujisawa.gr.jp>
 *
 *  HISTORY:
 *  ----------- -------- ---- ------------------------------------------
 *     DATE       name   ver.                Comments
 *  ----------- -------- ---- ------------------------------------------
 *  Jan  2 2001 fujisawa ---- Create from scratch
 *  Jan  2 2001 fujisawa 0.00 Search only version
 *  Jan  3 2001 fujisawa 0.01 Display matorix
 *  Jan  4 2001 fujisawa 0.02 Add many options
 *  Jan  8 2001 fujisawa 1.00 Release for Linux Magazine Mar 2001
 *  Jan 15 2001 fujisawa 1.10 "AND" disp, [$]->[S], [+]->[T], speedup
 *  Feb  3 2001 fujisawa 1.11 Change max_skip, [#]->[s]
 *  Feb  4 2001 fujisawa 1.20 Release for Linux Magazine Apr 2001
 *  Feb 11 2001 fujisawa 1.21 Release for www.fujisawa.gr.jp
 *  Feb 14 2001 fujisawa 1.22 Release for www.fujisawa.gr.jp
 *  Feb 23 2001 fujisawa 1.23 Release for www.fujisawa.gr.jp
 *  Mar 12 2001 fujisawa 1.24 Release for Linux Magazine Apr 2001
 *  Mar 23 2001 fujisawa 1.25 Release for www.fujisawa.gr.jp
 */
#define XBIBLE_VERSION "version-1.25 Mar 23 2001"
/*
 * CHANGES:
 *
 *   Ver1.00 
 *     o  RELEASE for Linux Magazine Mar 20001
 *
 *   Ver1.10 
 *     o  Display a table only if all argument words are exist.
 *     o  Print the word count number for all words
 *     o  Change mapping(bible to ascii) [$]-->[S], [+]-->[T]
 *     o  5% speedup.
 *     o  Cnage the mark char (pair char).
 *
 *   Ver1.11
 *     o  An improvement of max_skip
 *     o  Change mapping(bible to ascii) [#] --> [s]
 *
 *   Ver1.20 (cvs tag release_1_20)
 *     o  Change license ( GPL to BSD ) 
 *     o  RELEASE for Linux Magazine Apr 20001
 *
 *   Ver1.21 (cvs tag release_1_21)
 *     o  Add a random text mode ( "-r" option ).
 *     o  Speedup by using char index cache. (13min to 27sec)
 *     o  Speedup by using each length comprare.
 *     o  Save memory of bible data. ( half memory )
 *     o  Remove -v option.
 *     o  "-x" option change to "-g" and "-G".
 *     o  Add hebrew alt error message.
 *     o  RELEASE for www.fujisawa.gr.jp
 *
 *   Ver1.22 (cvs tag release_1_22)
 *     o  search bug fix
 *     o  RELEASE for www.fujisawa.gr.jp
 *
 *   Ver1.23 (cvs tag release_1_23)
 *     o  add -m option ( display match count by distance sort )
 *     o  RELEASE for www.fujisawa.gr.jp
 *
 *   Ver1.24 (cvs tag release_1_24)
 *     o  RELEASE for Linux Magazine May 20001
 *
 *   Ver1.25 (cvs tag release_1_25)
 *     o  RELEASE for www.fujisawa.gr.jp
 */
/*===========================================================================*/
/*  Include files                                                            */
/*===========================================================================*/
#include <stdio.h>  /* printf()                   */
#include <stdlib.h> /* abs()                      */
#include <string.h> /* strlen() strcpy() strchr() */
#include <limits.h> /* INT_MIN INT_MAX            */
/*===========================================================================*/
/*  Mingw32 has no getopt include file                                       */
/*===========================================================================*/
#ifdef __MINGW32__  /* mingw32(gcc) is a cross compiler for Win32 on FreeBSD */
    extern char *optarg;
    extern int   optind, opterr, optopt;
    int          getopt(int, char * const [], const char *);
#else
    #include <unistd.h> /* getopt() */
#endif
/*===========================================================================*/
/*  Use 'bsd_lrand48' insted of libc's lrand48 for same random in all OS.    */
/*===========================================================================*/
#ifndef __P
#define __P(x)         x
#endif
#define srand48(x)     bsd_srand48(x)
#define lrand48(x)     bsd_lrand48(x)
#define _rand48_seed   bsd__rand48_seed
#define _rand48_mult   bsd__rand48_mult
#define _rand48_add    bsd__rand48_add
#define _dorand48      bsd__dorand48
#define BSD_RAND_MAX   0x7fffffff
#include "lib_FreeBSD/_rand48.c" /* FreeBSD-3.3 <src/lib/libc/gen/_rand48.c> */
#include "lib_FreeBSD/srand48.c" /* FreeBSD-3.3 <src/lib/libc/gen/srand48.c> */
#include "lib_FreeBSD/lrand48.c" /* FreeBSD-3.3 <src/lib/libc/gen/lrand48.c> */
/*===========================================================================*/
/* Error macro definition                                                    */
/*===========================================================================*/
#define fatal(fmt...) { printf("ERROR: "); \
                        printf(##fmt); putchar('\n'); exit(1); }
/*---------------------------------------------------------------------------*/
#define warn(fmt...)  { printf("!!WARNING: "); \
                        printf(##fmt); putchar('\n'); }
/*---------------------------------------------------------------------------*/
#define bug(fmt...)   { printf("%s(%d): [BUG] ", __FILE__, __LINE__ ); \
                        printf(##fmt); putchar('\n'); exit(1); }
/*---------------------------------------------------------------------------*/
#ifdef DEBUG
/*---------------------------------------------------------------------------*/
#define debug(fmt...) { printf("%s(%d): ", __FILE__, __LINE__ ); \
                        printf(##fmt); putchar('\n');  }
/*---------------------------------------------------------------------------*/
#else
/*---------------------------------------------------------------------------*/
#define debug(fmt...)  /* none */
/*---------------------------------------------------------------------------*/
#endif
/*===========================================================================*/
/* Bible text is defined in other file                                       */
/*===========================================================================*/
extern char      bible_data[];
/*===========================================================================*/
/* Constant definition                                                       */
/*===========================================================================*/
#define BIBLE_LEN           304805
#define MAX_WORD                16
#define DEF_MAX_SKIP     BIBLE_LEN
#define DEF_WIN_COL             80
#define DEF_WIN_ROW             -1
#define DEF_LINESKIP_DIV         1
#define DEF_LINESKIP_ADD         0
#define DEF_OFFSET_COL           0
#define DEF_OFFSET_ROW           0
#define LIMIT_MAX_X            200
#define LIMIT_MAX_Y            200
#define OUTSIDE        ((char)127)
#define MAX_WORD_LIST         4096
#define MAX_OPT_M             1000
/*===========================================================================*/
/* Global Option variables                                                   */
/*===========================================================================*/
typedef enum { false=0, true=1 } bool_t;
/*---------------------------------------------------------------------------*/
static char  **cmd_argv;                     /* option argv */
static int     cmd_argc;                     /* option argc */
/*---------------------------------------------------------------------------*/
static bool_t  opt_g     = false;            /* grep display                 */
static bool_t  opt_G     = false;            /* grep display (vervose mode)  */
static bool_t  opt_r     = false;            /* random mode (not use bible)  */
static int     opt_c     = 1;                /* count of output              */
static int     opt_m     = MAX_OPT_M;        /* mark count                   */
static int     opt_w_col = DEF_WIN_COL;      /* window row of terminal       */
static int     opt_w_row = DEF_WIN_ROW;      /* window col of terminal       */
static int     opt_l_add = DEF_LINESKIP_ADD; /* line skip of x for display   */
static int     opt_l_div = DEF_LINESKIP_DIV; /* line skip of y for display   */
static int     opt_o_col = DEF_OFFSET_COL;   /* offset col for display       */
static int     opt_o_row = DEF_OFFSET_ROW;   /* offset row for display       */
/*---------------------------------------------------------------------------*/
/*     int     opt_n_min = 0;                 * number begin on bible text   */
/*     int     opt_n_max = BIBLE_LEN;         * number end on bible text     */
/*     int     opt_s_min = 1;                 * skip min length              */
/*     int     opt_s_max = DEF_MAX_SKIP;      * skip max length              */
/*===========================================================================*/
/* Function definitions                                                      */
/*===========================================================================*/
static void
usage(void)
{
  printf("BIBLE CODE EXTRACT PROGRAM  %s\n", XBIBLE_VERSION);
  printf("Copyright(c) 2001 Toshiki Fujisawa <xbible@fujisawa.gr.jp>\n");
  printf("\n");
  printf("usage: xbible [options] words...\n");
  printf("       -g               : grep mode (all search)\n");
  printf("       -G               : grep mode (display near chars)\n");
  printf("       -r               : random text mode (not use bible text)\n");
  printf("       -c {count}       : count of table number\n");
  printf("       -m {match_count} : match word count\n");
  printf("       -w {win_size}    : window size of terminal\n");
  printf("       -n {num_range}   : number range from top in bible\n");
  printf("       -s {skip_range}  : skip min and max\n");
  printf("       -l {line_div}    : line divide number\n");
  printf("       -o {ofset_disp}  : offset col and row of display\n");
  printf("       {win_size}    -w80x25\n");
  printf("       {num_range}   -n20000 -n2000-2999 -n2000+999\n");
  printf("       {skip_range}  -s4772  -s4772-6772 -s4772+2000\n");
  printf("       {line_div}    -l3 -l3:+1 -l3:-1 -l:4772 \n");
  printf("       {offset_disp} -o+3+4 -o-3-4\n");
  printf("\n");
  printf("ex1: xbible -G  ycxqr\n");
  printf("ex2: xbible -n254245 -s4772 -w78x18 -o-1,+2 ycxqrbyn xcryrsaxcvr\n");
  exit(0);
}
/*---------------------------------------------------------------------------*/
static inline void
revcpy(register char *dst, register char *src)
{
	dst += strlen(src);
	*dst-- = '\0';
	while( *src ){
		*dst-- = *src++;			
	}
}
/*---------------------------------------------------------------------------*/
static inline void
setup_index(char *top, char *end, register char c, register char **ans_index)
{
	register char    *p;

	for(p=top; p<end; p++){
		if( *p == c ){
			*ans_index++ = p;
		}
	}
	*ans_index++ = end + 1;
}
/*---------------------------------------------------------------------------*/
static inline void
rand_init(void)
{
	bsd_srand48(0x12345678);
}
/*---------------------------------------------------------------------------*/
static inline long
rand_bible(void)
{
	return (long)(
	    ( (long long)(BIBLE_LEN-1) * (unsigned long long)bsd_lrand48() )
	    / (long long) BSD_RAND_MAX
	);
}
/*---------------------------------------------------------------------------*/
typedef struct { char heb_char; int count; } freq_t;
/*---------------------------------------------------------------------------*/
static freq_t freq_table[] = {
	/* Number of each char bytes in BIBLE_LEN(304805bytes) */
	{ 'S',  1833 }, /*  1 */
	{ 'T',  1804 }, /*  2 */
	{ 'a', 27059 }, /*  3 */
	{ 'b', 16345 }, /*  4 */
	{ 'c',  3962 }, /*  5 */
	{ 'd',  7032 }, /*  6 */
	{ 'g',  2109 }, /*  7 */
	{ 'h', 28056 }, /*  8 */
	{ 'k', 11968 }, /*  9 */
	{ 'l', 21570 }, /* 10 */
	{ 'm', 25090 }, /* 11 */
	{ 'n', 14126 }, /* 12 */
	{ 'o', 11250 }, /* 13 */
	{ 'p',  4805 }, /* 14 */
	{ 'q',  4695 }, /* 15 */
	{ 'r', 18125 }, /* 16 */
	{ 's', 15595 }, /* 17 */
	{ 't', 17950 }, /* 18 */
	{ 'v', 30513 }, /* 19 */
	{ 'x',  7189 }, /* 20 */
	{ 'y', 31531 }, /* 21 */
	{ 'z',  2198 }  /* 22 */
};
#define MAX_FREQ (sizeof(freq_table)/sizeof(freq_t))
/*---------------------------------------------------------------------------*/
void
setup_random(char *bible)
{
	int     i;
	long    n;
	int     t = 0;
	int     f;
	int	sum_freq[MAX_FREQ+1];

	rand_init();

	for(f=0;f<MAX_FREQ; f++){
		t += freq_table[f].count;
		sum_freq[f] = t;
	}

	for(i=0; i<BIBLE_LEN; i++){
		n = rand_bible();
		for(f=0;f<MAX_FREQ; f++){
			if( n < sum_freq[f] ){
				break;
			}
		}
		*bible++ = freq_table[f].heb_char;
	}
	#if defined(MAKE_RANDOM_TEXT)
	{
		FILE *fp=fopen("random.txt","w");
		if( fp == NULL ) fatal("Can't open random.txt");
		if( fwrite(bible_data,BIBLE_LEN,1,fp)!= 1){
			fatal("Can't write to randm.txt");
		}
		exit(0);
	}
	#endif
}
/*---------------------------------------------------------------------------*/
static char
is_hebrew( int c )
{
	static char     lower[0x80] =
	/*      "0123456789ABCDEF"          */
	        "----------------"  /* 0x00 */ 
	        "----------------"  /* 0x10 */ 
	        "----------------"  /* 0x20 */ 
	        "----------------"  /* 0x30 */ 
	        "----------------"  /* 0x40 */ 
	        "---ST-----------"  /* 0x50 */ 
	        "-abcd--gh--klmno"  /* 0x60 */ 
	        "pqrst-v-xyz-----"; /* 0x70 */

	return ( c < 0 || 0x7f < c ) ? '-' : lower[ c ];
}
/*---------------------------------------------------------------------------*/
static char
hebrew_alt( int c )
{
	static char     lower[0x80] =
	/*      "0123456789ABCDEF"          */
	        "----------------"  /* 0x00 */ 
	        "----------------"  /* 0x10 */ 
	        "----------------"  /* 0x20 */ 
	        "----------------"  /* 0x30 */ 
	        "----------------"  /* 0x40 */ 
	        "----------------"  /* 0x50 */ 
	        "-----ap--yg-----"  /* 0x60 */ 
	        "-----v-v--------"; /* 0x70 */

	return ( c < 0 || 0x7f < c ) ? '-' : lower[ c ];
}
/*---------------------------------------------------------------------------*/
static char *pair_cur;
static char  pair_open[]  = "[<{(`\":";
static char  pair_close[] = "]>})'\":";
#define      PAIR_END              ':'
/*---------------------------------------------------------------------------*/
static void
mark_char_init(void)
{
	pair_cur = pair_open;
}
/*---------------------------------------------------------------------------*/
static void
mark_char_next(void)
{
	if( *pair_cur != PAIR_END ){
		pair_cur++;
	}
}
/*---------------------------------------------------------------------------*/
static char
mark_char_get(void)
{
	return *pair_cur;
}
/*---------------------------------------------------------------------------*/
static char
get_right_pair(char c, char numb_char)
{
	char *s;

	if( opt_m != MAX_OPT_M ){
		if( numb_char != '1' ){
			return numb_char;
		}
	}
	if( c == '|' ){
		return '|';
	}
	if( ( s = strchr( pair_open, c )) == NULL ){
		bug("bad pair [%c]=0x%02x", c, c);
	}
	return pair_close[s-pair_open];
}
/*---------------------------------------------------------------------------*/
static void
check_roman_to_ascii(char *src)
{
	while( *src ){
		if( is_hebrew(*src) == '-' ){
			if( hebrew_alt(*src) == '-' ){
				fatal("[%c] is illegual", *src);
			}
			fatal("[%c] is'nt hebrew char.Use [%c] insted of [%c]",
				*src, hebrew_alt(*src), *src );
		}
		++src;
	}
}
/*---------------------------------------------------------------------------*/
static char
get_bible_char( int n, int skip, int i )
{
	int     j = n + i*skip;
	int     buf_len = BIBLE_LEN;

	if( 0 <= j && j < buf_len ){
		return bible_data[j];
	}
	return '_';
}
/*---------------------------------------------------------------------------*/
static void
disp_left_right_word( int n, int skip, int len)
{
	#define MAX_ADD 4
	char    *s;
	int     sub = MAX_ADD;
	int     add = MAX_ADD;
	char    left[MAX_ADD+1];
	char    mid[MAX_WORD+1];
	char    right[MAX_ADD+1];
	int     i;

	for(s=left,  i=-sub; i<0;       i++){
		*s++ = get_bible_char(n, skip, i );
	}
	*s = '\0';
	for(s=mid,   i=0;    i<len;     i++){
		*s++ = get_bible_char( n, skip, i );
	}
	*s = '\0';
	for(s=right, i=len;  i<len+add; i++){
		*s++ = get_bible_char( n, skip, i );
	}
	*s = '\0';
	printf("echo %s_%s_%s ; ", left, mid, right );
}
/*---------------------------------------------------------------------------*/
static int
get_default_opt_w_row(int col)
{
	return (20*col)/80;
}
/*---------------------------------------------------------------------------*/
static int
str_print(int col, char *str)
{
	if( opt_w_col < col + strlen(str) + 4 ){
		printf("\\\n"); fflush(stdout); col = 0;
		printf(" "); col += strlen(" ");
	}
	printf(str);
	return col + strlen(str);
}
/*---------------------------------------------------------------------------*/
static void
print_cmd_line(int n, int skip, char **word_list)
{
	char    buf[4096];
	char    **wl = word_list;
	int     col;

	col = 0;
	/* ----------------------- */
	sprintf(buf, "xbible -n%d -s%d ", n, skip );
	col = str_print(col, buf);
	/* ----------------------- */
	if( opt_r != false ){
		sprintf(buf, "-r ");
		col = str_print(col, buf );
	}
	/* ----------------------- */
	if( opt_w_col != DEF_WIN_COL || 
	    opt_w_row != get_default_opt_w_row(DEF_WIN_COL) ){
		if( opt_w_row != get_default_opt_w_row(opt_w_col) ){
			sprintf(buf, "-w%dx%d ", opt_w_col, opt_w_row);
		}else{
			sprintf(buf, "-w%d ", opt_w_col );
		}
		col = str_print(col, buf );
	}
	/* ----------------------- */
	if( opt_m != MAX_OPT_M ){
		sprintf(buf,"-m%d ", opt_m );
		col = str_print(col, buf );
	}
	/* ----------------------- */
	if( opt_l_div != DEF_LINESKIP_DIV || opt_l_add != DEF_LINESKIP_ADD ){
		if( opt_l_add != DEF_LINESKIP_ADD ){
			sprintf(buf,"-l%d:%d ", opt_l_div, opt_l_add );
		}else{
			sprintf(buf,"-l%d ", opt_l_div );
		}
		col = str_print(col, buf );
	}
	/* ----------------------- */
	if( opt_o_col != DEF_OFFSET_COL || opt_o_row != DEF_OFFSET_COL ){
		if( opt_o_row != DEF_OFFSET_COL ){
			sprintf(buf,"-o%d,%d ", opt_o_col, opt_o_row );
		}else{
			sprintf(buf,"-o%d ", opt_o_col );
		}
		col = str_print(col, buf );
	}
	/* ================================================================= */
	for(wl=word_list; *wl; ){
		sprintf(buf, "%s", *wl++  );
		col = str_print(col, buf );
		col = str_print(col, " " );
	}
	putchar('\n'); fflush(stdout);
}
/*---------------------------------------------------------------------------*/
static void
grep_disp_exec(int n, int skip, char **word_list )
{
	if( opt_G ){
		disp_left_right_word( n, skip, strlen( *word_list ) );
	}

	print_cmd_line(n, skip, word_list );
}
/*---------------------------------------------------------------------------*/
static inline bool_t
compare_str( char *buf, register int skip, char *str )
{
	register char   *p, *w;

	p=buf, w=str;
	if(*p!=*w){ return (*w=='\0') ? true : false; } p+=skip,w++;/*1*/
	if(*p!=*w){ return (*w=='\0') ? true : false; } p+=skip,w++;/*2*/
	if(*p!=*w){ return (*w=='\0') ? true : false; } p+=skip,w++;/*3*/
	if(*p!=*w){ return (*w=='\0') ? true : false; } p+=skip,w++;/*4*/
	if(*p!=*w){ return (*w=='\0') ? true : false; } p+=skip,w++;/*5*/
	if(*p!=*w){ return (*w=='\0') ? true : false; } p+=skip,w++;/*6*/
	if(*p!=*w){ return (*w=='\0') ? true : false; } p+=skip,w++;/*7*/
	if(*p!=*w){ return (*w=='\0') ? true : false; } p+=skip,w++;/*8*/
	if(*p!=*w){ return (*w=='\0') ? true : false; } p+=skip,w++;/*9*/

	for( ; *p == *w  ; p+=skip, w++ );
	return (*w=='\0') ? true : false; 
}
/*---------------------------------------------------------------------------*/
typedef struct {
	int   dst;
	int   pos;
	int   len;
	int   skip;
} mark_list_t;
/*---------------------------------------------------------------------------*/
int
sort_cmp( const void *pa, const void *pb)
{
	register int a = ((mark_list_t *)pa)->dst;
	register int b = ((mark_list_t *)pb)->dst;

	if( a == b ){
		if( ((mark_list_t *)pa)->pos > ((mark_list_t *)pb)->pos ){
			return 1;
		}else{
			return -1;
		}
	}
	return ( a > b ) ? 1 : -1 ;
}
/*---------------------------------------------------------------------------*/
inline int
find_one( int find_count, mark_list_t *list, int max_find_count,
          int max_x, int max_y, char *text, char *mark,
          int word_len, char *text_last, int cur_skip,
          char *word_fwd, char **index_fwd,
          char *word_rev, char **index_rev )
{
	int             cur_x, cur_y;
	int             next_x, next_y;
	int             last_x, last_y;
	register char   **idx;
	register char   *p;
	int              n;
	enum { fwd=0, rev=1 } dir;
	char            *word;
	char            *word_array[]   = { word_fwd,  word_rev  };
	char            **index_array[] = { index_fwd, index_rev };

	for(dir=fwd; dir <= rev; dir++){
		word = word_array[dir];
		for(idx = index_array[dir]; (p = *idx++) < text_last;){
			n = p - text;
			cur_x  = n % max_x;
			cur_y  = n / max_x;
			next_x = ( n + cur_skip ) % max_x;
			next_y = ( n + cur_skip ) / max_x;
			last_x  = cur_x + ( next_x - cur_x ) * (word_len-1);
			last_y  = cur_y + ( next_y - cur_y ) * (word_len-1);
			if( last_x < 0 || max_x <= last_x ||
			    last_y < 0 || max_y <= last_y   ){
				continue;
			}
			if(compare_str( p, cur_skip, word ) == true){
				if( max_find_count <= find_count ){
					fatal("too many find_count(%d)\n",
							 max_find_count);
				}
				list[find_count].dst =
					  (next_x-cur_x)*(next_x-cur_x)
					+ (next_y-cur_y)*(next_y-cur_y);
				list[find_count].pos  = p - text;
				list[find_count].skip = cur_skip;
				list[find_count].len  = word_len;
				find_count++;
			}
		}
	}
	return find_count;
}
/*---------------------------------------------------------------------------*/
void
exec_mark(char *mark,char *numb,int n,int pos,int skip,int word_len,int max_x)
{
	char    mark_char;
	int     i;

	mark_char = (skip==max_x) ? '|' : mark_char_get();

	for(i=0; i<word_len; i++){
		if( mark[pos+i*skip] == ' ' ){
			mark[pos+i*skip] = mark_char;
			numb[pos+i*skip] = ( n < 10 ) ? n + '1' : '*';
		}
	}
}
/*---------------------------------------------------------------------------*/
static int
mark_all(int max_x, int max_y, char *text, char *mark, char *numb, char *word)
{
	register int    cur_skip;
	int             max_skip;
	int             word_len = strlen(word);
	int             find_count = 0;
	int		max_x_y = max_x * max_y;
	char            *index_fwd[LIMIT_MAX_X*LIMIT_MAX_Y];
	char            *index_rev[LIMIT_MAX_X*LIMIT_MAX_Y];
	char            word_fwd[MAX_WORD+1];
	char            word_rev[MAX_WORD+1];
	char            *text_last;
	mark_list_t     list[MAX_OPT_M];
	int             n;

	strcpy(word_fwd, word);
	revcpy(word_rev, word);

	setup_index(text, text+max_x_y, *word_fwd, index_fwd);
	setup_index(text, text+max_x_y, *word_rev, index_rev);

	max_skip = ( max_x_y ) / (word_len-1);
	for(cur_skip=1; cur_skip <= max_skip ; cur_skip++){
		text_last = text + max_x_y - cur_skip*(word_len-1);
		find_count = find_one( find_count, list, MAX_OPT_M,
		                  max_x, max_y, text, mark,
		                  word_len, text_last, cur_skip,
				  word_fwd, index_fwd,
				  word_rev, index_rev	 );
	}
	qsort(list, find_count, sizeof(mark_list_t), sort_cmp );
	find_count = ( opt_m < find_count ) ? opt_m : find_count;
	for(n=0; n<find_count; n++){
		exec_mark( mark, numb, n,
			list[n].pos, list[n].skip, list[n].len, max_x );
	}
	return find_count;
}
/*---------------------------------------------------------------------------*/
static char
get_xy(char *mark, int max_x, int max_y, int x, int y )
{
	if( x < 0 )     { return OUTSIDE; }
	if( y < 0 )     { return OUTSIDE; }
	if( max_x <= x ){ return OUTSIDE; }
	if( max_y <= y ){ return OUTSIDE; }

	return mark[max_x * y + x];
}
/*---------------------------------------------------------------------------*/
static void
put_mark_char(char *mark, char *numb, int max_x, int max_y, int x, int y)
{
	int     cur = max_x * y + x;
	int     l2, l1, c0, r1;
	int	numb_l1;

	c0 =  get_xy(mark, max_x, max_y, x,   y );
	l1 =  get_xy(mark, max_x, max_y, x+1, y );
	l2 =  get_xy(mark, max_x, max_y, x+2, y );
	r1 =  get_xy(mark, max_x, max_y, x-1, y );

	numb_l1 =  get_xy(numb, max_x, max_y, x+1, y );

	if( c0 == OUTSIDE ){
		bug("outside in put_mark_char");
	}
	/*  l l c r                        */
	/*  2 1 0 1                        */
	/*  =-=-=-=                        */
	/*     ^----- print position       */
	/*  B+A+B     case 1 putchar('+')  */
	/*  ?-B+A+B   case 2 putchar('+')  */    
	/*  ??B|A     case 3 putchar('|')  */    
	/*  $$$[A     case 4 putchar('[')  */    
	/*  ??s s     case 5 putchar(' ')  */    
	/*  A<B>      case 6 putchar('>')  */    
	/*  ??A-A     case 7 putchar('-')  */    
	/*  A<B>A     case 8 putchar('>')  */    
	/*  B-B[A     case 9 putchar('[')  */    
	/*  ?? [A     case 9 putchar('[')  */    
	/*  ??B[A     case 9 putchar('[')  */    
	/*  ??B[A     case 9 putchar('[')  */    
	/*  ???[A     case 9 putchar('[')  */    
	/*  ??B[A     case 9 putchar('[')  */    

	if( c0 == '|' && l1 != ' ' && l1 == r1 ){
		putchar( '+' ); /* case 1 */
		return;
	}
	if( l1 == '|' ){
		if( c0 != ' ' && l2 == c0 ){
			putchar( '+' ); /* case 2 */
		}else{
			putchar( '|' ); /* case 3 */
		}
		return;
	}
	if( l1 == OUTSIDE ){
		putchar( mark[cur] ); /* case 4 */
		return;
	}
	if( c0 == ' ' ){
		if( l1 == ' ' ){
			/* "2 1 0" */
			/* "??   " */
			putchar( ' ' ); /* case 5 */
			return;
		}else{
			/* "2 1 0" */
			/* "??B> " */
			putchar( get_right_pair(l1, numb_l1 ) );
			                           /* case 6 */
			return;
		}
	}
	if( l1 == c0  ){
		/* "2 1 0" */
		/* "??A-A" */
		putchar( '-' ); /* case 7 */
		return;
	}
	if( l2 == c0 && l1 != ' ' ){ /* && l1 != c0 && c0 != ' ' */
		/* "2 1 0" */
		/* "A<B>A" */
		putchar( get_right_pair(l1, numb_l1) ); /* case 9 */
		return;
	}
	putchar( mark[cur] ); /* case 8 */
}
/*---------------------------------------------------------------------------*/
#if 0
static char *
get_cmd_name(char *path)
{
	char  *str = path;
	char  *last = NULL;

	for(;; ){
		if( *str == '\0' ){
			return ( last == NULL ) ? path : last;
		}
		if( *str++ == '/' ){
			last = str;
		}
	}
}
#endif
/*---------------------------------------------------------------------------*/
static void
header_info_print(int n,int skip,int max_x,char **word_list,int *match_list)
{
	char    buf[80];
	char    **wl = word_list;
	int     *ml  = match_list;
	int     no;
	int     col;

	/* ================================================================= */
	for(col=0; col<opt_w_col-1; col++) putchar('=');
	putchar('\n'); fflush(stdout);
	/* ================================================================= */
	print_cmd_line(n, skip, word_list);
	/* ================================================================= */
	for(col=0; col<opt_w_col-1; col++) putchar('=');
	putchar('\n'); fflush(stdout);
	/* ================================================================= */
	mark_char_init();
	for(col=0, wl=word_list; ; ){
		char l = mark_char_get();
		char r = get_right_pair( l, '1' );
		sprintf(buf, "%c%s%c=%d", l, *wl, r, *ml++ );
		col = str_print(col, buf );
		if( *++wl == NULL ){
			break;
		}
		col = str_print(col, " " );
		mark_char_next();
	}
	putchar('\n'); fflush(stdout);
	/* ================================================================= */
	for(col=0; col<opt_w_col-1; col++) putchar('-');
	putchar('\n'); fflush(stdout);
	/* ================================================================= */
	for(no=max_x ; 0 < no ; no-- ){
		printf(" %d", no%10 );
	}
	putchar('\n'); fflush(stdout); /* for 'tail -f' command */
	/* ================================================================= */
}
/*---------------------------------------------------------------------------*/
static void
window_value_setup(
	int skip, int len,
	int *ans_max_x, int *ans_max_y,
	int *ans_mar_x, int *ans_mar_y,
	int *ans_n_line )
{
	int     mar_x, mar_y;
	int     max_x, max_y;
	int     add_x, add_y;
	int     n_line;
	int     word_width;
	int     word_height;

	if( opt_w_row == -1 ){
		opt_w_row = get_default_opt_w_row( opt_w_col );
	}
	max_x = (opt_w_col-2)/2;
	max_y =  opt_w_row;

	if( skip*len < max_x ){
		/* all char of words is in one horizontal line */
		mar_y = ( max_y ) / 2 ;
		mar_x = ( max_x - skip*len ) / 2 + 5  ;
		n_line = max_x;
		if( opt_l_div != DEF_LINESKIP_DIV ){
			fatal("illegual -l value");
		}
		if( opt_l_add != DEF_LINESKIP_ADD ){
			n_line = opt_l_add ;
		}
	}else{
		/* words are in vertical line */
		if( skip < max_x ){
			max_x = skip;
		}
		if( opt_l_div != 0 ){
			n_line = skip / opt_l_div + opt_l_add ;
			add_x = ( skip - n_line*opt_l_div ) % max_x;
			add_y = ( skip / n_line  );
		}else{
			n_line = opt_l_add ;
			add_x = ( skip - n_line ) % max_x;
			add_y = skip / n_line;
		}
		word_width  = len*abs(add_x);
		if( max_x < word_width-1 ){
		    fatal("too small window (min_col=%d)", (word_width-1)*2+2);
		}
		mar_x = ( max_x - (word_width-1) ) / 2;
		if( add_x < 0 ){
			mar_x += (word_width-1);
		}
		word_height = len*abs(add_y)-1;
		if( max_y < word_height ){
			fatal("too small window (min_row=%d)", word_height );
		}
		mar_y = ( max_y - word_height ) / 2;
	}
	if( LIMIT_MAX_X <= max_x || LIMIT_MAX_Y <= max_y ){
		fatal("too large -w option");
	}
	*ans_max_x  = max_x;
	*ans_max_y  = max_y;
	*ans_mar_x  = mar_x;
	*ans_mar_y  = mar_y;
	*ans_n_line = n_line;
}
/*---------------------------------------------------------------------------*/
static void
disp_matrix(char **word_list, char *arg_p, int skip)
{
	char    text_buf[(LIMIT_MAX_X*LIMIT_MAX_Y+1)*2];
	char    mark_buf[ LIMIT_MAX_X*LIMIT_MAX_Y+1 ];
	char    numb_buf[ LIMIT_MAX_X*LIMIT_MAX_Y+1 ];
	int     x, y, len;
	int     mar_x, mar_y;
	int     max_x, max_y;
	int     n_line;
	int     cur;
	char    *text = text_buf;
	char    *mark = mark_buf;
	char    *numb = numb_buf;
	char    *bible = bible_data;
	int     match_count;
	char    *str;
	char    **wl;
	int     match_list[MAX_WORD_LIST];
	int     *ml = match_list;
	int	n;
	int	arg_n = arg_p - bible;

	str = *word_list;
	len  = strlen(str);

	mark_char_init();
	window_value_setup(skip, len, &max_x, &max_y, &mar_x, &mar_y, &n_line);

	text_buf[max_x*max_y] = '\0';
	mark_buf[max_x*max_y] = '\0';
	numb_buf[max_x*max_y] = '\0';

	n = arg_n - ( opt_o_col + opt_o_row *n_line );
	for(y=0; y<max_y; y++){
		for(x=0; x<max_x ; x++){
			cur = n + ( y*n_line + x ) - ( mar_y*n_line + mar_x  );
			*text++ = ( 0<=cur &&cur<BIBLE_LEN )?bible[cur]:'_';
			*mark++ = ' ';
			*numb++ = ' ';
		}
	}
	memset( text, OUTSIDE, max_x * max_y + 1 );

	/* The 1st word of word_list is important,must be mark before others */
	text = text_buf;
	mark = mark_buf;
	numb = numb_buf;
	for( wl=word_list; *wl ; wl++){
		match_count = mark_all( max_x, max_y, text, mark, numb, *wl );
		if( match_count == 0 ){
			return;
		}
		*ml++ = match_count;
		mark_char_next();
	}
	if( opt_g == true ){
		grep_disp_exec( n, skip, word_list );
		if( opt_c != INT_MAX && opt_c > 0 ){
			if( --opt_c == 0 ){ exit(0); }
		}
		return;
	}
	header_info_print( arg_n, skip, max_x, word_list, match_list );

	/* mark all words */
	for(y=0; y<max_y; y++){
		for(x=max_x-1; 0 <= x ; x-- ){
			cur = max_x*y + x;
			put_mark_char(mark, numb, max_x, max_y, x, y );
			if( text[cur] == '\0' ){
				bug("NULL char in text");
			}else{
				putchar(text[cur]);
			}
		}
		cur = max_x*y;
		if( mark[cur] != ' ' ){
			putchar( get_right_pair(mark[cur], numb[cur] ) );
		}else{
			putchar(' ');
		}
		putchar('\n'); fflush(stdout); /* for 'tail -f' command */
	}
	putchar('\n'); fflush(stdout); /* for 'tail -f' command */
	if( opt_c != INT_MAX && opt_c > 0 ){
		if( --opt_c == 0 ){ exit(0); }
	}
}
/*---------------------------------------------------------------------------*/
#define SEARCH_LOCAL_VAR_DEFINE \
	register char *b; \
	register int   skip; \
	register char  **idx; \
	char           *last; \
	char           word_fwd[MAX_WORD+1]; \
	char           word_rev[MAX_WORD+1]; \
	char          *index_fwd[BIBLE_LEN]; \
	char          *index_rev[BIBLE_LEN]; \
	char          *bible_end   = bible_data + BIBLE_LEN; \
	char          *bible_n_max = bible_data + n_max; \
	int            len_minus_1 = strlen(*word_list) - 1; \
	strcpy(word_fwd, *word_list); \
	revcpy(word_rev, *word_list); \
	setup_index(bible_data, bible_end, *word_fwd, index_fwd ); \
	setup_index(bible_data, bible_end, *word_rev, index_rev );
/*---------------------------------------------------------------------------*/
#define SEARCH_LAST_SETUP \
	last = bible_end - skip * len_minus_1; \
	if( bible_n_max < last ){ last = bible_n_max; }
/*---------------------------------------------------------------------------*/
static void
search_len_2( int n_min, int n_max, int s_min, int s_max, char **word_list )
{
	SEARCH_LOCAL_VAR_DEFINE
	for(skip=s_min; skip <= s_max ; skip++){
		SEARCH_LAST_SETUP
		for(idx = index_fwd; (b = *idx++) <= last ;){
			/*  *b == word_fwd[ 0] */             b += skip;
			if( *b != word_fwd[ 1] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
		for(idx = index_rev; (b = *idx++) <= last ;){
			/*  *b == word_rev[ 0] */             b += skip;
			if( *b != word_rev[ 1] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
	}
}
/*---------------------------------------------------------------------------*/
static void
search_len_3( int n_min, int n_max, int s_min, int s_max, char **word_list )
{
	SEARCH_LOCAL_VAR_DEFINE
	for(skip=s_min; skip <= s_max ; skip++){
		SEARCH_LAST_SETUP
		for(idx = index_fwd; (b = *idx++) <= last ;){
			/*  *b == word_fwd[ 0] */             b += skip;
			if( *b != word_fwd[ 1] ){ continue; } b += skip;
			if( *b != word_fwd[ 2] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
		for(idx = index_rev; (b = *idx++) <= last ;){
			/*  *b == word_rev[ 0] */             b += skip;
			if( *b != word_rev[ 1] ){ continue; } b += skip;
			if( *b != word_rev[ 2] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
	}
}
/*---------------------------------------------------------------------------*/
static void
search_len_4( int n_min, int n_max, int s_min, int s_max, char **word_list )
{
	SEARCH_LOCAL_VAR_DEFINE
	for(skip=s_min; skip <= s_max ; skip++){
		SEARCH_LAST_SETUP
		for(idx = index_fwd; (b = *idx++) <= last ;){
			/*  *b == word_fwd[ 0] */             b += skip;
			if( *b != word_fwd[ 1] ){ continue; } b += skip;
			if( *b != word_fwd[ 2] ){ continue; } b += skip;
			if( *b != word_fwd[ 3] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
		for(idx = index_rev; (b = *idx++) <= last ;){
			/*  *b == word_rev[ 0] */             b += skip;
			if( *b != word_rev[ 1] ){ continue; } b += skip;
			if( *b != word_rev[ 2] ){ continue; } b += skip;
			if( *b != word_rev[ 3] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
	}
}
/*---------------------------------------------------------------------------*/
static void
search_len_5( int n_min, int n_max, int s_min, int s_max, char **word_list )
{
	SEARCH_LOCAL_VAR_DEFINE
	for(skip=s_min; skip <= s_max ; skip++){
		SEARCH_LAST_SETUP
		for(idx = index_fwd; (b = *idx++) <= last ;){
			/*  *b == word_fwd[ 0] */             b += skip;
			if( *b != word_fwd[ 1] ){ continue; } b += skip;
			if( *b != word_fwd[ 2] ){ continue; } b += skip;
			if( *b != word_fwd[ 3] ){ continue; } b += skip;
			if( *b != word_fwd[ 4] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
		for(idx = index_rev; (b = *idx++) <= last ;){
			/*  *b == word_rev[ 0] */             b += skip;
			if( *b != word_rev[ 1] ){ continue; } b += skip;
			if( *b != word_rev[ 2] ){ continue; } b += skip;
			if( *b != word_rev[ 3] ){ continue; } b += skip;
			if( *b != word_rev[ 4] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
	}
}
/*---------------------------------------------------------------------------*/
static void
search_len_6( int n_min, int n_max, int s_min, int s_max, char **word_list )
{
	SEARCH_LOCAL_VAR_DEFINE
	for(skip=s_min; skip <= s_max ; skip++){
		SEARCH_LAST_SETUP
		for(idx = index_fwd; (b = *idx++) <= last ;){
			/*  *b == word_fwd[ 0] */             b += skip;
			if( *b != word_fwd[ 1] ){ continue; } b += skip;
			if( *b != word_fwd[ 2] ){ continue; } b += skip;
			if( *b != word_fwd[ 3] ){ continue; } b += skip;
			if( *b != word_fwd[ 4] ){ continue; } b += skip;
			if( *b != word_fwd[ 5] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
		for(idx = index_rev; (b = *idx++) <= last ;){
			/*  *b == word_rev[ 0] */             b += skip;
			if( *b != word_rev[ 1] ){ continue; } b += skip;
			if( *b != word_rev[ 2] ){ continue; } b += skip;
			if( *b != word_rev[ 3] ){ continue; } b += skip;
			if( *b != word_rev[ 4] ){ continue; } b += skip;
			if( *b != word_rev[ 5] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
	}
}
/*---------------------------------------------------------------------------*/
static void
search_len_7( int n_min, int n_max, int s_min, int s_max, char **word_list )
{
	SEARCH_LOCAL_VAR_DEFINE
	for(skip=s_min; skip <= s_max ; skip++){
		SEARCH_LAST_SETUP
		for(idx = index_fwd; (b = *idx++) <= last ;){
			/*  *b == word_fwd[ 0] */             b += skip;
			if( *b != word_fwd[ 1] ){ continue; } b += skip;
			if( *b != word_fwd[ 2] ){ continue; } b += skip;
			if( *b != word_fwd[ 3] ){ continue; } b += skip;
			if( *b != word_fwd[ 4] ){ continue; } b += skip;
			if( *b != word_fwd[ 5] ){ continue; } b += skip;
			if( *b != word_fwd[ 6] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
		for(idx = index_rev; (b = *idx++) <= last ;){
			/*  *b == word_rev[ 0] */             b += skip;
			if( *b != word_rev[ 1] ){ continue; } b += skip;
			if( *b != word_rev[ 2] ){ continue; } b += skip;
			if( *b != word_rev[ 3] ){ continue; } b += skip;
			if( *b != word_rev[ 4] ){ continue; } b += skip;
			if( *b != word_rev[ 5] ){ continue; } b += skip;
			if( *b != word_rev[ 6] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
	}
}
/*---------------------------------------------------------------------------*/
static void
search_len_8( int n_min, int n_max, int s_min, int s_max, char **word_list )
{
	SEARCH_LOCAL_VAR_DEFINE
	for(skip=s_min; skip <= s_max ; skip++){
		SEARCH_LAST_SETUP
		for(idx = index_fwd; (b = *idx++) <= last ;){
			/*  *b == word_fwd[ 0] */             b += skip;
			if( *b != word_fwd[ 1] ){ continue; } b += skip;
			if( *b != word_fwd[ 2] ){ continue; } b += skip;
			if( *b != word_fwd[ 3] ){ continue; } b += skip;
			if( *b != word_fwd[ 4] ){ continue; } b += skip;
			if( *b != word_fwd[ 5] ){ continue; } b += skip;
			if( *b != word_fwd[ 6] ){ continue; } b += skip;
			if( *b != word_fwd[ 7] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
		for(idx = index_rev; (b = *idx++) <= last ;){
			/*  *b == word_rev[ 0] */             b += skip;
			if( *b != word_rev[ 1] ){ continue; } b += skip;
			if( *b != word_rev[ 2] ){ continue; } b += skip;
			if( *b != word_rev[ 3] ){ continue; } b += skip;
			if( *b != word_rev[ 4] ){ continue; } b += skip;
			if( *b != word_rev[ 5] ){ continue; } b += skip;
			if( *b != word_rev[ 6] ){ continue; } b += skip;
			if( *b != word_rev[ 7] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
	}
}
/*---------------------------------------------------------------------------*/
static void
search_len_9( int n_min, int n_max, int s_min, int s_max, char **word_list )
{
	SEARCH_LOCAL_VAR_DEFINE
	for(skip=s_min; skip <= s_max ; skip++){
		SEARCH_LAST_SETUP
		for(idx = index_fwd; (b = *idx++) <= last ;){
			/*  *b == word_fwd[ 0] */             b += skip;
			if( *b != word_fwd[ 1] ){ continue; } b += skip;
			if( *b != word_fwd[ 2] ){ continue; } b += skip;
			if( *b != word_fwd[ 3] ){ continue; } b += skip;
			if( *b != word_fwd[ 4] ){ continue; } b += skip;
			if( *b != word_fwd[ 5] ){ continue; } b += skip;
			if( *b != word_fwd[ 6] ){ continue; } b += skip;
			if( *b != word_fwd[ 7] ){ continue; } b += skip;
			if( *b != word_fwd[ 8] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
		for(idx = index_rev; (b = *idx++) <= last ;){
			/*  *b == word_rev[ 0] */             b += skip;
			if( *b != word_rev[ 1] ){ continue; } b += skip;
			if( *b != word_rev[ 2] ){ continue; } b += skip;
			if( *b != word_rev[ 3] ){ continue; } b += skip;
			if( *b != word_rev[ 4] ){ continue; } b += skip;
			if( *b != word_rev[ 5] ){ continue; } b += skip;
			if( *b != word_rev[ 6] ){ continue; } b += skip;
			if( *b != word_rev[ 7] ){ continue; } b += skip;
			if( *b != word_rev[ 8] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
	}
}
/*---------------------------------------------------------------------------*/
static void
search_len10( int n_min, int n_max, int s_min, int s_max, char **word_list )
{
	SEARCH_LOCAL_VAR_DEFINE
	for(skip=s_min; skip <= s_max ; skip++){
		SEARCH_LAST_SETUP
		for(idx = index_fwd; (b = *idx++) <= last ;){
			/*  *b == word_fwd[ 0] */             b += skip;
			if( *b != word_fwd[ 1] ){ continue; } b += skip;
			if( *b != word_fwd[ 2] ){ continue; } b += skip;
			if( *b != word_fwd[ 3] ){ continue; } b += skip;
			if( *b != word_fwd[ 4] ){ continue; } b += skip;
			if( *b != word_fwd[ 5] ){ continue; } b += skip;
			if( *b != word_fwd[ 6] ){ continue; } b += skip;
			if( *b != word_fwd[ 7] ){ continue; } b += skip;
			if( *b != word_fwd[ 8] ){ continue; } b += skip;
			if( *b != word_fwd[ 9] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
		for(idx = index_rev; (b = *idx++) <= last ;){
			/*  *b == word_rev[ 0] */             b += skip;
			if( *b != word_rev[ 1] ){ continue; } b += skip;
			if( *b != word_rev[ 2] ){ continue; } b += skip;
			if( *b != word_rev[ 3] ){ continue; } b += skip;
			if( *b != word_rev[ 4] ){ continue; } b += skip;
			if( *b != word_rev[ 5] ){ continue; } b += skip;
			if( *b != word_rev[ 6] ){ continue; } b += skip;
			if( *b != word_rev[ 7] ){ continue; } b += skip;
			if( *b != word_rev[ 8] ){ continue; } b += skip;
			if( *b != word_rev[ 9] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
	}
}
/*---------------------------------------------------------------------------*/
static void
search_len11( int n_min, int n_max, int s_min, int s_max, char **word_list )
{
	SEARCH_LOCAL_VAR_DEFINE
	for(skip=s_min; skip <= s_max ; skip++){
		SEARCH_LAST_SETUP
		for(idx = index_fwd; (b = *idx++) <= last ;){
			/*  *b == word_fwd[ 0] */             b += skip;
			if( *b != word_fwd[ 1] ){ continue; } b += skip;
			if( *b != word_fwd[ 2] ){ continue; } b += skip;
			if( *b != word_fwd[ 3] ){ continue; } b += skip;
			if( *b != word_fwd[ 4] ){ continue; } b += skip;
			if( *b != word_fwd[ 5] ){ continue; } b += skip;
			if( *b != word_fwd[ 6] ){ continue; } b += skip;
			if( *b != word_fwd[ 7] ){ continue; } b += skip;
			if( *b != word_fwd[ 8] ){ continue; } b += skip;
			if( *b != word_fwd[ 9] ){ continue; } b += skip;
			if( *b != word_fwd[10] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
		for(idx = index_rev; (b = *idx++) <= last ;){
			/*  *b == word_rev[ 0] */             b += skip;
			if( *b != word_rev[ 1] ){ continue; } b += skip;
			if( *b != word_rev[ 2] ){ continue; } b += skip;
			if( *b != word_rev[ 3] ){ continue; } b += skip;
			if( *b != word_rev[ 4] ){ continue; } b += skip;
			if( *b != word_rev[ 5] ){ continue; } b += skip;
			if( *b != word_rev[ 6] ){ continue; } b += skip;
			if( *b != word_rev[ 7] ){ continue; } b += skip;
			if( *b != word_rev[ 8] ){ continue; } b += skip;
			if( *b != word_rev[ 9] ){ continue; } b += skip;
			if( *b != word_rev[10] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
	}
}
/*---------------------------------------------------------------------------*/
static void
search_len12( int n_min, int n_max, int s_min, int s_max, char **word_list )
{
	SEARCH_LOCAL_VAR_DEFINE
	for(skip=s_min; skip <= s_max ; skip++){
		SEARCH_LAST_SETUP
		for(idx = index_fwd; (b = *idx++) <= last ;){
			/*  *b == word_fwd[ 0] */             b += skip;
			if( *b != word_fwd[ 1] ){ continue; } b += skip;
			if( *b != word_fwd[ 2] ){ continue; } b += skip;
			if( *b != word_fwd[ 3] ){ continue; } b += skip;
			if( *b != word_fwd[ 4] ){ continue; } b += skip;
			if( *b != word_fwd[ 5] ){ continue; } b += skip;
			if( *b != word_fwd[ 6] ){ continue; } b += skip;
			if( *b != word_fwd[ 7] ){ continue; } b += skip;
			if( *b != word_fwd[ 8] ){ continue; } b += skip;
			if( *b != word_fwd[ 9] ){ continue; } b += skip;
			if( *b != word_fwd[10] ){ continue; } b += skip;
			if( *b != word_fwd[11] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
		for(idx = index_rev; (b = *idx++) <= last ;){
			/*  *b == word_rev[ 0] */             b += skip;
			if( *b != word_rev[ 1] ){ continue; } b += skip;
			if( *b != word_rev[ 2] ){ continue; } b += skip;
			if( *b != word_rev[ 3] ){ continue; } b += skip;
			if( *b != word_rev[ 4] ){ continue; } b += skip;
			if( *b != word_rev[ 5] ){ continue; } b += skip;
			if( *b != word_rev[ 6] ){ continue; } b += skip;
			if( *b != word_rev[ 7] ){ continue; } b += skip;
			if( *b != word_rev[ 8] ){ continue; } b += skip;
			if( *b != word_rev[ 9] ){ continue; } b += skip;
			if( *b != word_rev[10] ){ continue; } b += skip;
			if( *b != word_rev[11] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
	}
}
/*---------------------------------------------------------------------------*/
static void
search_len13( int n_min, int n_max, int s_min, int s_max, char **word_list )
{
	SEARCH_LOCAL_VAR_DEFINE
	for(skip=s_min; skip <= s_max ; skip++){
		SEARCH_LAST_SETUP
		for(idx = index_fwd; (b = *idx++) <= last ;){
			/*  *b == word_fwd[ 0] */             b += skip;
			if( *b != word_fwd[ 1] ){ continue; } b += skip;
			if( *b != word_fwd[ 2] ){ continue; } b += skip;
			if( *b != word_fwd[ 3] ){ continue; } b += skip;
			if( *b != word_fwd[ 4] ){ continue; } b += skip;
			if( *b != word_fwd[ 5] ){ continue; } b += skip;
			if( *b != word_fwd[ 6] ){ continue; } b += skip;
			if( *b != word_fwd[ 7] ){ continue; } b += skip;
			if( *b != word_fwd[ 8] ){ continue; } b += skip;
			if( *b != word_fwd[ 9] ){ continue; } b += skip;
			if( *b != word_fwd[10] ){ continue; } b += skip;
			if( *b != word_fwd[11] ){ continue; } b += skip;
			if( *b != word_fwd[12] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
		for(idx = index_rev; (b = *idx++) <= last ;){
			/*  *b == word_rev[ 0] */             b += skip;
			if( *b != word_rev[ 1] ){ continue; } b += skip;
			if( *b != word_rev[ 2] ){ continue; } b += skip;
			if( *b != word_rev[ 3] ){ continue; } b += skip;
			if( *b != word_rev[ 4] ){ continue; } b += skip;
			if( *b != word_rev[ 5] ){ continue; } b += skip;
			if( *b != word_rev[ 6] ){ continue; } b += skip;
			if( *b != word_rev[ 7] ){ continue; } b += skip;
			if( *b != word_rev[ 8] ){ continue; } b += skip;
			if( *b != word_rev[ 9] ){ continue; } b += skip;
			if( *b != word_rev[10] ){ continue; } b += skip;
			if( *b != word_rev[11] ){ continue; } b += skip;
			if( *b != word_rev[12] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
	}
}
/*---------------------------------------------------------------------------*/
static void
search_len14( int n_min, int n_max, int s_min, int s_max, char **word_list )
{
	SEARCH_LOCAL_VAR_DEFINE
	for(skip=s_min; skip <= s_max ; skip++){
		SEARCH_LAST_SETUP
		for(idx = index_fwd; (b = *idx++) <= last ;){
			/*  *b == word_fwd[ 0] */             b += skip;
			if( *b != word_fwd[ 1] ){ continue; } b += skip;
			if( *b != word_fwd[ 2] ){ continue; } b += skip;
			if( *b != word_fwd[ 3] ){ continue; } b += skip;
			if( *b != word_fwd[ 4] ){ continue; } b += skip;
			if( *b != word_fwd[ 5] ){ continue; } b += skip;
			if( *b != word_fwd[ 6] ){ continue; } b += skip;
			if( *b != word_fwd[ 7] ){ continue; } b += skip;
			if( *b != word_fwd[ 8] ){ continue; } b += skip;
			if( *b != word_fwd[ 9] ){ continue; } b += skip;
			if( *b != word_fwd[10] ){ continue; } b += skip;
			if( *b != word_fwd[11] ){ continue; } b += skip;
			if( *b != word_fwd[12] ){ continue; } b += skip;
			if( *b != word_fwd[13] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
		for(idx = index_rev; (b = *idx++) <= last ;){
			/*  *b == word_rev[ 0] */             b += skip;
			if( *b != word_rev[ 1] ){ continue; } b += skip;
			if( *b != word_rev[ 2] ){ continue; } b += skip;
			if( *b != word_rev[ 3] ){ continue; } b += skip;
			if( *b != word_rev[ 4] ){ continue; } b += skip;
			if( *b != word_rev[ 5] ){ continue; } b += skip;
			if( *b != word_rev[ 6] ){ continue; } b += skip;
			if( *b != word_rev[ 7] ){ continue; } b += skip;
			if( *b != word_rev[ 8] ){ continue; } b += skip;
			if( *b != word_rev[ 9] ){ continue; } b += skip;
			if( *b != word_rev[10] ){ continue; } b += skip;
			if( *b != word_rev[11] ){ continue; } b += skip;
			if( *b != word_rev[12] ){ continue; } b += skip;
			if( *b != word_rev[13] ){ continue; }
			disp_matrix(word_list, idx[-1], skip );
		}
	}
}
/*---------------------------------------------------------------------------*/
void
search_lenXX( int n_min, int n_max, int s_min, int s_max, char **word_list )
{
	register char *w;
	register int  l;
	SEARCH_LOCAL_VAR_DEFINE

	for(skip=s_min; skip <= s_max ; skip++){
		SEARCH_LAST_SETUP
		for(idx = index_fwd; (b = *idx++) <= last ;){
			w = word_fwd + 1; b+= skip; l = len_minus_1;
			for( ; *b == *w  ; b+=skip, w++ ){
				if( --l == 0 ){
					disp_matrix(word_list, idx[-1], skip );
					break;
				}
			}
		}
		for(idx = index_rev; (b = *idx++) <= last ;){
			w = word_rev + 1; b+= skip; l = len_minus_1;
			for( ; *b == *w  ; b+=skip, w++ ){
				if( --l == 0 ){
					disp_matrix(word_list, idx[-1], skip );
					break;
				}
			}
		}
	}
}
/*---------------------------------------------------------------------------*/
static void
search_all(int n_min, int n_max, int s_min, int s_max, char **word_list)
{
	/* search by each length for speedup */
	switch( strlen(*word_list) ){
	case  2:  search_len_2( n_min, n_max, s_min, s_max, word_list ); break;
	case  3:  search_len_3( n_min, n_max, s_min, s_max, word_list ); break;
	case  4:  search_len_4( n_min, n_max, s_min, s_max, word_list ); break;
	case  5:  search_len_5( n_min, n_max, s_min, s_max, word_list ); break;
	case  6:  search_len_6( n_min, n_max, s_min, s_max, word_list ); break;
	case  7:  search_len_7( n_min, n_max, s_min, s_max, word_list ); break;
	case  8:  search_len_8( n_min, n_max, s_min, s_max, word_list ); break;
	case  9:  search_len_9( n_min, n_max, s_min, s_max, word_list ); break;
	case 10:  search_len10( n_min, n_max, s_min, s_max, word_list ); break;
	case 11:  search_len11( n_min, n_max, s_min, s_max, word_list ); break;
	case 12:  search_len12( n_min, n_max, s_min, s_max, word_list ); break;
	case 13:  search_len13( n_min, n_max, s_min, s_max, word_list ); break;
	case 14:  search_len14( n_min, n_max, s_min, s_max, word_list ); break;
	default:  search_lenXX( n_min, n_max, s_min, s_max, word_list ); break;
	}
}
/*---------------------------------------------------------------------------*/
static char **
get_word_list( int argc, char **argv )
{
	static char     *list[MAX_WORD_LIST];
	char            **cur_list;
	char            *s;

	if( argc == 0 ){
		return NULL;
	}
	cur_list = list;
	while(argc--){
		s = *argv++;
		if( *s == '\0' ){
			fatal("NULL word detected");
		}
		if( MAX_WORD < strlen(s) ){
			fatal("too long word (max=%d)", MAX_WORD);
		}
		if( strlen(s) < 2 ){
			fatal("too short word (min=2)");
		}
		if( list+sizeof(list) <= cur_list ){
			fatal("number of word overflow (max=%d)",
				sizeof(list)/sizeof(char *) );
		}
		check_roman_to_ascii(s);
		*cur_list++ = s;
	}
	*cur_list = NULL;
	return list;
}
/*---------------------------------------------------------------------------*/
static void
setup_optval(char *inp_arg, char *mode, int *ans_min, int *ans_max )
{
	char    *s;
	int     min = *ans_min;
	int     max = *ans_max;
	char    buf[80];

	if( sizeof(buf) <= strlen(inp_arg) ){
		fatal("too long arg");
	}
	strcpy(buf, inp_arg);

	if( strcmp(mode, "+-") == 0 ){
		if(       (s=strchr(buf, '-')), s != NULL ){
			*s++ = '\0';
			min = atoi(buf);
			max   = atoi(s);
		}else if( (s=strchr(buf, '+')), s != NULL ){
			*s++ = '\0';
			min = atoi(buf);
			max = min + atoi(s);
		}else {
			min = atoi(buf);
			max = min;
		}
		if( max < min ){
			fatal("illegual range [%d...%d]", min, max);
			exit(1);
		}
	}else if(   strcmp(mode, "x") == 0
		 || strcmp(mode, ",") == 0  
		 || strcmp(mode, ":") == 0  ){
		if( (s = strchr(buf, mode[0] )) == NULL ){
			min = atoi(buf);
		}else{
			if( *s == mode[0] ){
				*s++ = '\0';
				min = atoi(buf);
				max   = atoi(s);
			}
		}
	}else{
		bug("illegual mode in setup_optval()");
	}
	*ans_min = min;
	*ans_max = max;
}
/*---------------------------------------------------------------------------*/
int
main(int argc, char **argv)
{
	int          opt_n_min = 0;            /* number begin on bible text */
	int          opt_n_max = BIBLE_LEN;    /* number end on bible text   */
	int          opt_s_min = 1;            /* skip min length            */
	int          opt_s_max = DEF_MAX_SKIP; /* skip max length            */
	int          c;
	int          len;
	int          count     = INT_MIN;
	char         **word_list;

	cmd_argv = argv;
	cmd_argc = argc;
	if( argc == 1 ) usage();
	while ((c = getopt(argc, argv, "hgGrc:w:n:s:l:o:m:")) != -1){
		switch (c) {
		case 'g': opt_g = true;
		          break;
		case 'G': opt_G = true; opt_g = true;
		          break;
		case 'r': opt_r = true; setup_random( bible_data );
		          break;
		case 'c': count = atoi(optarg);
		          break;
		case 'm': opt_m = atoi(optarg);
		          break;
		case 'w': setup_optval(optarg, "x",  &opt_w_col, &opt_w_row );
		          break;
		case 'n': setup_optval(optarg, "+-", &opt_n_min, &opt_n_max );
		          break;
		case 's': setup_optval(optarg, "+-", &opt_s_min, &opt_s_max );
		          break;
		case 'l': setup_optval(optarg, ":",  &opt_l_div, &opt_l_add );
		          break;
		case 'o': setup_optval(optarg, ",",  &opt_o_col, &opt_o_row );
		          break;
		case 'h': usage();
		case '?': fatal("type xbible -h for help");
		default:  fatal("illegual option: xbible -h for help");
		}
	}
	opt_c = ( count != INT_MIN ) ? count : ( opt_g ? INT_MAX : 1 );
	argc -= optind;
	argv += optind;
	word_list = get_word_list( argc, argv );
	if(  word_list == NULL  ){ fatal("no word list"); }
	if( *word_list == NULL  ){ fatal("no word list"); }
	if( opt_n_min < 0 || BIBLE_LEN <= opt_n_min ){
		fatal("illegual -n option");
	}
	if( opt_n_max < 0 || opt_n_max < BIBLE_LEN ){
		opt_n_max = BIBLE_LEN;
	}
	len = strlen(*word_list);
	/*
	 1234567890123456
	 abc     
	 a b c    
	 a  b  c  
	 a   b   c
	 a    b    c
	 a     b     c
	 a      b      c    7 == (16-2)/(len-1)
	 a   b   c   d      4 == (16-2)/(len-1)
	*/
	if( (BIBLE_LEN-opt_n_min)/(len-1) < opt_s_max ){
		opt_s_max = (BIBLE_LEN-opt_n_min)/(len-1);
	}
	search_all(opt_n_min, opt_n_max, opt_s_min, opt_s_max, word_list);
	return 0;
}
/*---------------------------------------------------------------------------*/
