/* # skkinput (Simple Kana-Kanji Input)
 * skkldrec.c --- 
 * 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 <string.h>
#include <X11/Intrinsic.h>

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

/*
 * 定数定義。
 */
#define HENKANKAKUTEIHASHSIZE	(128)
#define	HASHKEY_ASCIIETC	(0)
#define HASHKEY_ALPHABET	(1)
#define HASHKEY_ALPHABETSIZE	('z'-'a'+1)
#define HASHKEY_KANJI		(HASHKEY_ALPHABET+HASHKEY_ALPHABETSIZE)
#define HASHKEY_KANJISIZE	(HENKANKAKUTEIHASHSIZE-HASHKEY_KANJI)

/*
 * 必要なのは、過去から未来へと最も最近利用された「変換された文字列」を記憶
 * するデータ構造と、過去に変換されたかどうかを効率的に知るデータ構造である。
 * 前者はリスト構造を用いて、先頭に近い程最近であるという形にすれば良い。
 * 後者はハッシュ＋二分木にする必要があろう…？ しかし、ハッシュ＋二分木で
 * は枝の数が足りない…(;_;) これは新しいデータ構造を作れということか。
 */
typedef struct _tmp_listUndHash {
  struct _tmp_listUndHash *left ;	/* 左の子供へ。*/
  struct _tmp_listUndHash *right ;	/* 右の子供へ。*/
  struct _tmp_listUndHash *past ;	/* 過去へ。*/
  struct _tmp_listUndHash *future ;	/* 過去へ。*/
  SkkinpSearchVector *henkankey ;	/* 記憶バッファへのポインタ。*/
  int position, length ;		/* 位置、長さ。*/
  VectorIndex *henkanresults ;		/* 変換結果を格納。*/
} SkkinpHenkanKakuteiKouho ;

/*
 * プロトタイプ宣言。
 */
/* skksvect.c */
SkkinpSearchVector *makeNewSkkinpSearchVectorNode( void ) ;
extern int copyCandidate
( struct myChar *dest, SkkinpSearchVector *sNode,
  int position, int length ) ;
VectorIndex *alloc_NewVectorIndexNode( void ) ;
int cmpCandidate
( SkkinpSearchVector *sNode1, int pos1, int len1,
  SkkinpSearchVector *sNode2, int pos2, int len2 ) ;
extern SkkinpSearchVector *add_SkkinpSearchVector
( SkkinpSearchVector *topp,   struct myChar *str,
  SkkinpSearchVector **rnode, int *pos ) ;
VectorIndex *makeSearchVectorIndex
( SkkinpSearchVector *top, int *totalnum, int flag ) ;
void free_SkkinpSearchVector( SkkinpSearchVector **top ) ;
void free_VectorIndex( VectorIndex **top ) ;
/* skkldic.c */
void writeVectorIndexToFile( FILE *fp, VectorIndex *vNode ) ;
SkkinpSearchVector *j_search_skkinput_jisyo_with_fileptr
( struct myChar *key, SkkinpSearchVector *top, int okuri, FILE *fp ) ;
/* skkldrec.c */
static int searchHNodeWithHenkanKeyAndResult
( SkkinpHenkanKakuteiKouho **hTop,
  struct myChar *henkankey, struct myChar *hresult,
  SkkinpHenkanKakuteiKouho **rhpNode,
  SkkinpHenkanKakuteiKouho **rhNode,
  VectorIndex **rvNode ) ;
static int searchHNodeWithHenkanKeyAndResultWithVI
( SkkinpHenkanKakuteiKouho **hTop,
  struct myChar *henkankey, VectorIndex *keyVNode,
  SkkinpHenkanKakuteiKouho **rhpNode,
  SkkinpHenkanKakuteiKouho **rhNode,
  VectorIndex **rvNode ) ;
static void delete_NodeInBinaryTree
( SkkinpHenkanKakuteiKouho **top,
  SkkinpHenkanKakuteiKouho **rTop,
  SkkinpHenkanKakuteiKouho *parentNode,
  SkkinpHenkanKakuteiKouho *deleteNode ) ;
static void skkinput_KakoToMiraiWoIrekaeru
( SkkinpHenkanKakuteiKouho **top ) ;

#ifdef DEBUG
static void debug_dump_kouhotree
( SkkinpHenkanKakuteiKouho *top ) ;
#endif

/*
 * グローバル変数宣言。
 */
/* 変換確定されて来た文字列を記憶するために利用されるバッファリストの先 *
 * 頭。*/
static SkkinpSearchVector *henkanKakuteiBufferListTop ;

/* 最も最近変換確定された文字列を記憶する線型リスト。リストの先頭の方が *
 * 最近であり、後ろへ行く程過去になる。送り仮名有りの場合には、ここには *
 * つながらない。*/
static SkkinpHenkanKakuteiKouho *recentlyKakuteiKouho ;

/* 過去に変換確定された文字列を検索するために利用されるハッシュテーブル *
 * (送り仮名無し用)
 */
static SkkinpHenkanKakuteiKouho *henkanKakuteiHash[ HENKANKAKUTEIHASHSIZE ] ;

/* 過去に変換確定された文字列を検索するために利用されるハッシュテーブル *
 * (送り仮名有り用)
 */
static SkkinpHenkanKakuteiKouho
*okuriHenkanKakuteiHash[ HENKANKAKUTEIHASHSIZE ] ;
/*
 * 過去に変換確定された候補を覚えているリスト(送り仮名あり)
 */
static SkkinpHenkanKakuteiKouho *recentlyOkuriariKakuteiKouho ;

/*
 * 辞書の更新フラグ。
 */
extern Boolean skkinput_jisyo_dirty ;
extern Boolean skkinput_autosave_jisyo_dirty ;

/*
 * purge された候補を覚えるためのリスト。
 *----
 * 送り仮名変換とそうでないのとのために二つ必要である。
 */
static SkkinpHenkanKakuteiKouho *purgeSaretaKouho[ HENKANKAKUTEIHASHSIZE ] ;
static SkkinpHenkanKakuteiKouho *purgeSaretaOkuriKouho[ HENKANKAKUTEIHASHSIZE ] ;
static SkkinpHenkanKakuteiKouho *recentlyPurgeKouho ;
static SkkinpHenkanKakuteiKouho *recentlyOkuriariPurgeKouho ;

/*
 * バッファ。
 */
static struct myChar hashTempBuffer[ TEXTBUFSIZE ] ;

/*
 * ハッシュテーブルの初期化をする関数。起動時に必ず呼ぶこと。
 */
void initHenkanKakuteiHashTable( void )
{
  int i ;
  /* ハッシュテーブル自体の初期化。*/
  for( i = 0 ; i < HENKANKAKUTEIHASHSIZE ; i ++ ){
    henkanKakuteiHash[ i ]      = NULL ;
    okuriHenkanKakuteiHash[ i ] = NULL ;
    purgeSaretaKouho[ i ]       = NULL ;
    purgeSaretaOkuriKouho[ i ]  = NULL ;
  }
  /* その他のリストの初期化。*/
  recentlyKakuteiKouho         = NULL ;
  recentlyOkuriariKakuteiKouho = NULL ;
  recentlyPurgeKouho           = NULL ;
  recentlyOkuriariPurgeKouho   = NULL ;
  henkanKakuteiBufferListTop   = NULL ;
  return ;
}

/*
 * ハッシュ関数。
 */
static int henkanKakuteiHashFunction( struct myChar *henkankey )
{
  unsigned int ret ;
  int len = myCharStrlen( henkankey ) ;

  /* 変換キーの長さが二文字未満であるか、最初の文字が漢字でないなら。*/
  switch( henkankey->charset ){
  case CHARSET_ISO8859_1 :
  case CHARSET_ISO8859_2 :
  case CHARSET_ISO8859_3 :
  case CHARSET_ISO8859_4 :
  case CHARSET_ISO8859_5 :
  case CHARSET_ISO8859_6 :
  case CHARSET_ISO8859_7 :
  case CHARSET_ISO8859_8 :
  case CHARSET_ISO8859_9 :
  case CHARSET_JISX0201_1976 :
    /* 漢字でなくてアルファベット？ */
    if( 'a' <= henkankey->chara && henkankey->chara <= 'z' )
      return HASHKEY_ALPHABET -'a' + henkankey->chara ;
    if( 'A' <= henkankey->chara && henkankey->chara <= 'Z' )
      return HASHKEY_ALPHABET -'A' + henkankey->chara ;
    return HASHKEY_ASCIIETC ;
  case CHARSET_JISX0208_1978 :
  case CHARSET_JISX0208_1983 :
  case CHARSET_JISX0212_1990 :
  case CHARSET_GB2312_1980 :
  case CHARSET_KSC5601_1987 :
    if( len < 2 ){
      ret = ( henkankey->chara & 0x00FF ) + len * 21 + HASHKEY_KANJI ;
    } else {
      ret = ( henkankey->chara & 0x00FF ) + len * 17 ;
      henkankey ++ ;
      ret = ( ret + ( henkankey->chara & 0x00FF ) ) % HENKANKAKUTEIHASHSIZE ;
    }
    return ret % HENKANKAKUTEIHASHSIZE ;
  default :
    return HASHKEY_ASCIIETC ;
  }
}

static SkkinpHenkanKakuteiKouho *allocate_NewHenkanKakuteiKouhoSub
( void )
{
  SkkinpHenkanKakuteiKouho *hNode;

  /* malloc しておく。*/
  hNode = ( SkkinpHenkanKakuteiKouho *)
    malloc( sizeof( SkkinpHenkanKakuteiKouho ) ) ;
  if( hNode == NULL ){
    fprintf( stderr, "Memory fault.\n" ) ;
    exit( 1 ) ;
  }
  hNode->left = hNode->right = hNode->past = hNode->future = NULL ;
  hNode->henkanresults = NULL ;
  return hNode ;
}

/*
 * 変換キーを登録して、変換キーを覚えるリストのノードを確保する関数。
 */
static SkkinpHenkanKakuteiKouho *allocate_NewHenkanKakuteiKouho
( struct myChar *henkankey )
{
  SkkinpHenkanKakuteiKouho *hNode ;
  /* まず、malloc する。*/
  hNode = allocate_NewHenkanKakuteiKouhoSub() ;
  /* 新しくこの変換キーを登録する。*/
  henkanKakuteiBufferListTop = add_SkkinpSearchVector
    ( henkanKakuteiBufferListTop, henkankey,
      &hNode->henkankey, &hNode->position ) ;
  hNode->length    = myCharStrlen( henkankey ) ;
  /* 答えを返す。*/
  return hNode ;
}

/*
 * 文字列と SkkinpSearchVector 内の文字列を比較する関数。
 * ----
 * 注意！ int が 24 bit 以下だと動きません。
 */
static int cmpStringAndCandidate
( struct myChar *string, SkkinpSearchVector *sNode, int pos, int len )
{
  int ret ;
  struct myChar *ptr = sNode->text + pos ;
  int lchara1, lchara2 ;

  /* 取り敢えず長さの分かっている sNode->text 回だけ回る。*/
  while( len > 0 ){
    if( IS_END_OF_STRING( *ptr ) ){
      sNode = sNode->next ;
      /* この場合、string は続くけど、先に sNode->text の方が尽きた *
       * ので、string の方が大きい。*/
      if( sNode == NULL )
	return ( 1 ) ;
      ptr = sNode->text ;
    }
    if( IS_END_OF_STRING( *string ) ){
      /* この場合、sNode->text の方がまだ残っているので、string の *
       * 方が小さい。*/
      return ( -1 ) ;
    }
    /* 二数の差を取る。*/
    if( IS_ASCII_CHARA( *string ) ){
      lchara1 = ( CHARSET_ASCII << 16 ) | string->chara ;
    } else {
      lchara1 = ( string->charset << 16 ) | string->chara ;
    }
    if( IS_ASCII_CHARA( *string ) ){
      lchara2 = ( CHARSET_ASCII << 16 ) | ptr->chara ;
    } else {
      lchara2 = ( ptr->charset << 16 ) | ptr->chara ;
    }
    if( ( ret = lchara1 - lchara2 ) )
      return ret ;
    /* 次の候補を捜す。*/
    string ++ ;
    ptr ++ ;
    len -- ;
  }
  if( !IS_END_OF_STRING( *string ) )
    return 1 ;
  return 0 ;
}

/*
 * 変換キーはかつて変換に使われた？を調べる関数。
 */
static SkkinpHenkanKakuteiKouho *sudeniHenkanKakuteiShitaNo
( struct myChar *henkankey, SkkinpHenkanKakuteiKouho **hTable )
{
  int hashkey ;
  int ret ;
  SkkinpHenkanKakuteiKouho *hNode ;

  /* どちらのハッシュ関数を利用したら良いの？ */
  hashkey = henkanKakuteiHashFunction( henkankey ) ;

  /* ハッシュの中にないなら、絶対に無い。*/
  if( ( hNode = hTable[ hashkey ] ) == NULL )
    return NULL ;

  /* 二分探索を行う。*/
  while( hNode != NULL ){
    ret = cmpStringAndCandidate
      ( henkankey, hNode->henkankey, hNode->position, hNode->length ) ;
    if( ret == 0 ){
      /* おめでとう見付かりました…パンパカパーン。*/
      return hNode ;
    } else if( ret > 0 ){
      /* 見付かりませんでした…じゃあ、次に行ってみましょう！ */
      hNode = hNode->right ;
    } else {
      hNode = hNode->left ;
    }
  }
  /* 最後まで見付かりませんでした…。*/
  return NULL ;
}

/*
 * 内部に登録してあった文字列を skkserv からの返事のように見せてしまう
 * 関数。そんなことをするのは、サーチとか作り直すのが面倒だから…。
 * ちょっと、効率は落ちるかな？ 
 */
static SkkinpSearchVector *makeSkkSearchVectorFromVectorIndex
( SkkinpSearchVector *top, VectorIndex *vNode )
{
  int len, rest ;
  struct myChar *ptr ;

  /* 局所変数の初期化。*/
  ptr = hashTempBuffer ;
  MYCHAR_SET_CHARA( *ptr, '/' ) ;
  ptr ++ ;
  MYCHAR_SET_END_OF_STRING( *ptr ) ;
  len     = 1 ;

  /* メインループ。VectorIndex の続く限りまわる。*/
  while( vNode != NULL ){
    if( vNode->length > TEXTMAXLEN ){
      /* はっきり言ってこんな長い変換結果はとても利用できないので無 *
       * 視する。*/
      vNode = vNode->next ;
      continue ;
    }
    /* 未使用領域を見る。*/
    rest = TEXTMAXLEN - len - vNode->length ;
    if( rest == 0 ){
      /* 候補を抜き出す。*/
      copyCandidate( ptr, vNode->node, vNode->position, vNode->length ) ;
      /* バッファがいっぱいになったので、取り敢えず登録する。*/
      top = add_SkkinpSearchVector( top, hashTempBuffer, NULL, NULL ) ;
      /* 先頭に戻す。*/
      ptr  = hashTempBuffer ;
      /* 候補の間の区切り文字を入れる。*/
      MYCHAR_SET_CHARA( *ptr, '/' ) ;
      ptr ++ ;
      /* きちんと終端する。*/
      MYCHAR_SET_END_OF_STRING( *ptr ) ;
      /* 一文字だけ入っている状態。*/
      len     = 1 ;
    } else if( rest > 0 ){
      /* 候補を抜き出す。*/
      copyCandidate( ptr, vNode->node, vNode->position, vNode->length ) ;
      /* 候補の分の長さだけ進める。*/
      ptr += vNode->length ;
      len += vNode->length ;
      /* 候補の間の区切り文字を入れる。*/
      MYCHAR_SET_CHARA( *ptr, '/' ) ;
      ptr ++ ;
      /* きちんと終端する。*/
      MYCHAR_SET_END_OF_STRING( *ptr ) ;
      /* "/" を入れたから、一文字増えたことを覚える。*/
      len  ++ ;
    } else {
      /* バッファがいっぱいになったので、取り敢えず登録する。*/
      top = add_SkkinpSearchVector( top, hashTempBuffer, NULL, NULL ) ;
      /* 前に "/" を入れてあるから、今回は入れずに先頭に戻すだけで良い。*/
      ptr  = hashTempBuffer ;
      MYCHAR_SET_END_OF_STRING( *ptr ) ;
      len  = 0 ;
      /* 今回は特に vNode を進めたわけじゃないから…*/
      continue ;
    }
    /* 次の候補へと進む。*/
    vNode = vNode->next ;
  }
  if( len > 0 ){
    top = add_SkkinpSearchVector( top, hashTempBuffer, NULL, NULL ) ;
  }
  return top ;
}

/*
 * 既に変換確定したものの中から検索する関数。
 */
SkkinpSearchVector *searchSudeniHenkanKakuteiShitamono
( struct myChar *henkankey, SkkinpSearchVector *top, int okuri )
{
  SkkinpHenkanKakuteiKouho *hNode, **hTable ;

  /* 送り仮名変換？ */
  hTable = ( okuri )? okuriHenkanKakuteiHash : henkanKakuteiHash ;

  /* 既に変換確定したものにあるのかどうかを調べる…。*/
  hNode = sudeniHenkanKakuteiShitaNo( henkankey, hTable )  ;
  if( hNode == NULL )
    return top ;

  top = makeSkkSearchVectorFromVectorIndex( top, hNode->henkanresults ) ;
  return top ;
}

/*
 * 既に変換確定したものの中から検索する関数。
 */
SkkinpSearchVector *searchSudeniPurgeSaretamono
( struct myChar *henkankey, SkkinpSearchVector *top, int okuri )
{
  SkkinpHenkanKakuteiKouho *hNode, **hTable ;

  /* 送り仮名変換？ */
  hTable = ( okuri )? purgeSaretaOkuriKouho : purgeSaretaKouho ;

  /* 既に変換確定したものにあるのかどうかを調べる…。*/
  hNode = sudeniHenkanKakuteiShitaNo( henkankey, hTable ) ;
  if( hNode == NULL )
    return top ;

  top = makeSkkSearchVectorFromVectorIndex( top, hNode->henkanresults ) ;
  return top ;
}
			       
/*
 * 変換確定した結果を SearchVector の形にして入れる関数。
 */
static SkkinpSearchVector *add_CandidateToSkkinpSearchVector
( SkkinpSearchVector *top, VectorIndex *vNode )
{
  SkkinpSearchVector *node, *sNode ;
  int len = vNode->length ;
  struct myChar *dest, *src ;

  /* 果たして先頭があるの？ */
  if( top == NULL ){
    node = top = makeNewSkkinpSearchVectorNode() ;
    node->next = NULL ;
  } else {
    node = top ;
    while( node->next != NULL )
      node = node->next ;
  }

  /* copy 前の初期化。*/
  sNode = vNode->node ;
  src   = sNode->text + vNode->position ;
  dest  = node->text + node->used ;

  /* 新しい開始位置を登録する。*/
  vNode->position = node->used ;
  vNode->node     = node ;

  while( len > 0 ){
    if( node->used >= TEXTMAXLEN ){
      /* きちんと終端しておく。*/
      MYCHAR_SET_END_OF_STRING( node->text[ TEXTMAXLEN ] ) ;
      /* 新しくノードを確保する。*/
      node->next = makeNewSkkinpSearchVectorNode() ;
      node = node->next ;
      node->next = NULL ;
      node->used = 0 ;
      dest = node->text ;
    }
    if( IS_END_OF_STRING( *src ) ){
      /* む？ 文字列をたどりきらない内にもうバッファが切れてしまって *
       * いる？ */
      if( sNode->next == NULL ){
	/* これ以上 copy できないので、仕方ないからここまでの分だけ  *
	 * 残して返す。*/
	MYCHAR_SET_END_OF_STRING( *dest ) ;
	/* 長さを修正しておく。入れられなかった分だけ抜く。*/
	vNode->length = vNode->length - len ;
	return top ;
      }
    }
    *dest ++ = *src ++ ;
    node->used ++ ;
    len -- ;
  }
  /* 終了。*/
  MYCHAR_SET_END_OF_STRING( *dest ) ;
  return top ;
}

/*
 * j-completion に利用するために最も最近変換に使われた文字列から最も過
 * 去に変換に使われた文字列まで覚えておかないといけない…。それを覚え
 * るために作ったリストに、今回の変換キーを入れる関数。
 */
static void miraiKaraKakoHetoHenkanSaretaKouhoWoOberu
( SkkinpHenkanKakuteiKouho **top, SkkinpHenkanKakuteiKouho *hNode )
{
  /* 既に一番最近に行われた変換っていうふうに登録されていたのなら、*
   * 覚える必要もない。*/
  if( *top == hNode )
    return ;
  /* これは現在にも過去にも未来にも…いや未来は違ったけど、覚えたこ *
   * とのない候補じゃ！ */
  if( hNode->past == NULL && hNode->future == NULL ){
    /* この変換より一つ過去に行われた変換はリストの先頭にある。*/
    hNode->past = *top ;
    /* 既にリストに何か入っていたのなら、その先頭の一つ未来で行われ *
     * た変換っていうのが今回の変換である。*/
    if( *top != NULL )
      ( *top )->future = hNode ;
    /* 先頭を移動させる。*/
    *top = hNode ;
  } else {
    /* そうじゃなかったら、最近って思わないといけないから、先頭へと *
     * 移動させる。*/
    if( hNode->past != NULL )
      ( hNode->past )->future = hNode->future ;
    if( hNode->future != NULL )
      ( hNode->future )->past = hNode->past ;
    hNode->past   = *top ;
    hNode->future = NULL ;
    /* まあ、NULL ってことだけは無いと思うけどね。*/
    if( *top != NULL )
      ( *top )->future = hNode ;
    /* 先頭を移動させる。*/
    *top = hNode ;
  }
  return ;
}

/*
 * 二分木を文字列 henkankey で探索し、該当する候補がなければ新しく節点
 * をつくる関数。
 *--------
 * 引数の hNode は二分木の根っこ。
 */
static SkkinpHenkanKakuteiKouho *henkanKouhoWoSagashiteNakattaraMalloc
( SkkinpHenkanKakuteiKouho *hNode, struct myChar *henkankey,
  SkkinpHenkanKakuteiKouho *stringProvideNode )
{
  int ret ;

  while( 1 ){
    /* 同じ変換キーですか？ */
    ret = cmpStringAndCandidate
      ( henkankey, hNode->henkankey, hNode->position, hNode->length ) ;
    if( ret == 0 ){
      /* 同じ変換キーです。*/
      break ;
    } else if( ret > 0 ){
      /* 右の子供をたどる場合。*/
      if( hNode->right != NULL ){
	/* 右の子供がまだ存在した場合。*/
	hNode = hNode->right ;
      } else {
	/* 右にはこれ以上子供が存在しない場合。*/
	/* 葉っぱに到達したということは、この変換キーは使われたこ * 
	 * とがなかったということ。*/
	if( stringProvideNode == NULL ){
	  hNode->right = allocate_NewHenkanKakuteiKouho
	    ( henkankey ) ;
	  hNode = hNode->right ;
	} else {
	  hNode->right = allocate_NewHenkanKakuteiKouhoSub() ;
	  hNode = hNode->right ;
	  /* 文字列の方は既に確保されているので、それを共有する。*/
	  hNode->henkankey = stringProvideNode->henkankey ;
	  hNode->position  = stringProvideNode->position ;
	  hNode->length    = stringProvideNode->length ;
	}
	hNode->henkanresults = NULL ;
	break ;
      }
    } else {
      /* 左の子供をたどる場合。*/
      if( hNode->left != NULL ){
	/* 左の子供がまだ存在した場合。*/
	hNode = hNode->left ;
      } else {
	/* 左にはこれ以上子供が存在しない場合。*/
	/* 葉っぱに到達したということは、この変換キーは使われたこ * 
	 * とがなかったということ。*/
	if( stringProvideNode == NULL ){
	  hNode->left = allocate_NewHenkanKakuteiKouho
	    ( henkankey ) ;
	  hNode = hNode->left ;
	} else {
	  hNode->left = allocate_NewHenkanKakuteiKouhoSub() ;
	  hNode = hNode->left ;
	  /* 文字列の方は既に確保されているので、それを共有する。*/
	  hNode->henkankey = stringProvideNode->henkankey ;
	  hNode->position  = stringProvideNode->position ;
	  hNode->length    = stringProvideNode->length ;
	}
	hNode->henkanresults = NULL ;
	break ;
      }
    }
  }
  return hNode ;
}

/*
 * 指定された変換結果が以前に存在していたかどうかを調べる関数。
 */
static VectorIndex *katsuteNoHenkanKekkaWoSenkeiTansaku
( VectorIndex *maeNoHenkanKekka, VectorIndex *konkaiNoHenkanKekka )
{
  /* 過去に変換された結果がある間はループ…線型探索なんだな。*/
  while( maeNoHenkanKekka != NULL ){
    /* 既に登録されていましたか？ */
    if( !cmpCandidate
	( maeNoHenkanKekka->node, maeNoHenkanKekka->position,
	  maeNoHenkanKekka->length,
	  konkaiNoHenkanKekka->node, konkaiNoHenkanKekka->position,
	  konkaiNoHenkanKekka->length ) ){
      return maeNoHenkanKekka ;
    }
    /* 次の変換結果を見る。*/
    maeNoHenkanKekka = maeNoHenkanKekka->next ;
  }
  return NULL ;
}

/*
 * VectorIndex で作られたリストの中の1節点をリストの先頭へと移動させる
 * 関数。
 */
static void shiteisaretaVectorIndexWoListNoSentouNiMottekuru
	( VectorIndex **vTop, VectorIndex *vNode )
{
  /* 先頭ならこれ以上持って来ようにも出来ない。*/
  if( *vTop == vNode )
    return ;
  /* 先頭じゃなかったら…まず、双方向リストの中から外す。*/
  if( vNode->prev != NULL )
    ( vNode->prev )->next = vNode->next ;
  if( vNode->next != NULL )
    ( vNode->next )->prev = vNode->prev ;
  /* このノードの次のノードは先頭のノードである。*/
  vNode->next = *vTop ;
  /* このノードより前のノードは存在しない。*/
  vNode->prev = NULL ;
  /* 以前に先頭のノードが存在していたら、そのノードの前のノード *
   * はこのノードである。*/
  if( *vTop != NULL )
    ( *vTop )->prev = vNode ;
  /* 先頭ノードはこのノードである。*/
  *vTop = vNode ;
  /* 終了。*/
  return ;
}

/*
 * 既に変換確定に使われたキーとその変換結果情報の中に新しい変換キーと
 * その変換結果を加える関数。
 */
void add_SudeniHenkanKakuteiShitamono
( struct myChar *henkankey, VectorIndex **vectorindextop,
  VectorIndex *hresult, int okuri )
{
  int ret, kanzenSairiyou = False ;
  SkkinpHenkanKakuteiKouho *hNode,  **hTop, **rhTop ;
  SkkinpHenkanKakuteiKouho *phNode, **pTop, *phpNode, **rpTop ;
  VectorIndex *vNode1, *vNode2, *pvNode ;

  /* 内部に持っている辞書に登録するから、True */
  skkinput_jisyo_dirty          = True ;
  /* オートセーブも行うように指示する。*/
  skkinput_autosave_jisyo_dirty = True ;

  vNode1 = hresult ;
  ret = henkanKakuteiHashFunction( henkankey ) ;

#ifdef DEBUG
  fprintf( stderr, "Touroku : \"%s\"\n", henkankey ) ;
  fflush( stderr ) ;
#endif
  /* 送り仮名変換かどうかを確認する。*/
  if( okuri ){
    /* 送り仮名変換だった場合の処理。*/
    hTop  = &okuriHenkanKakuteiHash[ ret ] ;
    pTop  = &purgeSaretaOkuriKouho[ ret ] ;
    rhTop = &recentlyOkuriariKakuteiKouho ;
    rpTop = &recentlyOkuriariPurgeKouho ;
  } else {
    /* 送り仮名変換でなかった場合の処理。*/
    hTop  = &henkanKakuteiHash[ ret ] ;
    pTop  = &purgeSaretaKouho[ ret ] ;
    rhTop = &recentlyKakuteiKouho ;
    rpTop = &recentlyPurgeKouho ;
  }

  /* Purge リストに既にこの変換確定結果は入ってしまっているのじゃな *
   * いのかな？ すると、それを抹消しないと危険ですよ？ */
  /* 以前変換したものの中に存在する？ */
  if( searchHNodeWithHenkanKeyAndResultWithVI
      ( pTop, henkankey, vNode1, &phpNode, &phNode, &pvNode ) ){
    if( pvNode == phNode->henkanresults ){
      /* 全ての変換結果が削除されたことになるの？ */
      if( ( phNode->henkanresults = pvNode->next ) == NULL ){
	/* 二分木から節点を削除する。free はしない。*/
	delete_NodeInBinaryTree
	  ( pTop, rpTop, phpNode, phNode ) ;
	kanzenSairiyou = True ;
      } else {
	( pvNode->next )->prev = NULL ;
	kanzenSairiyou = False ;
      }
    } else {
      /* 双方向リストからパージ結果を削除する。*/
      if( pvNode->next != NULL )
	( pvNode->next )->prev = pvNode->prev ;
      if( pvNode->prev != NULL )
	( pvNode->prev )->next = pvNode->next ;
      kanzenSairiyou = False ;
    }
    /* こっちでは、vNode1 があるので、vNode を再利用する意味はない… *
     * と言いたいところだけど、vNode1 の持っている文字列情報ははっき *
     * り言っておいしいから、それのために解放できない。*/
  } else {
    phNode = NULL ;
    pvNode  = NULL ;
  }

  /* ハッシュがヒットしたかどうかを見る。*/
  if( *hTop == NULL ){
    /* ハッシュの中に今回のような変換は登録されておりません。*/
#ifdef DEBUG
    fprintf( stderr, "Hast Table is NULL.\n" ) ;
    fflush( stderr ) ;
#endif
    /* 新しくノードを確保する。*/
    if( phNode != NULL ){
      /* hNode 毎完全に再利用可能かな？ */
      if( kanzenSairiyou ){
	hNode = phNode ;
	hNode->left = hNode->right =
	  hNode->future = hNode->past = NULL ;
      } else {
	/* そうじゃなかったら、指しているストリングだけ共有する。*/
	hNode = allocate_NewHenkanKakuteiKouhoSub() ;
	hNode->henkankey = phNode->henkankey ;
	hNode->position  = phNode->position ;
	hNode->length    = phNode->length ;
      }
    } else {
      /* というわけで、新しくノードを確保します。*/
      hNode = allocate_NewHenkanKakuteiKouho( henkankey ) ;
    }
    /* ハッシュテーブルも更新します。*/
    *hTop = hNode ;
    hNode->henkanresults = NULL ;
  } else {
    /* 以下、二分探索で同じ変換キーが使われたことがあるかどうかを調 *
     * べます。*/
    hNode = henkanKouhoWoSagashiteNakattaraMalloc
      ( *hTop, henkankey, phNode ) ;
    /* こちらでは高々文字列の共有しかできませんので、phNode自体は解 *
     * 放されます。*/
    if( phNode != NULL && kanzenSairiyou )
      free( phNode ) ;
  }
  /* 確定されたものを時間順に入れているリストに加える。*/
  miraiKaraKakoHetoHenkanSaretaKouhoWoOberu( rhTop, hNode ) ;

  /* 変換キーに対して過去に行われた変換の結果を見る。*/
  vNode2 = katsuteNoHenkanKekkaWoSenkeiTansaku
    ( hNode->henkanresults, vNode1 ) ;
  if( vNode2 != NULL ){
    /* もう登録されてるんだったら、それを一番先頭に持って来る。*/
    shiteisaretaVectorIndexWoListNoSentouNiMottekuru
      ( &hNode->henkanresults, vNode2 ) ;
    /* パージリストの方で見付かった情報は不要だから抹消する。*/
    if( pvNode != NULL )
      free( pvNode ) ;
    return ;
  }
  /* 先頭だったら、それを指しているポインタもずらす。*/
  if( vNode1->prev == NULL ){
    *vectorindextop = vNode1->next ;
  } else {
    ( vNode1->prev )->next = vNode1->next ;
  }
  if( vNode1->next != NULL ){
    ( vNode1->next )->prev = vNode1->prev ;
  }
  if( vNode1 == *vectorindextop ){
    *vectorindextop = vNode1->next ;
  }
  /* 登録されていなかったんだったら、この vNode1 が解放されない内に *
   * vNode1 だけ奪って…SkkinpSearchVector の方は自前のものに移動さ *
   * せる。*/
  if( pvNode != NULL ){
    vNode1->node     = pvNode->node ;
    vNode1->length   = pvNode->length ;
    vNode1->position = pvNode->position ;
    /* 文字列情報を渡したので不要になった…。*/
    free( pvNode ) ;
  } else {
    henkanKakuteiBufferListTop = 
      add_CandidateToSkkinpSearchVector
      ( henkanKakuteiBufferListTop, vNode1 ) ;
  }
  /* vNode1 をリストの先頭へと持ってくる。*/
  shiteisaretaVectorIndexWoListNoSentouNiMottekuru
    ( &hNode->henkanresults, vNode1 ) ;
  return ;
}

/*
 * 既に変換確定に使われたキーとその変換結果情報の中に新しい変換キーと
 * その変換結果を加える関数。
 */
void add_SudeniHenkanKakuteiShitamonoByString
( struct myChar *henkankey, struct myChar *hresult, int okuri )
{
  int ret, kanzenSairiyou = False ;
  SkkinpHenkanKakuteiKouho *hNode,  **hTop, **rhTop ;
  SkkinpHenkanKakuteiKouho *phNode, **pTop, *phpNode, **rpTop ;
  VectorIndex *vNode, *pvNode ;

  /* 内部に持っている辞書に登録するから、True */
  skkinput_jisyo_dirty          = True ;
  skkinput_autosave_jisyo_dirty = True ;

  ret = henkanKakuteiHashFunction( henkankey ) ;

  /* 送り仮名変換なの？ それとも違うの？ */
  if( okuri ){
    hTop  = &okuriHenkanKakuteiHash[ ret ] ;
    pTop  = &purgeSaretaOkuriKouho[ ret ] ;
    rhTop = &recentlyOkuriariKakuteiKouho ;
    rpTop = &recentlyOkuriariPurgeKouho ;
  } else {
    /* 送り仮名変換じゃなかった場合の処理。*/
    hTop  = &henkanKakuteiHash[ ret ] ;
    pTop  = &purgeSaretaKouho[ ret ] ;
    rhTop = &recentlyKakuteiKouho ;
    rpTop = &recentlyPurgeKouho ;
  }

  /* Purge リストに既にこの変換確定結果は入ってしまっているのじゃな *
   * いのかな？ すると、それを抹消しないと危険ですよ？ */
  /* 以前変換したものの中に存在する？ */
  if( searchHNodeWithHenkanKeyAndResult
      ( pTop, henkankey, hresult, &phpNode, &phNode, &pvNode ) ){
    if( pvNode == phNode->henkanresults ){
      /* 全ての変換結果が削除されたことになるの？ */
      if( ( phNode->henkanresults = pvNode->next ) == NULL ){
	/* 二分木から節点を削除する。free はしない。*/
	delete_NodeInBinaryTree( pTop, rpTop, phpNode, phNode ) ;
	kanzenSairiyou = True ;
      } else {
	( pvNode->next )->prev = NULL ;
	kanzenSairiyou = False ;
      }
    } else {
      /* 双方向リストからパージ結果を削除する。*/
      if( pvNode->next != NULL )
	( pvNode->next )->prev = pvNode->prev ;
      if( pvNode->prev != NULL )
	( pvNode->prev )->next = pvNode->next ;
      kanzenSairiyou = False ;
    }
    /* ここで、pvNode は利用価値があるかもしれないので解放せずに確 *
     * 保しておく。*/
  } else {
    phNode = NULL ;
    pvNode  = NULL ;
    kanzenSairiyou = False ;
  }
#ifdef DEBUG
  fprintf( stderr, "Touroku : \"%s\"\n", henkankey ) ;
  fflush( stderr ) ;
#endif
  /* ノードが存在するか否か。*/
  if( *hTop == NULL ){
    /* ノードが存在しなかった場合の処理。*/
#ifdef DEBUG
    fprintf( stderr, "Hast Table is NULL.\n" ) ;
    fflush( stderr ) ;
#endif
    /* 新しくノードを確保する。*/
    if( phNode != NULL ){
      /* hNode 毎完全に再利用可能かな？ */
      if( kanzenSairiyou ){
	hNode = phNode ;
	hNode->left = hNode->right =
	  hNode->future = hNode->past = NULL ;
      } else {
	/* そうじゃなかったら、指しているストリングだけ共有する。*/
	hNode = allocate_NewHenkanKakuteiKouhoSub() ;
	hNode->henkankey = phNode->henkankey ;
	hNode->position  = phNode->position ;
	hNode->length    = phNode->length ;
      }
    } else {
      /* 全く共有できない…。*/
      hNode = allocate_NewHenkanKakuteiKouho( henkankey ) ;
    }
    *hTop = hNode ;
    hNode->henkanresults = NULL ;
  } else {
    /* 二分探索を用いて、今回と同じ変換が存在するかどうかを調べる。*/
    hNode = henkanKouhoWoSagashiteNakattaraMalloc
      ( *hTop, henkankey, phNode ) ;
    if( phNode != NULL && kanzenSairiyou )
      free( phNode ) ;
  }
  /* 変換キーが変換された結果を見る。*/
  vNode = hNode->henkanresults ;
  /* 変換された結果というものが存在するの？ するなら、ループ。*/
  while( vNode != NULL ){
    /* 既に登録されていましたか？ 線型探索…(^^;;; */
    if( !cmpStringAndCandidate
	( hresult, vNode->node, vNode->position, vNode->length ) ){
      /* もう登録されてるんだったら、それを一番先頭に持って来る。*/
      shiteisaretaVectorIndexWoListNoSentouNiMottekuru
	( &hNode->henkanresults, vNode ) ;
      /* free していないので、ここで free する。*/
      if( pvNode != NULL )
	free( pvNode ) ;
      /* 終了。*/
      return ;
    }
    /* 次の変換結果を見る。*/
    vNode = vNode->next ;
  }
  /* 確定されたものを時間順に入れているリストに加える。*/
  miraiKaraKakoHetoHenkanSaretaKouhoWoOberu( rhTop, hNode ) ;

  /* 何処にも今回のような変換はなかった…その場合の処理。*/
  if( pvNode != NULL ){
    vNode = pvNode ;
  } else {
    vNode = alloc_NewVectorIndexNode() ;
    /* 変換結果を覚える。*/
    henkanKakuteiBufferListTop = 
      add_SkkinpSearchVector
      ( henkanKakuteiBufferListTop, hresult,
	&vNode->node, &vNode->position ) ;
    vNode->length   = myCharStrlen( hresult ) ;
  }
  /* hNode->henkanresults == vNode であるということは絶対にない。*/
  vNode->next     = hNode->henkanresults ;
  vNode->prev     = NULL ;
  if( hNode->henkanresults != NULL )
    ( hNode->henkanresults )->prev = vNode ;
  hNode->henkanresults = vNode ;
  return ;
}

/*
 * この関数は既に変換確定したもののリストの中から該当する候補を削除し、
 * Purge リストにつなぐ操作をする関数。
 *------
 * 登録するときはこの逆で、purgeリストから抹消し、変換確定した候補に加
 * えるということをする。ただ、skkinputsearchvector の消去ができるのが、
 * save した時だけだから…ちょっと面倒かもしれない。
 */
void purge_SudeniHenkanKakuteiShitamono( struct SKKInputNode *node )
{
  int ret, kanzenSairiyou ;
  SkkinpHenkanKakuteiKouho *hNode, *hpNode, **hTop, **rhTop ;
  SkkinpHenkanKakuteiKouho *hNodePurge, **pTop, **rpTop ;
  VectorIndex *vNodeTarget, *vNode, *vNodePurge ;
  struct myChar *henkankey ;

  /* パージされても辞書に変更があったことになる。*/
  skkinput_jisyo_dirty          = True ;
  skkinput_autosave_jisyo_dirty = True ;

  vNodeTarget = node->j_current_henkan_vector_index ;
  henkankey   = ( node->j_okurigana_mode )?
    node->j_henkan_key : node->j_search_key ;

#ifdef DEBUG
  printf( "purge_SudeniHenkanKakuteiShitamono: \"" ) ;
  myCharFputstring( stdout, henkankey ) ;
  printf( "\"\n" ) ;
  debug_dump_kouhotree( recentlyPurgeKouho ) ;
#endif

  ret = henkanKakuteiHashFunction( henkankey ) ;

  /* 送り仮名変換なの？ それとも違うの？ */
  if( node->j_okurigana_mode ){
    hTop  = &okuriHenkanKakuteiHash[ ret ] ;
    pTop  = &purgeSaretaOkuriKouho[ ret ] ;
    rhTop = &recentlyOkuriariKakuteiKouho ;
    rpTop = &recentlyOkuriariPurgeKouho ; ;
  } else {
    /* 送り仮名変換じゃなかった場合の処理。*/
    hTop  = &henkanKakuteiHash[ ret ] ;
    pTop  = &purgeSaretaKouho[ ret ] ;
    rhTop = &recentlyKakuteiKouho ;
    rpTop = &recentlyPurgeKouho ;
  }
  /* 以前変換したものの中に存在する？ */
  if( searchHNodeWithHenkanKeyAndResultWithVI
      ( hTop, henkankey, vNodeTarget, &hpNode, &hNode, &vNode ) ){
    if( vNode == hNode->henkanresults ){
      /* 全ての変換結果が削除されたことになるの？ */
      if( ( hNode->henkanresults = vNode->next ) == NULL ){
	delete_NodeInBinaryTree
	  ( hTop, rhTop, hpNode, hNode ) ;
	kanzenSairiyou = True ;
      } else {
	( vNode->next )->prev = NULL ;
	kanzenSairiyou = False ;
      }
#ifdef DEBUG
      printf( "Hit....? \"" ) ;
      myCharFputstrning( stdout, vNode->node->text + vNode->position, vNode->length ) ;
      printf( "\"\n" ) ;
#endif
    } else {
      if( vNode->next != NULL )
	( vNode->next )->prev = vNode->prev ;
      if( vNode->prev != NULL )
	( vNode->prev )->next = vNode->next ;
      kanzenSairiyou = False ;
#ifdef DEBUG
      printf( "Hit....? \"" ) ;
      myCharFputstrning( stdout, vNode->node->text + vNode->position, vNode->length ) ;
      printf( "\"\n" ) ;
#endif
    }
  } else {
#ifdef DEBUG
    printf( "hoge?\n" ) ;
#endif
    vNode = NULL ;
    hNode = NULL ;
    kanzenSairiyou = False ;
  }
  /* ハッシュがヒットしたかどうかを見る。*/
  if( *pTop == NULL ){
    /* ハッシュの中に今回のような変換は登録されておりません。*/
    if( hNode != NULL ){
      if( kanzenSairiyou ){
	hNodePurge = hNode ;
	hNode->left = hNode->right =
	  hNode->future = hNode->past = NULL ;
      } else {
	hNodePurge = allocate_NewHenkanKakuteiKouhoSub() ;
	/* 文字列情報だけ抜き出す。*/
	hNodePurge->henkankey = hNode->henkankey ;
	hNodePurge->position  = hNode->position ;
	hNodePurge->length    = hNode->length ;
      }
    } else {
      /* というわけで、新しくノードを確保します。*/
      hNodePurge = allocate_NewHenkanKakuteiKouho( henkankey ) ;
    }
    /* ハッシュテーブルも更新します。*/
    *pTop = hNodePurge ;
    hNodePurge->henkanresults = NULL ;
  } else {
    /* 以下、二分探索で同じ変換キーが使われたことがあるかどうかを調 *
     * べます。*/
    hNodePurge = henkanKouhoWoSagashiteNakattaraMalloc
      ( *pTop, henkankey, hNode ) ;
    if( hNode != NULL && kanzenSairiyou )
      free( hNode ) ;
  }
  /* 確定されたものを時間順に入れているリストに加える。*/
  miraiKaraKakoHetoHenkanSaretaKouhoWoOberu( rpTop, hNodePurge ) ;

  /* 変換キーが変換された結果を見る。*/
  vNodePurge = katsuteNoHenkanKekkaWoSenkeiTansaku
    ( hNodePurge->henkanresults, vNodeTarget ) ;
  /* 既に登録されていましたか？ */
  if( vNodePurge != NULL ){
    /* もう登録されてるんだったら、終了。*/
    if( vNode != NULL )
      free( vNode ) ;
    return ;
  }
  /* 何処にも今回のような変換はなかった…その場合の処理。*/
  vNodePurge = vNodeTarget ;
  /* 先頭だったら、それを指しているポインタもずらす。*/
  if( vNodePurge->prev == NULL ){
    node->j_henkan_vector_index_top = vNodePurge->next ;
  } else {
    ( vNodePurge->prev )->next = vNodePurge->next ;
  }
  if( vNodePurge->next != NULL ){
    ( vNodePurge->next )->prev = vNodePurge->prev ;
  }
  node->j_current_henkan_vector_index = node->j_henkan_vector_index_top ;

  if( vNode != NULL ){
    vNodePurge->node     = vNode->node ;
    vNodePurge->length   = vNode->length ;
    vNodePurge->position = vNode->position ;
    /* 文字列情報を渡したので不要になった…。*/
    free( vNode ) ;
  } else {
    /* 変換結果を覚える。*/
    henkanKakuteiBufferListTop = 
      add_CandidateToSkkinpSearchVector
      ( henkanKakuteiBufferListTop, vNodePurge ) ;
  }
  if( hNodePurge->henkanresults != NULL )
    ( hNodePurge->henkanresults )->prev = vNodePurge ;
  vNodePurge->next = hNodePurge->henkanresults ;
  vNodePurge->prev = NULL ;
  hNodePurge->henkanresults = vNodePurge ;
#ifdef DEBUG
  printf( "Hit....? \"" ) ;
  myCharFputstrning( stdout, vNodePurge->node->text + vNodePurge->position, vNodePurge->length ) ;
  printf( "\"\n" ) ;
  printf( "purge_SudeniHenkanKakuteiShitamono last: \n" ) ;
  debug_dump_kouhotree( recentlyPurgeKouho ) ;
#endif
  return ;
}

/*
 * この関数は既に変換確定したもののリストの中から該当する候補を削除し、
 * Purge リストにつなぐ操作をする関数。
 *------
 * 登録するときはこの逆で、purgeリストから抹消し、変換確定した候補に加
 * えるということをする。ただ、skkinputsearchvector の消去ができるのが、
 * save した時だけだから…ちょっと面倒かもしれない。
 */
void purge_SudeniHenkanKakuteiShitamonoByString
( struct myChar *henkankey, struct myChar *hresult, int okuri )
{
  int ret, kanzenSairiyou = False ;
  SkkinpHenkanKakuteiKouho *hNode, *hpNode, **hTop, **rhTop ;
  SkkinpHenkanKakuteiKouho *hNodePurge, **pTop, **rpTop ;
  VectorIndex *vNode, *vNodePurge ;

  /* パージされても辞書に変更があったことになる。*/
  skkinput_jisyo_dirty          = True ;
  skkinput_autosave_jisyo_dirty = True ;

  ret = henkanKakuteiHashFunction( henkankey ) ;
  /* 送り仮名変換なの？ それとも違うの？ */
  if( okuri ){
    hTop  = &okuriHenkanKakuteiHash[ ret ] ;
    pTop  = &purgeSaretaOkuriKouho[ ret ] ;
    rhTop = &recentlyOkuriariKakuteiKouho ;
    rpTop = &recentlyOkuriariPurgeKouho ;
  } else {
    /* 送り仮名変換じゃなかった場合の処理。*/
    hTop  = &henkanKakuteiHash[ ret ] ;
    pTop  = &purgeSaretaKouho[ ret ] ;
    rhTop = &recentlyKakuteiKouho ;
    rpTop = &recentlyPurgeKouho ;
  }
  /* 以前変換したものの中に存在する？ */
  if( searchHNodeWithHenkanKeyAndResult
      ( hTop, henkankey, hresult, &hpNode, &hNode, &vNode ) ){
    if( vNode == hNode->henkanresults ){
      /* 全ての変換結果が削除されたことになるの？ */
      if( ( hNode->henkanresults = vNode->next ) == NULL ){
	delete_NodeInBinaryTree
	  ( hTop, rhTop, hpNode, hNode ) ;
	kanzenSairiyou = True ;
      } else {
	( vNode->next )->prev = NULL ;
	kanzenSairiyou = False ;
      }
    } else {
      if( vNode->next != NULL )
	( vNode->next )->prev = vNode->prev ;
      if( vNode->prev != NULL )
	( vNode->prev )->next = vNode->next ;
      kanzenSairiyou = False ;
    }
  } else {
    vNode = NULL ;
    hNode = NULL ;
  }
  /* ハッシュがヒットしたかどうかを見る。*/
  if( *pTop == NULL ){
    /* ハッシュの中に今回のような変換は登録されておりません。*/
    if( hNode != NULL ){
      if( kanzenSairiyou ){
	hNodePurge = hNode ;
	hNodePurge->left = hNodePurge->right = hNodePurge->future =
	  hNode->past = NULL ;
      } else {
	hNodePurge = allocate_NewHenkanKakuteiKouhoSub() ;
	/* 文字列情報だけ抜き出す。*/
	hNodePurge->henkankey = hNode->henkankey ;
	hNodePurge->position  = hNode->position ;
	hNodePurge->length    = hNode->length ;
      }
    } else {
      /* というわけで、新しくノードを確保します。*/
      hNodePurge = allocate_NewHenkanKakuteiKouho( henkankey ) ;
    }
    /* ハッシュテーブルも更新します。*/
    *pTop = hNodePurge ;
    hNodePurge->henkanresults = NULL ;
  } else {
    /* 以下、二分探索で同じ変換キーが使われたことがあるかどうかを調 *
     * べます。*/
    hNodePurge = henkanKouhoWoSagashiteNakattaraMalloc
      ( *pTop, henkankey, hNode ) ;
    if( hNode != NULL && kanzenSairiyou )
      free( hNode ) ;
  }
  /* 確定されたものを時間順に入れているリストに加える。*/
  miraiKaraKakoHetoHenkanSaretaKouhoWoOberu( rpTop, hNodePurge ) ;

  /* 変換キーが変換された結果を見る。*/
  vNodePurge = hNodePurge->henkanresults ;
  /* 変換された結果というものが存在するの？ するなら、ループ。*/
  while( vNodePurge != NULL ){
    /* 既に登録されていましたか？ 線型探索…(^^;;; */
    if( !cmpStringAndCandidate
	( hresult, vNodePurge->node,
	  vNodePurge->position, vNodePurge->length ) ){
      /* もう登録されてるんだったら、終了。*/
      if( vNode != NULL )
	free( vNode ) ;
      return ;
    }
    /* 次の変換結果を見る。*/
    vNodePurge = vNodePurge->next ;
  }
  /* 何処にも今回のような変換はなかった…その場合の処理。*/
  if( vNode != NULL ){
    vNodePurge = vNode ;
  } else {
    /* 何処にも今回のような変換はなかった…その場合の処理。*/
    vNodePurge = alloc_NewVectorIndexNode() ;
    /* 変換結果を覚える。*/
    henkanKakuteiBufferListTop = 
      add_SkkinpSearchVector( henkanKakuteiBufferListTop, hresult,
			      &vNodePurge->node, &vNodePurge->position ) ;
    vNodePurge->length   = myCharStrlen( hresult ) ;
  }
  if( hNodePurge->henkanresults != NULL )
    ( hNodePurge->henkanresults )->prev = vNodePurge ;
  vNodePurge->next = hNodePurge->henkanresults ;
  vNodePurge->prev = NULL ;
  hNodePurge->henkanresults = vNodePurge ;
  return ;
}

/*
 * 最も最近変換に使われたキーから順にファイルに書き出す関数。
 */
unsigned long recentlyHenkanKakuteiKouhoWoFileNiKaku
( FILE *fp, FILE *motoJisyoFp, int okuri )
{
  SkkinpHenkanKakuteiKouho *hNode ;
  SkkinpSearchVector *top, *purgeTop ;
  VectorIndex *vTop, *vTopPurge ;
  int len ;
  unsigned long lines = 0 ;
  struct myChar search_key[ TRANSBUFSIZE ] ;

  /* 送り仮名の有無によって見るべきリストが異なる。*/
  if( okuri ){
    hNode = recentlyOkuriariKakuteiKouho ;
  } else {
    hNode = recentlyKakuteiKouho ;
  }
  /* 何も変換確定されたことがなかったら、そのまま帰る。*/
  if( hNode == NULL )
    return lines ;
  /* 局所変数の初期化。*/
  vTop = vTopPurge = NULL ;
  /* あるだけ順にファイルへ持っていく。*/
  while( hNode != NULL ){
    top = NULL ;
    /* まず過去に変換確定したものから取り出して、その変換結果を見る。*/
    top = makeSkkSearchVectorFromVectorIndex
      ( top, hNode->henkanresults ) ;
    /* 変換キーを抜き出す。*/
    copyCandidate
      ( search_key,
	hNode->henkankey, hNode->position, hNode->length ) ;
    /* 変換キーが辞書の中にあるかどうかを調べる。
     * ここでもとの辞書のファイルポインタは飛んでしまうことに注意。*/
    top = j_search_skkinput_jisyo_with_fileptr
      ( search_key, top, okuri, motoJisyoFp ) ;
    /* さて…と。*/
    if( top != NULL ){
      /* 候補が存在したみたいだから、パージ側の検索でもしましょうか。*/
      purgeTop = NULL ;
      purgeTop = searchSudeniPurgeSaretamono
	( search_key, purgeTop, okuri ) ;
      /* パージ側の候補をあらかじめハッシュにでも叩き込みましょう。*/
      if( purgeTop != NULL ){
	vTopPurge = makeSearchVectorIndex( purgeTop, &len, False ) ;
      }
      /* これらに対して VectorIndex を作る。*/
      if( ( vTop = makeSearchVectorIndex( top, &len, True ) ) != NULL ){
#ifdef DEBUG
	printf( "Search-Key..." ) ;
	myCharFputstring( stdout, search_key ) ;
	printf( "\n" ) ;
#endif
	/* 変換キーを辞書に書き出す。*/
	myCharFputstring( fp, search_key ) ;
	fputc( ' ', fp ) ;
	/* VectorIndex をファイルに掃き出す…この人は最後に'\n' を入 *
	 * れてくれる。*/
	writeVectorIndexToFile( fp, vTop ) ;
	lines ++ ;
	/* 確保したメモリを解放する。*/
	free_VectorIndex( &vTop ) ;
      }
      free_VectorIndex( &vTopPurge ) ;
      free_SkkinpSearchVector( &top ) ;
      free_SkkinpSearchVector( &purgeTop ) ;
    }
    hNode = hNode->past ;
  }
  return lines ;
}

/*
 * 木構造を辿って、全ての節点を解放する関数。
 *-----
 * この関数は再帰的に呼び出される。
 */
static void clearHenkanKakuteiHashSub( SkkinpHenkanKakuteiKouho *hNode )
{
  /* 左の子供を解放する。*/
  if( hNode->left != NULL ){
    clearHenkanKakuteiHashSub( hNode->left ) ;
    hNode->left = NULL ;
  }
  /* 右の子供を解放する。*/
  if( hNode->right != NULL ){
    clearHenkanKakuteiHashSub( hNode->right ) ;
    hNode->left = NULL ;
  }
  /* その変換キーにぶらさがっている変換結果を抹消する。*/
  free_VectorIndex( &hNode->henkanresults ) ;
  /* まあ、取り敢えず free するんだけど、NULL にしておきましょう。*/
  hNode->henkanresults = NULL ;
  hNode->henkankey     = NULL ;
  hNode->past          = NULL ;
  hNode->future        = NULL ;
  /* 自分を解放する。*/
  free( hNode ) ;
  return ;
}

/*
 * ハッシュテーブルをクリアする関数。
 */
void clearHenkanKakuteiHash( void )
{
  int i ;
  /* ハッシュテーブルを眺めて、その要素を全て捨てる。*/
  for( i = 0 ; i < HENKANKAKUTEIHASHSIZE ; i ++ ){
    /* その場所は使われてしまっているのなら、スキップする。*/
    if( okuriHenkanKakuteiHash[ i ] != NULL ){
      clearHenkanKakuteiHashSub( okuriHenkanKakuteiHash[ i ] ) ;
      /* ハッシュテーブルから抹消する。*/
      okuriHenkanKakuteiHash[ i ] = NULL ;
    }
    if( henkanKakuteiHash[ i ] != NULL ){
      clearHenkanKakuteiHashSub( henkanKakuteiHash[ i ] ) ;
      /* ハッシュテーブルから抹消する。*/
      henkanKakuteiHash[ i ] = NULL ;
    }
    if( purgeSaretaKouho[ i ] != NULL ){
      clearHenkanKakuteiHashSub( purgeSaretaKouho[ i ] ) ;
      /* ハッシュテーブルから抹消する。*/
      purgeSaretaKouho[ i ]       = NULL ;
    }
    if( purgeSaretaOkuriKouho[ i ] != NULL ){
      clearHenkanKakuteiHashSub( purgeSaretaOkuriKouho[ i ] ) ;
      /* ハッシュテーブルから抹消する。*/
      purgeSaretaOkuriKouho[ i ]  = NULL ;
    }
  }
  /* 変換確定した結果を入れておくバッファもクリアする。*/
  if( henkanKakuteiBufferListTop != NULL ){
    free_SkkinpSearchVector( &henkanKakuteiBufferListTop ) ;
    henkanKakuteiBufferListTop   = NULL ;
  }
  recentlyKakuteiKouho         = NULL ;
  recentlyOkuriariKakuteiKouho = NULL ;
  recentlyPurgeKouho           = NULL ;
  recentlyOkuriariPurgeKouho   = NULL ;
  return ;
}

/*
 * 変換キーでもってテーブルから検索し、その結果を引数に入れる関数。
 */
static int searchHNodeWithHenkanKeyAndResult
( SkkinpHenkanKakuteiKouho **hTop,
  struct myChar *henkankey, struct myChar *hresult,
  SkkinpHenkanKakuteiKouho **rhpNode, SkkinpHenkanKakuteiKouho **rhNode,
  VectorIndex **rvNode )
{
  int ret ;
  SkkinpHenkanKakuteiKouho *hNode, *hpNode ;
  VectorIndex *vNode ;
  /* ハッシュテーブルをひく。*/
  hNode = *hTop ;
  /* ノードが存在するか否か。*/
  if( hNode == NULL )
    return False ;
  /* 親ノード。*/
  hpNode = NULL ;
  /* 二分探索する。*/
  while( 1 ){
    /* 同じ変換キーですか？ */
    ret = cmpStringAndCandidate
      ( henkankey, hNode->henkankey, hNode->position, hNode->length ) ;
    if( ret == 0 ){
      /* 同じ変換キーです。*/
      break ;
    } else if( ret > 0 ){
      /* 右の子供をたどる場合。*/
      if( hNode->right == NULL )
	return False ;
      /* 親ノードを保存する。*/
      hpNode = hNode ;
      /* 右の子供がまだ存在した場合。*/
      hNode = hNode->right ;
    } else {
      /* 左の子供をたどる場合。*/
      if( hNode->left == NULL )
	return False ;
      /* 親ノードを保存する。*/
      hpNode = hNode ;
      /* 左の子供がまだ存在した場合。*/
      hNode = hNode->left ;
    }
  }
  /* 変換された結果というものが存在するの？ するなら、ループ。*/
  vNode = hNode->henkanresults ;
  while( vNode != NULL ){
    /* 既に登録されていましたか？ 線型探索…(^^;;; */
    if( !cmpStringAndCandidate
	( hresult, vNode->node, vNode->position, vNode->length ) ){
      break ;
    }
    /* 次の変換結果を見る。*/
    vNode = vNode->next ;
  }
  /* 結局最後まで見付からなかった場合。*/
  if( vNode == NULL )
    return False ;
  /* 見付かった場合。*/
  *rhpNode = hpNode ;
  *rhNode  = hNode ;
  *rvNode  = vNode ;
  return True ;
}

/*
 * 変換キーでもってテーブルから検索し、その結果を引数に入れる関数。
 */
static int searchHNodeWithHenkanKeyAndResultWithVI
( SkkinpHenkanKakuteiKouho **hTop,
  struct myChar *henkankey, VectorIndex *keyVNode,
  SkkinpHenkanKakuteiKouho **rhpNode, SkkinpHenkanKakuteiKouho **rhNode,
  VectorIndex **rvNode )
{
  int ret ;
  SkkinpHenkanKakuteiKouho *hNode, *hpNode ;
  VectorIndex *vNode ;
  /* ハッシュテーブルをひく。*/
  hNode = *hTop ;
  /* ノードが存在するか否か。*/
  if( hNode == NULL )
    return False ;
  /* 親ノード。*/
  hpNode = NULL ;
  /* 二分探索する。*/
  while( 1 ){
    /* 同じ変換キーですか？ */
    ret = cmpStringAndCandidate
      ( henkankey, hNode->henkankey, hNode->position, hNode->length ) ;
    if( ret == 0 ){
      /* 同じ変換キーです。*/
      break ;
    } else if( ret > 0 ){
      /* 右の子供をたどる場合。*/
      if( hNode->right == NULL )
	return False ;
      /* 親ノードを保存する。*/
      hpNode = hNode ;
      /* 右の子供がまだ存在した場合。*/
      hNode = hNode->right ;
    } else {
      /* 左の子供をたどる場合。*/
      if( hNode->left == NULL )
	return False ;
      /* 親ノードを保存する。*/
      hpNode = hNode ;
      /* 左の子供がまだ存在した場合。*/
      hNode = hNode->left ;
    }
  }
  /* 変換された結果というものが存在するの？ するなら、ループ。*/
  vNode = hNode->henkanresults ;
  while( vNode != NULL ){
    /* 既に登録されていましたか？ 線型探索…(^^;;; */
    if( !cmpCandidate
	( keyVNode->node, keyVNode->position, keyVNode->length,
	  vNode->node, vNode->position, vNode->length ) ){
      break ;
    }
    /* 次の変換結果を見る。*/
    vNode = vNode->next ;
  }
  /* 結局最後まで見付からなかった場合。*/
  if( vNode == NULL )
    return False ;
  *rhpNode = hpNode ;
  *rhNode  = hNode ;
  *rvNode  = vNode ;
  return True ;
}

/*
 * ハッシュテーブルの二分木から節点を一つ削除する関数。
 */
static void delete_NodeInBinaryTree
( SkkinpHenkanKakuteiKouho **top, SkkinpHenkanKakuteiKouho **rTop,
  SkkinpHenkanKakuteiKouho *parentNode, SkkinpHenkanKakuteiKouho *deleteNode )
{
  SkkinpHenkanKakuteiKouho *hNode ;
  /* 子供のいない葉っぱだった場合。*/
  if( deleteNode->left == NULL && deleteNode->right == NULL ){
    /* 根っこだった？ */
    if( parentNode == NULL )
      *top = NULL ;
    goto exit_delete_NodeInBinaryTree ;
  }
  /* 左の子供がいなかった場合。*/
  if( deleteNode->left == NULL ){
    /* この節点が根っこだった場合には、根っこを指すポインタを修正する。*/
    if( parentNode == NULL ){
      *top = deleteNode->right ;
    } else {
      /* 根っこじゃなかった場合には、その親の子供を指すポインタを修正する。*/
      if( parentNode->right == deleteNode ){
	parentNode->right = deleteNode->right ;
      } else {
	parentNode->left = deleteNode->right ;
      }
    }
    goto exit_delete_NodeInBinaryTree ;
  }
  /* 右の子供がいなかった場合。*/
  if( deleteNode->right == NULL ){
    /* この節点が根っこだった場合には、根っこを指すポインタを修正する。*/
    if( parentNode == NULL ){
      *top = deleteNode->left ;
    } else {
      /* 根っこじゃなかった場合には、その親の子供を指すポインタを修 *
       * 正する。*/
      if( parentNode->right == deleteNode ){
	parentNode->right = deleteNode->left ;
      } else {
	parentNode->left = deleteNode->left ;
      }
    }
    goto exit_delete_NodeInBinaryTree ;
  }
  /* 左右の子供が両方いる場合には…最も削除されようとしている節点の *
   * 親節点の持つ番号より大きな最も近い値を持った葉っぱを探す。*/
  hNode = deleteNode->right ;
  /* その右の子供のずっと左の左の…左の子供の葉っぱの持つ値は、削除 *
   * されようとしている節点の左の子供の値より大きい。これは二分木よ *
   * り自明である。*/
  while( hNode->left != NULL )
    hNode = hNode->left ;
  hNode->left = deleteNode->left ;
  /* 削除されようとしている節点の親の内、左右どちらの子供が消されよ *
   * うとしているの？ それとも根っこなの？ */
  if( parentNode == NULL ){
    *top = hNode ;
  } else if( parentNode->right == deleteNode ){
    /* 右の子供に先程探していた点をつなぐ。*/
    parentNode->right = hNode ;
  } else {
    /* 左の子供に先程探していた点をつなぐ。*/
    parentNode->left = hNode ;
  }

exit_delete_NodeInBinaryTree:
  if( rTop != NULL ){
    /* 現在過去未来のリストを修正する。*/
    if( *rTop == deleteNode ){
      *rTop = deleteNode->past ;
      if( deleteNode->past != NULL )
	( deleteNode->past )->future = NULL ;
    } else {
      if( deleteNode->past != NULL )
	( deleteNode->past )->future = deleteNode->future ;
      if( deleteNode->future != NULL )
	( deleteNode->future )->past = deleteNode->past ;
    }
  }
  deleteNode->past = deleteNode->future = NULL ;
#if 0
  /* 後に共有に使えるかもしれないので、ここで解放するのは損というもの。*/
  free( deleteNode ) ;
#endif
  return ;
}

/*
 * 変換確定されて内部に蓄えられている候補から completion しようとする関数。
 */
SkkinpSearchVector *j_completion_search_sudeniKakuteiShitaKouho
( SkkinpSearchVector *top, struct myChar *completion_key )
{
  SkkinpHenkanKakuteiKouho *hNode = recentlyKakuteiKouho ;  
  VectorIndex vNode ;
  int length = myCharStrlen( completion_key ) ;

  /* 候補が尽きるまで線型探索場合。*/
  while( hNode != NULL ){
    /* 探索キーよりも長くなければ、駄目である。*/
    if( hNode->length > length ){
      /* 探索キーと前方一致しなければ駄目である。*/
      if( !cmpStringAndCandidate
	  ( completion_key, hNode->henkankey, hNode->position, length ) ){
	/* 見付かった場合の処理。*/
	vNode.node     = hNode->henkankey ;
	vNode.length   = hNode->length ;
	vNode.position = hNode->position ;
	vNode.next     = NULL ;
	top = makeSkkSearchVectorFromVectorIndex( top, &vNode ) ;
      }
    }
    /* 過去へと探索は続く。*/
    hNode = hNode->past ;
  }
  return top ;
}

/*
 * 変換確定されて内部に蓄えられている候補から completion しようとする関数。
 */
SkkinpSearchVector *j_completion_search_sudeniPurgeSaretamono
( SkkinpSearchVector *top, struct myChar *completion_key )
{
  SkkinpHenkanKakuteiKouho *hNode = recentlyPurgeKouho ;  
  VectorIndex vNode ;
  int length = myCharStrlen( completion_key ) ;

  /* 候補が尽きるまで線型探索場合。*/
  while( hNode != NULL ){
    /* 探索キーよりも長くなければ、駄目である。*/
    if( hNode->length > length ){
      /* 探索キーと前方一致しなければ駄目である。*/
      if( !cmpStringAndCandidate
	  ( completion_key, hNode->henkankey, hNode->position, length ) ){
	/* 見付かった場合の処理。*/
	vNode.node     = hNode->henkankey ;
	vNode.length   = hNode->length ;
	vNode.position = hNode->position ;
	vNode.next     = NULL ;
	top = makeSkkSearchVectorFromVectorIndex( top, &vNode ) ;
      }
    }
    /* 過去へと探索は続く。*/
    hNode = hNode->past ;
  }
  return top ;
}

static void skkinputDumpVectorIndexNode
( FILE *fp, SkkinpSearchVector *sNode, int position, int length )
{
  struct myChar *ptr ;
  int i ;
  ptr   = sNode->text + position ;
#ifdef DEBUG
  printf( "Dump ... \"" ) ;
#endif
  /* 候補を一気に書き出す。*/
  for( i = 0 ; i < length ; i ++, ptr ++, position ++ ){
    if( IS_END_OF_STRING( *ptr ) ){
      if( ( sNode = sNode->next ) == NULL )
	break ;
      ptr      = sNode->text ;
      position = 0 ;
    }
#ifdef DEBUG
    myCharFputEUCc( *ptr, stdout ) ;
#endif
    fputc( ptr->charset, fp ) ;
    fputc( ( ( ptr->chara ) & 0x00FF ), fp ) ;
    fputc( ( ( ptr->chara ) >> 8 ), fp ) ;
  }
#ifdef DEBUG
  printf( "\"\n" ) ;
#endif
  fputc( '\0', fp ) ;
  fputc( '\0', fp ) ;
  fputc( '\0', fp ) ;
  return ;
}

/*
 * オートセーブの際に内部に登録されている単語をファイルに書き出す関数。
 */
void skkinputNoNaibuNoKouhoWoFileNiKaku
( FILE *fp, int okuri, int purge )
{
  SkkinpHenkanKakuteiKouho *hNode ;
  VectorIndex *vNode ;
  int mode ;

#ifdef DEBUG
  printf( "PurgeTree ---- \n" ) ;
  debug_dump_kouhotree( recentlyPurgeKouho ) ;
  printf( "\n" ) ;
#endif

  /* 送り仮名の有無によって見るべきリストが異なる。*/
  if( purge ){
    if( okuri ){
      hNode = recentlyOkuriariPurgeKouho ;
      mode  = 0 ;
    } else {
      hNode = recentlyPurgeKouho ;
      mode  = 1 ;
    }
  } else {
    if( okuri ){
      hNode = recentlyOkuriariKakuteiKouho ;
      mode  = 2 ;
    } else {
      hNode = recentlyKakuteiKouho ;
      mode  = 3 ;
    }
  }
  /* 何も変換確定されたことがなかったら、そのまま帰る。*/
  if( hNode == NULL )
    return ;
#ifdef DEBUG
  printf( "autosave-mode ... %d\n", mode ) ;
#endif
  /* あるだけ順にファイルへ持っていく。*/
  while( hNode != NULL ){
    /* charset の数が 255 まで行くとまずい実装になっている。*/
    fputc( 0xFF, fp ) ;
    fputc( mode, fp ) ;
    fputc( '\0', fp ) ;
    /* 変換キーを書き出している。*/
    skkinputDumpVectorIndexNode
      ( fp, hNode->henkankey, hNode->position, hNode->length ) ;
    vNode = hNode->henkanresults ; 
    while( vNode != NULL ){
      skkinputDumpVectorIndexNode
	( fp, vNode->node, vNode->position, vNode->length ) ;
      vNode = vNode->next ;
    }
    fputc( '\0', fp ) ;
    fputc( '\0', fp ) ;
    fputc( '\0', fp ) ;

    hNode = hNode->past ;
  }
  return ;
}

static int skkinputRead3ByteAndReturnMyChar
( FILE *fp, struct KanjiBuffer *kbuf )
{
  unsigned char buffer[ 3 ] ;
  struct myChar mchara ;

  while( !feof( fp ) ){
    fread( buffer, sizeof( unsigned char ), 3, fp ) ;
    if( buffer[ 0 ] == 0 &&
	buffer[ 1 ] == 0 &&
	buffer[ 2 ] == 0 )
      break ;
#ifdef DEBUG
    printf( "[%d,%d,%d] ", buffer[ 0 ], buffer[ 1 ], buffer[ 2 ] ) ;
#endif
    mchara.charset = buffer[ 0 ] ;
    mchara.chara   = ( ( unsigned short )buffer[ 1 ] |
		       ( ( ( unsigned short )buffer[ 2 ] )<< 8 ) ) ;
    add_kanjibuffer( kbuf, mchara ) ;
  }
#ifdef DEBUG
  printf( "\n" ) ;
#endif
  return True ;
}

/*
 * オートセーブファイルを読んで単語を復元する関数。
 * えらくしくしくな名前なじゃない機能な関数です。どうやって元に戻そうか？
 */
void skkinputAutoSaveFileWoYondeFukugenSuru
( FILE *fp )
{
  struct KanjiBuffer henkankey, henkanresult ;
  unsigned char buffer[ 3 ] ;
  int map ;

  while( !feof( fp ) ){
    /* ファイルから読み込む。*/
    fread( buffer, sizeof( unsigned char ), 3, fp ) ;
    /* 機能コードじゃないということは嘘である。エラーリターン。*/
    if( buffer[ 0 ] != 0xFF )
      return ;
    map = buffer[ 1 ] ;

    /* 変換キーを見切る。*/
    init_kanjibuffer( &henkankey ) ;
    skkinputRead3ByteAndReturnMyChar( fp, &henkankey ) ;
    if( henkankey.usage <= 0 ){
      /* 変換キーが無かったら登録は出来ない＝オートセーブファイルが壊
       * れていると考えることにする。*/ 
      close_kanjibuffer( &henkankey ) ;
      break ;
    }
    /* 変換キーを見切る。*/
    for( ; ; ){
      init_kanjibuffer( &henkanresult ) ;
      skkinputRead3ByteAndReturnMyChar( fp, &henkanresult ) ;
#ifdef DEBUG
      printf( "[ %d ]\n", henkanresult.usage ) ;
#endif
      if( henkanresult.usage <= 0 )
	break ;
#ifdef DEBUG
      printf( "Map: %d, Key :\"", map ) ;
      myCharFputstring( stdout, henkankey.buffer ) ;
      printf( "\"   Word :\"" ) ;
      myCharFputstring( stdout, henkanresult.buffer ) ;
      printf( "\"\n" ) ;
#endif
      /* 一気に登録する。*/
      switch( map ){
      case 0 :
	purge_SudeniHenkanKakuteiShitamonoByString
	  ( henkankey.buffer, henkanresult.buffer, True ) ;
	break ;
      case 1 :
	purge_SudeniHenkanKakuteiShitamonoByString
	  ( henkankey.buffer, henkanresult.buffer, False ) ;
	break ;
      case 2 :
	add_SudeniHenkanKakuteiShitamonoByString
	  ( henkankey.buffer, henkanresult.buffer, True ) ;
	break ;
      case 3 :
	add_SudeniHenkanKakuteiShitamonoByString
	  ( henkankey.buffer, henkanresult.buffer, False ) ;
	break ;
      default :
	break ;
      }
      close_kanjibuffer( &henkanresult ) ;
    }
    close_kanjibuffer( &henkankey ) ;
  }
  /* 過去から未来への枝を交換する。
   * 若葉    「それって…えっと…どういうことなんでしょうか？」
   * たたり  「時間は可逆だが歴史は変えられないということだよ(謎)」
   * フィリー「何わけのわかんないこと言ってるのよ、たたり！」
   * たたり  「これが分かったら、天才どころか変人だよ(さらに謎)」
   * フィリー「(処置なしね…)」
   */
  skkinput_KakoToMiraiWoIrekaeru( &recentlyKakuteiKouho ) ;
  skkinput_KakoToMiraiWoIrekaeru( &recentlyOkuriariKakuteiKouho ) ;
  skkinput_KakoToMiraiWoIrekaeru( &recentlyPurgeKouho ) ;
  skkinput_KakoToMiraiWoIrekaeru( &recentlyOkuriariPurgeKouho ) ;
  return ;
}

/*
 * 「時間は可逆だ。だが運命は変えられん」
 * 「運命ってじゃあ何なんだ？」
 * 「ブリンダーの木に記述された事象のことだ」
 * じゃないけど、登録順序が逆になっているのでひっくり返す関数。
 */
static void skkinput_KakoToMiraiWoIrekaeru
( SkkinpHenkanKakuteiKouho **top )
{
  SkkinpHenkanKakuteiKouho *tail, *node, *nextNode ;
  if( *top == NULL )
    return ;
  for( node = *top ; node->past != NULL ; node = node->past )
    ;
  /* 尻尾を見付けた。*/
  tail = node ;
  while( node != NULL ){
    /* 過去と未来の枝を交換する。*/
    nextNode     = node->future ;
    node->future = node->past ;
    node->past   = nextNode ;
    /* 一番過去に自分はいるので、(もとの)未来へとたどる必要がある。交
     * 換された今は過去への枝となっているが。*/
    node = nextNode ;
  }
  *top = tail ;
  return ;
}

#ifdef DEBUG
static void debug_dump_kouhotree
( SkkinpHenkanKakuteiKouho *top )
{
  VectorIndex *node ;
  if( top == NULL ){
    printf( "Empty\n" ) ;
    return ;
  }
  printf( "--- Node ---\n" ) ;
  printf( "Henkankey: " ) ;
  myCharFputstrning
    ( stdout, top->henkankey->text + top->position, top->length ) ;
  printf( "\n" ) ;
  printf( "Result: " ) ;
  for( node = top->henkanresults ; node != NULL ; node = node->next ){
    myCharFputstrning( stdout, node->node->text + node->position, node->length ) ;
    printf( "<-->" ) ;
  }
  printf( "\n" ) ;
  printf( "Past : \n" ) ;
  debug_dump_kouhotree( top->past ) ;
  return ;
}
#endif
