/* # skkinput (Simple Kana-Kanji Input)
 * skkldic.c --- search/create/update local dictionary.
 * This file is part of skkinput.
 * Copyright (C) 1997
 * Takashi SAKAMOTO (sakamoto@yajima.kuis.kyoto-u.ac.jp)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with skkinput; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <X11/Intrinsic.h>

#include "commondef.h"
#include "buffers.h"
#include "config.h"

/*
 * マクロの定義。
 */
#define SafeFree(ptr)	do{ if( (ptr) != NULL ) free((ptr)) ; } while(0)

/*
 * 内部関数のプロトタイプ宣言。
 */
int check_skkinput_jisyo_code( unsigned char *path ) ;
static int skkinput_copyFile( unsigned char *from, unsigned char *to ) ;

/*
 * 外部関数のプロトタイプ宣言。
 */
extern SkkinpSearchVector *add_SkkinpSearchVector
( SkkinpSearchVector *topp,   struct myChar *str,
  SkkinpSearchVector **rnode, int *rpos ) ;
extern SkkinpSearchVector *searchSudeniHenkanKakuteiShitamono
( struct myChar *henkankey, SkkinpSearchVector *top, int okuri ) ;
extern VectorIndex *makeSearchVectorIndex
( SkkinpSearchVector *top, int *totalnum, int flag ) ;
extern void free_SkkinpSearchVector( SkkinpSearchVector **top ) ;
extern void free_VectorIndex( VectorIndex **top ) ;
/* skkldrec.c */
extern unsigned long recentlyHenkanKakuteiKouhoWoFileNiKaku
( FILE *fp, FILE *motoJisyoFp, int okuri ) ;
extern void clearHenkanKakuteiHash( void ) ;
extern SkkinpSearchVector *searchSudeniPurgeSaretamono
( struct myChar *henkankey, SkkinpSearchVector *top, int okuri ) ;
extern void skkinputNoNaibuNoKouhoWoFileNiKaku
( FILE *fp, int okuri, int purge ) ;
extern void skkinputAutoSaveFileWoYondeFukugenSuru( FILE *fp ) ;
/* parseStr.c */
unsigned char *expand_file_name( unsigned char *path ) ;

/*
 * グローバル変数宣言。
 */
/* skkinput が利用する局所辞書のパス。*/
static unsigned char	*skkinput_local_jisyo_path ;
static unsigned char	*skkinput_autosave_path ;
/* local-jisyo の元のパーミッション。*/
static mode_t		skkinput_local_jisyo_mode ;
/* skkinput が利用する局所辞書のバックアップのパス。*/
static unsigned char	*skkinput_jisyo_backup_path ;
/* skk が利用する局所辞書のパス。*/
static unsigned char	*skk_local_jisyo_path ;
/* skkinput record のパス。*/
static unsigned char	*skkinput_record_path ;
static unsigned char	*skkinput_record_path_tmp ;
/* skkinput が利用する局所辞書の漢字コード。*/
static int		skkinput_local_jisyo_coding_system ;
/* skkinput が利用する局所辞書の backup の漢字コード。*/
static int		skkinput_jisyo_backup_coding_system ;
/* ~/.skk-jisyo が利用する局所辞書の漢字コード。*/
static int		skk_local_jisyo_coding_system ;
/* 辞書が汚れているかどうか…。*/
extern Boolean		skkinput_jisyo_dirty ;
extern Boolean		skkinput_autosave_jisyo_dirty ;

/* skk-local-jisyo を探索するかどうか。*/
extern int		skkinput_search_skk_jisyo ;
/* skkinput-record を作成するかどうか。*/
extern int		skkinput_keep_record ;

extern unsigned long	skkinput_j_count_kakutei ;
extern unsigned long	skkinput_j_count_touroku ;

/*
 * 局所辞書に関する設定を行う関数。
 */
int set_localjisyo
( unsigned char *jisyo_path,  unsigned char *jisyobak_path,
  unsigned char *record_path, unsigned char *master_localjisyo )
{
  struct stat local_jisyo_buf ;

  /* 辞書の正確なパスを調べる。*/
  skkinput_local_jisyo_path  = expand_file_name( jisyo_path ) ;
  skkinput_jisyo_backup_path = expand_file_name( jisyobak_path ) ;
  skkinput_record_path       = expand_file_name( record_path ) ;
  skk_local_jisyo_path       = expand_file_name( master_localjisyo ) ;
  /* record の並びが逆だもんで中間ファイルが必要になる。*/
  skkinput_record_path_tmp   =
    malloc( strlen( skkinput_record_path ) + 2 ) ;
  if( skkinput_record_path_tmp != NULL ){
    strcpy( skkinput_record_path_tmp, skkinput_record_path ) ;
    strcat( skkinput_record_path_tmp, "~" ) ;
  }
  if( ( skkinput_autosave_path = malloc
	( strlen( skkinput_local_jisyo_path ) + 10 ) ) != NULL ){
    strcpy( skkinput_autosave_path, skkinput_local_jisyo_path ) ;
    strcat( skkinput_autosave_path, "-autosave" ) ;
  }
  /* coding system をチェックする。*/
  skkinput_local_jisyo_coding_system =
    check_skkinput_jisyo_code( skkinput_local_jisyo_path ) ;
  skkinput_jisyo_backup_coding_system =
    check_skkinput_jisyo_code( skkinput_jisyo_backup_path ) ;
  skk_local_jisyo_coding_system =
    check_skkinput_jisyo_code( skk_local_jisyo_path ) ;
  
  /* skkinput-local-jisyo のパーミッションを調べる。*/
  if( stat( skkinput_local_jisyo_path, &local_jisyo_buf ) ){
    /* まず、umask を得る。仮の umask は 000 である。*/
    skkinput_local_jisyo_mode = umask( 0 ) ;
    /* 元の umask の戻す。*/
    umask( skkinput_local_jisyo_mode ) ;
    /* umask をかけた残りのモードを求める。*/
    skkinput_local_jisyo_mode = 
      ( S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ) &
      ( ~( skkinput_local_jisyo_mode ) ) ;
  } else {
    /* もとあったパーミッションを継承する。*/
    skkinput_local_jisyo_mode = local_jisyo_buf.st_mode ;
  }
  skkinput_j_count_touroku = 0 ;
  skkinput_j_count_kakutei = 0 ;
  return 0 ;
}

/*
 * 局所辞書のために確保していたメモリを解放する関数。
 */
int close_localjisyo( void )
{
  SafeFree( skkinput_local_jisyo_path ) ;
  SafeFree( skkinput_jisyo_backup_path ) ;
  SafeFree( skkinput_record_path ) ;
  SafeFree( skkinput_record_path_tmp ) ;
  SafeFree( skkinput_autosave_path ) ;
  SafeFree( skk_local_jisyo_path ) ;
  return 0 ;
}

/*
 * "[" を "\0" と交換する関数。
 */
static int replace_kakkoToNul( struct myChar *string )
{
  while( !IS_END_OF_STRING( *string ) ){
    if( IS_ASCII_EQUAL( *string, '[' ) ){
      MYCHAR_SET_END_OF_STRING( *string ) ;
      return True ;
    }
    string ++ ;
  }
  return False ;
}

/*
 * ある文字列が登場するまで、ファイルを読み飛ばす関数。
 */
int skip_file_until( FILE *fp, unsigned char *string, int coding_system )
{
  struct myChar *oneline = NULL ;

  while( !feof( fp ) ){
    oneline = mychar_readOneLine( fp, coding_system ) ;
    if( oneline == NULL )
      return False ;
    if( !myCharCharStrcmp( oneline, string ) ){
      free( oneline ) ;
      return True ;
    }
    free( oneline ) ;
  }
  return False ;
}

static SkkinpSearchVector *j_okurinasi_search_skkinput_jisyo
( FILE *jisyoFp, struct myChar *key,
  SkkinpSearchVector *top, int coding_system )
{
  int status = 0, kflag, klen, found ;
  struct myChar *oneline = NULL ;

  /* 一応辞書の先頭まで戻すこととする。*/
  rewind( jisyoFp ) ;
  /* 送り仮名の開始位置まで飛ぶ。*/
  skip_file_until( jisyoFp, ";; okuri-nasi entries.", coding_system ) ;

  /* 検索キーの長さを求めておく。これは strncmp のために必要。*/
  klen = myCharStrlen( key ) ;
  found = False ;

  /* ファイルの先頭から順に検索していく。*/
  while( !feof( jisyoFp ) ){
    /* 文字コードを見て、それを EUC へと変換する。*/
    if( ( oneline = mychar_readOneLine( jisyoFp, coding_system ) ) == NULL )
      break ;	/* ここは本当は continue ではないのか？ */
    /* コメントの行だった場合には、改行まで読み飛ばす。*/
    if( IS_ASCII_EQUAL( *oneline, ';' ) ||
	myCharStrncmp( oneline, key, klen ) || 
	!IS_ASCII_EQUAL( oneline[ klen ], ' ' ) ){
      /* 送り仮名ありになってしまったら終了する。*/
      if( !myCharCharStrcmp( oneline, ";; okuri-ari entries." ) ){
	/* まず malloc した文字列を解放する。*/
	free( oneline ) ;
	oneline = NULL ;
	/* まだあるかもしれないエントリを探す…。*/
	if( !skip_file_until
	    ( jisyoFp, ";; okuri-nasi entries.", coding_system ) )
	  break ;
      } else {
	/* まず malloc した文字列を解放する。*/
	free( oneline ) ;
	oneline = NULL ;
      }
      status = 0 ;
      kflag = False ;
    } else {
      /* 検索している文字列が見付かった場合の処理。*/
      found = True ;
      break ;
    }
  }
  /* 見付からなかった場合には、NULL を返す。*/
  if( !found || oneline == NULL ){
    return top ;
  }
  /* 見付かった場合の処理…読み込んだ文字数によっては一行を全て読み *
   * 込めてない可能性もある。それに注意。*/
  top = add_SkkinpSearchVector
    ( top, oneline + klen + 1, NULL, NULL ) ;
  free( oneline ) ;
  return top ;
}


/*
 * 送り仮名が無い場合の局所辞書の検索を行う関数。
 */
static SkkinpSearchVector *j_okurinasi_search_skkinput_jisyo_with_path
( unsigned char *jisyo_path, struct myChar *key,
  SkkinpSearchVector *top, int coding_system )
{
  FILE *fp ;
  /* まず辞書が開けなければ、お話にならない。*/
  if( ( fp = fopen( jisyo_path, "rb" ) ) == NULL )
    return top ;
  top = j_okurinasi_search_skkinput_jisyo
    ( fp, key, top, coding_system ) ;
  fclose( fp ) ;
  return top ;
}

/*
 * 送り仮名がある場合の局所辞書の検索を行う関数。
 */
static SkkinpSearchVector *j_okuriari_search_skkinput_jisyo
( FILE *jisyoFp, struct myChar *key,
  SkkinpSearchVector *top, int coding_system )
{
  int status = 0, kflag, klen, found ;
  struct myChar *oneline = NULL ;

  /* 検索キーの長さを求めておく。これは strncmp のために必要。*/
  klen = myCharStrlen( key ) ;
  found = False ;

  rewind( jisyoFp ) ;
  /* 送り仮名の開始位置まで飛ぶ。*/
  skip_file_until( jisyoFp, ";; okuri-ari entries.", coding_system ) ;

  /* ファイルの先頭から順に検索していく。*/
  while( !feof( jisyoFp ) ){
    /* 文字コードを見て、それを EUC へと変換する。*/
    if( ( oneline = mychar_readOneLine
	  ( jisyoFp, coding_system ) ) == NULL )
      break ;	/* ここは本当は continue ではないのか？ */

    /* コメントの行だった場合には、改行まで読み飛ばす。*/
    if( IS_ASCII_EQUAL( *oneline, ';' ) ||
	myCharStrncmp( oneline, key, klen ) ||
	!IS_ASCII_EQUAL( oneline[ klen ], ' ' ) ){
      /* 送り仮名無しになってしまったら終了する。*/
      if( !myCharCharStrcmp( oneline, ";; okuri-nasi entries." ) ){
	free( oneline ) ;
	oneline = NULL ;
	/* まだあるかもしれないエントリを探す…。*/
	if( !skip_file_until
	    ( jisyoFp, ";; okuri-ari entries.", coding_system ) )
	  break ;
      } else {
	free( oneline ) ;
	oneline = NULL ;
      }
      status = 0 ;
      kflag = False ;
    } else {
      /* 検索している文字列が見付かった場合の処理。*/
      found = True ;
      break ;
    }
  }
  /* 見付からなかった場合には、NULL を返す。*/
  if( !found || oneline == NULL ){
    return top ;
  }

  /* 直接変換には関係の無い "/[ば/呼]/" に相当する部分を消去する。*/
  replace_kakkoToNul( oneline ) ;
  top = add_SkkinpSearchVector( top, oneline + klen + 1, NULL, NULL ) ;
  free( oneline ) ;
  return top ;
}

/*
 * 送り仮名がある場合の局所辞書の検索を行う関数。
 */
static SkkinpSearchVector *j_okuriari_search_skkinput_jisyo_with_path
( unsigned char *jisyo_path, struct myChar *key,
  SkkinpSearchVector *top, int coding_system )
{
  FILE *fp ;

  /* まず辞書が開けなければ、お話にならない。*/
  if( ( fp = fopen( jisyo_path, "rb" ) ) == NULL )
    return top ;
  top = j_okuriari_search_skkinput_jisyo
    ( fp, key, top, coding_system ) ;
  fclose( fp ) ;
  return top ;
}

/*
 * 局所辞書の検索を行う関数。
 */
SkkinpSearchVector *j_search_skkinput_local_jisyo
( struct myChar *key, SkkinpSearchVector *top, int okuri )
{
  if( okuri ){
    /* 局所辞書から探しましょう…。*/
    top = j_okuriari_search_skkinput_jisyo_with_path
      ( skkinput_local_jisyo_path, key, top,
	skkinput_local_jisyo_coding_system ) ;
    /* .skk-jisyo を見るのみないの？ */
    if( skkinput_search_skk_jisyo )
      top = j_okuriari_search_skkinput_jisyo_with_path
	( skk_local_jisyo_path, key, top,
	  skk_local_jisyo_coding_system ) ;
  } else {
    /* 局所辞書から探しましょう…。*/
    top = j_okurinasi_search_skkinput_jisyo_with_path
      ( skkinput_local_jisyo_path, key, top,
	skkinput_local_jisyo_coding_system ) ;
    /* .skk-jisyo を見るのみないの？ */
    if( skkinput_search_skk_jisyo )
      top = j_okurinasi_search_skkinput_jisyo_with_path
	( skk_local_jisyo_path, key, top,
	  skk_local_jisyo_coding_system ) ;
  }
  return top ;
}

/*
 * 局所辞書のbackupから検索を行う関数。
 */
SkkinpSearchVector *j_search_skkinput_jisyo_with_fileptr
( struct myChar *key, SkkinpSearchVector *top, int okuri, FILE *fp )
{
  if( okuri ){
    /* 局所辞書から探しましょう…。*/
    return j_okuriari_search_skkinput_jisyo
      ( fp, key, top, skkinput_local_jisyo_coding_system ) ;
  } else {
    /* 局所辞書から探しましょう…。*/
    return j_okurinasi_search_skkinput_jisyo
      ( fp, key, top, skkinput_local_jisyo_coding_system ) ;
  }
}

/*
 * 局所辞書の completion 検索を行う関数。
 */
static SkkinpSearchVector *j_completion_skkinput_local_jisyo_sub
( unsigned char *jisyo_path, struct myChar *key,
  SkkinpSearchVector *top,  int coding_system )
{
  FILE *fp ;
  int status = 0, kflag, klen, found ;
  struct myChar *oneline, *ptr ;
  static struct myChar kouhoNoSeparator[] = {
    { CHARSET_ASCII, '/' }, { CHARSET_ASCII, '\0' },
  } ;

  /* まず辞書が開けなければ、お話にならない。*/
  if( ( fp = fopen( jisyo_path, "rb" ) ) == NULL )
    return top ;

  /* 送り仮名の開始位置まで飛ぶ。*/
  skip_file_until( fp, ";; okuri-nasi entries.", coding_system ) ;
  
  /* 検索キーの長さを求めておく。これは strncmp のために必要。*/
  klen = myCharStrlen( key ) ;
  found = False ;

  /* ファイルの先頭から順に検索していく。*/
  while( !feof( fp ) ){
    if( ( oneline = mychar_readOneLine( fp, coding_system ) ) == NULL )
      break ;
    /* コメントの行だった場合には、改行まで読み飛ばす。*/
    if( IS_ASCII_EQUAL( *oneline, ';' ) || 
	myCharStrncmp( oneline, key, klen ) ){
      /* 送り仮名ありになってしまったら終了する。*/
      if( !myCharCharStrcmp( oneline, ";; okuri-ari entries." ) ){
	free( oneline ) ;
	oneline = NULL ;
	/* まだあるかもしれないエントリを探す…。*/
	if( !skip_file_until
	    ( fp, ";; okuri-nasi entries.", coding_system ) )
	  break ;
      } else {
	free( oneline ) ;
	oneline = NULL ;
      }
      status = 0 ;
      kflag = False ;
    } else {
      /* 検索している文字列が見付かった場合の処理。*/
      if( !found ){
	/* 先頭に '/' を入れておく。*/
	top = add_SkkinpSearchVector
	  ( top, kouhoNoSeparator, NULL, NULL ) ;
	found = True ;
      }
      /* スペースが入っていては不味いので、そこを削ります。*/
      ptr = oneline ;
      while( !IS_ASCII_EQUAL( *ptr, ' ' ) && !IS_END_OF_STRING( *ptr ) )
	ptr ++ ;
      MYCHAR_SET_END_OF_STRING( *ptr ) ;
      /* 登録します。*/
      top = add_SkkinpSearchVector
	( top, oneline, NULL, NULL ) ;
      top = add_SkkinpSearchVector
	( top, kouhoNoSeparator, NULL, NULL ) ;
      free( oneline ) ;
      oneline = NULL ;
    }
  }
  fclose( fp ) ;
  return top ;
}

/*
 * completion search を局所辞書に対してかける関数。
 */
SkkinpSearchVector *j_completion_skkinput_local_jisyo
( SkkinpSearchVector *top, struct myChar *completion_key )
{
  top = j_completion_skkinput_local_jisyo_sub
    ( skkinput_local_jisyo_path, completion_key, top,
      skkinput_local_jisyo_coding_system ) ;
  /* .skk-jisyo を見るのみないの？ */
  if( skkinput_search_skk_jisyo )
    top = j_completion_skkinput_local_jisyo_sub
      ( skk_local_jisyo_path, completion_key, top,
	skk_local_jisyo_coding_system ) ;
  return top ;
}

/*
 * ファイルを眺めて、ESC が入っていれば JIS、8bit 目が立っているコード
 * が入っていたら EUC とか勝手に判断するかなり適当な関数。
 *----
 * skkinput-jisyo に変な漢字コードを入れないように…。
 */
int check_skkinput_jisyo_code( unsigned char *path )
{
  FILE *fp ;
  int ret = KANJI_CODE_UNKNOWN, flag8bit = False ;
  unsigned char buffer[ TEXTBUFSIZE ] ;

  /* 辞書ファイルが存在しない場合には、こちらで勝手にcoding systemを *
   * 選択する。*/
  if( ( fp = fopen( path, "rb" ) ) == NULL ){
    return CODING_SYSTEM_IS_EUC ;
  }
  while( !feof( fp ) ){
    fgets( buffer, TEXTMAXLEN, fp ) ;
    buffer[ TEXTMAXLEN ] = '\0' ;
    ret = check_kanjiCode( buffer, strlen( buffer ) ) ;
    /* もし 8bit 目は立っていたが、判別不可能であった場合。*/
    if( ret == KANJI_CODE_EUC_SJIS_UNKNOWN ){
      flag8bit = True ;
      continue ;
    }
    /* 何のコードか判別可能だった場合。*/
    if( ret != KANJI_CODE_UNKNOWN )
      break ;
  }
  if( ret == KANJI_CODE_EUC_SJIS_UNKNOWN ){
    ret = KANJI_CODE_EUC ;
  } else if( ret == KANJI_CODE_UNKNOWN ){
    ret = ( flag8bit )? KANJI_CODE_EUC : KANJI_CODE_JIS ;
  }
  fclose( fp ) ;
  return ret ;
}

/*
 * VectorIndexList にある候補を片っ端からファイルへと転送する関数。
 */
void writeVectorIndexToFile( FILE *fp, VectorIndex *vNode )
{
  int len ;
  SkkinpSearchVector *sNode ;
  struct myChar *ptr ;

  /* まず最初の "/" を表示する。*/
  fputc( '/', fp ) ;
#ifdef DEBUG
  printf( "%c", 0x2f ) ;
#endif

  /* 以下、候補をたどる。*/
  while( vNode != NULL ){
    sNode = vNode->node ;
    ptr   = sNode->text + vNode->position ;
    len   = vNode->length ;
    while( len > 0 ){
      if( IS_END_OF_STRING( *ptr ) ){
	sNode = sNode->next ;
	/* まさか次が無いとは…不覚じゃった。しゃあない、ループを抜 *
	 * けよか。*/
	if( sNode == NULL ){
#ifdef DEBUG
	  fprintf( stderr, "I lost next skkinput-search-vector!\n" ) ;
#endif
	  fputc( '\n', fp ) ;
	  return ;
	}
	ptr = sNode->text ;
      }
#ifdef DEBUG
      myCharFputEUCc( *ptr, stdout ) ;
#endif
      myCharFputEUCc( *ptr ++, fp ) ;
      len -- ;
    }
#ifdef DEBUG
    printf( "%c", 0x2f ) ;
#endif
    fputc( 0x2f, fp ) ;
    vNode = vNode->next ;
  }
#ifdef DEBUG
  printf( "\n" ) ;
#endif
  fputc( '\n', fp ) ;
  return ;
}

/*
 * 変換の結果を書き出す関数
 *-----
 * int okuri と char *daimoku によって送りありとなしの区別をつけてます。
 */
static unsigned long skkinput_updateHenkanResult
( FILE *fpSrc, FILE *fpDest, int okuri,
  unsigned char *daimoku, int coding_system )
{
  SkkinpSearchVector *top, *purgeTop ;
  VectorIndex *vTop, *vTopPurge ;
  int len ;
  unsigned long count = 0 ;
  struct myChar *search_key = NULL, *ptr ;

  /* まず、お題目を唱える。オンカカカサンマシエイソワカ。*/
  fprintf( fpDest, "%s\n", daimoku ) ;
  fflush( fpDest ) ;
  count ++ ;

  /* ここに既に内部に登録されている変換キーから並べる処理をする。*/
  count += recentlyHenkanKakuteiKouhoWoFileNiKaku
    ( fpDest, fpSrc, okuri ) ;

  /* 参考にするファイルが無いんだったら、以下の処理は無視！ する必要 *
   * はない！ */
  if( fpSrc == NULL )
    return count ;

  /* 一応、ファイルの先頭まで戻しておく。*/
  rewind( fpSrc ) ;

  /* 送り仮名の開始位置まで飛ぶ。*/
  skip_file_until( fpSrc, daimoku, coding_system ) ;

  /* ファイルの終了までスキャンする。*/
  while( !feof( fpSrc ) ){
    /* 文字コードを見て、それを EUC へと変換する。*/
    if( ( search_key = mychar_readOneLine( fpSrc, coding_system ) ) == NULL )
      break ;

    /* コメントの行だった場合には、改行まで読み飛ばす。*/
    /* また、長過ぎる変換キーもすっとばす。そんなの使えないもん！ */
    if( IS_ASCII_EQUAL( search_key[ 0 ], ';' ) ||
	IS_END_OF_STRING( search_key[ 0 ] ) ||
	IS_ASCII_EQUAL( search_key[ 0 ], '\n' ) ||
	IS_ASCII_EQUAL( search_key[ 0 ], '\r' ) ||
	IS_ASCII_EQUAL( search_key[ 0 ], ' ' ) ){
      free( search_key ) ;
      search_key = NULL ;
      /* まだあるかもしれないエントリを探す…。*/
      if( !skip_file_until( fpSrc, daimoku, coding_system ) )
	break ;
      /* 何らかのエントリが見付かった場合には…次回に託す…。少なく *
       * とも今回の ";" なんてボケボケを表示してはいけない。*/
      continue ;
    }
    for( ptr = search_key ; !IS_END_OF_STRING( *ptr ) ; ptr ++ ){
      if( IS_ASCII_EQUAL( *ptr, ' ' ) ){
	MYCHAR_SET_END_OF_STRING( *ptr ) ;
	ptr ++ ;
	break ;
      }
    }
    if( IS_END_OF_STRING( *ptr ) ){
      free( search_key ) ;
      search_key = ptr = NULL ;
      continue ;
    }
    top = NULL ;
    /* 既に変換確定したものたちの中に存在するかどうかチェックする。*/
    top = searchSudeniHenkanKakuteiShitamono
      ( search_key, top, okuri ) ;
    if( top == NULL ){
      purgeTop = NULL ;
      /* パージされるべきものであるかどうかの判断も必要である。*/
      purgeTop = searchSudeniPurgeSaretamono
	( search_key, purgeTop, okuri ) ;
      if( purgeTop != NULL ){
	/* どうやら、purge されたものがあるらしい。*/
	top = add_SkkinpSearchVector( top, ptr, NULL, NULL ) ;
	/* 一旦、パージ側をハッシュテーブルに叩き込む。*/
	vTopPurge = makeSearchVectorIndex( purgeTop, &len, False ) ;
	/* それから、ハッシュテーブルを見ながら単語を切り出す。*/
	/* 切り出した後にハッシュがクリアされては困るので、False であ  */
	/* る。どれか一つでもヒットしなければ、パージリストを検索する  */
	/* 意味は全く無い。*/
	vTop = makeSearchVectorIndex( top, &len, True ) ;
	/* 一つでも変換結果が存在するのなら…？ */
	if( vTop != NULL && len > 0 ){
	  /* 今回見付かった変換キーを書き出す。*/
	  myCharFputstring( fpDest, search_key ) ;
	  fputc( ' ', fpDest ) ;
	  /* VectorIndex をファイルに掃き出す…この人は最後に'\n' を入 *
	   * れてくれる。*/
	  writeVectorIndexToFile( fpDest, vTop ) ;
	  count ++ ;
	}
	/* 確保したメモリの解放。*/
	free_VectorIndex( &vTop ) ;
	free_VectorIndex( &vTopPurge ) ;
	free_SkkinpSearchVector( &top ) ;
	free_SkkinpSearchVector( &purgeTop ) ;
      } else {
	/* 今回見付かった変換キーを書き出す。*/
	myCharFputstring( fpDest, search_key ) ;
	fputc( ' ', fpDest ) ;
	/* 既に変換確定したものの中に入っていないということは、そのま *
	 * ま書き出しても大丈夫…おめでとう、おめでとう。*/
	myCharFputstring( fpDest, ptr ) ;
	fputc( '\n', fpDest ) ;
	fflush( fpDest ) ;
	count ++ ;
      }
    } else {
      /* 既に変換確定していたものは打ち出されている。だから捨てる。*/
      free_SkkinpSearchVector( &top ) ;
    }
    free( search_key ) ;
    search_key = ptr = NULL ;
  }
  return count ;
}

/*
 * ファイルの存在の有無を調べる関数。
 */
static int isFileExist( unsigned char *path )
{
  FILE *fp ;
  /* ファイルを読み込みモードで開いてみる。*/
  if( ( fp = fopen( path, "rb" ) ) == NULL )
    return False ;
  /* 開けたのなら存在する。ファイルを閉じて return True! */ 
  fclose( fp ) ;
  return True ;
}

static void skkinput_updateRecord( unsigned long lines )
{
  FILE *fpd, *fps ;
  time_t tval ;
  struct tm *lt ;
  char buffer[ TRANSBUFSIZE ] ;
  int exist_record_p ;
  static char *skk_week_alist[] = {
    "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat",
  } ;
  static char *skk_month_alist[] = {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
    "Oct", "Nov", "Dec",  
  } ;

  /* 1970/1/1からの経過時刻を得る。*/
  if( time( &tval ) == -1 )
    return ;

  /* 一回も確定したことがなかったら捨てる。*/
  if( skkinput_j_count_kakutei <= 0 )
    return ;
  /* エラーが発生したら駄目でしょう。*/
  exist_record_p = isFileExist( skkinput_record_path ) ;
  if( exist_record_p ){
    /* ファイルをコピーする。link でも良いけどねえ…。*/
    if( !skkinput_copyFile
       ( skkinput_record_path, skkinput_record_path_tmp ) ){
      return ;
    }
  }
  /* ファイルが開けなかったら、帰る。*/
  if( ( fpd = fopen( skkinput_record_path, "w" ) ) == NULL )
    return ;
  /* 西暦何年の何月何日何曜日であるかを得る。*/
#if 0
  (void)strftime
    ( buffer, TEXTBUFSIZE, "%a %b %d %T %Y", localtime( &tval ) ) ;
  fprintf
    ( fpd, "%s  登録: %3ld  確定: %4ld  確定率: %3ld%%  語数: %6ld\n",
     buffer, skkinput_j_count_touroku, skkinput_j_count_kakutei,
     ( skkinput_j_count_kakutei - skkinput_j_count_touroku ) * 100 /
     skkinput_j_count_kakutei, lines ) ;
#else
  lt = localtime( &tval ) ;
  fprintf( fpd, "%s %s %2d %02d:%02d:%02d %d",
	   skk_week_alist [ lt->tm_wday ],
	   skk_month_alist[ lt->tm_mon  ],
	   lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec,
	   lt->tm_year + 1900 ) ;
  fprintf
    ( fpd, "  登録: %3ld  確定: %4ld  確定率: %3ld%%  語数: %6ld\n",
      skkinput_j_count_touroku, skkinput_j_count_kakutei,
      ( skkinput_j_count_kakutei - skkinput_j_count_touroku ) * 100 /
      skkinput_j_count_kakutei, lines ) ;
#endif

  if( exist_record_p ){
    size_t num ;

    if( ( fps = fopen( skkinput_record_path_tmp, "r" ) ) == NULL ){
      fclose( fpd ) ;
      return ;
    }
    /* 残りはコピーする。*/
    while( !feof( fps ) ){
      num = fread( buffer, sizeof( char ), TEXTBUFSIZE, fps ) ;
      /* ここでファイルシステムフルのチェックってあって良いと思うのだ */
      /* けど？ */
      if( fwrite( buffer, sizeof( char ), num, fpd ) < num ){
	/* 何とエラー発生。これは危険です。*/
	fclose( fps ) ;
	fclose( fpd ) ;
	/* 今回書こうとしたファイルは止め。削除。*/
	unlink( skkinput_record_path ) ;
	/* もとに戻す。ここではエラーチェックしない。だってここで駄目 */
	/* だったらどうするの？ */
	/* 若葉「それじゃ、たたりさん。どうしましょうか？」*/
	/* 1. 若葉を看板娘にする。*/
	/* 2. 街の人に試食してもらう。*/
	/* 3. 俺は知らんぞ。*/
	link( skkinput_record_path_tmp, skkinput_record_path ) ;
	return ;
      }
    }
    fclose( fps ) ;
    /* 一時ファイルを消去する。*/
    unlink( skkinput_record_path_tmp ) ;
  }
  fclose( fpd ) ;
  skkinput_j_count_kakutei = 0 ;
  skkinput_j_count_touroku  = 0 ;
  return ;
}

/*
 * ファイルをコピーする関数…。
 */
static int skkinput_copyFile( unsigned char *from, unsigned char *to )
{
  FILE *fpTo, *fpFrom ;
  int rnum ;
  unsigned char buffer[ TRANSBUFSIZE ] ;

  /* コピー先が開けなかったら絶対に不味い。*/
  if( ( fpTo = fopen( to, "wb" ) ) == NULL )
    return False ;
  /* コピー元が開けなかっても失敗。*/
  if( ( fpFrom = fopen( from, "rb" ) ) == NULL ){
    fclose( fpTo ) ;
    return False ;
  }
  rnum = 0 ;
  while( !feof( fpFrom ) ){
    rnum =
      fread( buffer, sizeof( unsigned char ), TRANSBUFSIZE, fpFrom ) ;
    /* 読み込めた量だけ書き込む…。*/
    if( fwrite( buffer, sizeof( unsigned char ), rnum, fpTo ) < rnum ){
      /* 書き込む量が何故だか少なくなっている…これは異常事態発生！ */
      /* 普通はファイルシステムフルを疑うんだけどね。*/
      fclose( fpFrom ) ;
      fclose( fpTo ) ;
      /* というわけで、コピーを中止して作りかけのファイルを消去する。*/
      unlink( to ) ;
      /* 失敗、と。*/
      return False ;
    }
  }
  fclose( fpTo ) ;
  fclose( fpFrom ) ;
  return True ;
}

/*
 * 辞書に今迄に変換確定してきた候補を書き込む関数。
 */
void skkinput_updateLocalJisyo( void )
{
  FILE *fpSrc, *fpDest ;
  unsigned long lines ;
  char tempfilename[64] ; /* "/tmp" + 16 + 1? */
  int tmpfd;

#ifdef DEBUG_LDIC
  fprintf( stderr, "Now saving ....\"%s\"\n", skkinput_local_jisyo_path ) ;
#endif
  /*
   * 「うーむ。どうやって実装しようか…謎だ…。」
   * 「そう人一人の持てる真実などその程度のものでしかないのだ」
   * 「だからこそより素晴しいアルゴリズムを求める」
   * 「だけど、みんなはボクのアルゴリズムを嫌いじゃないのかな？」
   * 「あんた、バカァ？」
   */
  /*
   * じゃなくて…、
   * 辞書の中から確定したものだけ拾っているのなら出来ることは一つだけ。
   * 辞書を A->B へと COPY しながら、その中で付け加えるべきものがあったら
   * そこだけ更新する。
   * A を backup にして、backup から B 新しいファイルにする。
   * これで OK のはずではあるけど。
   * 「…君は purge という言葉を忘れているな、冬月。」
   */
  /* バックアップ辞書を消去する。うーん。ちょっと可哀そうかな？ */

  /* もとから BACKUP JISYO が存在しない場合、そして、LOCAL-JISYO が *
   * 存在しない場合はどうするのか？ */

  /* 辞書の更新を行う際に利用されるテンポラリファイル。*/
  strcpy(tempfilename, "/tmp/skkinputXXXXXX");
  tmpfd = mkstemp(tempfilename);
  if (tmpfd < 0) {
      perror("skkinput: cannot create temp file");
      return;
  }

  /* 局所辞書のバックアップを削除する。失敗した場合なんかは知らない。*/
  unlink( skkinput_jisyo_backup_path ) ;

  /* 一旦テンポラリファイルへとコピーする。
   * コピーに成功しようが失敗しようが、必ず tempfile は作られることに
   * 注意すること。だから、この後の fopen が失敗することはない。*/ 
  skkinput_copyFile
    ( skkinput_local_jisyo_path, tempfilename ) ;
  /* 局所辞書のコピーが存在しない。これは不味い状態である。*/
  if( ( fpSrc = fdopen( tmpfd, "rb" ) ) == NULL ){
    close(tmpfd);
#ifdef DEBUG_LDIC
    fprintf( stderr, "cannot create backup jisyo...error!\n" ) ;
#endif
    return ;
  }
  /* 書き込めない…これは無理だ…。*/
  if( ( fpDest = fopen( skkinput_local_jisyo_path, "wb" ) ) == NULL ){
#ifdef DEBUG_LDIC
    fprintf( stderr, "cannot create local jisyo...error!\n" ) ;
#endif
    fclose( fpSrc ) ;
    /* backup を元の名前にリネームする。失敗するかもしれないけど…そ *
     * の場合には無視するしかないかな？ */
    skkinput_copyFile
      ( tempfilename, skkinput_local_jisyo_path ) ;
    /* permission を元に戻す。*/
    chmod( skkinput_local_jisyo_path, skkinput_local_jisyo_mode ) ;
    /* file coding system を調べ直す。*/
    skkinput_local_jisyo_coding_system = 
      check_skkinput_jisyo_code( skkinput_local_jisyo_path ) ;
    unlink( tempfilename ) ;
    return ;
  }
  lines = 0 ;
  /* 送り仮名変換の結果を書き込む。*/
  lines += skkinput_updateHenkanResult
    ( fpSrc, fpDest, True, ";; okuri-ari entries.",
      skkinput_jisyo_backup_coding_system ) ;
  /* 送り仮名無し変換の結果を書き込む。*/
  lines += skkinput_updateHenkanResult
    ( fpSrc, fpDest, False, ";; okuri-nasi entries.",
      skkinput_jisyo_backup_coding_system ) ;
  /* ファイルポインタを閉じる。*/
  fclose( fpSrc ) ;
  fclose( fpDest ) ;
  /* permission の設定。*/
  chmod( skkinput_local_jisyo_path, skkinput_local_jisyo_mode ) ;
  /* backup 辞書にコピーする。*/
  skkinput_copyFile
    ( tempfilename, skkinput_jisyo_backup_path ) ;
  /* これでバックアップの coding system は skkinput-local-jisyo の * 
   * coding system と同じになった。*/
  skkinput_jisyo_backup_coding_system =
    skkinput_local_jisyo_coding_system ;
  /* permission の設定。*/
  chmod( skkinput_jisyo_backup_path, skkinput_local_jisyo_mode ) ;
  /* 一時ファイルを消去する。*/
  unlink( tempfilename ) ;
  /* ハッシュをクリアし、辞書をチェックし直す。*/
  clearHenkanKakuteiHash() ;
  skkinput_local_jisyo_coding_system =
    check_skkinput_jisyo_code( skkinput_local_jisyo_path ) ;
  /* skkinput-keep-record ならば…。*/
  if( skkinput_keep_record ){
    /* record file を更新する。*/
    skkinput_updateRecord( lines ) ;
  }
  /* セーブされたのだから、ファイルと skkinput の内部の辞書は同一のも
   * のとなったのだから、辞書の更新(変更)フラグは False になる。*/
  skkinput_jisyo_dirty          = False ;
  skkinput_autosave_jisyo_dirty = False ;
  /* オートセーブファイルがあった場合には削除する。本当にセーブされた
   * のだから、中間ファイルはもう不要です。*/
  if( skkinput_autosave_path != NULL ){
    unlink( skkinput_autosave_path ) ;
  }
#ifdef DEBUG_LDIC
  fprintf( stderr, ".......Done\n" ) ;
#endif
  return ;
}

/*
 * 辞書に今迄に変換確定してきた候補を書き込む関数。
 */
void skkinput_autosaveLocalJisyo( void )
{
  FILE *fp ;
  if( skkinput_autosave_path == NULL )
    return ;
  /* 書けなかったり作れなかったら知らない。*/
  if( ( fp = fopen( skkinput_autosave_path, "wb" ) ) == NULL )
    return ;
  /* ざくっと書き込む。*/
  skkinputNoNaibuNoKouhoWoFileNiKaku( fp, False, False ) ;
  skkinputNoNaibuNoKouhoWoFileNiKaku( fp, True,  False ) ;
  /* こっちがパージの情報。*/
  skkinputNoNaibuNoKouhoWoFileNiKaku( fp, False, True  ) ;
  /* ファイルを閉じて終了。*/
  fclose( fp ) ;
  return ;
}

/*
 * 起動時に一回だけ参照される関数…。オートセーブの結果を読んで復元す
 * る機能を持つ。バグってない可能性は無いが…どうしたものやら。
 */
void skkinput_readAutoSavedLocalJisyo( void )
{
  FILE *fp ;
  if( skkinput_autosave_path == NULL )
    return ;
  /*
   * 開けなかったら知らない…
   * ティナ「たたりさん、卑怯です！ そのファイルの名前分かってるの
   *         に…」
   * たたり「いや、その…名前は分かってても実体がないこともあるし…」
   */
  if( ( fp = fopen( skkinput_autosave_path, "rb" ) ) == NULL )
    return ;
  skkinputAutoSaveFileWoYondeFukugenSuru( fp ) ;
  fclose( fp ) ;
  /* オートセーブされていたファイルを読んだばかりなのだから、こっちは
   * 綺麗な筈である。*/
  skkinput_autosave_jisyo_dirty = False ;
  return ;
}
