#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#ifndef STARDICT_DATA_DIR
#define STARDICT_DATA_DIR "/usr/share/stardict"
#endif

#include <sys/stat.h>
#include <cstdlib>
#include <cstring>
#include <zlib.h>
#include <algorithm>
#include <cerrno>

#ifdef HAVE_MMAP
#include <sys/types.h>
#include <fcntl.h>
#include <sys/mman.h>
#endif

#ifndef _WIN32
#  include <sys/types.h>
#endif

#include "lib_utils.h"
#include "distance.h"


#include "libstardict.h"

#define OPTIMIZE_LOOKUP

// Notice: read src/tools/DICTFILE_FORMAT for the dictionary 
// file's format information!

inline gboolean bIsVowel(gchar inputchar)
{
  gchar ch = g_ascii_toupper(inputchar);
  return( ch=='A' || ch=='E' || ch=='I' || ch=='O' || ch=='U' );
}

//===================================================================
DictBase::DictBase()
{
  sametypesequence = NULL;
  dictfile = NULL;
  dictdzfile = NULL;
  cache_cur =0;
}

DictBase::~DictBase()
{
  g_free(sametypesequence);
  if(dictfile)
    fclose(dictfile);
  if(dictdzfile)
    dict_data_close(dictdzfile);
}

gchar* DictBase::GetWordData(glong idxitem_offset, glong idxitem_size)
{
  for(int i=0; i<WORDDATA_CACHE_NUM; i++)
    if((cache[i].data) && cache[i].offset == idxitem_offset)
      return cache[i].data;
  
  if(dictfile)
    fseek(dictfile, idxitem_offset, SEEK_SET);
  
  gchar *data;
  if(sametypesequence){
    gchar *origin_data;
    origin_data = (gchar *)g_malloc(idxitem_size);
		
    if(dictfile)
      fread(origin_data,idxitem_size,1,dictfile);
    else
      dict_data_read (dictdzfile, origin_data, idxitem_offset, idxitem_size);
    
    glong data_size;
    gint sametypesequence_len;
    sametypesequence_len = strlen(sametypesequence);
    //there have sametypesequence_len char being omitted.
    data_size = idxitem_size + sizeof(glong) + sametypesequence_len;
    //if the last item's size is determined by the end up '\0',then +=sizeof(gchar);
    //if the last item's size is determined by the head glong type data,then +=sizeof(glong);
    switch(sametypesequence[sametypesequence_len-1]){
    case 'm':
    case 't':
    case 'y':
    case 'o':
      data_size += sizeof(gchar);
      break;
    case 'W':
    case 'P':
      data_size += sizeof(glong);				
      break;
    }			
    data = (gchar *)g_malloc(data_size);
    gchar *p1,*p2;
    p1 = data + sizeof(glong);
    p2 = origin_data;
    glong sec_size;
    //copy the head items.
    for(int i=0; i< sametypesequence_len-1; i++) {
      memcpy(p1, &(sametypesequence[i]), sizeof(gchar));
      p1+= sizeof(gchar);
      switch (sametypesequence[i]) {
      case 'm':
      case 't':
      case 'y':
      case 'o':
	sec_size = strlen(p2)+1;
	memcpy(p1, p2, sec_size);
	p1+= sec_size;
	p2+= sec_size;
	break;
      case 'W':
      case 'P':
	memcpy(&sec_size, p2, sizeof(glong));
	sec_size += sizeof(glong);
	memcpy(p1, p2, sec_size);
	p1+= sec_size;
	p2+= sec_size;
	break;
      }							
    }	
    //calculate the last item 's size.
    sec_size = idxitem_size - (p2-origin_data);
    memcpy(p1, &(sametypesequence[sametypesequence_len-1]), sizeof(gchar));
    p1+= sizeof(gchar);
    switch (sametypesequence[sametypesequence_len-1]) {
    case 'm':
    case 't':
    case 'y':
    case 'o':
      memcpy(p1, p2, sec_size);
      p1 += sec_size;				
      memcpy(p1, "", sizeof(gchar)); //add the end up '\0';
      break;
    case 'W':
    case 'P':
      memcpy(p1,&(sec_size), sizeof(glong)); //add the head glong size data.
      p1 += sizeof(glong);
      memcpy(p1, p2, sec_size);
      break;
    }		
    g_free(origin_data);		
  }
  else{		
    data = (gchar *)g_malloc(idxitem_size + sizeof(glong));
    if (dictfile)
      fread(data+sizeof(glong),idxitem_size,1,dictfile);		
    else
      dict_data_read (dictdzfile, data+sizeof(glong), idxitem_offset, idxitem_size);
  }
  memcpy(data,&(idxitem_size),sizeof(glong));
  if(cache[cache_cur].data)
    g_free(cache[cache_cur].data);
  
  cache[cache_cur].data = data;
  cache[cache_cur].offset = idxitem_offset;
  cache_cur++;
  if(cache_cur==WORDDATA_CACHE_NUM)
    cache_cur =0;
  return data;
}

bool DictBase::get_dict_info(const gchar *ifofilename, 
			     TDictInfo & dict_info, 
			     bool istreedict)
{

  dict_info.ifofilename=dict_info.bookname=dict_info.author=
    dict_info.email=dict_info.website=dict_info.date=dict_info.description=
    dict_info.sametypesequence="";
  dict_info.wordcount=dict_info.idxfilesize=0;
  dict_info.ifofilename=ifofilename;
  struct stat stats;		
  if(stat(ifofilename, &stats) == -1){
    return false;
  }
		
  FILE *file;
  if (!(file = fopen (ifofilename, "rb"))) {
    return false;
  }
  gchar *buffer = (gchar *)g_malloc (stats.st_size + 1);
  fread (buffer, 1, stats.st_size, file);
  buffer[stats.st_size] = '\0';
  fclose (file);

#define DICT_MAGIC_DATA "StarDict's dict ifo file\nversion=2.4.2\n"
#define TREEDICT_MAGIC_DATA "StarDict's treedict ifo file\nversion=2.4.2\n"
  
  if(!(istreedict ? 
       g_str_has_prefix(buffer, TREEDICT_MAGIC_DATA)
       :
       g_str_has_prefix(buffer, DICT_MAGIC_DATA))) {
    g_free(buffer);
    return false;
  }

  gchar *p1,*p2,*p3;
  if(istreedict)
    p1 = buffer + sizeof(TREEDICT_MAGIC_DATA) -1 -1;
  else
    p1 = buffer + sizeof(DICT_MAGIC_DATA) -1 -1;

  p2 = strstr(p1,"\nidxfilesize=");
  if(!p2){
    g_free(buffer);
    return false;
  }
  p3 = strchr(p2+ sizeof("\nidxfilesize=")-1,'\n');
  gchar *tmpstr = (gchar *)g_memdup(p2+sizeof("\nidxfilesize=")-1, p3-(p2+sizeof("\nidxfilesize=")-1)+1);
  tmpstr[p3-(p2+sizeof("\nidxfilesize=")-1)] = '\0';
  dict_info.idxfilesize = atol(tmpstr);
  g_free(tmpstr);

  dict_info.wordcount=0;
  p2 = strstr(p1,"\nwordcount=");
  if(p2){
    p3 = strchr(p2+ sizeof("\nwordcount=")-1,'\n');
    gchar *tmpstr = (gchar *)g_memdup(p2+sizeof("\nwordcount=")-1, p3-(p2+sizeof("\nwordcount=")-1)+1);
    tmpstr[p3-(p2+sizeof("\nwordcount=")-1)] = '\0';
    dict_info.wordcount = atol(tmpstr);
    g_free(tmpstr);
  }
	
  p2 = strstr(p1,"\nbookname=");
  if (p2) {
    p2 = p2 + sizeof("\nbookname=") -1;
    p3 = strchr(p2, '\n');
    dict_info.bookname.assign(p2, p3-p2);
  }
  
  p2 = strstr(p1,"\nsametypesequence=");
  if(p2){
    p2+=sizeof("\nsametypesequence=")-1;
    p3 = strchr(p2, '\n');
    dict_info.sametypesequence.assign(p2, p3-p2);
  }

  p2 = strstr(p1,"\nauthor=");
  if(p2){
    p2 = p2 + sizeof("\nauthor=") -1;
    p3 = strchr(p2, '\n');
    dict_info.author.assign(p2, p3-p2);
  }

  p2 = strstr(p1,"\nemail=");
  if (p2) {
    p2 = p2 + sizeof("\nemail=") -1;
    p3 = strchr(p2, '\n');
    dict_info.email.assign(p2, p3-p2);
  }

  p2 = strstr(p1,"\nwebsite=");
  if (p2) {
    p2 = p2 + sizeof("\nwebsite=") -1;
    p3 = strchr(p2, '\n');
    dict_info.website.assign(p2, p3-p2);
  }

  p2 = strstr(p1,"\ndate=");
  if (p2) {
    p2 = p2 + sizeof("\ndate=") -1;
    p3 = strchr(p2, '\n');
    dict_info.date.assign(p2, p3-p2);
  }

  p2 = strstr(p1,"\ndescription=");
  if (p2) {
    p2 = p2 + sizeof("\ndescription=") -1;
    p3 = strchr(p2, '\n');
    dict_info.description.assign(p2, p3-p2);
  }
  
  g_free(buffer);
  return true;				
}


//===================================================================
Dictionary::Dictionary(const gchar *_cache_dir) : cache_dir(_cache_dir)
{
  need_free_wordoffset=false;
  wordcount=0;
  bookname = NULL;
  idxfile = NULL;
  wordlist = NULL;
  idxdatabuffer = NULL;
  m_ifofilename=NULL;
}

Dictionary::~Dictionary()
{  
  g_free(bookname);
  if(idxfile){
    fclose(idxfile);
    
    if(need_free_wordoffset)
      g_free(wordoffset);  
  }
  else{
    if(wordlist)
      g_free(wordlist);    
  }
  g_free(m_ifofilename);
}

bool Dictionary::load(const char *ifofilename)
{	
  gulong idxfilesize;
  if(!load_ifofile(ifofilename, &idxfilesize))
    return false;

  gchar fullfilename[256];
	
  strcpy(fullfilename, ifofilename);
  strcpy(fullfilename+strlen(fullfilename)-sizeof("ifo") +1, "dict.dz");
	
  if(g_file_test(fullfilename, G_FILE_TEST_EXISTS)){
    dictdzfile = dict_data_open(fullfilename, 0);
    if(!dictdzfile){
      //g_message("open file %s failed!\n",fullfilename);
      return false;
    }
  }
  else {
    fullfilename[strlen(fullfilename)-3] = '\0';
    dictfile = fopen(fullfilename,"rb");
    if(!dictfile){
      //g_message("open file %s failed!\n",fullfilename);
      return false;
    }
  }

  strcpy(fullfilename, ifofilename);
  strcpy(fullfilename+strlen(fullfilename)-sizeof("ifo") +1, "idx.gz");	
	
  if(g_file_test(fullfilename, G_FILE_TEST_EXISTS)) {
    gzFile in = gzopen(fullfilename,"rb");
    if(in == NULL){
      //g_message("Open file %s failed!\n",fullfilename);
      return false;
    }

    idxdatabuffer = (gchar *)g_malloc(idxfilesize);

    gulong len;
    len = gzread(in, idxdatabuffer, idxfilesize);
    if (len < 0)
      return false;
    gzclose(in);
    if(len != idxfilesize)
      return false;
    loadwordlist();
  }
  else{
    fullfilename[strlen(fullfilename)-3] = '\0';
    if (!loadwordoffset(fullfilename, idxfilesize)) {
      if (wordoffset) {
	g_free(wordoffset);
	wordoffset = NULL;
      }
      return false;
    }
    if(!(idxfile = fopen (fullfilename, "rb")))
      return false;
    
    cur_wordindex = -2;	// so it is always invalid in GetWord();
  }
#ifdef DEBUG
  g_message("bookname: %s , wordcount %ld",bookname, wordcount);
#endif  
  return true;
}

bool Dictionary::load_ifofile(const char *ifofilename, gulong *idxfilesize)
{
  struct stat stats;		
  if (stat (ifofilename, &stats) == -1) {
    //g_message("File: %s don't exist!\n",idxfilename);
    return false;
  }

  FILE *file;
  if (!(file = fopen (ifofilename, "rb"))) {
    //g_message("Open file %s failed!\n",idxfilename);
    return false;
  }
  gchar *buffer = (gchar *)g_malloc (stats.st_size + 1);
  fread (buffer, 1, stats.st_size, file);
  buffer[stats.st_size] = '\0';
  fclose (file);
	
  if(!g_str_has_prefix(buffer, "StarDict's dict ifo file\nversion=2.4.2\n")) {
    g_warning("Bad dict ifo file %s, skiped!", ifofilename);
    g_free(buffer);
    return false;
  }
  gchar *p1= buffer + sizeof("StarDict's dict ifo file\nversion=2.4.2\n")-1 -1;
	
  gchar *p2,*p3;

  p2 = strstr(p1,"\nidxfilesize=");
  if(!p2){
    g_free(buffer);
    return false;
  }
  p3 = strchr(p2+ sizeof("\nidxfilesize=")-1,'\n');
  gchar *tmpstr = (gchar *)g_memdup(p2+sizeof("\nidxfilesize=")-1, p3-(p2+sizeof("\nidxfilesize=")-1)+1);
  tmpstr[p3-(p2+sizeof("\nidxfilesize=")-1)] = '\0';
  *idxfilesize = atol(tmpstr);
  g_free(tmpstr);

  p2 = strstr(p1,"\nwordcount=");
  if(!p2){
    g_free(buffer);
    return false;
  }
  p3 = strchr(p2+ sizeof("\nwordcount=")-1,'\n');
  tmpstr = (gchar *)g_memdup(p2+sizeof("\nwordcount=")-1, p3-(p2+sizeof("\nwordcount=")-1)+1);
  tmpstr[p3-(p2+sizeof("\nwordcount=")-1)] = '\0';
  wordcount = atol(tmpstr);
  g_free(tmpstr);
  
  p2 = strstr(p1,"\nbookname=");
  if(!p2){
    g_free(buffer);
    return false;
  }
  p3 = strchr(p2+ sizeof("\nbookname=")-1,'\n');
  bookname = (gchar *)g_memdup(p2+sizeof("\nbookname=")-1, p3-(p2+sizeof("\nbookname=")-1)+1);
  bookname[p3-(p2+sizeof("\nbookname=")-1)] = '\0';
  
  p2 = strstr(p1,"\nsametypesequence=");
  if(p2){
    p3 = strchr(p2+sizeof("\nsametypesequence=")-1,'\n');
    sametypesequence = (gchar *)g_memdup(p2+sizeof("\nsametypesequence=")-1, p3-(p2+sizeof("\nsametypesequence=")-1)+1);
    sametypesequence[p3-(p2+sizeof("\nsametypesequence=")-1)] = '\0';
  }

  g_free(buffer);
  m_ifofilename=g_strdup(ifofilename);
  return true;
}

void Dictionary::loadwordlist(void)
{
  wordlist = (gchar **)g_malloc((wordcount+1) * sizeof(gchar *));
  gchar *p1 = idxdatabuffer;

  std::vector<gunichar> alphabet;
  alphabet.push_back(g_unichar_tolower(g_utf8_get_char(p1)));
  glong alpha_index=0;
  alphabet_index.push_back(alpha_index);
  for(int i=0; i<wordcount; i++) {		
    wordlist[i] = p1;
    gunichar ch=g_unichar_tolower(g_utf8_get_char(p1));
    if(alphabet[alphabet.size()-1]!=ch){
      alphabet.push_back(ch);
      alphabet_index.push_back(i);
    }
    p1 += strlen(p1) +1 + 2*sizeof(glong);
  }
#ifdef DEBUG_SHOW_ALPHABET
  for(std::vector<gunichar>::iterator ptr=alphabet.begin(); ptr!=alphabet.end();
		  ++ptr)
	  printf("%c  ", *ptr);
  putchar('\n');
#endif  
  wordlist[wordcount] = p1;
  if(!alphabet.empty()){/*may be it's happen:)*/
    gunichar prev_val=alphabet[0];
    TSeqBundle seq_bundle(prev_val, prev_val);
    bundle_list.push_back(seq_bundle);
    for(size_t i=1; i<alphabet.size(); ++i){
      gunichar ch=alphabet[i];
      if(ch!=prev_val+1){
	seq_bundle.val=ch;
	seq_bundle.dif=ch-i;
	bundle_list.push_back(seq_bundle);
      }
      prev_val=ch;
    }    
  }
}

bool Dictionary::loadwordoffset(const char *idxfilename, gulong idxfilesize)
{
  bool res=true;
  gchar 
    *oftfilename=NULL,
    *oftfilename2=NULL;

  oftfilename=g_strdup_printf("%s.oft", idxfilename);
  if(cache_dir){
    gchar *basename=g_path_get_basename(idxfilename);
    oftfilename2=g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s.oft", cache_dir, basename);
    g_free(basename);
  }

  wordoffset = NULL;
#define MAGIC_CACHE_DATA "Cache StarDict data, Version 0.03"
  if(map_wordoffset.open(oftfilename) 
     && strcmp(map_wordoffset.begin(), MAGIC_CACHE_DATA)==0
     || 
     cache_dir && map_wordoffset.open(oftfilename2) 
     && strcmp(map_wordoffset.begin(), MAGIC_CACHE_DATA)==0){
#ifdef DEBUG	  
      g_message("load cache");
#endif      
      const char *ptr=map_wordoffset.begin();
      ptr+=strlen(MAGIC_CACHE_DATA)+1;
      size_t bundle_list_size=*((size_t *)ptr);
      ptr+=sizeof(size_t);
      bundle_list.resize(bundle_list_size);
      memcpy(&(bundle_list[0]), ptr, bundle_list_size*sizeof(TSeqBundle));
      ptr+=sizeof(TSeqBundle)*bundle_list_size;
      size_t alphabet_index_size=*((size_t *)ptr);
      ptr+=sizeof(size_t);
      alphabet_index.resize(alphabet_index_size);
      memcpy(&(alphabet_index[0]), ptr, sizeof(glong)*alphabet_index_size);
      ptr+=sizeof(glong)*alphabet_index_size;
#if 1
      wordoffset=(glong *)ptr;
#else      
      wordoffset = (glong *)g_memdup(ptr, (wordcount+1) * sizeof(glong));
      need_free_wordoffset=true;
      map_wordoffset.close();
#endif      
  }
  else{
    map_wordoffset.close();

    TMapFile map_file;
    if(!map_file.open(idxfilename, idxfilesize)){
      res=false;
      goto out;
    }
    idxdatabuffer=(gchar *)map_file.begin();
    
    wordoffset = (glong *)g_malloc((wordcount+1)*sizeof(glong));
    need_free_wordoffset=true;
    const gchar *p1 = idxdatabuffer;

    std::vector<gunichar> alphabet;
    alphabet.push_back(g_unichar_tolower(g_utf8_get_char(p1)));
    glong alpha_index=0;
    alphabet_index.push_back(alpha_index);
    for(int i=0; i<wordcount; ++i){
    
      wordoffset[i] = p1 - idxdatabuffer;

      gunichar ch=g_unichar_tolower(g_utf8_get_char(p1));
      if(alphabet[alphabet.size()-1]!=ch){
	alphabet.push_back(ch);
	alphabet_index.push_back(i);
      }
      p1 += strlen(p1) +1 + 2*sizeof(glong);
      // We can check the word len < 256 here.
      // or we can save the max word length, then wordentry_buf=g_malloc(max_wordlen);
      // but wordentry_buf[256] should be enough for most case. 
      //Get the max length will slow down the loading a little.
    }
    wordoffset[wordcount] = p1 - idxdatabuffer;
    if(!alphabet.empty()){/*may be it's happen:)*/
      gunichar prev_val=alphabet[0];
      TSeqBundle seq_bundle(prev_val, prev_val);
      bundle_list.push_back(seq_bundle);
      for(size_t i=1; i<alphabet.size(); ++i){
	gunichar ch=alphabet[i];
	if(ch!=prev_val+1){
	  seq_bundle.val=ch;
	  seq_bundle.dif=ch-i;
	  bundle_list.push_back(seq_bundle);
	}
	prev_val=ch;
      }
      
    }
#ifdef DEBUG
    g_message("create cache");
#endif    
    FILE *f;
    if((f=fopen(oftfilename, "wb"))!=NULL || (cache_dir && (f=fopen(oftfilename2, "wb"))!=NULL)){
      fwrite(MAGIC_CACHE_DATA, sizeof(gchar), strlen(MAGIC_CACHE_DATA)+1, f);
      size_t bundle_list_size=bundle_list.size();
      fwrite(&bundle_list_size, sizeof(size_t), 1, f);
      fwrite(&(bundle_list[0]), sizeof(TSeqBundle), bundle_list_size, f);
      size_t alphabet_index_size=alphabet_index.size();
      fwrite(&alphabet_index_size, sizeof(size_t), 1, f);
      fwrite(&(alphabet_index[0]), sizeof(glong), alphabet_index_size, f);
      fwrite(wordoffset, sizeof(glong), wordcount+1, f);
      fclose(f);
    }
    else
      g_warning("Can not create %s.", oftfilename);    
  }
 out:
  g_free(oftfilename);
  g_free(oftfilename2);

  return res;
}

bool Dictionary::Lookup(const char* sWord,glong *pIndex)
{
  bool bFound=false;
  glong iTo=length()-1;
  if (stardict_strcmp(sWord, GetWord(0))<0) {
    *pIndex = 0;
  }
  else if (stardict_strcmp(sWord, GetWord(iTo)) >0 ) {
    *pIndex = INVALID_INDEX;
  }
  else {
    glong iThisIndex=0;
    glong iFrom=0;
#ifdef OPTIMIZE_LOOKUP
    gunichar ch=g_unichar_tolower(g_utf8_get_char(sWord));
    guint beg_ind=0, end_ind=bundle_list.size()-1,
      mid_ind, most_close_ind=guint(-1);

    gunichar tmp_ch;
    while((mid_ind=end_ind-beg_ind)>1){
      mid_ind>>=1;
      mid_ind+=beg_ind;
      tmp_ch=bundle_list[mid_ind].val;
      if(ch<tmp_ch)
	end_ind=mid_ind;
      else if(ch>tmp_ch)
	beg_ind=mid_ind;
      else{
	most_close_ind=mid_ind;
	break;
      }
    }
    if(most_close_ind==guint(-1)){
      if(ch>=bundle_list[end_ind].val)
	most_close_ind=end_ind;
      else
	most_close_ind=beg_ind;
    }
    
    //find out current sequence size
    guint next_ind=most_close_ind+1;
    guint seq_size=((next_ind!=bundle_list.size()) ? 
		    bundle_list[next_ind].val-bundle_list[next_ind].dif
		    :
		    alphabet_index.size()-1
		    )
      -(bundle_list[most_close_ind].val-bundle_list[most_close_ind].dif)+1;
    guint char_index=(ch-bundle_list[most_close_ind].val<seq_size) ? 
      ch-bundle_list[most_close_ind].dif : 
      bundle_list[most_close_ind].val-bundle_list[most_close_ind].dif+seq_size-1;
    iFrom=alphabet_index[char_index];

    if(char_index!=alphabet_index.size()-1)//if not last
      iTo=alphabet_index[char_index+1]-1;

#endif
    int cmpint;

    while(iFrom<=iTo){
      iThisIndex=(iFrom+iTo)/2;
      cmpint = stardict_strcmp(sWord, GetWord(iThisIndex));
      //g_message("lookup %s %d\n",GetWord(iThisIndex),cmpint);
      if(cmpint>0)
	iFrom=iThisIndex+1;
      else if(cmpint<0)
	iTo=iThisIndex-1;
      else
	break;
    }
    
    if(iFrom>iTo){
#if 0
      glong len = g_utf8_strlen(sWord, -1);
      gchar *last_str = g_utf8_offset_to_pointer(sWord, len-1);
      gunichar last = g_utf8_get_char(last_str);
      if (((g_unichar_isspace(last) || g_unichar_ispunct(last)) || g_unichar_isdigit(last))
	  && (g_ascii_strncasecmp(sWord, GetWord(iTo), (last_str - sWord))==0))
	*pIndex = iTo;      //previous
      else 
	*pIndex = iFrom;    //next
#endif
      *pIndex = iFrom;//next
    }
    else{
      *pIndex = iThisIndex;
      bFound=true;
    }
  }
  
  return bFound;
}

bool Dictionary::LookupWithRule(GPatternSpec *pspec, glong *aIndex, gint iBuffLen)
{
  gint iIndexCount=0;
  
  for(glong i=0; i<length() && iIndexCount<iBuffLen-1; i++)
    if(g_pattern_match_string(pspec, GetWord(i)))
      aIndex[iIndexCount++]=i;
    
  aIndex[iIndexCount]= -1; // -1 is the end.
	
  return (iIndexCount>0);
}

gchar *Dictionary::GetWord(glong index)
{
  if(idxfile){
    if(index == cur_wordindex +1){
      //needn't fseek().
    }// (index == cur_wordindex) seldom happen, so don't determine this here.
    else{
      fseek(idxfile, wordoffset[index], SEEK_SET);
    }
    cur_wordindex = index;
    
    fread(wordentry_buf, wordoffset[index+1] - wordoffset[index] - 2*sizeof(glong), 1, idxfile);
    //g_message("%s\n", wordentry_buf);
    fread(&wordentry_offset, sizeof(glong), 1, idxfile);
    wordentry_offset = g_ntohl(wordentry_offset);
    fread(&wordentry_size, sizeof(glong), 1, idxfile);
    wordentry_size = g_ntohl(wordentry_size);
    return wordentry_buf;
  }
  else{
    return wordlist[index];
  }
}

gchar *Dictionary::GetWordData(glong index)
{
  if(idxfile){
    if(index == cur_wordindex){
      // wordentry_offset and wordentry_size are already cached by GetWord();
    }
    else{
      cur_wordindex = index;
      fseek(idxfile, wordoffset[index+1] - 2*sizeof(glong), SEEK_SET);
      fread(&wordentry_offset, sizeof(glong), 1, idxfile);
      wordentry_offset = g_ntohl(wordentry_offset);
      fread(&wordentry_size, sizeof(glong), 1, idxfile);
      wordentry_size = g_ntohl(wordentry_size);			
    }		
    return DictBase::GetWordData(wordentry_offset, wordentry_size);
  }
  else {
    gchar *p1 = wordlist[index+1] - 2*sizeof(glong);
    glong offset, size;
    memcpy(&offset,p1,sizeof(glong));
    offset = g_ntohl(offset);
    p1 = p1 + sizeof(glong);
    memcpy(&size, p1, sizeof(glong));
    size = g_ntohl(size);
    return DictBase::GetWordData(offset, size);
  }
}

//===================================================================
Library::Library(gchar *_cache_dir) : cache_dir(_cache_dir)
{
  if(mkdir(cache_dir, S_IRWXU)==-1 && errno!=EEXIST){/*error of creating*/
    g_warning("Can not create cache_dir");
    g_free(cache_dir);
    cache_dir=NULL;
  }

  iMaxFuzzyDistance  = MAX_FUZZY_DISTANCE; //need to read from cfg.
}

Library::~Library()
{
  for(std::vector<Dictionary *>::iterator ptr=oLib.begin(); ptr!=oLib.end(); ++ptr)
    delete *ptr;

  g_free(cache_dir);
}

/********************************************************************/

void Library::LoadDir(gchar *dirname, const GSList *order_list, const GSList *disable_list)
{	
  GDir *dir = g_dir_open(dirname, 0, NULL);	
  if(dir){
    const gchar *filename;	
    gchar *fullfilename=NULL;
    Dictionary *dict;
    bool loaded;
    const GSList *tmplist1,*tmplist2;
    bool disabled;
    while((filename = g_dir_read_name(dir))!=NULL){
      g_free(fullfilename);
      fullfilename=g_build_filename(dirname, filename, NULL);
      if(g_file_test(fullfilename, G_FILE_TEST_IS_DIR)){
	LoadDir(fullfilename, order_list, disable_list);
      }
      else if(g_str_has_suffix(filename, ".ifo")){
	tmplist1 = order_list;
	loaded = false;
	while(tmplist1){
	  if(strcmp((gchar *)(tmplist1->data), fullfilename) == 0){
	    loaded = true;
	    break;
	  }
	  tmplist1 = g_slist_next(tmplist1);
	}
	if(loaded)
	  continue;
	
	tmplist2 = disable_list;
	disabled = false;
	while(tmplist2){
	  if(strcmp((gchar *)(tmplist2->data), fullfilename) == 0){
	    disabled = true;
	    break;
	  }
	  tmplist2 = g_slist_next(tmplist2);
	}
	if (disabled)
	  continue;
	
	dict = new Dictionary(cache_dir);
	if(dict->load(fullfilename)){
	  oLib.push_back(dict);					
	}
	else{
	  delete dict;
	}				
      }
    }	
    g_dir_close(dir);
    g_free(fullfilename);
  }  
}

void Library::Load(const GSList *order_list, const GSList  *disable_list, const gchar *data_dir)
{	
  gchar *idxfilename;
  const GSList *tmplist1,*tmplist2;
  bool disabled;
  Dictionary *dict;
  tmplist1 = order_list;	
  while(tmplist1){
    idxfilename = (gchar *)(tmplist1->data);
    tmplist1 = g_slist_next(tmplist1);
    tmplist2 = disable_list;
    disabled = false;
    while(tmplist2){
      if(strcmp((gchar *)(tmplist2->data), idxfilename) == 0) {
	disabled = true;
	break;
      }
      tmplist2 = g_slist_next(tmplist2);
    }
    if(disabled)
      continue;
    
    dict = new Dictionary(cache_dir);
    if(dict->load(idxfilename))
      oLib.push_back(dict);
    else 
      delete dict;			
  }

#ifdef _WIN32
  gchar *filename = g_build_filename(stardict_data_dir, "dic", NULL);
  LoadDir(filename, order_list, disable_list);
  g_free(filename);
#else
  gchar *home_dir=g_build_filename(g_get_home_dir(), ".stardict", "dic", NULL);
  LoadDir(home_dir, order_list, disable_list);
  g_free(home_dir);
  if(data_dir){
    gchar *data_dir_dic=g_build_filename(data_dir, "dic", NULL);
    LoadDir(data_dir_dic, order_list, disable_list);
    g_free(data_dir_dic);
  }
  else
    LoadDir(STARDICT_DATA_DIR "/dic", order_list, disable_list);
#endif
}

void Library::ReLoad(const GSList *order_list, const GSList *disable_list, const gchar *data_dir)
{
  for(std::vector<Dictionary *>::iterator ptr=oLib.begin(); ptr!=oLib.end(); ++ptr){
    delete *ptr;
    *ptr=NULL;
  }
  oLib.clear();
  Load(order_list, disable_list, data_dir);
}


void Library::load_dict_info_list_from_dir(const gchar *dir_name, TDictInfoList & dict_info_list)
{
  GDir *dir = g_dir_open(dir_name, 0, NULL);
  if(dir){
    const gchar *filename;	
    gchar *fullfilename=NULL;
    while((filename = g_dir_read_name(dir))!=NULL){	
      g_free(fullfilename);
      fullfilename=g_build_filename(dir_name, filename, NULL);
      if (g_file_test(fullfilename, G_FILE_TEST_IS_DIR)) {
	load_dict_info_list_from_dir(fullfilename, dict_info_list);
      }
      else if(g_str_has_suffix(filename,".ifo")){
	TDictInfo dict_info;
	if(DictBase::get_dict_info(fullfilename, dict_info))
	  dict_info_list.push_back(dict_info);
      }
    }		
    g_dir_close(dir);
    g_free(fullfilename);
  }	
}

void Library::get_dict_info_list(TDictInfoList & dict_info_list, const gchar *data_dir)
{
#ifdef _WIN32
  gchar *filename = g_build_filename(stardict_data_dir, "dic", NULL);
  load_dict_info_list_from_dir(filename, dict_info_list);
  g_free(filename);
#else
  gchar *home_dir=g_strdup_printf("%s/.stardict/dic", g_get_home_dir());
  load_dict_info_list_from_dir(home_dir, dict_info_list);
  g_free(home_dir);
  if(data_dir){
    gchar *data_dir_dic=g_strdup_printf("%s/dic", data_dir);
    load_dict_info_list_from_dir(data_dir_dic, dict_info_list);
    g_free(data_dir_dic);
  }
  else
    load_dict_info_list_from_dir(STARDICT_DATA_DIR "/dic", dict_info_list);
#endif
}

gchar *Library::poGetCurrentWord(glong * iCurrent)
{
  gchar * poCurrentWord = NULL;
  gchar *word;
  for(int iLib=0;iLib<total_libs(); iLib++) {
    if (iCurrent[iLib]==INVALID_INDEX)
      continue;
    if ( iCurrent[iLib]>=iLength(iLib) || iCurrent[iLib]<0)
      continue;
    if ( poCurrentWord == NULL ) {
      poCurrentWord = poGetWord(iCurrent[iLib],iLib);
    }
    else {
      word = poGetWord(iCurrent[iLib],iLib);
      if (stardict_strcmp(poCurrentWord, word) > 0 )
	poCurrentWord = word;
    }
  }
  return poCurrentWord;
}

gchar *Library::poGetNextWord(const gchar *sWord, glong * iCurrent)
{
  // the input can be:
  // (word,iCurrent),read word,write iNext to iCurrent,and return next word. used by TopWin::NextCallback();
  // (NULL,iCurrent),read iCurrent,write iNext to iCurrent,and return next word. used by AppCore::ListWords();
  gchar * poCurrentWord = NULL;
  gint iCurrentLib=0;

  gchar *word;
  for(int iLib=0;iLib<total_libs(); iLib++){
    if(sWord)
      oLib[iLib]->Lookup(sWord, &(iCurrent[iLib]));
    if(iCurrent[iLib]==INVALID_INDEX)
      continue;
    if(iCurrent[iLib]>=iLength(iLib) || iCurrent[iLib]<0)
      continue;
    if(poCurrentWord == NULL){
      poCurrentWord = poGetWord(iCurrent[iLib],iLib);
      iCurrentLib = iLib;
    }
    else{
      word = poGetWord(iCurrent[iLib],iLib);
      if(stardict_strcmp(poCurrentWord, word) > 0){
	poCurrentWord = word;
	iCurrentLib = iLib;
      }
    }
  }
  if(poCurrentWord){
    iCurrent[iCurrentLib]++;
    for(int iLib=0; iLib<total_libs(); iLib++) {
      if(iLib == iCurrentLib)
	continue;
      if(iCurrent[iLib]==INVALID_INDEX)
	continue;
      if(iCurrent[iLib]>=iLength(iLib) || iCurrent[iLib]<0)
	continue;
      if(strcmp(poCurrentWord, poGetWord(iCurrent[iLib],iLib)) == 0 )
	iCurrent[iLib]++;
    }
    poCurrentWord = poGetCurrentWord(iCurrent);
  }
  return poCurrentWord;
}

gchar *Library::poGetPreWord(glong * iCurrent)
{
  // used by TopWin::PreviousCallback(); the iCurrent is cached by AppCore::TopWinWordChange();
  gchar * poCurrentWord = NULL;
  int iCurrentLib=0;

  gchar *word;
  for(int iLib=0; iLib<total_libs(); iLib++){
    if(iCurrent[iLib]==INVALID_INDEX)
      continue;
    if(iCurrent[iLib]>=iLength(iLib) || iCurrent[iLib]<=0)
      continue;
    if(poCurrentWord == NULL){
      poCurrentWord = poGetWord(iCurrent[iLib]-1,iLib);
      iCurrentLib = iLib;
    }
    else{
      word = poGetWord(iCurrent[iLib]-1,iLib);
      if(stardict_strcmp(poCurrentWord, word) < 0){
	poCurrentWord = word;
	iCurrentLib = iLib;
      }
    }
  }
	
  if(poCurrentWord){
    iCurrent[iCurrentLib]--;
    for(int iLib=0; iLib<total_libs(); iLib++) {
      if(iLib == iCurrentLib)
	continue;
      if(iCurrent[iLib]==INVALID_INDEX)
	continue;
      if(iCurrent[iLib]>=iLength(iLib) || iCurrent[iLib]<=0)
	continue;
      if(strcmp(poCurrentWord, poGetWord(iCurrent[iLib]-1,iLib)) == 0)
	iCurrent[iLib]--;
    }
  }
  return poCurrentWord;
}

bool Library::LookupSimilarWord(const gchar* sWord,glong& iWordIndex,int iLib)
{
  glong iIndex;
  bool bFound=false;
  gchar *casestr;
  
  if(!bFound){
    // to lower case.
    casestr = g_utf8_strdown(sWord, -1);
    if(strcmp(casestr, sWord)){
      if(oLib[iLib]->Lookup(casestr,&iIndex))
	bFound=true;
    }
    g_free(casestr);
    // to upper case.
    if(!bFound){
      casestr = g_utf8_strup(sWord, -1);
      if(strcmp(casestr, sWord)){
	if(oLib[iLib]->Lookup(casestr,&iIndex))
	  bFound=true;
      }
      g_free(casestr);
    }	
    // to upper the first character.
    if(!bFound){
      gchar *nextchar = g_utf8_next_char(sWord);
      gchar *firstchar = g_utf8_strup(sWord, nextchar - sWord);
      casestr = g_strdup_printf("%s%s", firstchar, nextchar);
      g_free(firstchar);
      if(strcmp(casestr, sWord)){
	if(oLib[iLib]->Lookup(casestr,&iIndex))
	  bFound=true;
      }
      g_free(casestr);
    }
  }
  
  if(bIsPureEnglish(sWord)){		
    // If not Found , try other status of sWord.
    int iWordLen=strlen(sWord);
    gboolean isupcase;
    
    gchar *sNewWord = (gchar *)g_malloc(iWordLen + 1);
    
    //cut one char "s" or "d"
    if(!bFound && iWordLen>1) {
      isupcase = (sWord[iWordLen-1]=='S' || (!strncmp(&sWord[iWordLen-2],"ED",2)));
      if (isupcase || sWord[iWordLen-1]=='s' || (!strncmp(&sWord[iWordLen-2],"ed",2))) {
	strcpy(sNewWord,sWord);
	sNewWord[iWordLen-1]='\0'; // cut "s" or "d"
	if(oLib[iLib]->Lookup(sNewWord,&iIndex))
	  bFound=true;
	else if (isupcase || g_ascii_isupper(sWord[0])) {
	  casestr = g_ascii_strdown(sNewWord, -1);
	  if (strcmp(casestr, sNewWord)) {
	    if(oLib[iLib]->Lookup(casestr,&iIndex))
	      bFound=true;
	  }
	  g_free(casestr);
	}
      }
    }
    
    //cut "ly"
    if(!bFound && iWordLen>2) {
      isupcase = !strncmp(&sWord[iWordLen-2],"LY",2);
      if (isupcase || (!strncmp(&sWord[iWordLen-2],"ly",2))) {
	strcpy(sNewWord,sWord);
	sNewWord[iWordLen-2]='\0';  // cut "ly"
	if(iWordLen>5 && (sNewWord[iWordLen-3]==sNewWord[iWordLen-4])
	   && !bIsVowel(sNewWord[iWordLen-4]) && bIsVowel(sNewWord[iWordLen-5]) ){  //doubled
		      
	  sNewWord[iWordLen-3]='\0';
	  if( oLib[iLib]->Lookup(sNewWord,&iIndex) )
	    bFound=true;
	  else {
	    if (isupcase || g_ascii_isupper(sWord[0])) {
	      casestr = g_ascii_strdown(sNewWord, -1);
	      if (strcmp(casestr, sNewWord)) {
		if(oLib[iLib]->Lookup(casestr,&iIndex))
		  bFound=true;
	      }
	      g_free(casestr);
	    }
	    if (!bFound)
	      sNewWord[iWordLen-3]=sNewWord[iWordLen-4];  //restore
	  }					                    	
	}
	if (!bFound) {
	  if (oLib[iLib]->Lookup(sNewWord,&iIndex))
	    bFound=true;
	  else if (isupcase || g_ascii_isupper(sWord[0])) {
	    casestr = g_ascii_strdown(sNewWord, -1);
	    if (strcmp(casestr, sNewWord)) {
	      if(oLib[iLib]->Lookup(casestr,&iIndex))
		bFound=true;
	    }
	    g_free(casestr);
	  }
	}
      }
    }
    
    //cut "ing"
    if(!bFound && iWordLen>3) {
      isupcase = !strncmp(&sWord[iWordLen-3],"ING",3);
      if (isupcase || !strncmp(&sWord[iWordLen-3],"ing",3) ) {
	strcpy(sNewWord,sWord);
	sNewWord[iWordLen-3]='\0';
	if(iWordLen>6 && (sNewWord[iWordLen-4]==sNewWord[iWordLen-5]) &&
	   !bIsVowel(sNewWord[iWordLen-5]) && 
	   bIsVowel(sNewWord[iWordLen-6]) ) {  //doubled
	  
	  sNewWord[iWordLen-4]='\0';
	  if (oLib[iLib]->Lookup(sNewWord,&iIndex))
	    bFound=true;
	  else {
	    if (isupcase || g_ascii_isupper(sWord[0])) {
	      casestr = g_ascii_strdown(sNewWord, -1);
	      if (strcmp(casestr, sNewWord)) {
		if(oLib[iLib]->Lookup(casestr,&iIndex))
		  bFound=true;
	      }
	      g_free(casestr);
	    }
	    if (!bFound)
	      sNewWord[iWordLen-4]=sNewWord[iWordLen-5];  //restore
	  }
	}
	if( !bFound ) {
	  if (oLib[iLib]->Lookup(sNewWord,&iIndex))
	    bFound=true;
	  else if (isupcase || g_ascii_isupper(sWord[0])) {
	    casestr = g_ascii_strdown(sNewWord, -1);
	    if (strcmp(casestr, sNewWord)) {
	      if(oLib[iLib]->Lookup(casestr,&iIndex))
		bFound=true;
	    }
	    g_free(casestr);
	  }						
	}
	if(!bFound){
	  if (isupcase)
	    strcat(sNewWord,"E"); // add a char "E"
	  else
	    strcat(sNewWord,"e"); // add a char "e"
	  if(oLib[iLib]->Lookup(sNewWord,&iIndex))
	    bFound=true;
	  else if (isupcase || g_ascii_isupper(sWord[0])) {
	    casestr = g_ascii_strdown(sNewWord, -1);
	    if (strcmp(casestr, sNewWord)) {
	      if(oLib[iLib]->Lookup(casestr,&iIndex))
		bFound=true;
	    }
	    g_free(casestr);
	  }						
	}
      }
    }

    //cut two char "es"
    if(!bFound && iWordLen>3) {
      isupcase = (!strncmp(&sWord[iWordLen-2],"ES",2) && 
		  (sWord[iWordLen-3] == 'S' || sWord[iWordLen-3] == 'X' || sWord[iWordLen-3] == 'O'
		   || (iWordLen >4 && sWord[iWordLen-3] == 'H' && (sWord[iWordLen-4] == 'C' || sWord[iWordLen-4] == 'S'))));
      if (isupcase || 
	  (!strncmp(&sWord[iWordLen-2],"es",2) && 
	   (sWord[iWordLen-3] == 's' || sWord[iWordLen-3] == 'x' || sWord[iWordLen-3] == 'o'
	    || (iWordLen >4 && sWord[iWordLen-3] == 'h' && (sWord[iWordLen-4] == 'c' || sWord[iWordLen-4] == 's'))))){
	strcpy(sNewWord,sWord);
	sNewWord[iWordLen-2]='\0';
	if(oLib[iLib]->Lookup(sNewWord,&iIndex))
	  bFound=true;
	else if (isupcase || g_ascii_isupper(sWord[0])) {
	  casestr = g_ascii_strdown(sNewWord, -1);
	  if (strcmp(casestr, sNewWord)) {
	    if(oLib[iLib]->Lookup(casestr,&iIndex))
	      bFound=true;
	  }
	  g_free(casestr);
	}
      }
    }
    
    //cut "ed"
    if( !bFound && iWordLen>3) {
      isupcase = !strncmp(&sWord[iWordLen-2],"ED",2);
      if (isupcase || !strncmp(&sWord[iWordLen-2],"ed",2) ){
	strcpy(sNewWord,sWord);
	sNewWord[iWordLen-2]='\0';
	if ( iWordLen>5 && (sNewWord[iWordLen-3]==sNewWord[iWordLen-4])
	     && !bIsVowel(sNewWord[iWordLen-4]) && bIsVowel(sNewWord[iWordLen-5]) ){   //doubled	            
	  sNewWord[iWordLen-3]='\0';
	  if( oLib[iLib]->Lookup(sNewWord,&iIndex) )
	    bFound=true;
	  else {
	    if (isupcase || g_ascii_isupper(sWord[0])) {
	      casestr = g_ascii_strdown(sNewWord, -1);
	      if (strcmp(casestr, sNewWord)) {
		if(oLib[iLib]->Lookup(casestr,&iIndex))
		  bFound=true;
	      }
	      g_free(casestr);
	    }
	    if (!bFound)
	      sNewWord[iWordLen-3]=sNewWord[iWordLen-4];  //restore
	  }
	}
	if( !bFound ) {
	  if (oLib[iLib]->Lookup(sNewWord,&iIndex))
	    bFound=true;
	  else if (isupcase || g_ascii_isupper(sWord[0])) {
	    casestr = g_ascii_strdown(sNewWord, -1);
	    if (strcmp(casestr, sNewWord)) {
	      if(oLib[iLib]->Lookup(casestr,&iIndex))
		bFound=true;
	    }
	    g_free(casestr);
	  }
	}
      }
    }
    
    // cut "ied" , add "y".
    if(!bFound && iWordLen>3) {
      isupcase = !strncmp(&sWord[iWordLen-3],"IED",3);
      if (isupcase || (!strncmp(&sWord[iWordLen-3],"ied",3))){
	strcpy(sNewWord,sWord);
	sNewWord[iWordLen-3]='\0';
	if (isupcase)
	  strcat(sNewWord,"Y"); // add a char "Y"
	else
	  strcat(sNewWord,"y"); // add a char "y"
	if(oLib[iLib]->Lookup(sNewWord,&iIndex))
	  bFound=true;
	else if (isupcase || g_ascii_isupper(sWord[0])) {
	  casestr = g_ascii_strdown(sNewWord, -1);
	  if (strcmp(casestr, sNewWord)) {
	    if(oLib[iLib]->Lookup(casestr,&iIndex))
	      bFound=true;
	  }
	  g_free(casestr);
	}
      }
    }
    
    // cut "ies" , add "y".
    if(!bFound && iWordLen>3) {
      isupcase = !strncmp(&sWord[iWordLen-3],"IES",3);
      if (isupcase || (!strncmp(&sWord[iWordLen-3],"ies",3))){
	strcpy(sNewWord,sWord);
	sNewWord[iWordLen-3]='\0';
	if (isupcase)
	  strcat(sNewWord,"Y"); // add a char "Y"
	else
	  strcat(sNewWord,"y"); // add a char "y"
	if(oLib[iLib]->Lookup(sNewWord,&iIndex))
	  bFound=true;
	else if (isupcase || g_ascii_isupper(sWord[0])) {
	  casestr = g_ascii_strdown(sNewWord, -1);
	  if (strcmp(casestr, sNewWord)) {
	    if(oLib[iLib]->Lookup(casestr,&iIndex))
	      bFound=true;
	  }
	  g_free(casestr);
	}
      }
    }
    
    // cut "er".
    if(!bFound && iWordLen>2) {
      isupcase = !strncmp(&sWord[iWordLen-2],"ER",2);
      if (isupcase || (!strncmp(&sWord[iWordLen-2],"er",2))){
	strcpy(sNewWord,sWord);
	sNewWord[iWordLen-2]='\0';
	if(oLib[iLib]->Lookup(sNewWord,&iIndex))
	  bFound=true;
	else if (isupcase || g_ascii_isupper(sWord[0])) {
	  casestr = g_ascii_strdown(sNewWord, -1);
	  if (strcmp(casestr, sNewWord)) {
	    if(oLib[iLib]->Lookup(casestr,&iIndex))
	      bFound=true;
	  }
	  g_free(casestr);
	}
      }
    }
    
    // cut "est".
    if(!bFound && iWordLen>3) {
      isupcase = !strncmp(&sWord[iWordLen-3], "EST", 3);
      if (isupcase || (!strncmp(&sWord[iWordLen-3],"est", 3))){
	strcpy(sNewWord,sWord);
	sNewWord[iWordLen-3]='\0';
	if(oLib[iLib]->Lookup(sNewWord,&iIndex))
	  bFound=true;
	else if (isupcase || g_ascii_isupper(sWord[0])) {
	  casestr = g_ascii_strdown(sNewWord, -1);
	  if (strcmp(casestr, sNewWord)) {
	    if(oLib[iLib]->Lookup(casestr,&iIndex))
	      bFound=true;
	  }
	  g_free(casestr);
	}
      }
    }
    
    g_free(sNewWord);
  }	
  
  if (bFound)
    iWordIndex = iIndex;
  else {
    //don't change iWordIndex here.
    //when LookupSimilarWord all failed too, we want to use the old LookupWord index to list words.
    //iWordIndex = INVALID_INDEX;
  }
  
  return bFound;
}

bool Library::SimpleLookupWord(const gchar* sWord, glong& iWordIndex, int iLib)
{
  bool bFound = oLib[iLib]->Lookup(sWord, &iWordIndex);
  if(!bFound)
    bFound = LookupSimilarWord(sWord, iWordIndex, iLib);
  return bFound;
}

bool Library::SimpleLookup(const gchar* sWord, gchar ** & ppWord, gchar ** & ppWordData, gchar * & SearchWord)
{
  if(sWord==NULL || sWord[0]=='\0')
    return true;
  SearchWord = g_strdup(sWord);
  char * EndPointer,*P1,*P2;
  P1=(char *)sWord;
  P2=SearchWord;
  // delete chinese space at the begining
  while( *P1 && g_unichar_isspace(g_utf8_get_char(P1)))
    P1 = g_utf8_next_char(P1);
  //format word, delete any spilth blanks.    
  while(*P1){
    if (g_unichar_isspace(g_utf8_get_char(P1))){
      *P2++=' ';
      P1 = g_utf8_next_char(P1);
      while(g_unichar_isspace(g_utf8_get_char(P1)))
	P1 = g_utf8_next_char(P1);
    }
    else{
      g_utf8_strncpy(P2,P1,1);
      P1 = g_utf8_next_char(P1);
      P2 = g_utf8_next_char(P2);
    }
  }
  *P2='\0';
  EndPointer=SearchWord+strlen(SearchWord);

  ppWord = (gchar **)g_malloc(sizeof(gchar *) * total_libs());
  ppWordData = (gchar **)g_malloc(sizeof(gchar *) * total_libs());

  //find the word use most biggest length
  while (EndPointer>SearchWord){
    // delete end spaces
    while ( EndPointer>SearchWord && *EndPointer==' ' )
      *EndPointer--='\0';
        
    glong iIndex;		

    bool bFound = false;
    for (int iLib=0; iLib<total_libs();iLib++){
      if(SimpleLookupWord(SearchWord,iIndex,iLib)){
	ppWord[iLib] = poGetWord(iIndex,iLib);
	ppWordData[iLib] = poGetWordData(iIndex,iLib);
	bFound = true;
      }
      else {
	ppWord[iLib] = NULL;
	ppWordData[iLib] = NULL;
      }
    }
    if(bFound)
      return true;
    
    // delete last word
    if (bIsPureEnglish(SearchWord)){
      while ( EndPointer>=SearchWord && *EndPointer!=' ' )
	EndPointer--;
      if (EndPointer>=SearchWord)
	*EndPointer='\0';
    }
    else{ // delete one character per time		
      EndPointer = g_utf8_find_prev_char(SearchWord,EndPointer);
      if (EndPointer)
	*EndPointer='\0';
      else
	EndPointer = SearchWord-1; // so < SearchWord
    }
  }

  // not found

  return false;
}

//the input can be:
// (sWord,NULL,false) look up the sWord.
// (sWord,piIndex,false),look up the sWord,and set piIndex to the new indexes that found.
// (sWord,piIndex,true), show word by piIndex's information. it will always found, so bTryMoreIfNotFound is useless.

bool Library::SimpleLookup(const char* sWord, glong* piIndex,
			   char ** & ppWord, char ** & ppWordData,
			   bool piIndexValid, 
			   bool bTryMoreIfNotFound)
{
  if(sWord==NULL || sWord[0]=='\0')
    return true;

  bool bFound = false;
  ppWord = (gchar **)g_malloc(sizeof(gchar *) * total_libs());
  ppWordData = (gchar **)g_malloc(sizeof(gchar *) * total_libs());
  glong *iIndex;
  if (!piIndex)
    iIndex = (glong *)g_malloc(sizeof(glong) * total_libs());
  else
    iIndex = piIndex;

  for(int iLib=0; iLib<total_libs();iLib++) {
    if(!piIndexValid) {
      if(LookupWord(sWord,iIndex[iLib],iLib)) {
	ppWord[iLib] = poGetWord(iIndex[iLib],iLib);
	ppWordData[iLib] = poGetWordData(iIndex[iLib],iLib);
	bFound = true;
      }
      else {
	ppWord[iLib] = NULL;
	ppWordData[iLib] = NULL;
      }
    }
    else{
      if(piIndex[iLib] != INVALID_INDEX && (!strcmp((ppWord[iLib] = poGetWord(piIndex[iLib],iLib)),sWord))) {
	ppWordData[iLib] = poGetWordData(piIndex[iLib],iLib);
	bFound = true;
      }
      else{
	ppWord[iLib] = NULL;
	ppWordData[iLib] = NULL;
      }
    }
  }
  if(!bFound && !piIndexValid){
    for(int iLib=0; iLib<total_libs();iLib++){
      if(LookupSimilarWord(sWord,iIndex[iLib],iLib)){
	ppWord[iLib] = poGetWord(iIndex[iLib],iLib);
	ppWordData[iLib] = poGetWordData(iIndex[iLib],iLib);
	bFound = true;
      }
      else{
	ppWord[iLib] = NULL;
	ppWordData[iLib] = NULL;
      }
    }
  }

  if(!bFound) {		
    if (bTryMoreIfNotFound) {		
      gchar *word = g_strdup(sWord);
      gchar *hword;
      hword = GetHeadWord(word);
      if (*hword) {
	if(strcmp(hword,sWord)!=0) {

	  for (int iLib=0; iLib<total_libs();iLib++) {
	    if(LookupWord(hword,iIndex[iLib],iLib)) {
	      ppWord[iLib] = poGetWord(iIndex[iLib],iLib);
	      ppWordData[iLib] = poGetWordData(iIndex[iLib],iLib);
	      bFound = true;
	    }
	    else {
	      ppWord[iLib] = NULL;
	      ppWordData[iLib] = NULL;
	    }
	  }
	  if (!bFound) {
	    for (int iLib=0;iLib<total_libs();iLib++) {
	      if (LookupSimilarWord(hword,iIndex[iLib],iLib)) {
		ppWord[iLib] = poGetWord(iIndex[iLib],iLib);
		ppWordData[iLib] = poGetWordData(iIndex[iLib],iLib);
		bFound = true;
	      }
	      else {
		ppWord[iLib] = NULL;
		ppWordData[iLib] = NULL;
	      }
	    }
	  }
	}
      }
     
      g_free(word);
    }
  }
  
  if(!piIndex)
    g_free(iIndex);
  
  return bFound;
}

int Library::FuzzystructCompare(const void * s1, const void * s2)
{
  if (s1==NULL || s2==NULL)
    return 0;
  const struct Fuzzystruct * o1 = (struct Fuzzystruct*)s1;
  const struct Fuzzystruct * o2 = (struct Fuzzystruct*)s2;
    
  if ( o1->iMatchWordDistance > o2->iMatchWordDistance )
    return 1;
  else if ( o1->iMatchWordDistance < o2->iMatchWordDistance )
    return -1;
  else if ( o1->pMatchWord && o2->pMatchWord )
    return stardict_strcmp(o1->pMatchWord, o2->pMatchWord);
  else
    return 0;
}

bool Library::LookupWithFuzzy(const gchar *sWord, Fuzzystruct * & oFuzzystruct, TProgressFunc progress_func)
{
  if(NULL==sWord || sWord[0] == '\0')
    return false;
             
  oFuzzystruct = (Fuzzystruct *)g_malloc(MAX_FUZZY_MATCH_ITEM*sizeof(Fuzzystruct));

  for(int i=0; i<MAX_FUZZY_MATCH_ITEM; i++){
    oFuzzystruct[i].pMatchWord = NULL;
    oFuzzystruct[i].iMatchWordDistance = iMaxFuzzyDistance;
  }
  int iMaxDistance = iMaxFuzzyDistance;
  int iDistance;
  bool Found = false;
  EditDistance oEditDistance;

  glong iCheckWordLen;
  int sCheckLen;
  const char *sCheck;
  gunichar *ucs4_str1,*ucs4_str2;
  glong ucs4_str2_len;
  char *sLowerCheckWord;
  gchar *sLowerWord = g_utf8_strdown(sWord, -1);	
  ucs4_str2 = g_utf8_to_ucs4_fast(sLowerWord,-1,&ucs4_str2_len);
  g_free(sLowerWord);
  for(int iLib=0; iLib<total_libs(); iLib++){
    if(progress_func)
      progress_func();
    if((stardict_strcmp(sWord, poGetWord(0,iLib))>=0) && 
       (stardict_strcmp(sWord, poGetWord(iLength(iLib)-1,iLib))<=0)){ 
      //there are Chinese dicts and English dicts...

      const int iwords = iLength(iLib);
      for (int index=0;index<iwords;index++){
	//ProcessGtkEvent(); // too slow if here
	sCheck = poGetWord(index,iLib);
	// tolower and skip too long or too short words
	sCheckLen = strlen(sCheck);
	iCheckWordLen = g_utf8_strlen(sCheck, sCheckLen);
	if (iCheckWordLen-ucs4_str2_len>=iMaxDistance || ucs4_str2_len-iCheckWordLen>=iMaxDistance )
	  continue;				
	sLowerCheckWord = g_utf8_strdown(sCheck, sCheckLen);
	if (iCheckWordLen > ucs4_str2_len)
	  (*g_utf8_offset_to_pointer(sLowerCheckWord, ucs4_str2_len)) = '\0';
	ucs4_str1 = g_utf8_to_ucs4_fast(sLowerCheckWord, -1,NULL);
	g_free(sLowerCheckWord);
	iDistance = oEditDistance.CalEditDistance(ucs4_str1,ucs4_str2,iMaxDistance);				
	g_free(ucs4_str1);								
	if(iDistance < iMaxDistance && iDistance < ucs4_str2_len){
	  // when ucs4_str2_len=1,2 we need less fuzzy.
                
	  Found = true;
	  bool bAlreadyInList = false;
	  int iMaxDistanceAt=0;
	  for (int j=0;j<MAX_FUZZY_MATCH_ITEM;j++){
	    if(oFuzzystruct[j].pMatchWord && strcmp(oFuzzystruct[j].pMatchWord,sCheck)==0 ){   
	      //already in list						
	      bAlreadyInList = true;
	      break;
	    }
	    //find the position,it will certainly be found (include the first time) as iMaxDistance is set by last time.
	    if(oFuzzystruct[j].iMatchWordDistance == iMaxDistance)
	      iMaxDistanceAt = j;	    
	  }
	  if(!bAlreadyInList ){
	    if(oFuzzystruct[iMaxDistanceAt].pMatchWord)
	      g_free(oFuzzystruct[iMaxDistanceAt].pMatchWord);
	    oFuzzystruct[iMaxDistanceAt].pMatchWord = g_strdup(sCheck);
	    oFuzzystruct[iMaxDistanceAt].iMatchWordDistance = iDistance;
	    // calc new iMaxDistance
	    iMaxDistance = iDistance;
	    for(int j=0; j<MAX_FUZZY_MATCH_ITEM; j++){
	      if(oFuzzystruct[j].iMatchWordDistance > iMaxDistance)
		iMaxDistance = oFuzzystruct[j].iMatchWordDistance;
	    } // calc new iMaxDistance
	  }   // add to list
	}   // find one
      }   // each word
    }   // ok for search
  }   // each lib
  g_free(ucs4_str2);
	

  if(Found)// sort with distance
    qsort(oFuzzystruct, MAX_FUZZY_MATCH_ITEM, sizeof(Fuzzystruct),
	  FuzzystructCompare);

  return Found;
}

bool Library::LookupWithFuzzy(const gchar *sWord, gchar *** & pppWord, gchar *** & pppWordData, gchar ** & ppOriginWord, gint & count, TProgressFunc progress_func)
{
  gint real_count;
  pppWord=pppWordData=NULL;
  ppOriginWord=NULL;
  if(NULL==sWord || sWord[0] == '\0')
    return false;
    	    
  Fuzzystruct oFuzzystruct[MAX_FLOAT_WINDOW_FUZZY_MATCH_ITEM];
  for(int i=0; i<MAX_FLOAT_WINDOW_FUZZY_MATCH_ITEM; i++){
    oFuzzystruct[i].pMatchWord = NULL;
    oFuzzystruct[i].iMatchWordDistance = iMaxFuzzyDistance;
  }
  int iMaxDistance = iMaxFuzzyDistance;
  int iDistance;
  bool Found = false;
  EditDistance oEditDistance;

  glong iCheckWordLen;
  int sCheckLen;
  const char * sCheck;
  gunichar *ucs4_str1,*ucs4_str2;
  glong ucs4_str2_len;    
  gchar *sLowerCheckWord;
  gchar *sLowerWord = g_utf8_strdown(sWord, -1);
  ucs4_str2 = g_utf8_to_ucs4_fast(sLowerWord, -1,&ucs4_str2_len);
  g_free(sLowerWord);
  for(int iLib=0; iLib<total_libs(); iLib++){
    if(progress_func)
      progress_func();
    if(stardict_strcmp(sWord, poGetWord(0, iLib))>=0 && 
       stardict_strcmp(sWord, poGetWord(iLength(iLib)-1,iLib))<=0){
      //there are Chinese dicts and English dicts...
      const int iwords = iLength(iLib);
      for (int index=0;index<iwords;index++){
	//ProcessGtkEvent(); // too slow if here
	sCheck = poGetWord(index,iLib);
	// tolower and skip too long or too short words
	sCheckLen = strlen(sCheck);
	iCheckWordLen = g_utf8_strlen(sCheck, sCheckLen);
	if (iCheckWordLen-ucs4_str2_len>=iMaxDistance || 
	    ucs4_str2_len-iCheckWordLen>=iMaxDistance )
	  continue;
	sLowerCheckWord = g_utf8_strdown(sCheck, sCheckLen);
	if (iCheckWordLen > ucs4_str2_len)
	  (*g_utf8_offset_to_pointer(sLowerCheckWord, ucs4_str2_len)) = '\0';
	ucs4_str1 = g_utf8_to_ucs4_fast(sLowerCheckWord, -1,NULL);
	g_free(sLowerCheckWord);
	iDistance = oEditDistance.CalEditDistance(ucs4_str1,ucs4_str2,iMaxDistance);				
	g_free(ucs4_str1);				
	if ( iDistance < iMaxDistance && iDistance < ucs4_str2_len){
	  // when ucs4_str2_len=1,2 we need less fuzzy.
               
	  Found = true;
	  bool bAlreadyInList = false;
	  int iMaxDistanceAt=0;
	  for (int j=0;j<MAX_FLOAT_WINDOW_FUZZY_MATCH_ITEM;j++){
	    if ( oFuzzystruct[j].pMatchWord && 
		 strcmp(oFuzzystruct[j].pMatchWord,sCheck)==0 ){
	      //already in list
						
	      bAlreadyInList = true;
	      break;
	    }
	    if ( oFuzzystruct[j].iMatchWordDistance == iMaxDistance ){
	      iMaxDistanceAt = j;
	    }
	  }
	  if (!bAlreadyInList ){
	    if (oFuzzystruct[iMaxDistanceAt].pMatchWord)
	      g_free(oFuzzystruct[iMaxDistanceAt].pMatchWord);
	    oFuzzystruct[iMaxDistanceAt].pMatchWord = g_strdup(sCheck);
	    oFuzzystruct[iMaxDistanceAt].iMatchWordDistance = iDistance;
	    // calc new iMaxDistance
	    iMaxDistance = iDistance;
	    for (int j=0;j<MAX_FLOAT_WINDOW_FUZZY_MATCH_ITEM;j++){
	      if ( oFuzzystruct[j].iMatchWordDistance > iMaxDistance )
		iMaxDistance = oFuzzystruct[j].iMatchWordDistance;
	    } // calc new iMaxDistance
	  }   // add to list
	}   // find one
      }   // each word
    }   // ok for search
  }   // each lib
  g_free(ucs4_str2);
	
  if (Found){
    // sort with distance
    qsort(oFuzzystruct,MAX_FLOAT_WINDOW_FUZZY_MATCH_ITEM,
	  sizeof(Fuzzystruct),  FuzzystructCompare);
    gint i;
    count=0;
    for(i=0;i<MAX_FLOAT_WINDOW_FUZZY_MATCH_ITEM;i++){
      if (oFuzzystruct[i].pMatchWord)
	count++;
      else
	break;
    }
    pppWord = (gchar ***)g_malloc(sizeof(gchar **) * count);
    pppWordData = (gchar ***)g_malloc(sizeof(gchar **) * count);
    ppOriginWord = (gchar **)g_malloc(sizeof(gchar *) * count);
    gchar **ppWord;
    gchar **ppWordData;
    glong iRetIndex;
    real_count=count;
    for(i=0; i<count; i++){
      bool bFound = false;
      ppWord = (gchar **)g_malloc(sizeof(gchar *) * total_libs());
      ppWordData = (gchar **)g_malloc(sizeof(gchar *) * total_libs());
      
      ppOriginWord[i] = oFuzzystruct[i].pMatchWord;
      for(gint iLib=0;iLib<total_libs(); iLib++){
	if(SimpleLookupWord(oFuzzystruct[i].pMatchWord, iRetIndex, iLib)){
	  ppWord[iLib] = poGetWord(iRetIndex,iLib);
	  ppWordData[iLib] = poGetWordData(iRetIndex,iLib);
	  bFound = true;
	}
	else {
	  ppWord[iLib] = NULL;
	  ppWordData[iLib] = NULL;
	}
      }
      if(bFound){ // it is certainly be true.
	pppWord[i]=ppWord;
	pppWordData[i]=ppWordData;
      }
      else{
	  g_free(ppWord);
	  pppWord[i]=NULL;
	  g_free(ppWordData);
	  pppWordData[i]=NULL;
	  --real_count;
      }
    }
  }
  if(Found)
    Found=(real_count!=0);

  return Found;
}

int Library::MatchWordCompare(const void * s1, const void * s2)
{
  const gchar **o1 = (const gchar **)s1;
  const gchar **o2 = (const gchar **)s2;
  return stardict_strcmp(*o1, *o2);
}

gint Library::LookupWithRule(const gchar *word, gchar ** & ppMatchWord, TProgressFunc progress_func)
{		
  glong aiIndex[MAX_MATCH_ITEM_PER_LIB+1];
  gint iMatchCount = 0;
  ppMatchWord = (gchar **)g_malloc(sizeof(gchar *)*MAX_MATCH_ITEM_PER_LIB*total_libs());
  GPatternSpec *pspec = g_pattern_spec_new(word);
	
  for(gint iLib=0; iLib<total_libs();iLib++){
    //if(oLibs.LookdupWordsWithRule(pspec,aiIndex,MAX_MATCH_ITEM_PER_LIB+1-iMatchCount,iLib)) // -iMatchCount,so save time,but may got less result and the word may repeat.
    if(LookdupWordsWithRule(pspec,aiIndex,MAX_MATCH_ITEM_PER_LIB+1,iLib)){
      if(progress_func)
	progress_func();
      for(gint i=0;aiIndex[i]!=-1;i++){
	gchar * sMatchWord = poGetWord(aiIndex[i],iLib);
	gboolean bAlreadyInList = FALSE;
	for(gint j=0;j<iMatchCount;j++){
	  if ( strcmp(ppMatchWord[j],sMatchWord) == 0 ){//already in list                    
	    bAlreadyInList = TRUE;
	    break;
	  }
	}
	if ( !bAlreadyInList)
	  ppMatchWord[iMatchCount++] = g_strdup(sMatchWord);
      }
    }
  }
  g_pattern_spec_free(pspec);
  
  if(iMatchCount)// sort it.
    qsort(ppMatchWord, iMatchCount, sizeof(gchar *), MatchWordCompare);
  
  return iMatchCount;
}
