/*
 * Copyright (c) 1997-2006  Motoyuki Kasahara
 *
 * 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.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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.
 */

#include "build-pre.h"
#include "eb.h"
#include "error.h"
#include "build-post.h"

/*
 * Unexported functions.
 */
static void eb_fix_word(EB_Book *book, const EB_Search *search, char *word,
    char *canonicalized_word);
static EB_Error_Code eb_convert_latin(EB_Book *book, const char *input_word,
    char *word, EB_Word_Code *word_code);
static EB_Error_Code eb_convert_euc_jp(EB_Book *book, const char *input_word,
    char *word, EB_Word_Code *word_code);
static void eb_convert_katakana_jis(char *word);
static void eb_convert_hiragana_jis(char *word);
static void eb_convert_lower_latin(char *word);
static void eb_convert_lower_jis(char *word);
static void eb_delete_marks_jis(char *word);
static void eb_convert_long_vowels_jis(char *word);
static void eb_delete_long_vowels_jis(char *word);
static void eb_convert_double_consonants_jis(char *word);
static void eb_convert_contracted_sounds_jis(char *word);
static void eb_convert_small_vowels_jis(char *word);
static void eb_convert_voiced_consonants_jis(char *word);
static void eb_convert_p_sounds_jis(char *word);
static void eb_delete_spaces_latin(char *word);
static void eb_delete_spaces_jis(char *word);
static void eb_reverse_word_latin(char *word);
static void eb_reverse_word_jis(char *word);


/*
 * Make a fixed word and a cannonicalized word for `WORD SEARCH'.
 *
 * If `inputword' is a KANA word,  EB_WORD_KANA is returned.
 * If `inputword' is a alphabetic word, EB_WORD_ALPHABET is returned.
 * Otherwise, -1 is returned.  It means that an error occurs.
 */
EB_Error_Code
eb_set_word(EB_Book *book, const char *input_word, char *word,
    char *canonicalized_word, EB_Word_Code *word_code)
{
    EB_Error_Code error_code;
    const EB_Search *search;

    LOG(("in: eb_set_word(book=%d, input_word=%s)", (int)book->code,
	eb_quoted_string(input_word)));

    /*
     * Make a fixed word and a canonicalized word from `input_word'.
     */
    if (book->character_code == EB_CHARCODE_ISO8859_1)
	error_code = eb_convert_latin(book, input_word, word, word_code);
    else
	error_code = eb_convert_euc_jp(book, input_word, word, word_code);
    if (error_code != EB_SUCCESS)
	goto failed;
    strcpy(canonicalized_word, word);

    /*
     * Determine search method.
     */
    switch (*word_code) {
    case EB_WORD_ALPHABET:
	if (book->subbook_current->word_alphabet.start_page != 0)
	    search = &book->subbook_current->word_alphabet;
	else if (book->subbook_current->word_asis.start_page != 0)
	    search = &book->subbook_current->word_asis;
	else {
	    error_code = EB_ERR_NO_SUCH_SEARCH;
	    goto failed;
	}
	break;

    case EB_WORD_KANA:
	if (book->subbook_current->word_kana.start_page != 0)
	    search = &book->subbook_current->word_kana;
	else if (book->subbook_current->word_asis.start_page != 0)
	    search = &book->subbook_current->word_asis;
	else {
	    error_code = EB_ERR_NO_SUCH_SEARCH;
	    goto failed;
	}
	break;

    case EB_WORD_OTHER:
	if (book->subbook_current->word_asis.start_page != 0)
	    search = &book->subbook_current->word_asis;
	else {
	    error_code = EB_ERR_NO_SUCH_SEARCH;
	    goto failed;
	}
	break;

    default:
	error_code = EB_ERR_NO_SUCH_SEARCH;
	goto failed;
    }

    /*
     * Fix the word.
     */
    eb_fix_word(book, search, word, canonicalized_word);

    LOG(("out: eb_set_word(word=%s, canonicalized_word=%s, word_code=%d) = %s",
	eb_quoted_string(word), eb_quoted_string(canonicalized_word),
	(int)*word_code, eb_error_string(EB_SUCCESS)));

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    *word = '\0';
    *canonicalized_word = '\0';
    *word_code = EB_WORD_INVALID;
    LOG(("out: eb_set_word() = %s", eb_error_string(error_code)));
    return error_code;
}


/*
 * Make a fixed word and a cannonicalized word for `ENDWORD SEARCH'.
 *
 * If `input_word' is a KANA word,  EB_WORD_KANA is retuend.
 * If `input_word' is a alphabetic word,  EB_WORD_ALPHABET is retuend.
 * Otherwise, -1 is returned.  It means that an error occurs.
 */
EB_Error_Code
eb_set_endword(EB_Book *book, const char *input_word, char *word,
    char *canonicalized_word, EB_Word_Code *word_code)
{
    EB_Error_Code error_code;
    const EB_Search *search;

    LOG(("in: eb_set_endword(book=%d, input_word=%s)", (int)book->code,
	eb_quoted_string(input_word)));

    /*
     * Make a fixed word and a canonicalized word from `input_word'.
     */
    if (book->character_code == EB_CHARCODE_ISO8859_1)
	error_code = eb_convert_latin(book, input_word, word, word_code);
    else
	error_code = eb_convert_euc_jp(book, input_word, word, word_code);
    if (error_code != EB_SUCCESS)
	goto failed;
    strcpy(canonicalized_word, word);

    /*
     * Determine search method.
     */
    switch (*word_code) {
    case EB_WORD_ALPHABET:
	if (book->subbook_current->endword_alphabet.start_page != 0)
	    search = &book->subbook_current->endword_alphabet;
	else if (book->subbook_current->endword_asis.start_page != 0)
	    search = &book->subbook_current->endword_asis;
	else {
	    error_code = EB_ERR_NO_SUCH_SEARCH;
	    goto failed;
	}
	break;

    case EB_WORD_KANA:
	if (book->subbook_current->endword_kana.start_page != 0)
	    search = &book->subbook_current->endword_kana;
	else if (book->subbook_current->endword_asis.start_page != 0)
	    search = &book->subbook_current->endword_asis;
	else {
	    error_code = EB_ERR_NO_SUCH_SEARCH;
	    goto failed;
	}
	break;

    case EB_WORD_OTHER:
	if (book->subbook_current->endword_asis.start_page != 0)
	    search = &book->subbook_current->endword_asis;
	else {
	    error_code = EB_ERR_NO_SUCH_SEARCH;
	    goto failed;
	}
	break;

    default:
	error_code = EB_ERR_NO_SUCH_SEARCH;
	goto failed;
    }

    /*
     * Fix the word.
     */
    eb_fix_word(book, search, word, canonicalized_word);

    /*
     * Reverse the word.
     */
    if (book->character_code == EB_CHARCODE_ISO8859_1) {
	eb_reverse_word_latin(word);
	eb_reverse_word_latin(canonicalized_word);
    } else {
	eb_reverse_word_jis(word);
	eb_reverse_word_jis(canonicalized_word);
    }

    LOG(("out: eb_set_endword(word=%s, canonicalized_word=%s, word_code=%d) \
= %s",
	eb_quoted_string(word), eb_quoted_string(canonicalized_word),
	(int)*word_code, eb_error_string(EB_SUCCESS)));

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    *word = '\0';
    *canonicalized_word = '\0';
    *word_code = EB_WORD_INVALID;
    LOG(("out: eb_set_endword() = %s", eb_error_string(error_code)));
    return error_code;
}


/*
 * Make a fixed word and a cannonicalized word for `KEYWORD SEARCH'
 * or `CROSS SEARCH'.
 *
 * If `inputword' is a KANA word,  EB_WORD_KANA is returned.
 * If `inputword' is a alphabetic word, EB_WORD_ALPHABET is returned.
 * Otherwise, -1 is returned.  It means that an error occurs.
 */
EB_Error_Code
eb_set_keyword(EB_Book *book, const char *input_word, char *word,
    char *canonicalized_word, EB_Word_Code *word_code)
{
    EB_Error_Code error_code;

    LOG(("in: eb_set_keyword(book=%d, input_word=%s)", (int)book->code,
	eb_quoted_string(input_word)));

    /*
     * Make a fixed word and a canonicalized word from `input_word'.
     */
    if (book->character_code == EB_CHARCODE_ISO8859_1)
	error_code = eb_convert_latin(book, input_word, word, word_code);
    else
	error_code = eb_convert_euc_jp(book, input_word, word, word_code);
    if (error_code != EB_SUCCESS)
	goto failed;
    strcpy(canonicalized_word, word);

    /*
     * Fix the word.
     */
    eb_fix_word(book, &book->subbook_current->keyword, word,
	canonicalized_word);

    LOG(("out: eb_set_keyword(word=%s, canonicalized_word=%s, word_code=%d) \
= %s",
	eb_quoted_string(word), eb_quoted_string(canonicalized_word),
	(int)*word_code, eb_error_string(EB_SUCCESS)));

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    *word = '\0';
    *canonicalized_word = '\0';
    *word_code = EB_WORD_INVALID;
    LOG(("out: eb_set_keyword() = %s", eb_error_string(error_code)));
    return error_code;
}


/*
 * Make a fixed word and a cannonicalized word for `MULTI SEARCH'.
 *
 * If `inputword' is a KANA word,  EB_WORD_KANA is returned.
 * If `inputword' is a alphabetic word, EB_WORD_ALPHABET is returned.
 * Otherwise, -1 is returned.  It means that an error occurs.
 */
EB_Error_Code
eb_set_multiword(EB_Book *book, EB_Multi_Search_Code multi_id,
    EB_Multi_Entry_Code entry_id, const char *input_word, char *word,
    char *canonicalized_word, EB_Word_Code *word_code)
{
    EB_Error_Code error_code;
    EB_Search *search;

    LOG(("in: eb_set_multiword(book=%d, input_word=%s)", (int)book->code,
	eb_quoted_string(input_word)));

    /*
     * Make a fixed word and a canonicalized word from `input_word'.
     */
    if (book->character_code == EB_CHARCODE_ISO8859_1)
	error_code = eb_convert_latin(book, input_word, word, word_code);
    else
	error_code = eb_convert_euc_jp(book, input_word, word, word_code);
    if (error_code != EB_SUCCESS)
	goto failed;
    strcpy(canonicalized_word, word);

    /*
     * Fix the word.
     */
    search = &book->subbook_current->multis[multi_id].entries[entry_id];
    eb_fix_word(book, search, word, canonicalized_word);

    LOG(("out: eb_set_multiword(word=%s, canonicalized_word=%s, word_code=%d) \
= %s",
	eb_quoted_string(word), eb_quoted_string(canonicalized_word),
	(int)*word_code, eb_error_string(EB_SUCCESS)));

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    *word = '\0';
    *canonicalized_word = '\0';
    *word_code = EB_WORD_INVALID;
    LOG(("out: eb_set_multiword() = %s", eb_error_string(error_code)));
    return error_code;
}


/*
 * Fix `canonicalized_word' and `word' according with `book->character_code'
 * and `search'.
 */
static void
eb_fix_word(EB_Book *book, const EB_Search *search, char *word,
    char *canonicalized_word)
{
    LOG(("in: eb_fix_word(book=%d, word=%s, canonicalized_word=%s)",
	(int)book->code, eb_quoted_string(word),
	eb_quoted_string(canonicalized_word)));

    if (search->index_id == 0xa1 && search->candidates_page != 0)
	return;

    if (book->character_code == EB_CHARCODE_ISO8859_1) {
	if (search->space == EB_INDEX_STYLE_DELETE)
	    eb_delete_spaces_latin(canonicalized_word);

	if (search->lower == EB_INDEX_STYLE_CONVERT)
	    eb_convert_lower_latin(canonicalized_word);

    } else {
	if (search->space == EB_INDEX_STYLE_DELETE)
	    eb_delete_spaces_jis(canonicalized_word);

	if (search->katakana == EB_INDEX_STYLE_CONVERT)
	    eb_convert_katakana_jis(canonicalized_word);
	else if (search->katakana == EB_INDEX_STYLE_REVERSED_CONVERT)
	    eb_convert_hiragana_jis(canonicalized_word);

	if (search->lower == EB_INDEX_STYLE_CONVERT)
	    eb_convert_lower_jis(canonicalized_word);

	if (search->mark == EB_INDEX_STYLE_DELETE)
	    eb_delete_marks_jis(canonicalized_word);

	if (search->long_vowel == EB_INDEX_STYLE_CONVERT)
	    eb_convert_long_vowels_jis(canonicalized_word);
	else if (search->long_vowel == EB_INDEX_STYLE_DELETE)
	    eb_delete_long_vowels_jis(canonicalized_word);

	if (search->double_consonant == EB_INDEX_STYLE_CONVERT)
	    eb_convert_double_consonants_jis(canonicalized_word);

	if (search->contracted_sound == EB_INDEX_STYLE_CONVERT)
	    eb_convert_contracted_sounds_jis(canonicalized_word);

	if (search->small_vowel == EB_INDEX_STYLE_CONVERT)
	    eb_convert_small_vowels_jis(canonicalized_word);

	if (search->voiced_consonant == EB_INDEX_STYLE_CONVERT)
	    eb_convert_voiced_consonants_jis(canonicalized_word);

	if (search->p_sound == EB_INDEX_STYLE_CONVERT)
	    eb_convert_p_sounds_jis(canonicalized_word);
    }

    if (search->index_id != 0x70 && search->index_id != 0x90)
	strcpy(word, canonicalized_word);

    LOG(("out: eb_fix_word(word=%s, canonicalized_word=%s)",
	eb_quoted_string(word), eb_quoted_string(canonicalized_word)));
}


/*
 * Convert `input_word' to ISO 8859 1 and put it into `word'.
 *
 * If `input_word' is a valid string to search, EB_WORD_ALPHABET is returned.
 * Otherwise, -1 is returned.
 */
static EB_Error_Code
eb_convert_latin(EB_Book *book, const char *input_word, char *word,
    EB_Word_Code *word_code)
{
    EB_Error_Code error_code;
    unsigned char *wp = (unsigned char *) word;
    const unsigned char *inp = (const unsigned char *) input_word;
    const unsigned char *tail;
    unsigned char c1;
    int word_length = 0;

    LOG(("in: eb_convert_latin(book=%d, input_word=%s)", (int)book->code,
	eb_quoted_string(input_word)));

    /*
     * Find the tail of `input_word'.
     */
    tail = (const unsigned char *) input_word + strlen(input_word) - 1;
    while ((const unsigned char *)input_word <= tail
	&& (*tail == ' ' || *tail == '\t'))
	tail--;
    tail++;

    /*
     * Ignore spaces and tabs in the beginning of `input_word'.
     */
    while (*inp == ' ' || *inp == '\t')
	inp++;

    while (inp < tail) {
	/*
	 * Check for the length of the word.
	 * If exceeds, return with an error code.
	 */
	if (EB_MAX_WORD_LENGTH < word_length + 1) {
	    error_code = EB_ERR_TOO_LONG_WORD;
	    goto failed;
	}

	c1 = *inp++;

	/*
	 * Tabs are translated to spaces.
	 */
	if (c1 == '\t')
	    c1 = ' ';

	*wp++ = c1;

	/*
	 * Skip successive spaces and tabs.
	 */
	if (c1 == ' ') {
	    while (*inp == '\t' || *inp == ' ')
		inp++;
	}

	word_length++;
    }
    *wp = '\0';

    if (word_length == 0) {
	error_code = EB_ERR_EMPTY_WORD;
	goto failed;
    }
    *word_code = EB_WORD_ALPHABET;

    LOG(("out: eb_convert_latin(word=%s, word_code=%d) = %s",
	eb_quoted_string(word), (int)*word_code, eb_error_string(EB_SUCCESS)));

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    *word = '\0';
    *word_code = EB_WORD_INVALID;
    LOG(("out: eb_convert_latin() = %s", eb_error_string(error_code)));
    return error_code;
}


/*
 * Table used to convert JIS X 0208 to ASCII.
 */
static const unsigned int jisx0208_table[] = {
    /* 0x20 -- 0x2f */
    0x2121, 0x212a, 0x2149, 0x2174, 0x2170, 0x2173, 0x2175, 0x2147,
    0x214a, 0x214b, 0x2176, 0x215c, 0x2124, 0x215d, 0x2125, 0x213f,
    /* 0x30 -- 0x3f */
    0x2330, 0x2331, 0x2332, 0x2333, 0x2334, 0x2335, 0x2336, 0x2337,
    0x2338, 0x2339, 0x2127, 0x2128, 0x2163, 0x2161, 0x2164, 0x2129,
    /* 0x40 -- 0x4f */
    0x2177, 0x2341, 0x2342, 0x2343, 0x2344, 0x2345, 0x2346, 0x2347,
    0x2348, 0x2349, 0x234a, 0x234b, 0x234c, 0x234d, 0x234e, 0x234f,
    /* 0x50 -- 0x5f */
    0x2350, 0x2351, 0x2352, 0x2353, 0x2354, 0x2355, 0x2356, 0x2357,
    0x2358, 0x2359, 0x235a, 0x214e, 0x2140, 0x214f, 0x2130, 0x2132,
    /* 0x60 -- 0x6f */
    0x2146, 0x2361, 0x2362, 0x2363, 0x2364, 0x2365, 0x2366, 0x2367,
    0x2368, 0x2369, 0x236a, 0x236b, 0x236c, 0x236d, 0x236e, 0x236f,
    /* 0x70 -- 0x7e */
    0x2370, 0x2371, 0x2372, 0x2373, 0x2374, 0x2375, 0x2376, 0x2377,
    0x2378, 0x2379, 0x237a, 0x2150, 0x2143, 0x2151, 0x2141
};

/*
 * Table used to convert JIS X 0201 KATAKANA to JIS X 0208.
 */
static const unsigned int jisx0201_table[] = {
    /* 0xa0 -- 0xaf */
    0x0000, 0x2123, 0x2156, 0x2157, 0x2122, 0x2126, 0x2572, 0x2521,
    0x2523, 0x2525, 0x2527, 0x2529, 0x2563, 0x2565, 0x2567, 0x2543,
    /* 0xb0 -- 0xbf */
    0x213c, 0x2522, 0x2524, 0x2526, 0x2528, 0x252a, 0x252b, 0x252d,
    0x252f, 0x2531, 0x2533, 0x2535, 0x2537, 0x2539, 0x253b, 0x253d,
    /* 0xc0 -- 0xcf */
    0x253f, 0x2541, 0x2544, 0x2546, 0x2548, 0x254a, 0x254b, 0x254c,
    0x254d, 0x254e, 0x254f, 0x2552, 0x2555, 0x2558, 0x255b, 0x255e,
    /* 0xd0 -- 0xdf */
    0x255f, 0x2560, 0x2561, 0x2562, 0x2564, 0x2566, 0x2568, 0x2569,
    0x256a, 0x256b, 0x256c, 0x256d, 0x256f, 0x2573, 0x212b, 0x212c
};

/*
 * Convert `input_word' to JIS X0208 and put it into `word'.
 *
 * If `input_word' is a valid string to search, EB_WORD_ALPHABET or
 * EB_WORD_KANA is returned.
 * Otherwise, -1 is returned.
 */
static EB_Error_Code
eb_convert_euc_jp(EB_Book *book, const char *input_word, char *word,
    EB_Word_Code *word_code)
{
    EB_Error_Code error_code;
    unsigned char *wp = (unsigned char *) word;
    const unsigned char *inp = (const unsigned char *) input_word;
    const unsigned char *tail;
    unsigned char c1 = 0, c2 = 0;
    int kana_count = 0;
    int alphabet_count = 0;
    int kanji_count = 0;
    int word_length = 0;

    LOG(("in: eb_convert_euc_jp(book=%d, input_word=%s)", (int)book->code,
	eb_quoted_string(input_word)));

    /*
     * Find the tail of `input_word'.
     */
    tail = (const unsigned char *) input_word + strlen(input_word) - 1;
    for (;;) {
	if (inp < tail && (*tail == ' ' || *tail == '\t'))
	    tail--;
	else if (inp < tail - 1 && *tail == 0xa1 && *(tail - 1) == 0xa1)
	    tail -= 2;
	else
	    break;
    }
    tail++;

    /*
     * Ignore spaces and tabs in the beginning of `input_word'.
     */
    for (;;) {
	if (*inp == ' ' || *inp == '\t')
	    inp++;
	else if (*inp == 0xa1 && *(inp + 1) == 0xa1)
	    inp += 2;
	else
	    break;
    }

    while (inp < tail) {
	/*
	 * Check for the length of the word.
	 * If exceeds, return with an error code.
	 */
	if (EB_MAX_WORD_LENGTH < word_length + 2) {
	    error_code = EB_ERR_TOO_LONG_WORD;
	    goto failed;
	}

	/*
	 * Tabs are translated to spaces.
	 */
	c1 = *inp++;
	if (c1 == '\t')
	    c1 = ' ';

	if (0x20 <= c1 && c1 <= 0x7e) {
	    /*
	     * `c1' is a character in ASCII.
	     */
	    unsigned int c = jisx0208_table[c1 - 0x20];
	    c1 = c >> 8;
	    c2 = c & 0xff;
	} else if (0xa1 <= c1 && c1 <= 0xfe) {
	    /*
	     * `c1' is a character in JIS X 0208, or local character.
	     */
	    c2 = *inp++;

	    if (0xa1 <= c2 && c2 <= 0xfe) {
		c1 &= 0x7f;
		c2 &= 0x7f;
	    } else if (c2 < 0x20 || 0x7e < c2) {
		error_code = EB_ERR_BAD_WORD;
		goto failed;
	    }
	} else if (c1 == 0x8e) {
	    /*
	     * `c1' is SS2.
	     */
	    if (c2 < 0xa1 || 0xdf < c2) {
		error_code = EB_ERR_BAD_WORD;
		goto failed;
	    }
	    c2 = jisx0201_table[c2 - 0xa0];
	    c1 = 0x25;
	} else {
	    error_code = EB_ERR_BAD_WORD;
	    goto failed;
	}

	/*
	 * The following characters are recognized as alphabet.
	 *   2330 - 2339: `0' .. `9'
	 *   2341 - 235a: `A' .. `Z'
	 *   2361 - 237a: `a' .. `z' (convert to upper cases)
	 */
	*wp++ = c1;
	*wp++ = c2;

	if (c1 == 0x23)
	    alphabet_count++;
	else if (c1 == 0x24 || c1 == 0x25)
	    kana_count++;
	else if (c1 != 0x21)
	    kanji_count++;

	word_length += 2;
    }
    *wp = '\0';

    if (word_length == 0) {
	error_code = EB_ERR_EMPTY_WORD;
	goto failed;
    }
    if (alphabet_count == 0 && kana_count != 0 && kanji_count == 0)
	*word_code = EB_WORD_KANA;
    else if (alphabet_count != 0 && kana_count == 0 && kanji_count == 0)
	*word_code = EB_WORD_ALPHABET;
    else
	*word_code = EB_WORD_OTHER;

    LOG(("out: eb_convert_euc_jp(word=%s, word_code=%d) = %s",
	eb_quoted_string(word), (int)*word_code, eb_error_string(EB_SUCCESS)));

    return EB_SUCCESS;

    /*
     * An error occurs...
     */
  failed:
    *word = '\0';
    *word_code = EB_WORD_INVALID;
    LOG(("out: eb_convert_euc_jp() = %s", eb_error_string(error_code)));
    return error_code;
}


/*
 * Convert KATAKANA to HIRAGANA in `word'.
 */
static void
eb_convert_katakana_jis(char *word)
{
    unsigned char *wp = (unsigned char *) word;
    unsigned char c1, c2;

    LOG(("in: eb_convert_katakana_jis(word=%s)", eb_quoted_string(word)));

    while (*wp != '\0' && *(wp + 1) != '\0') {
	c1 = *wp;
	c2 = *(wp + 1);

	if (c1 == 0x25 && 0x21 <= c2 && c2 <= 0x76) {
	    /*
	     * This is a KATAKANA.  Convert to corresponding HIRAGANA.
	     */
	    *wp = 0x24;
	}
	wp += 2;
    }
    *wp = '\0';

    LOG(("out: eb_convert_katakana_jis()"));
}


/*
 * Convert HIRAGANA to KATAKANA in `word'.
 */
static void
eb_convert_hiragana_jis(char *word)
{
    unsigned char *wp = (unsigned char *) word;
    unsigned char c1, c2;

    LOG(("in: eb_convert_hiragana_jis(word=%s)", eb_quoted_string(word)));

    while (*wp != '\0' && *(wp + 1) != '\0') {
	c1 = *wp;
	c2 = *(wp + 1);

	if (c1 == 0x24 && 0x21 <= c2 && c2 <= 0x76) {
	    /*
	     * This is a HIRAGANA.  Convert to corresponding KATAKANA.
	     */
	    *wp = 0x25;
	}
	wp += 2;
    }
    *wp = '\0';

    LOG(("out: eb_convert_hiragana_jis()"));
}


/*
 * Convert lower case to upper case in `word'.
 */
static void
eb_convert_lower_latin(char *word)
{
    unsigned char *wp = (unsigned char *) word;

    LOG(("in: eb_convert_lower_latin(word=%s)", eb_quoted_string(word)));

    while (*wp != '\0') {
	if (('a' <= *wp && *wp <= 'z')
	    || (0xe0 <= *wp && *wp <= 0xf6) || (0xf8 <= *wp && *wp <= 0xfe)) {
	    /*
	     * This is a lower case letter.  Convert to upper case.
	     */
	    *wp -= 0x20;
	}
	wp++;
    }
    *wp = '\0';

    LOG(("out: eb_convert_lower_latin()"));
}


/*
 * Convert lower case to upper case in `word'.
 */
static void
eb_convert_lower_jis(char *word)
{
    unsigned char *wp = (unsigned char *) word;
    unsigned char c1, c2;

    LOG(("in: eb_convert_lower_jis(word=%s)", eb_quoted_string(word)));

    while (*wp != '\0' && *(wp + 1) != '\0') {
	c1 = *wp;
	c2 = *(wp + 1);

	if (c1 == 0x23 && 0x61 <= c2 && c2 <= 0x7a) {
	    /*
	     * This is a lower case letter.  Convert to upper case.
	     */
	    *(wp + 1) = c2 - 0x20;
	}
	wp += 2;
    }
    *wp = '\0';

    LOG(("out: eb_convert_lower_jis()"));
}


/*
 * Delete some marks in `word'.
 */
static void
eb_delete_marks_jis(char *word)
{
    unsigned char *in_wp = (unsigned char *) word;
    unsigned char *out_wp = (unsigned char *) word;
    unsigned char c1, c2;

    LOG(("in: eb_delete_marks_jis(word=%s)", eb_quoted_string(word)));

    while (*in_wp != '\0' && *(in_wp + 1) != '\0') {
	c1 = *in_wp;
	c2 = *(in_wp + 1);

	if (c1 != 0x21
	    || (c2 != 0x26 && c2 != 0x3e && c2 != 0x47 && c2 != 0x5d)) {
	    /*
	     * This is not a character to be deleted.
	     */
	    *out_wp = c1;
	    *(out_wp + 1) = c2;
	    out_wp += 2;
	}
	in_wp += 2;
    }
    *out_wp = '\0';

    LOG(("out: eb_delete_marks_jis()"));
}


/*
 * The table is used to convert long vowel marks.
 */
static const char long_vowel_table[] = {
    0x22, /* a(21) -> A(22) */		0x22, /* A(22) -> A(22) */
    0x24, /* i(23) -> I(24) */		0x24, /* I(24) -> I(24) */
    0x26, /* u(25) -> U(26) */		0x26, /* U(26) -> U(26) */
    0x28, /* e(27) -> E(28) */		0x28, /* E(28) -> E(28) */
    0x2a, /* o(29) -> O(2a) */		0x2a, /* O(2a) -> O(2a) */
    0x22, /* KA(2b) -> A(22) */		0x22, /* GA(2c) -> A(22) */
    0x24, /* KI(2d) -> I(24) */		0x24, /* GI(2e) -> I(24) */
    0x26, /* KU(2f) -> U(26) */		0x26, /* GU(30) -> U(26) */
    0x28, /* KE(31) -> E(28) */		0x28, /* GE(32) -> E(28) */
    0x2a, /* KO(33) -> O(2a) */		0x2a, /* GO(34) -> O(2a) */
    0x22, /* SA(35) -> A(22) */		0x22, /* ZA(36) -> A(22) */
    0x24, /* SI(37) -> I(24) */		0x24, /* ZI(38) -> I(24) */
    0x26, /* SU(39) -> U(26) */		0x26, /* ZU(3a) -> U(26) */
    0x28, /* SE(3b) -> E(28) */		0x28, /* ZE(3c) -> E(28) */
    0x2a, /* SO(3d) -> O(2a) */		0x2a, /* ZO(3e) -> O(2a) */
    0x22, /* TA(3f) -> A(22) */		0x22, /* DA(40) -> A(22) */
    0x24, /* TI(41) -> I(24) */		0x24, /* DI(42) -> I(24) */
    0x26, /* tu(43) -> U(26) */		0x26, /* TU(44) -> U(26) */
    0x26, /* DU(45) -> U(26) */		0x28, /* TE(46) -> E(28) */
    0x28, /* DE(47) -> E(28) */		0x2a, /* TO(48) -> O(2a) */
    0x2a, /* DO(49) -> O(2a) */		0x22, /* NA(4a) -> A(22) */
    0x24, /* NI(4b) -> I(24) */		0x26, /* NU(4c) -> U(26) */
    0x28, /* NE(4d) -> E(28) */		0x2a, /* NO(4e) -> O(2a) */
    0x22, /* HA(4f) -> A(22) */		0x22, /* BA(50) -> A(22) */
    0x22, /* PA(51) -> A(22) */		0x24, /* HI(52) -> I(24) */
    0x24, /* BI(53) -> I(24) */		0x24, /* PI(54) -> I(24) */
    0x26, /* HU(55) -> U(26) */		0x26, /* BU(56) -> U(26) */
    0x26, /* PU(57) -> U(26) */		0x28, /* HE(58) -> E(28) */
    0x28, /* BE(59) -> E(28) */		0x28, /* PE(5a) -> E(28) */
    0x2a, /* HO(5b) -> O(2a) */		0x2a, /* BO(5c) -> O(2a) */
    0x2a, /* PO(5d) -> O(2a) */		0x22, /* MA(5e) -> A(22) */
    0x24, /* MI(5f) -> I(24) */		0x26, /* MU(60) -> U(26) */
    0x28, /* ME(61) -> E(28) */		0x2a, /* MO(62) -> O(2a) */
    0x22, /* ya(63) -> A(22) */		0x22, /* YA(64) -> A(22) */
    0x26, /* yu(65) -> U(26) */		0x26, /* YU(66) -> U(26) */
    0x2a, /* yo(67) -> O(2a) */		0x2a, /* YO(68) -> O(2a) */
    0x22, /* RA(69) -> A(22) */		0x24, /* RI(6a) -> I(24) */
    0x26, /* RU(6b) -> U(26) */		0x28, /* RE(6c) -> E(28) */
    0x2a, /* RO(6d) -> O(2a) */		0x22, /* wa(6e) -> A(22) */
    0x22, /* WA(6f) -> A(22) */		0x24, /* WI(70) -> I(24) */
    0x28, /* WE(71) -> E(28) */		0x2a, /* WO(72) -> O(2a) */
    0x73, /* N (73) -> N(73) */		0x26, /* VU(74) -> U(26) */
    0x22, /* ka(75) -> A(22) */		0x28  /* ke(76) -> E(28) */
};


/*
 * Convert long vowel marks in `word' to the previous vowels.
 */
static void
eb_convert_long_vowels_jis(char *word)
{
    unsigned char *wp = (unsigned char *) word;
    unsigned char c1, c2;
    unsigned char previous_c1 = '\0', previous_c2 = '\0';

    LOG(("in: eb_convert_long_vowels_jis(word=%s)", eb_quoted_string(word)));

    while (*wp != '\0' && *(wp + 1) != '\0') {
	c1 = *wp;
	c2 = *(wp + 1);

	if (c1 == 0x21 && c2 == 0x3c) {
	    /*
	     * The is a long vowel mark.
	     * Convert to a vowel of the prev_ KANA character.
	     * If prev_ character is not KANA, the conversion is
	     * not done.
	     */
	    if ((previous_c1 == 0x24 || previous_c1 == 0x25)
		&& 0x21 <= previous_c2 && previous_c2 <= 0x76) {
		*wp = previous_c1;
		*(wp + 1) = long_vowel_table[previous_c2 - 0x21];
	    }
	}
	previous_c1 = c1;
	previous_c2 = c2;
	wp += 2;
    }
    *wp = '\0';

    LOG(("out: eb_convert_long_vowels_jis()"));
}


/*
 * Delete long vowel marks in `word'.
 */
static void
eb_delete_long_vowels_jis(char *word)
{
    unsigned char *in_wp = (unsigned char *) word;
    unsigned char *out_wp = (unsigned char *) word;
    unsigned char c1, c2;

    LOG(("in: eb_delete_long_vowels_jis(word=%s)", eb_quoted_string(word)));

    while (*in_wp != '\0' && *(in_wp + 1) != '\0') {
	c1 = *in_wp;
	c2 = *(in_wp + 1);

	if (c1 != 0x21 || c2 != 0x3c) {
	    /*
	     * The is not a long vowel mark.
	     */
	    *out_wp = c1;
	    *(out_wp + 1) = c2;
	    out_wp += 2;
	}
	in_wp += 2;
    }
    *out_wp = '\0';

    LOG(("out: eb_delete_long_vowels_jis()"));
}


/*
 * Convert the double consonant mark `tu' to `TU'.
 */
static void
eb_convert_double_consonants_jis(char *word)
{
    unsigned char *wp = (unsigned char *) word;
    unsigned char c1, c2;

    LOG(("in: eb_convert_double_consonants_jis(word=%s)",
	eb_quoted_string(word)));

    while (*wp != '\0' && *(wp + 1) != '\0') {
	c1 = *wp;
	c2 = *(wp + 1);

	if ((c1 == 0x24 || c1 == 0x25) && c2 == 0x43) {
	    /*
	     * This is a double sound mark.  Convert to the corresponding
	     * sound mark.
	     */
	    *(wp + 1) = c2 + 1;
	}
	wp += 2;
    }
    *wp = '\0';

    LOG(("out: eb_convert_double_consonants_jis()"));
}


/*
 * Convert the contracted sound marks to the corresponding
 * non-contracted sound marks.
 * (`ya', `yu', `yo', `wa', `ka', `ke' -> `YA', `YU', `YO', `WA', `KA', `KE')
 */
static void
eb_convert_contracted_sounds_jis(char *word)
{
    unsigned char *wp = (unsigned char *) word;
    unsigned char c1, c2;

    LOG(("in: eb_convert_contracted_sounds_jis(word=%s)",
	eb_quoted_string(word)));

    while (*wp != '\0' && *(wp + 1) != '\0') {
	c1 = *wp;
	c2 = *(wp + 1);

	if (c1 == 0x24 || c1 == 0x25) {
	    /*
	     * This is HIRAGANA or KANAKANA.
	     * If this is a contracted sound mark, convert to the
	     * corresponding uncontracted sound mark.
	     */
	    if (c2 == 0x63 || c2 == 0x65 || c2 == 0x67 || c2 == 0x6e)
		*(wp + 1) = c2 + 1;
	    else if (c2 == 0x75)
		*(wp + 1) = 0x2b;
	    else if (c2 == 0x76)
		*(wp + 1) = 0x31;
	}
	wp += 2;
    }
    *wp = '\0';

    LOG(("in: eb_convert_contracted_sounds_jis()"));
}


/*
 * Convert the small vowels to the normal vowels.
 * (`a', `i', `u', `e', `o' -> `A', `I', `U', `E', `O')
 */
static void
eb_convert_small_vowels_jis(char *word)
{
    unsigned char *wp = (unsigned char *) word;
    unsigned char c1, c2;

    LOG(("in: eb_convert_small_vowels_jis(word=%s)", eb_quoted_string(word)));

    while (*wp != '\0' && *(wp + 1) != '\0') {
	c1 = *wp;
	c2 = *(wp + 1);

	if (c1 == 0x24 || c1 == 0x25) {
	    /*
	     * This is HIRAGANA or KANAKANA.
	     * If this is a small vowel mark, convert to a normal vowel.
	     */
	    if (c2 == 0x21 || c2 == 0x23 || c2 == 0x25 || c2 == 0x27
		|| c2 == 0x29)
		*(wp + 1) = c2 + 1;
	}
	wp += 2;
    }
    *wp = '\0';

    LOG(("out: eb_convert_small_vowels_jis()"));
}


/*
 * The table is used to convert voiced consonant marks.
 */
static const char voiced_consonant_table[] = {
    0x21, /* a(21) -> a(22) */		0x22, /* A(22) -> A(22) */
    0x23, /* i(23) -> i(24) */		0x24, /* I(24) -> I(24) */
    0x25, /* u(25) -> u(26) */		0x26, /* U(26) -> U(26) */
    0x27, /* e(27) -> e(28) */		0x28, /* E(28) -> E(28) */
    0x29, /* o(29) -> o(2a) */		0x2a, /* O(2a) -> O(2a) */
    0x2b, /* KA(2b) -> KA(2b) */	0x2b, /* GA(2c) -> KA(2b) */
    0x2d, /* KI(2d) -> KI(2d) */	0x2d, /* GI(2e) -> KI(2d) */
    0x2f, /* KU(2f) -> KU(2f) */	0x2f, /* GU(30) -> KU(2f) */
    0x31, /* KE(31) -> KE(31) */	0x31, /* GE(32) -> KE(31) */
    0x33, /* KO(33) -> KO(33) */	0x33, /* GO(34) -> KO(33) */
    0x35, /* SA(35) -> SA(35) */	0x35, /* ZA(36) -> SA(35) */
    0x37, /* SI(37) -> SI(37) */	0x37, /* ZI(38) -> SI(37) */
    0x39, /* SU(39) -> SU(39) */	0x39, /* ZU(3a) -> SU(39) */
    0x3b, /* SE(3b) -> SE(3b) */	0x3b, /* ZE(3c) -> SE(3b) */
    0x3d, /* SO(3d) -> SO(3d) */	0x3d, /* ZO(3e) -> SO(3d) */
    0x3f, /* TA(3f) -> TA(3f) */	0x3f, /* DA(40) -> TA(3f) */
    0x41, /* TI(41) -> TI(41) */	0x41, /* DI(42) -> TI(41) */
    0x43, /* tu(43) -> TU(43) */	0x44, /* TU(44) -> TU(44) */
    0x44, /* DU(45) -> TU(44) */	0x46, /* TE(46) -> TE(46) */
    0x46, /* DE(47) -> TE(46) */	0x48, /* TO(48) -> TO(48) */
    0x48, /* DO(49) -> TO(48) */	0x4a, /* NA(4a) -> NA(4a) */
    0x4b, /* NI(4b) -> NI(4b) */	0x4c, /* NU(4c) -> NU(4c) */
    0x4d, /* NE(4d) -> NE(4d) */	0x4e, /* NO(4e) -> NO(4e) */
    0x4f, /* HA(4f) -> HA(4f) */	0x4f, /* BA(50) -> HA(4f) */
    0x51, /* PA(51) -> PA(51) */	0x52, /* HI(52) -> HI(52) */
    0x52, /* BI(53) -> HI(52) */	0x54, /* PI(54) -> PU(54) */
    0x55, /* HU(55) -> HU(55) */	0x55, /* BU(56) -> HU(55) */
    0x57, /* PU(57) -> PU(57) */	0x58, /* HE(58) -> HE(58) */
    0x58, /* BE(59) -> HE(58) */	0x5a, /* PE(5a) -> PE(5a) */
    0x5b, /* HO(5b) -> HO(5b) */	0x5b, /* BO(5c) -> HO(5b) */
    0x5d, /* PO(5d) -> PO(5d) */	0x5e, /* MA(5e) -> MA(5e) */
    0x5f, /* MI(5f) -> MI(5f) */	0x60, /* MU(60) -> MU(60) */
    0x61, /* ME(61) -> ME(61) */	0x62, /* MO(62) -> MO(62) */
    0x64, /* ya(63) -> ya(63) */	0x64, /* YA(64) -> YA(64) */
    0x66, /* yu(65) -> yu(65) */	0x66, /* YU(66) -> YU(66) */
    0x68, /* yo(67) -> yo(67) */	0x68, /* YO(68) -> YO(68) */
    0x69, /* RA(69) -> TA(69) */	0x6a, /* RI(6a) -> RI(6a) */
    0x6b, /* RU(6b) -> RU(6b) */	0x6c, /* RE(6c) -> RE(6c) */
    0x6d, /* RO(6d) -> RO(6d) */	0x6e, /* wa(6e) -> wa(6e) */
    0x6f, /* WA(6f) -> WA(6f) */	0x70, /* WI(70) -> WI(70) */
    0x71, /* WE(71) -> WE(71) */	0x72, /* WO(72) -> WO(72) */
    0x73, /* N(73) -> N(73) */		0x26, /* VU(74) -> U(26) */
    0x75, /* ka(75) -> ka(75) */	0x76  /* ke(76) -> ke(76) */
};

/*
 * Convert the contracted sound marks to the corresponding
 * non-contracted sound marks (e.g. `GA' to `KA').
 */
static void
eb_convert_voiced_consonants_jis(char *word)
{
    unsigned char *wp = (unsigned char *) word;
    unsigned char c1, c2;

    LOG(("in: eb_convert_voiced_consonants_jis(word=%s)",
	eb_quoted_string(word)));

    while (*wp != '\0' && *(wp + 1) != '\0') {
	c1 = *wp;
	c2 = *(wp + 1);

	if ((c1 == 0x24 || c1 == 0x25) && 0x21 <= c2 && c2 <= 0x76) {
	    /*
	     * This is a voiced constonat mark.  Convert to the
	     * corresponding unvoiced constonant mark.
	     */
	    *(wp + 1) = voiced_consonant_table[c2 - 0x21];
	}
	wp += 2;
    }
    *wp = '\0';

    LOG(("out: eb_convert_voiced_consonants_jis()"));
}


/*
 * Convert the p sound marks
 * (`PA', `PI', `PU', `PE', `PO' -> `HA', `HI', `HU', `HE', `HO')
 */
static void
eb_convert_p_sounds_jis(char *word)
{
    unsigned char *wp = (unsigned char *) word;
    unsigned char c1, c2;

    LOG(("in: eb_convert_p_sounds_jis(word=%s)", eb_quoted_string(word)));

    while (*wp != '\0' && *(wp + 1) != '\0') {
	c1 = *wp;
	c2 = *(wp + 1);

	if (c1 == 0x24 || c1 == 0x25) {
	    /*
	     * This is HIRAGANA or KANAKANA.
	     * If this is a p-sound mark, convert to the corresponding
	     * unvoiced consonant mark.
	     */
	    if (c2 == 0x51 || c2 == 0x54 || c2 == 0x57 || c2 == 0x5a
		|| c2 == 0x5d)
		*(wp + 1) = c2 - 2;
	}
	wp += 2;
    }
    *wp = '\0';

    LOG(("out: eb_convert_p_sounds_jis()"));
}


/*
 * Delete spaces in `word'.
 */
static void
eb_delete_spaces_latin(char *word)
{
    unsigned char *in_wp = (unsigned char *) word;
    unsigned char *out_wp = (unsigned char *) word;

    LOG(("in: eb_delete_space_latin(word=%s)", eb_quoted_string(word)));

    while (*in_wp != '\0') {
	if (*in_wp != ' ') {
	    /*
	     * This is not a space character of ISO 8859 1.
	     */
	    *out_wp = *in_wp;
	    out_wp++;
	}
	in_wp++;
    }
    *out_wp = '\0';

    LOG(("out: eb_delete_space_latin()"));
}


/*
 * Delete spaces in `word'.
 */
static void
eb_delete_spaces_jis(char *word)
{
    unsigned char *in_wp = (unsigned char *) word;
    unsigned char *out_wp = (unsigned char *) word;
    unsigned char c1, c2;

    LOG(("in: eb_delete_space_jis(word=%s)", eb_quoted_string(word)));

    while (*in_wp != '\0' && *(in_wp + 1) != '\0') {
	c1 = *in_wp;
	c2 = *(in_wp + 1);

	if (c1 != 0x21 || c2 != 0x21) {
	    /*
	     * This is not a space character of JIS X 0208.
	     */
	    *out_wp = c1;
	    *(out_wp + 1) = c2;
	    out_wp += 2;
	}
	in_wp += 2;
    }
    *out_wp = '\0';

    LOG(("out: eb_delete_space_jis()"));
}


/*
 * Reverse a word for ENDWORD SEARCH.
 *
 * `word' is a word to reverse.  It must be an alphabetic word.
 * The reversed word is also put into `word'.
 */
static void
eb_reverse_word_latin(char *word)
{
    char *p1, *p2;
    int word_length;
    char c;

    LOG(("in: eb_reverse_word_latin(word=%s)", eb_quoted_string(word)));

    word_length = strlen(word);
    if (word_length == 0)
	return;
    for (p1 = word, p2 = word + word_length - 1; p1 < p2; p1++, p2--) {
	c = *p1;
	*p1 = *p2;
	*p2 = c;
    }

    LOG(("out: eb_reverse_word_latin()"));
}


/*
 * Reverse a word for ENDWORD SEARCH.
 *
 * `word' is a word to reverse.  It must be a KANA word.
 * The reversed word is also put into `word'.
 */
static void
eb_reverse_word_jis(char *word)
{
    char *p1, *p2;
    int word_length;
    char c;

    LOG(("in: eb_reverse_word_jis(word=%s)", eb_quoted_string(word)));

    word_length = strlen(word);
    if (word_length % 2 == 1) {
	*(word + word_length - 1) = '\0';
	word_length--;
    }
    for (p1 = word, p2 = word + word_length - 2; p1 < p2; p1 += 2, p2 -= 2) {
	c = *p1;
	*p1 = *p2;
	*p2 = c;
	c = *(p1 + 1);
	*(p1 + 1) = *(p2 + 1);
	*(p2 + 1) = c;
    }

    LOG(("out: eb_reverse_word_jis()"));
}

