/* # skkinput (Simple Kana-Kanji Input)
 * skksvect.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/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>

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

/*
 * グローバル変数。
 */
static VectorIndex *vNodeHashTable[ VNODEHASHSIZE ] ;

/*
 * プロトタイプ宣言。
 */
static int isExistVectorIndexNode
( SkkinpSearchVector *sNode, int pos, int length ) ;
static int add_VectorHashTable
( SkkinpSearchVector *sNode, int pos, int length ) ;
static void clearVectorHashTable( void ) ;

int cmpCandidate
( SkkinpSearchVector *sNode1, int pos1, int len1,
  SkkinpSearchVector *sNode2, int pos2, int len2 ) ;

/*
 * リストをまるごと解放する関数。
 */
void free_SkkinpSearchVector( SkkinpSearchVector **top )
{
  SkkinpSearchVector *node, *nextNode ;
  if( top == NULL )
    return ;
  node = *top ;
  while( node != NULL ){
    nextNode = node->next ;
    free( node ) ;
    node     = nextNode ;
  }
  *top = NULL ;
  return ;
}

/*
 * 新しくノードに用いるメモリを確保する関数。
 */
SkkinpSearchVector *makeNewSkkinpSearchVectorNode( void )
{
  SkkinpSearchVector *node ;
  node = ( SkkinpSearchVector *)malloc( sizeof( SkkinpSearchVector ) ) ;
  if( node != NULL ){
    /* 初期化をする。*/
    node->used = 0 ;
    node->next = NULL ;
    MYCHAR_SET_END_OF_STRING( node->text[ 0 ] ) ;
  }
  return node ;
}

/*
 * Search Vector に新しいノードを付け加える関数。
 */
SkkinpSearchVector *add_SkkinpSearchVector
( SkkinpSearchVector *topp,   struct myChar *str,
  SkkinpSearchVector **rnode, int *pos )
{
  SkkinpSearchVector *node ;
  int length, overlen ;

  /* 先頭が NULL だった場合には先頭を確保する。*/
  if( topp == NULL ){
    topp = node = makeNewSkkinpSearchVectorNode() ;
  } else {
    node = topp ;
    /* 一番最後のノードを見付ける。*/
    while( node->next != NULL )
      node = node->next ;
  }
  if( rnode != NULL )
    *rnode = node ;
  if( pos != NULL )
    *pos = node->used ;
  /* 付け加えたい文字列が空でないのなら…。*/
  length = myCharStrlen( str ) ;
  while( length > 0 ){
    if( node->used < TEXTMAXLEN ){
      /* バッファの最後に付け加える。*/
      myCharStrncpy
	( node->text + node->used, str, TEXTMAXLEN - node->used ) ;
    }
    /* 一応、バッファを文字列として終端する。*/
    MYCHAR_SET_END_OF_STRING( node->text[ TEXTMAXLEN ] ) ;
    /* バッファがあふれるような事態が発生したのかどうかを確認する。*/
    overlen = length - ( TEXTMAXLEN - node->used ) ;
    if( overlen > 0 ){
      /* copyせにゃならん文字列の開始位置をずらす…バグ指摘 by sigoto */
      str       += TEXTMAXLEN - node->used ;
      /* そのノードは最後まで使いきったぞ、と。*/
      node->used = TEXTMAXLEN ;
      /* これが残っている文字数だぞ、と。*/
      length     = overlen ;
      /* あふれていたのなら、新しくバッファを確保する。*/
      if( ( node->next = makeNewSkkinpSearchVectorNode() ) == NULL )
	break ;
      /* 新しく確保した奴は使用してないので、まっさら。*/
      node       = node->next ;
      node->used = 0 ;
    } else {
      /* あふれていなければ、これ以上文字列を加える必要もないので、*
       * ループを抜ける。*/
      node->used += length ;
      length     = 0 ;
      break ;
    }
  }
  return topp ;
}


/*
 * 変換候補のインデックスに用いられていたメモリを解放する関数。
 */
void free_VectorIndex( VectorIndex **top )
{
  VectorIndex *node, *nextNode ;
  if( top == NULL )
    return ;
  node = *top ;
  while( node != NULL ){
    nextNode = node->next ;
    free( node ) ;
    node     = nextNode ;
  }
  *top = NULL ;
  return ;
}

/*
 * 新しくノードに用いるメモリを確保する関数。
 */
VectorIndex *alloc_NewVectorIndexNode( void )
{
  VectorIndex *node ;
  if( ( node = ( VectorIndex * )malloc( sizeof( VectorIndex ) ) ) != NULL ){
    node->next = NULL ;
  }
  return node ;
}

/*
 * 変換候補のインデックスを作成する関数。
 * ----
 * サーバから返って来るのはただの文字列なので、それを切り出して管理する必要
 * がある。
 */
static VectorIndex *add_vectorIndexNode
( VectorIndex *vitop, SkkinpSearchVector *sNode,
  int pos, int length, int *number_of_candidates )
{
  VectorIndex *vNode, *vNodeNxt ;

#ifdef DEBUG
  myCharFputstrning( stderr, sNode->text + pos, length ) ;
#endif
  /* 既に同じ文字が存在するかどうかを判定する。*/
  if( isExistVectorIndexNode( sNode, pos, length ) ){
#ifdef DEBUG
    fprintf( stderr, " ha sudeni atta...\n" ) ;
    fflush( stderr ) ;
#endif
    /* 既に同じ文字が候補として存在していたので、それは捨てる。*/
    return vitop ;
  }
  /* ハッシュに加える。*/
  add_VectorHashTable( sNode, pos, length ) ;

  /* ノードの一番先頭があるか無いかを見る。*/
  if( vitop == NULL ){
    /* ノードの一番先頭が無かった場合。*/
    vitop = vNode = alloc_NewVectorIndexNode() ;
    vNode->prev = NULL ;
    vNode->next = NULL ;
  } else {
    vNode = vitop ;
    /* そうでなかった場合には、リストの一番最後を捜す。*/
    while( vNode->next != NULL )
      vNode = vNode->next ;
    /* 一番最後のノードを見付けた場合には… */
    vNodeNxt       = alloc_NewVectorIndexNode() ;
    vNodeNxt->next = NULL ;
    vNodeNxt->prev = vNode ;
    vNode->next    = vNodeNxt ;
    vNode          = vNodeNxt ;
  }
  /* 参照している SkkInputVector。*/
  vNode->node     = sNode ;
  vNode->next     = NULL ;
  /* 参照している文字列の長さ。*/
  vNode->length   = length ;
  /* SkkInputVector->text 内での位置。*/
  vNode->position = pos ;
  /* 発見した候補の数を1増やす。*/
  ++ *number_of_candidates ;
  return vitop ;
}

/*
 * Search Vector から文字列を切り出すために利用される関数。
 * ----
 * 変換後の候補を求めるのに利用される。
 */
VectorIndex *makeSearchVectorIndex
( SkkinpSearchVector *top, int *totalnum, int flag )
{
  int doubleQuoteCheck, slashCheck ;
  int pPos, pos, count, number_of_candidates ;
  SkkinpSearchVector *node, *pNode ;
  VectorIndex *vitop = NULL ;
  struct myChar *ptr ;

  /* インデックスの初期化のために利用される局所変数の初期化。*/
  pPos  = 1 ;
  pos   = 1 ;			/* これは一番最初の位置には／があるため。*/
  pNode = node  = top ;
  ptr   = node->text + pos ;	/* これは一番最初の位置には／があるため。*/
  count                = 0 ;	/* これで候補の長さを数える。*/
  doubleQuoteCheck     = False ;
  slashCheck           = 0 ;
  number_of_candidates = 0 ;
  
  /* インデックスを作成する。*/
  while( node != NULL ){
    if( pos >= node->used ){
      /* これが無いとバグってしまうと思う。*/
      if( pPos == pos && pNode == node ){
	pPos  = 0 ;
	pNode = node->next ;
      }
      if( ( node = node->next ) == NULL )
	goto ex_makeindex ;
      pos   = 0 ;
      ptr   = node->text ;
    }
    /* ダブルクウォートを無視する＼をここで確認する。*/
    if( IS_ASCII_EQUAL( *ptr,  '\\' ) ){
      slashCheck = 2 ;
    }
    if( slashCheck <= 0 && IS_ASCII_CHARA( *ptr ) ){
      switch( ptr->chara ){
	/* Double Quote の間は要注意。バックスラッシュの次ではないダブ
	 * ルクウォートに囲まれた部分は一つの文字列(というか候補として
	 * 扱いたい。仮にその中に slash が入っていても無視することにな
	 * る。*/  
      case 0x22:
	if( doubleQuoteCheck == False ){
	  /* もしダブルクウォートの中になければ、ここから一つの文字
	   * 列が開始したことになる。*/
	  doubleQuoteCheck = True ;
	} else {
	  /* もしダブルクウォートの中でダブルクウォートを見付けたら…。 
	   * 当然、ダブルクウォートの中を抜けたことになる。*/
	  doubleQuoteCheck = False ;
	}
	/* カーソル位置の移動。*/
	ptr   ++ ;
	pos   ++ ;
	count ++ ;
	continue ;
	/* ／は候補と候補の間に必ず存在する。*/
      case '/' :
	/* ダブルクウォートの中に登場したのであれば、本来の機能は無効。*/
	if( doubleQuoteCheck )
	  break ;
	/* ここの位置の文字は終端したって構うまい。所詮区切る以外に役
	 * 立ってないのだから。それなら NUL 文字でも十分。*/
	MYCHAR_SET_END_OF_STRING( *ptr ) ;
	/* 新しいノードを一つ加える。*/
	if( count > 0 && ( pPos < pos || pNode != node ) ){
	  vitop = add_vectorIndexNode
	    ( vitop, pNode, pPos, count, &number_of_candidates ) ;
	}
	/* 場所及び、ポインタを一つ増やす。*/
	ptr ++ ;
	pos ++ ;
	/* 前回(つまり今の場所)、"/" を見付けた場所を pPos に記憶する。*/
	pPos  = pos ;
	/* 一つ前のノードを記憶しておく。*/
	pNode = node ;
	count = 0 ;
	continue ;
	/* 文字列の終端を見付けてしまった場合の処理。*/
      case '\n' :
      case '\r' :
      case '\0' :
	/* さすがに終端文字はダブルクウォートの中だろうが何だろうが無
	 * 視できない…。もしダブルクウォートの中に登場したらエラー以
	 * 外の何者でもない。*/
	if( ( node = node->next ) == NULL ){
	  goto ex_makeindex ;
	}
	/* 次の文字列バッファを読みに行く。*/
	pos = 0 ;
	ptr = node->text ;
	continue ;
      default :
	break ;
      }
    }
    ptr   ++ ;
    pos   ++ ;
    count ++ ;
    if( slashCheck > 0 )
      slashCheck -- ;
  }
ex_makeindex:
  *totalnum = number_of_candidates ;
  /* ハッシュはクリアしていいの？ */
  if( flag )
    /* 検索途中で利用したハッシュテーブルをクリアする。*/
    clearVectorHashTable() ;
  return vitop ;
}

/*
 * Search Vector から文字列を切り出すために利用される関数。
 * ----
 * 変換後の候補を求めるのに利用される。
 */
int copyCandidate
( struct myChar *dest, SkkinpSearchVector *sNode, int position, int length )
{
  struct myChar *sptr = sNode->text + position ;
  int count ;

#if 0
  /* 多分、ここのコードは無効にしないと、concat を理解できるし…無駄な
   * んじゃないかな？ */
  /* concat は無視させて戴きとうございます。*/
  if( IS_ASCII_EQUAL( *sptr,  0x22 ) )
    return 0 ;
#endif

  count = 0 ;
  /* それ以外なら今はなんとかなるようでござぁます。*/
  while( count < length ){
    if( position >= sNode->used ){
      if( ( sNode = sNode->next ) == NULL ){
	MYCHAR_SET_END_OF_STRING( *dest ) ;
	return count ;
      }
      sptr     = sNode->text ;
      position = 0 ;
      continue ;
    }
    if( IS_END_OF_STRING( *sptr ) )
      break ;
    *dest ++ = *sptr ++ ;
    count ++ ;
  }
  MYCHAR_SET_END_OF_STRING( *dest ) ;
  return count ;
}

/*
 * ハッシュテーブルを初期化する関数。起動時に必ず一度呼ぶこと。
 */
void initVectorHashTable( void )
{
  int i ;
  /* ハッシュテーブルを NULL で初期化する。*/
  for( i = 0 ; i < VNODEHASHSIZE ; i ++ )
    vNodeHashTable[ i ] = NULL ;
  return ;
}

static void clearVectorHashTableSub( VectorIndex *node )
{
  /* 左右の子供を解放してから…。*/
  if( node->prev != NULL ){
    clearVectorHashTableSub( node->prev ) ;
    node->prev = NULL ;
  }
  if( node->next != NULL ){
    clearVectorHashTableSub( node->next ) ;
    node->next = NULL ;
  }
  node->node     = NULL ;
  node->position = 0 ;
  node->length   = 0 ;
  /* 自分自身を解放して…終了する。*/
  free( node ) ;
  return ;
}

/*
 * ハッシュテーブルを一旦解放する関数。
 *-----
 * 変換が終了する度に呼ばれる必要があるが…。これは結構なコストになる
 * と思う。
 */
static void clearVectorHashTable( void )
{
  int i ;
  /* ハッシュテーブルを順に検索する。*/
  for( i = 0 ; i < VNODEHASHSIZE ; i ++ ){
    if( vNodeHashTable[ i ] == NULL )
      continue ;
    clearVectorHashTableSub( vNodeHashTable[ i ] ) ;
    vNodeHashTable[ i ] = NULL ;
  }
  return ;
}

/*
 * skkinpsearchvector 内の二つの候補を比較する関数。
 *----
 * strcmp を skkinpsearchvector の上にのせたものと思って下さい。
 */
int cmpCandidate
( SkkinpSearchVector *sNode1, int pos1, int len1,
  SkkinpSearchVector *sNode2, int pos2, int len2 )
{
  int ret, len, lchara1, lchara2 ;
  struct myChar *ptr1, *ptr2 ;

  /* 長さの内、短い方だけループすることになる。長さが違うんだったら * 
   * 呼ぶ必要無いやん…とか言われそうだが、これは Tree Search に必  *
   * 要なの。*/
  len = ( len1 < len2 )? len1 : len2 ;

  /* 比較開始位置。*/
  ptr1 = sNode1->text + pos1 ;
  ptr2 = sNode2->text + pos2 ;

  while( len > 0 ){
    /* バッファの最後まで来てしまった場合には、次のノードを見る。*/
    if( pos1 >= sNode1->used ){
      if( ( sNode1 = sNode1->next ) == NULL ){
	if( pos2 >= sNode2->used ){
	  if( ( sNode2 = sNode2->next ) == NULL )
	    return 0 ;
	}
	/* バッファの次を見ようにも、もう打ち止め…。すると、これ以 *
	 * 上の文字は存在しないということになる。ここで、結果が出て *
	 * いないとなると…つまり i == len になっていないということ *
	 * は、相手はまだまだ続くということだから、返り値は相手の勝 *
	 * ち。*/
	len = -1;
      } else {
	ptr1 = sNode1->text ;
      }
    }
    if( pos2 >= sNode2->used ){
      /* バッファの最後まで来てしまった場合には、次のノードを見る。*/
      if( ( sNode2 = sNode2->next ) == NULL ){
	/* バッファの次を見ようにも、もう打ち止め…。すると、これ以 *
	 * 上の文字は存在しないということになる。ここで、結果が出て *
	 * いないとなると…つまり i == len になっていないということ *
	 * は、相手はまだまだ続くということだから、返り値は相手の勝 *
	 * ち。*/
	len = -2;
      } else {
	ptr2 = sNode2->text ;
      }
    }

    if(len == -1 || IS_ASCII_EQUAL(*ptr1, ';')){
      /* 1 番目の候補が終わっている場合。 */
      if(len == -2 || IS_ASCII_EQUAL(*ptr2, ';'))
	return 0;
      return (-1);
    }
    if(len == -2 || IS_ASCII_EQUAL(*ptr2, ';')){
      /* 2 番目の候補が終わっている場合。 */
      return 1;
    }

    /* 二数の差を取る。*/
    if( IS_ASCII_CHARA( *ptr1 ) ){
      lchara1 = ( ( ( unsigned long )CHARSET_ASCII ) << 16 ) | 
	( ( unsigned long )( ptr1->chara ) ) ;
    } else {
      lchara1 = ( ( ( unsigned long )ptr1->charset ) << 16 ) | 
	( ( unsigned long )( ptr1->chara ) ) ;
    }
    if( IS_ASCII_CHARA( *ptr2 ) ){
      lchara2 = ( ( ( unsigned long )CHARSET_ASCII ) << 16 ) | 
	( ( unsigned long )( ptr2->chara ) ) ;
    } else {
      lchara2 = ( ( ( unsigned long )ptr2->charset ) << 16 ) | 
	( ( unsigned long )( ptr2->chara ) ) ;
    }
    if( ( ret = lchara1 - lchara2 ) )
      return ret ;
    /* 次の候補を捜す。*/
    ptr1 ++ ; pos1 ++ ;
    ptr2 ++ ; pos2 ++ ;
    len  -- ;
  }
  /* 自分の方が相手よりも長いってことは、自分の方が大きいの。*/
  if( len1 > len2 ){
    if( pos1 >= sNode1->used ){
      if( ( sNode1 = sNode1->next ) == NULL ){
	return 0;
      }
      ptr1 = sNode1->text ;
    }
    if(IS_ASCII_EQUAL(*ptr1, ';')){
      /* 註釈を見つけたら、そこで比較は終わり。 */
      return 0;
    }
    return 1;
  }

  /* 自分の方が短かったら、自分の方が小さいの。*/
  if( len1 < len2 ){
    if( pos2 >= sNode2->used ){
      if( ( sNode2 = sNode2->next ) == NULL ){
	return 0;
      }
      ptr2 = sNode2->text ;
    }
    if(IS_ASCII_EQUAL(*ptr2, ';')){
      /* 註釈を見つけたら、そこで比較は終わり。 */
      return 0;
    }
    return (-1);
  }

  /* 自分と相手が同じ文字数だったら、等しいんだけどね。*/
  return 0 ;
}

/*
 * ハッシュテーブル＋二分木に候補を加える関数。
 */
static int add_VectorHashTable
( SkkinpSearchVector *sNode, int pos, int length )
{
  VectorIndex *vNode ;
  int num ;
  num = VHASHFUNCTION( sNode, pos ) ;
  /* 該当する文字列は存在しなかった。*/
  if( ( vNode = vNodeHashTable[ num ] ) == NULL ){
    /* ノードの一番先頭が無かった場合。*/
    vNodeHashTable[ num ] = vNode = alloc_NewVectorIndexNode() ;
  } else {
    /* 既に何らかの候補が存在している場合…。*/
    while( 1 ){
      num = cmpCandidate
	( vNode->node, vNode->position, vNode->length, sNode, pos, length ) ;
      /* 検索結果を見る。*/
      if( num == 0 ){
	return True ;
      } else if( num < 0 ){
	/* これは、→の子供を見ないといけない。*/
	if( vNode->next == NULL ){
	  /* →の子供がいなかったら、そこへ加える。*/
	  if( ( vNode->next = alloc_NewVectorIndexNode() ) == NULL ){
	    /* メモリの確保に失敗していなければ、ハッシュに登録する。
	     * 不幸にしてメモリ確保に失敗する可能性があることを忘れて
	     * はならない。*/ 
	    return False ;
	  }
	  vNode = vNode->next ;
	  break ;
	} else {
	  /* →の子供がいたら、その子供を調べる。*/
	  vNode       = vNode->next ;
	}
      } else {
	/* これは、左の子供を見ないといけない。*/
	if( vNode->prev == NULL ){
	  /* 左の子供がいなかったら、そこへ加える。*/
	  if( ( vNode->prev = alloc_NewVectorIndexNode() ) == NULL ){
	    /* メモリの確保に失敗していなければ、ハッシュに登録する。
	     * 不幸にしてメモリ確保に失敗する可能性があることを忘れて
	     * はならない。*/ 
	    return False ;
	  }
	  vNode = vNode->prev ;
	  break ;
	} else {
	  /* 左の子供がいたら、その子供を調べる。*/
	  vNode       = vNode->prev ;
	}
      }
    }
  }
  /* 必ず葉っぱにくっつくので、子供はいない。*/
  vNode->next     = NULL ;
  vNode->prev     = NULL ;
  /* その他の情報を付加する。*/
  vNode->node     = sNode ;
  vNode->position = pos ;
  vNode->length   = length ;
  return True ;
}

/*
 * ハッシュテーブルを調べて、既に候補が存在しているかどうかをチェック
 * する関数。
 */
static int isExistVectorIndexNode
( SkkinpSearchVector *sNode, int pos, int length )
{
  int ret ;
  VectorIndex *vNode ;

  vNode = vNodeHashTable[ VHASHFUNCTION( sNode, pos ) ] ;
  /* 該当する文字列は存在しなかった。*/
  if( vNode == NULL )
    return False ;
  /* ハッシュ関数にぶらさがるリストを検索する。*/
  while( vNode != NULL ){
    ret = cmpCandidate
      ( vNode->node, vNode->position, vNode->length, sNode, pos, length ) ;
    /* 同一の候補が存在した。*/
    if( ret == 0 )
      return True ;
    /* 二分木になっているので、結果を見て枝のたどる方向を調べる。*/
    if( ret < 0 ){
      /* 今回検索しようとしている候補の方が登録されている候補よりも *
       * 文字コードが大きい場合。*/
      vNode = vNode->next ;
    } else {
      /* 今回検索しようとしている候補の方が登録されている候補よりも *
       * 文字コードが小さい場合。*/
      vNode = vNode->prev ;
    }
  }
  return False ;
}

#ifdef DEBUG
void dump_skkinpSearchVector( SkkinpSearchVector *node )
{
  while( node != NULL ){
    printf( "%s\n", node->text ) ;
    node = node->next ;
  }
  return ;
}
#endif
