/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "../include/userdict.h"
#include "../include/splparser.h"
#include "../include/ngram.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cutils/log.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <assert.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <pthread.h>
#include <math.h>

namespace ime_pinyin {

#ifdef ___DEBUG_PERF___
static uint64 _ellapse_ = 0;
static struct timeval _tv_start_, _tv_end_;
#define DEBUG_PERF_BEGIN \
    do { \
      gettimeofday(&_tv_start_, NULL); \
    } while(0)
#define DEBUG_PERF_END \
    do { \
      gettimeofday(&_tv_end_, NULL); \
      _ellapse_ = (_tv_end_.tv_sec - _tv_start_.tv_sec) * 1000000 + \
                  (_tv_end_.tv_usec - _tv_start_.tv_usec); \
    } while(0)
#define LOGD_PERF(message) \
    LOGD("PERFORMANCE[%s] %llu usec.", message, _ellapse_);
#else
#define DEBUG_PERF_BEGIN
#define DEBUG_PERF_END
#define LOGD_PERF(message)
#endif

// XXX File load and write are thread-safe by g_mutex_
static pthread_mutex_t g_mutex_ = PTHREAD_MUTEX_INITIALIZER;
static struct timeval g_last_update_ = {0, 0};

inline uint32 UserDict::get_dict_file_size(UserDictInfo * info) {
  return (4 + info->lemma_size + (info->lemma_count << 3)
#ifdef ___PREDICT_ENABLED___
          + (info->lemma_count << 2)
#endif
#ifdef ___SYNC_ENABLED___
          + (info->sync_count << 2)
#endif
          + sizeof(*info));
}

inline LmaScoreType UserDict::translate_score(int raw_score) {
  // 1) ori_freq: original user frequency
  uint32 ori_freq = extract_score_freq(raw_score);
  // 2) lmt_off: lmt index (week offset for example)
  uint64 lmt_off = ((raw_score & 0xffff0000) >> 16);
  if (kUserDictLMTBitWidth < 16) {
    uint64 mask = ~(1 << kUserDictLMTBitWidth);
    lmt_off &= mask;
  }
  // 3) now_off: current time index (current week offset for example)
  // assuming load_time_ is around current time
  uint64 now_off = load_time_.tv_sec;
  now_off = (now_off - kUserDictLMTSince) / kUserDictLMTGranularity;
  now_off = (now_off << (64 - kUserDictLMTBitWidth));
  now_off = (now_off >> (64 - kUserDictLMTBitWidth));
  // 4) factor: decide expand-factor
  int delta = now_off - lmt_off;
  if (delta > 4)
    delta = 4;
  int factor = 80 - (delta << 4);

  double tf = (double)(dict_info_.total_nfreq + total_other_nfreq_);
  return (LmaScoreType)(log((double)factor * (double)ori_freq / tf)
                        * NGram::kLogValueAmplifier);
}

inline int UserDict::extract_score_freq(int raw_score) {
  // Frequence stored in lowest 16 bits
  int freq = (raw_score & 0x0000ffff);
  return freq;
}

inline uint64 UserDict::extract_score_lmt(int raw_score) {
  uint64 lmt = ((raw_score & 0xffff0000) >> 16);
  if (kUserDictLMTBitWidth < 16) {
    uint64 mask = ~(1 << kUserDictLMTBitWidth);
    lmt &= mask;
  }
  lmt = lmt * kUserDictLMTGranularity + kUserDictLMTSince;
  return lmt;
}

inline int UserDict::build_score(uint64 lmt, int freq) {
  lmt = (lmt - kUserDictLMTSince) / kUserDictLMTGranularity;
  lmt = (lmt << (64 - kUserDictLMTBitWidth));
  lmt = (lmt >> (64 - kUserDictLMTBitWidth));
  uint16 lmt16 = (uint16)lmt;
  int s = freq;
  s &= 0x0000ffff;
  s = (lmt16 << 16) | s;
  return s;
}

inline int64 UserDict::utf16le_atoll(uint16 *s, int len) {
  int64 ret = 0;
  if (len <= 0)
    return ret;

  int flag = 1;
  const uint16 * endp = s + len;
  if (*s == '-') {
    flag = -1;
    s++;
  } else if (*s == '+') {
    s++;
  }

  while (*s >= '0' && *s <= '9' && s < endp) {
    ret += ret * 10 + (*s) - '0';
    s++;
  }
  return ret * flag;
}

inline int UserDict::utf16le_lltoa(int64 v, uint16 *s, int size) {
  if (!s || size <= 0)
    return 0;
  uint16 *endp = s + size;
  int ret_len = 0;
  if (v < 0) {
    *(s++) = '-';
    ++ret_len;
    v *= -1;
  }

  uint16 *b = s;
  while (s < endp && v != 0) {
    *(s++) = '0' + (v % 10);
    v = v / 10;
    ++ret_len;
  }

  if (v != 0)
    return 0;

  --s;

  while (b < s) {
    *b = *s;
    ++b, --s;
  }

  return ret_len;
}

inline void UserDict::set_lemma_flag(uint32 offset, uint8 flag) {
  offset &= kUserDictOffsetMask;
  lemmas_[offset] |= flag;
}

inline char UserDict::get_lemma_flag(uint32 offset) {
  offset &= kUserDictOffsetMask;
  return (char)(lemmas_[offset]);
}

inline char UserDict::get_lemma_nchar(uint32 offset) {
  offset &= kUserDictOffsetMask;
  return (char)(lemmas_[offset + 1]);
}

inline uint16 * UserDict::get_lemma_spell_ids(uint32 offset) {
  offset &= kUserDictOffsetMask;
  return (uint16 *)(lemmas_ + offset + 2);
}

inline uint16 * UserDict::get_lemma_word(uint32 offset) {
  offset &= kUserDictOffsetMask;
  uint8 nchar = get_lemma_nchar(offset);
  return (uint16 *)(lemmas_ + offset + 2 + (nchar << 1));
}

inline LemmaIdType UserDict::get_max_lemma_id() {
  // When a lemma is deleted, we don't not claim its id back for
  // simplicity and performance
  return start_id_ + dict_info_.lemma_count - 1;
}

inline bool UserDict::is_valid_lemma_id(LemmaIdType id) {
  if (id >= start_id_ && id <= get_max_lemma_id())
    return true;
  return false;
}

inline bool UserDict::is_valid_state() {
  if (state_ == USER_DICT_NONE)
    return false;
  return true;
}

UserDict::UserDict()
    : start_id_(0),
      version_(0),
      lemmas_(NULL),
      offsets_(NULL),
      scores_(NULL),
      ids_(NULL),
#ifdef ___PREDICT_ENABLED___
      predicts_(NULL),
#endif
#ifdef ___SYNC_ENABLED___
      syncs_(NULL),
      sync_count_size_(0),
#endif
      offsets_by_id_(NULL),
      lemma_count_left_(0),
      lemma_size_left_(0),
      dict_file_(NULL),
      state_(USER_DICT_NONE) {
  memset(&dict_info_, 0, sizeof(dict_info_));
  memset(&load_time_, 0, sizeof(load_time_));
#ifdef ___CACHE_ENABLED___
  cache_init();
#endif
}

UserDict::~UserDict() {
  close_dict();
}

bool UserDict::load_dict(const char *file_name, LemmaIdType start_id,
                         LemmaIdType end_id) {
#ifdef ___DEBUG_PERF___
  DEBUG_PERF_BEGIN;
#endif
  dict_file_ = strdup(file_name);
  if (!dict_file_)
    return false;

  start_id_ = start_id;

  if (false == validate(file_name) && false == reset(file_name)) {
    goto error;
  }
  if (false == load(file_name, start_id)) {
    goto error;
  }

  state_ = USER_DICT_SYNC;

  gettimeofday(&load_time_, NULL);

#ifdef ___DEBUG_PERF___
  DEBUG_PERF_END;
  LOGD_PERF("load_dict");
#endif
  return true;
 error:
  free((void*)dict_file_);
  start_id_ = 0;
  return false;
}

bool UserDict::close_dict() {
  if (state_ == USER_DICT_NONE)
    return true;
  if (state_ == USER_DICT_SYNC)
    goto out;

  // If dictionary is written back by others,
  // we can not simply write back here
  // To do a safe flush, we have to discard all newly added
  // lemmas and try to reload dict file.
  pthread_mutex_lock(&g_mutex_);
  if (load_time_.tv_sec > g_last_update_.tv_sec ||
    (load_time_.tv_sec == g_last_update_.tv_sec &&
     load_time_.tv_usec > g_last_update_.tv_usec)) {
    write_back();
    gettimeofday(&g_last_update_, NULL);
  }
  pthread_mutex_unlock(&g_mutex_);

 out:
  free((void*)dict_file_);
  free(lemmas_);
  free(offsets_);
  free(offsets_by_id_);
  free(scores_);
  free(ids_);
#ifdef ___PREDICT_ENABLED___
  free(predicts_);
#endif

  version_ = 0;
  dict_file_ = NULL;
  lemmas_ = NULL;
#ifdef ___SYNC_ENABLED___
  syncs_ = NULL;
  sync_count_size_ = 0;
#endif
  offsets_ = NULL;
  offsets_by_id_ = NULL;
  scores_ = NULL;
  ids_ = NULL;
#ifdef ___PREDICT_ENABLED___
  predicts_ = NULL;
#endif

  memset(&dict_info_, 0, sizeof(dict_info_));
  lemma_count_left_ = 0;
  lemma_size_left_ = 0;
  state_ = USER_DICT_NONE;

  return true;
}

size_t UserDict::number_of_lemmas() {
  return dict_info_.lemma_count;
}

void UserDict::reset_milestones(uint16 from_step, MileStoneHandle from_handle) {
  return;
}

MileStoneHandle UserDict::extend_dict(MileStoneHandle from_handle,
                                      const DictExtPara *dep,
                                      LmaPsbItem *lpi_items,
                                      size_t lpi_max, size_t *lpi_num) {
  if (is_valid_state() == false)
    return 0;

  bool need_extend = false;

#ifdef ___DEBUG_PERF___
  DEBUG_PERF_BEGIN;
#endif
  *lpi_num = _get_lpis(dep->splids, dep->splids_extended + 1,
                       lpi_items, lpi_max, &need_extend);
#ifdef ___DEBUG_PERF___
  DEBUG_PERF_END;
  LOGD_PERF("extend_dict");
#endif
  return ((*lpi_num > 0 || need_extend) ? 1 : 0);
}

int UserDict::is_fuzzy_prefix_spell_id(
    const uint16 * id1, uint16 len1, const UserDictSearchable *searchable) {
  if (len1 < searchable->splids_len)
    return 0;

  SpellingTrie &spl_trie = SpellingTrie::get_instance();
  uint32 i = 0;
  for (i = 0; i < searchable->splids_len; i++) {
    const char py1 = *spl_trie.get_spelling_str(id1[i]);
    uint16 off = 8 * (i % 4);
    const char py2 = ((searchable->signature[i/4] & (0xff << off)) >> off);
    if (py1 == py2)
      continue;
    return 0;
  }
  return 1;
}

int UserDict::fuzzy_compare_spell_id(
    const uint16 * id1, uint16 len1, const UserDictSearchable *searchable) {
  if (len1 < searchable->splids_len)
    return -1;
  if (len1 > searchable->splids_len)
    return 1;

  SpellingTrie &spl_trie = SpellingTrie::get_instance();
  uint32 i = 0;
  for (i = 0; i < len1; i++) {
    const char py1 = *spl_trie.get_spelling_str(id1[i]);
    uint16 off = 8 * (i % 4);
    const char py2 = ((searchable->signature[i/4] & (0xff << off)) >> off);
    if (py1 == py2)
      continue;
    if (py1 > py2)
      return 1;
    return -1;
  }
  return 0;
}

bool UserDict::is_prefix_spell_id(
    const uint16 * fullids, uint16 fulllen,
    const UserDictSearchable *searchable) {
  if (fulllen < searchable->splids_len)
    return false;

  uint32 i = 0;
  for (; i < searchable->splids_len; i++) {
    uint16 start_id = searchable->splid_start[i];
    uint16 count = searchable->splid_count[i];
    if (fullids[i] >= start_id && fullids[i] < start_id + count)
      continue;
    else
      return false;
  }
  return true;
}

bool UserDict::equal_spell_id(
    const uint16 * fullids, uint16 fulllen,
    const UserDictSearchable *searchable) {
  if (fulllen != searchable->splids_len)
    return false;

  uint32 i = 0;
  for (; i < fulllen; i++) {
    uint16 start_id = searchable->splid_start[i];
    uint16 count = searchable->splid_count[i];
    if (fullids[i] >= start_id && fullids[i] < start_id + count)
      continue;
    else
      return false;
  }
  return true;
}

int32 UserDict::locate_first_in_offsets(const UserDictSearchable * searchable) {
  int32 begin = 0;
  int32 end = dict_info_.lemma_count - 1;
  int32 middle = -1;

  int32 first_prefix = middle;
  int32 last_matched = middle;

  while (begin <= end) {
    middle = (begin + end) >> 1;
    uint32 offset = offsets_[middle];
    uint8 nchar = get_lemma_nchar(offset);
    const uint16 * splids = get_lemma_spell_ids(offset);
    int cmp = fuzzy_compare_spell_id(splids, nchar, searchable);
    int pre = is_fuzzy_prefix_spell_id(splids, nchar, searchable);

    if (pre)
      first_prefix = middle;

    if (cmp < 0) {
      begin = middle + 1;
    } else if (cmp > 0) {
      end = middle - 1;
    } else {
      end = middle - 1;
      last_matched = middle;
    }
  }

  return first_prefix;
}

void UserDict::prepare_locate(UserDictSearchable *searchable,
                             const uint16 *splid_str,
                             uint16 splid_str_len) {
  searchable->splids_len = splid_str_len;
  memset(searchable->signature, 0, sizeof(searchable->signature));

  SpellingTrie &spl_trie = SpellingTrie::get_instance();
  uint32 i = 0;
  for (; i < splid_str_len; i++) {
    if (spl_trie.is_half_id(splid_str[i])) {
      searchable->splid_count[i] =
          spl_trie.half_to_full(splid_str[i],
                                &(searchable->splid_start[i]));
    } else {
      searchable->splid_count[i] = 1;
      searchable->splid_start[i] = splid_str[i];
    }
    const unsigned char py = *spl_trie.get_spelling_str(splid_str[i]);
    searchable->signature[i>>2] |= (py << (8 * (i % 4)));
  }
}

size_t UserDict::get_lpis(const uint16 *splid_str, uint16 splid_str_len,
                          LmaPsbItem *lpi_items, size_t lpi_max) {
  return _get_lpis(splid_str, splid_str_len, lpi_items, lpi_max, NULL);
}

size_t UserDict::_get_lpis(const uint16 *splid_str,
                           uint16 splid_str_len, LmaPsbItem *lpi_items,
                           size_t lpi_max, bool * need_extend) {
  bool tmp_extend;
  if (!need_extend)
    need_extend = &tmp_extend;

  *need_extend = false;

  if (is_valid_state() == false)
    return 0;
  if (lpi_max <= 0)
    return 0;

  if (0 == pthread_mutex_trylock(&g_mutex_)) {
    if (load_time_.tv_sec < g_last_update_.tv_sec ||
      (load_time_.tv_sec == g_last_update_.tv_sec &&
       load_time_.tv_usec < g_last_update_.tv_usec)) {
      // Others updated disk file, have to reload
      pthread_mutex_unlock(&g_mutex_);
      flush_cache();
    } else {
      pthread_mutex_unlock(&g_mutex_);
    }
  } else {
  }

  UserDictSearchable searchable;
  prepare_locate(&searchable, splid_str, splid_str_len);

  uint32 max_off = dict_info_.lemma_count;
#ifdef ___CACHE_ENABLED___
  int32 middle;
  uint32 start, count;
  bool cached = cache_hit(&searchable, &start, &count);
  if (cached) {
    middle = start;
    max_off = start + count;
  } else {
    middle = locate_first_in_offsets(&searchable);
    start = middle;
  }
#else
  int32 middle = locate_first_in_offsets(&searchable);
#endif

  if (middle == -1) {
#ifdef ___CACHE_ENABLED___
    if (!cached)
      cache_push(USER_DICT_MISS_CACHE, &searchable, 0, 0);
#endif
    return 0;
  }

  size_t lpi_current = 0;

  bool fuzzy_break = false;
  bool prefix_break = false;
  while ((size_t)middle < max_off && !fuzzy_break && !prefix_break) {
    if (lpi_current >= lpi_max)
      break;
    uint32 offset = offsets_[middle];
    // Ignore deleted lemmas
    if (offset & kUserDictOffsetFlagRemove) {
      middle++;
      continue;
    }
    uint8 nchar = get_lemma_nchar(offset);
    uint16 * splids = get_lemma_spell_ids(offset);
#ifdef ___CACHE_ENABLED___
    if (!cached && 0 != fuzzy_compare_spell_id(splids, nchar, &searchable)) {
#else
    if (0 != fuzzy_compare_spell_id(splids, nchar, &searchable)) {
#endif
      fuzzy_break = true;
    }

    if (prefix_break == false) {
      if (is_fuzzy_prefix_spell_id(splids, nchar, &searchable)) {
        if (*need_extend == false &&
            is_prefix_spell_id(splids, nchar, &searchable)) {
          *need_extend = true;
        }
      } else {
        prefix_break = true;
      }
    }

    if (equal_spell_id(splids, nchar, &searchable) == true) {
      lpi_items[lpi_current].psb = translate_score(scores_[middle]);
      lpi_items[lpi_current].id = ids_[middle];
      lpi_items[lpi_current].lma_len = nchar;
      lpi_current++;
    }
    middle++;
  }

#ifdef ___CACHE_ENABLED___
  if (!cached) {
    count = middle - start;
    cache_push(USER_DICT_CACHE, &searchable, start, count);
  }
#endif

  return lpi_current;
}

uint16 UserDict::get_lemma_str(LemmaIdType id_lemma, char16* str_buf,
                               uint16 str_max) {
  if (is_valid_state() == false)
    return 0;
  if (is_valid_lemma_id(id_lemma) == false)
    return 0;
  uint32 offset = offsets_by_id_[id_lemma - start_id_];
  uint8 nchar = get_lemma_nchar(offset);
  char16 * str = get_lemma_word(offset);
  uint16 m = nchar < str_max -1 ? nchar : str_max - 1;
  int i = 0;
  for (; i < m; i++) {
    str_buf[i] = str[i];
  }
  str_buf[i] = 0;
  return m;
}

uint16 UserDict::get_lemma_splids(LemmaIdType id_lemma, uint16 *splids,
                                  uint16 splids_max, bool arg_valid) {
  if (is_valid_lemma_id(id_lemma) == false)
    return 0;
  uint32 offset = offsets_by_id_[id_lemma - start_id_];
  uint8 nchar = get_lemma_nchar(offset);
  const uint16 * ids = get_lemma_spell_ids(offset);
  int i = 0;
  for (; i < nchar && i < splids_max; i++)
    splids[i] = ids[i];
  return i;
}

size_t UserDict::predict(const char16 last_hzs[], uint16 hzs_len,
                         NPredictItem *npre_items, size_t npre_max,
                         size_t b4_used) {
  uint32 new_added = 0;
#ifdef ___PREDICT_ENABLED___
  int32 end = dict_info_.lemma_count - 1;
  int j = locate_first_in_predicts((const uint16*)last_hzs, hzs_len);
  if (j == -1)
    return 0;

  while (j <= end) {
    uint32 offset = predicts_[j];
    // Ignore deleted lemmas
    if (offset & kUserDictOffsetFlagRemove) {
      j++;
      continue;
    }
    uint32 nchar = get_lemma_nchar(offset);
    uint16 * words = get_lemma_word(offset);
    uint16 * splids = get_lemma_spell_ids(offset);

    if (nchar <= hzs_len) {
      j++;
      continue;
    }

    if (memcmp(words, last_hzs, hzs_len << 1) == 0) {
      if (new_added >= npre_max) {
        return new_added;
      }
      uint32 cpy_len =
          (nchar < kMaxPredictSize ? (nchar << 1) : (kMaxPredictSize << 1))
          - (hzs_len << 1);
      npre_items[new_added].his_len = hzs_len;
      npre_items[new_added].psb = get_lemma_score(words, splids, nchar);
      memcpy(npre_items[new_added].pre_hzs, words + hzs_len, cpy_len);
      if ((cpy_len >> 1) < kMaxPredictSize) {
        npre_items[new_added].pre_hzs[cpy_len >> 1] = 0;
      }
      new_added++;
    } else {
      break;
    }

    j++;
  }
#endif
  return new_added;
}

int32 UserDict::locate_in_offsets(char16 lemma_str[], uint16 splid_str[],
                                  uint16 lemma_len) {
  int32 max_off = dict_info_.lemma_count;

  UserDictSearchable searchable;
  prepare_locate(&searchable, splid_str, lemma_len);
#ifdef ___CACHE_ENABLED___
  int32 off;
  uint32 start, count;
  bool cached = load_cache(&searchable, &start, &count);
  if (cached) {
    off = start;
    max_off = start + count;
  } else {
    off = locate_first_in_offsets(&searchable);
    start = off;
  }
#else
  int32 off = locate_first_in_offsets(&searchable);
#endif

  if (off == -1) {
    return off;
  }

  while (off < max_off) {
    uint32 offset = offsets_[off];
    if (offset & kUserDictOffsetFlagRemove) {
      off++;
      continue;
    }
    uint16 * splids = get_lemma_spell_ids(offset);
#ifdef ___CACHE_ENABLED___
    if (!cached && 0 != fuzzy_compare_spell_id(splids, lemma_len, &searchable))
      break;
#else
    if (0 != fuzzy_compare_spell_id(splids, lemma_len, &searchable))
      break;
#endif
    if (equal_spell_id(splids, lemma_len, &searchable) == true) {
      uint16 * str = get_lemma_word(offset);
      uint32 i = 0;
      for (i = 0; i < lemma_len; i++) {
        if (str[i] == lemma_str[i])
          continue;
        break;
      }
      if (i < lemma_len) {
        off++;
        continue;
      }
#ifdef ___CACHE_ENABLED___
      // No need to save_cache here, since current function is invoked by
      // put_lemma. It's rarely possible for a user input same lemma twice.
      // That means first time user type a new lemma, it is newly added into
      // user dictionary, then it's possible that user type the same lemma
      // again.
      // Another reason save_cache can not be invoked here is this function
      // aborts when lemma is found, and it never knows the count.
#endif
      return off;
    }
    off++;
  }

  return -1;
}

#ifdef ___PREDICT_ENABLED___
uint32 UserDict::locate_where_to_insert_in_predicts(
    const uint16 * words, int lemma_len) {
  int32 begin = 0;
  int32 end = dict_info_.lemma_count - 1;
  int32 middle = end;

  uint32 last_matched = middle;

  while (begin <= end) {
    middle = (begin + end) >> 1;
    uint32 offset = offsets_[middle];
    uint8 nchar = get_lemma_nchar(offset);
    const uint16 * ws = get_lemma_word(offset);

    uint32 minl = nchar < lemma_len ? nchar : lemma_len;
    uint32 k = 0;
    int cmp = 0;

    for (; k < minl; k++) {
      if (ws[k] < words[k]) {
        cmp = -1;
        break;
      } else if (ws[k] > words[k]) {
        cmp = 1;
        break;
      }
    }
    if (cmp == 0) {
      if (nchar < lemma_len)
        cmp = -1;
      else if (nchar > lemma_len)
        cmp = 1;
    }

    if (cmp < 0) {
      begin = middle + 1;
      last_matched = middle;
    } else if (cmp > 0) {
      end = middle - 1;
    } else {
      end = middle - 1;
      last_matched = middle;
    }
  }

  return last_matched;
}

int32 UserDict::locate_first_in_predicts(const uint16 * words, int lemma_len) {
  int32 begin = 0;
  int32 end = dict_info_.lemma_count - 1;
  int32 middle = -1;

  int32 last_matched = middle;

  while (begin <= end) {
    middle = (begin + end) >> 1;
    uint32 offset = offsets_[middle];
    uint8 nchar = get_lemma_nchar(offset);
    const uint16 * ws = get_lemma_word(offset);

    uint32 minl = nchar < lemma_len ? nchar : lemma_len;
    uint32 k = 0;
    int cmp = 0;

    for (; k < minl; k++) {
      if (ws[k] < words[k]) {
        cmp = -1;
        break;
      } else if (ws[k] > words[k]) {
        cmp = 1;
        break;
      }
    }
    if (cmp == 0) {
      if (nchar >= lemma_len)
        last_matched = middle;
      if (nchar < lemma_len)
        cmp = -1;
      else if (nchar > lemma_len)
        cmp = 1;
    }

    if (cmp < 0) {
      begin = middle + 1;
    } else if (cmp > 0) {
      end = middle - 1;
    } else {
      end = middle - 1;
    }
  }

  return last_matched;
}

#endif

LemmaIdType UserDict::get_lemma_id(char16 lemma_str[], uint16 splids[],
                                   uint16 lemma_len) {
  int32 off = locate_in_offsets(lemma_str, splids, lemma_len);
  if (off == -1) {
    return 0;
  }

  return ids_[off];
}

LmaScoreType UserDict::get_lemma_score(LemmaIdType lemma_id) {
  if (is_valid_state() == false)
    return 0;
  if (is_valid_lemma_id(lemma_id) == false)
    return 0;

  return translate_score(_get_lemma_score(lemma_id));
}

LmaScoreType UserDict::get_lemma_score(char16 lemma_str[], uint16 splids[],
                                uint16 lemma_len) {
  if (is_valid_state() == false)
    return 0;
  return translate_score(_get_lemma_score(lemma_str, splids, lemma_len));
}

int UserDict::_get_lemma_score(LemmaIdType lemma_id) {
  if (is_valid_state() == false)
    return 0;
  if (is_valid_lemma_id(lemma_id) == false)
    return 0;

  uint32 offset = offsets_by_id_[lemma_id - start_id_];

  uint32 nchar = get_lemma_nchar(offset);
  uint16 * spl = get_lemma_spell_ids(offset);
  uint16 * wrd = get_lemma_word(offset);

  int32 off = locate_in_offsets(wrd, spl, nchar);
  if (off == -1) {
    return 0;
  }

  return scores_[off];
}

int UserDict::_get_lemma_score(char16 lemma_str[], uint16 splids[],
                                uint16 lemma_len) {
  if (is_valid_state() == false)
    return 0;

  int32 off = locate_in_offsets(lemma_str, splids, lemma_len);
  if (off == -1) {
    return 0;
  }

  return scores_[off];
}

#ifdef ___SYNC_ENABLED___
void UserDict::remove_lemma_from_sync_list(uint32 offset) {
  offset &= kUserDictOffsetMask;
  uint32 i = 0;
  for (; i < dict_info_.sync_count; i++) {
    unsigned int off = (syncs_[i] & kUserDictOffsetMask);
    if (off == offset)
      break;
  }
  if (i < dict_info_.sync_count) {
    syncs_[i] = syncs_[dict_info_.sync_count - 1];
    dict_info_.sync_count--;
  }
}
#endif

#ifdef ___PREDICT_ENABLED___
void UserDict::remove_lemma_from_predict_list(uint32 offset) {
  offset &= kUserDictOffsetMask;
  uint32 i = 0;
  for (; i < dict_info_.lemma_count; i++) {
    unsigned int off = (predicts_[i] & kUserDictOffsetMask);
    if (off == offset) {
      predicts_[i] |= kUserDictOffsetFlagRemove;
      break;
    }
  }
}
#endif

bool UserDict::remove_lemma_by_offset_index(int offset_index) {
  if (is_valid_state() == false)
    return 0;

  int32 off = offset_index;
  if (off == -1) {
    return false;
  }

  uint32 offset = offsets_[off];
  uint32 nchar = get_lemma_nchar(offset);

  offsets_[off] |= kUserDictOffsetFlagRemove;

#ifdef ___SYNC_ENABLED___
  // Remove corresponding sync item
  remove_lemma_from_sync_list(offset);
#endif

#ifdef ___PREDICT_ENABLED___
  remove_lemma_from_predict_list(offset);
#endif
  dict_info_.free_count++;
  dict_info_.free_size += (2 + (nchar << 2));

  if (state_ < USER_DICT_OFFSET_DIRTY)
    state_ = USER_DICT_OFFSET_DIRTY;
  return true;
}

bool UserDict::remove_lemma(LemmaIdType lemma_id) {
  if (is_valid_state() == false)
    return 0;
  if (is_valid_lemma_id(lemma_id) == false)
    return false;
  uint32 offset = offsets_by_id_[lemma_id - start_id_];

  uint32 nchar = get_lemma_nchar(offset);
  uint16 * spl = get_lemma_spell_ids(offset);
  uint16 * wrd = get_lemma_word(offset);

  int32 off = locate_in_offsets(wrd, spl, nchar);

  return remove_lemma_by_offset_index(off);
}

void UserDict::flush_cache() {
  LemmaIdType start_id = start_id_;
  const char * file = strdup(dict_file_);
  if (!file)
    return;
  close_dict();
  load_dict(file, start_id, kUserDictIdEnd);
  free((void*)file);
#ifdef ___CACHE_ENABLED___
  cache_init();
#endif
  return;
}

bool UserDict::reset(const char *file) {
  FILE *fp = fopen(file, "w+");
  if (!fp) {
    return false;
  }
  uint32 version = kUserDictVersion;
  size_t wred = fwrite(&version, 1, 4, fp);
  UserDictInfo info;
  memset(&info, 0, sizeof(info));
  // By default, no limitation for lemma count and size
  // thereby, reclaim_ratio is never used
  wred += fwrite(&info, 1, sizeof(info), fp);
  if (wred != sizeof(info) + sizeof(version)) {
    fclose(fp);
    unlink(file);
    return false;
  }
  fclose(fp);
  return true;
}

bool UserDict::validate(const char *file) {
  // b is ignored in POSIX compatible os including Linux
  // while b is important flag for Windows to specify binary mode
  FILE *fp = fopen(file, "rb");
  if (!fp) {
    return false;
  }

  size_t size;
  size_t readed;
  uint32 version;
  UserDictInfo dict_info;

  // validate
  int err = fseek(fp, 0, SEEK_END);
  if (err) {
    goto error;
  }

  size = ftell(fp);
  if (size < 4 + sizeof(dict_info)) {
    goto error;
  }

  err = fseek(fp, 0, SEEK_SET);
  if (err) {
    goto error;
  }

  readed = fread(&version, 1, sizeof(version), fp);
  if (readed < sizeof(version)) {
    goto error;
  }
  if (version != kUserDictVersion) {
    goto error;
  }

  err = fseek(fp, -1 * sizeof(dict_info), SEEK_END);
  if (err) {
    goto error;
  }

  readed = fread(&dict_info, 1, sizeof(dict_info), fp);
  if (readed != sizeof(dict_info)) {
    goto error;
  }

  if (size != get_dict_file_size(&dict_info)) {
    goto error;
  }

  fclose(fp);
  return true;

 error:
  fclose(fp);
  return false;
}

bool UserDict::load(const char *file, LemmaIdType start_id) {
  if (0 != pthread_mutex_trylock(&g_mutex_)) {
    return false;
  }
  // b is ignored in POSIX compatible os including Linux
  // while b is important flag for Windows to specify binary mode
  FILE *fp = fopen(file, "rb");
  if (!fp) {
    pthread_mutex_unlock(&g_mutex_);
    return false;
  }

  size_t readed, toread;
  UserDictInfo dict_info;
  uint8 *lemmas = NULL;
  uint32 *offsets = NULL;
#ifdef ___SYNC_ENABLED___
  uint32 *syncs = NULL;
#endif
  uint32 *scores = NULL;
  uint32 *ids = NULL;
  uint32 *offsets_by_id = NULL;
#ifdef ___PREDICT_ENABLED___
  uint32 *predicts = NULL;
#endif
  size_t i;
  int err;

  err = fseek(fp, -1 * sizeof(dict_info), SEEK_END);
  if (err) goto error;

  readed = fread(&dict_info, 1, sizeof(dict_info), fp);
  if (readed != sizeof(dict_info)) goto error;

  lemmas = (uint8 *)malloc(
      dict_info.lemma_size +
      (kUserDictPreAlloc * (2 + (kUserDictAverageNchar << 2))));

  if (!lemmas) goto error;

  offsets = (uint32 *)malloc((dict_info.lemma_count + kUserDictPreAlloc) << 2);
  if (!offsets) goto error;

#ifdef ___PREDICT_ENABLED___
  predicts = (uint32 *)malloc((dict_info.lemma_count + kUserDictPreAlloc) << 2);
  if (!predicts) goto error;
#endif

#ifdef ___SYNC_ENABLED___
  syncs = (uint32 *)malloc((dict_info.sync_count + kUserDictPreAlloc) << 2);
  if (!syncs) goto error;
#endif

  scores = (uint32 *)malloc((dict_info.lemma_count + kUserDictPreAlloc) << 2);
  if (!scores) goto error;

  ids = (uint32 *)malloc((dict_info.lemma_count + kUserDictPreAlloc) << 2);
  if (!ids) goto error;

  offsets_by_id = (uint32 *)malloc(
      (dict_info.lemma_count + kUserDictPreAlloc) << 2);
  if (!offsets_by_id) goto error;

  err = fseek(fp, 4, SEEK_SET);
  if (err) goto error;

  readed = 0;
  while (readed < dict_info.lemma_size && !ferror(fp) && !feof(fp)) {
    readed += fread(lemmas + readed, 1, dict_info.lemma_size - readed, fp);
  }
  if (readed < dict_info.lemma_size)
    goto error;

  toread = (dict_info.lemma_count << 2);
  readed = 0;
  while (readed < toread && !ferror(fp) && !feof(fp)) {
    readed += fread((((uint8*)offsets) + readed), 1, toread - readed, fp);
  }
  if (readed < toread)
    goto error;

#ifdef ___PREDICT_ENABLED___
  toread = (dict_info.lemma_count << 2);
  readed = 0;
  while (readed < toread && !ferror(fp) && !feof(fp)) {
    readed += fread((((uint8*)predicts) + readed), 1, toread - readed, fp);
  }
  if (readed < toread)
    goto error;
#endif

  readed = 0;
  while (readed < toread && !ferror(fp) && !feof(fp)) {
    readed += fread((((uint8*)scores) + readed), 1, toread - readed, fp);
  }
  if (readed < toread)
    goto error;

#ifdef ___SYNC_ENABLED___
  toread = (dict_info.sync_count << 2);
  readed = 0;
  while (readed < toread && !ferror(fp) && !feof(fp)) {
    readed += fread((((uint8*)syncs) + readed), 1, toread - readed, fp);
  }
  if (readed < toread)
    goto error;
#endif

  for (i = 0; i < dict_info.lemma_count; i++) {
    ids[i] = start_id + i;
    offsets_by_id[i] = offsets[i];
  }

  lemmas_ = lemmas;
  offsets_ = offsets;
#ifdef ___SYNC_ENABLED___
  syncs_ = syncs;
  sync_count_size_ = dict_info.sync_count + kUserDictPreAlloc;
#endif
  offsets_by_id_ = offsets_by_id;
  scores_ = scores;
  ids_ = ids;
#ifdef ___PREDICT_ENABLED___
  predicts_ = predicts;
#endif
  lemma_count_left_ = kUserDictPreAlloc;
  lemma_size_left_ = kUserDictPreAlloc * (2 + (kUserDictAverageNchar << 2));
  memcpy(&dict_info_, &dict_info, sizeof(dict_info));
  state_ = USER_DICT_SYNC;

  fclose(fp);

  pthread_mutex_unlock(&g_mutex_);
  return true;

 error:
  if (lemmas) free(lemmas);
  if (offsets) free(offsets);
#ifdef ___SYNC_ENABLED___
  if (syncs) free(syncs);
#endif
  if (scores) free(scores);
  if (ids) free(ids);
  if (offsets_by_id) free(offsets_by_id);
#ifdef ___PREDICT_ENABLED___
  if (predicts) free(predicts);
#endif
  fclose(fp);
  pthread_mutex_unlock(&g_mutex_);
  return false;
}

void UserDict::write_back() {
  // XXX write back is only allowed from close_dict due to thread-safe sake
  if (state_ == USER_DICT_NONE || state_ == USER_DICT_SYNC)
    return;
  int fd = open(dict_file_, O_WRONLY);
  if (fd == -1)
    return;
  switch (state_) {
    case USER_DICT_DEFRAGMENTED:
      write_back_all(fd);
      break;
    case USER_DICT_LEMMA_DIRTY:
      write_back_lemma(fd);
      break;
    case USER_DICT_OFFSET_DIRTY:
      write_back_offset(fd);
      break;
    case USER_DICT_SCORE_DIRTY:
      write_back_score(fd);
      break;
#ifdef ___SYNC_ENABLED___
    case USER_DICT_SYNC_DIRTY:
      write_back_sync(fd);
      break;
#endif
    default:
      break;
  }
  // It seems truncate is not need on Linux, Windows except Mac
  // I am doing it here anyway for safety.
  off_t cur = lseek(fd, 0, SEEK_CUR);
  ftruncate(fd, cur);
  close(fd);
  state_ = USER_DICT_SYNC;
}

#ifdef ___SYNC_ENABLED___
void UserDict::write_back_sync(int fd) {
  int err = lseek(fd, 4 + dict_info_.lemma_size
                  + (dict_info_.lemma_count << 3)
#ifdef ___PREDICT_ENABLED___
                  + (dict_info_.lemma_count << 2)
#endif
                  , SEEK_SET);
  if (err == -1)
    return;
  write(fd, syncs_, dict_info_.sync_count << 2);
  write(fd, &dict_info_, sizeof(dict_info_));
}
#endif

void UserDict::write_back_offset(int fd) {
  int err = lseek(fd, 4 + dict_info_.lemma_size, SEEK_SET);
  if (err == -1)
    return;
  write(fd, offsets_, dict_info_.lemma_count << 2);
#ifdef ___PREDICT_ENABLED___
  write(fd, predicts_, dict_info_.lemma_count << 2);
#endif
  write(fd, scores_, dict_info_.lemma_count << 2);
#ifdef ___SYNC_ENABLED___
  write(fd, syncs_, dict_info_.sync_count << 2);
#endif
  write(fd, &dict_info_, sizeof(dict_info_));
}

void UserDict::write_back_score(int fd) {
  int err = lseek(fd, 4 + dict_info_.lemma_size
                  + (dict_info_.lemma_count << 2)
#ifdef ___PREDICT_ENABLED___
                  + (dict_info_.lemma_count << 2)
#endif
                  , SEEK_SET);
  if (err == -1)
    return;
  write(fd, scores_, dict_info_.lemma_count << 2);
#ifdef ___SYNC_ENABLED___
  write(fd, syncs_, dict_info_.sync_count << 2);
#endif
  write(fd, &dict_info_, sizeof(dict_info_));
}

void UserDict::write_back_lemma(int fd) {
  int err = lseek(fd, 4, SEEK_SET);
  if (err == -1)
    return;
  // New lemmas are always appended, no need to write whole lemma block
  size_t need_write = kUserDictPreAlloc *
      (2 + (kUserDictAverageNchar << 2)) - lemma_size_left_;
  err = lseek(fd, dict_info_.lemma_size - need_write, SEEK_CUR);
  if (err == -1)
    return;
  write(fd, lemmas_ + dict_info_.lemma_size - need_write, need_write);

  write(fd, offsets_,  dict_info_.lemma_count << 2);
#ifdef ___PREDICT_ENABLED___
  write(fd, predicts_,  dict_info_.lemma_count << 2);
#endif
  write(fd, scores_, dict_info_.lemma_count << 2);
#ifdef ___SYNC_ENABLED___
  write(fd, syncs_, dict_info_.sync_count << 2);
#endif
  write(fd, &dict_info_, sizeof(dict_info_));
}

void UserDict::write_back_all(int fd) {
  // XXX lemma_size is handled differently in writeall
  // and writelemma. I update lemma_size and lemma_count in different
  // places for these two cases. Should fix it to make it consistent.
  int err = lseek(fd, 4, SEEK_SET);
  if (err == -1)
    return;
  write(fd, lemmas_, dict_info_.lemma_size);
  write(fd, offsets_, dict_info_.lemma_count << 2);
#ifdef ___PREDICT_ENABLED___
  write(fd, predicts_, dict_info_.lemma_count << 2);
#endif
  write(fd, scores_, dict_info_.lemma_count << 2);
#ifdef ___SYNC_ENABLED___
  write(fd, syncs_, dict_info_.sync_count << 2);
#endif
  write(fd, &dict_info_, sizeof(dict_info_));
}

#ifdef ___CACHE_ENABLED___
bool UserDict::load_cache(UserDictSearchable *searchable,
                          uint32 *offset, uint32 *length) {
  UserDictCache *cache = &caches_[searchable->splids_len - 1];
  if (cache->head == cache->tail)
    return false;

  uint16 j, sig_len = kMaxLemmaSize / 4;
  uint16 i = cache->head;
  while (1) {
    j = 0;
    for (; j < sig_len; j++) {
      if (cache->signatures[i][j] != searchable->signature[j])
        break;
    }
    if (j < sig_len) {
      i++;
      if (i >= kUserDictCacheSize)
        i -= kUserDictCacheSize;
      if (i == cache->tail)
        break;
      continue;
    }
    *offset = cache->offsets[i];
    *length = cache->lengths[i];
    return true;
  }
  return false;
}

void UserDict::save_cache(UserDictSearchable *searchable,
                          uint32 offset, uint32 length) {
  UserDictCache *cache = &caches_[searchable->splids_len - 1];
  uint16 next = cache->tail;

  cache->offsets[next] = offset;
  cache->lengths[next] = length;
  uint16 sig_len = kMaxLemmaSize / 4;
  uint16 j = 0;
  for (; j < sig_len; j++) {
    cache->signatures[next][j] = searchable->signature[j];
  }

  if (++next >= kUserDictCacheSize) {
    next -= kUserDictCacheSize;
  }
  if (next == cache->head) {
    cache->head++;
    if (cache->head >= kUserDictCacheSize) {
      cache->head -= kUserDictCacheSize;
    }
  }
  cache->tail = next;
}

void UserDict::reset_cache() {
  memset(caches_, 0, sizeof(caches_));
}

bool UserDict::load_miss_cache(UserDictSearchable *searchable) {
  UserDictMissCache *cache = &miss_caches_[searchable->splids_len - 1];
  if (cache->head == cache->tail)
    return false;

  uint16 j, sig_len = kMaxLemmaSize / 4;
  uint16 i = cache->head;
  while (1) {
    j = 0;
    for (; j < sig_len; j++) {
      if (cache->signatures[i][j] != searchable->signature[j])
        break;
    }
    if (j < sig_len) {
      i++;
      if (i >= kUserDictMissCacheSize)
        i -= kUserDictMissCacheSize;
      if (i == cache->tail)
        break;
      continue;
    }
    return true;
  }
  return false;
}

void UserDict::save_miss_cache(UserDictSearchable *searchable) {
  UserDictMissCache *cache = &miss_caches_[searchable->splids_len - 1];
  uint16 next = cache->tail;

  uint16 sig_len = kMaxLemmaSize / 4;
  uint16 j = 0;
  for (; j < sig_len; j++) {
    cache->signatures[next][j] = searchable->signature[j];
  }

  if (++next >= kUserDictMissCacheSize) {
    next -= kUserDictMissCacheSize;
  }
  if (next == cache->head) {
    cache->head++;
    if (cache->head >= kUserDictMissCacheSize) {
      cache->head -= kUserDictMissCacheSize;
    }
  }
  cache->tail = next;
}

void UserDict::reset_miss_cache() {
  memset(miss_caches_, 0, sizeof(miss_caches_));
}

void UserDict::cache_init() {
  reset_cache();
  reset_miss_cache();
}

bool UserDict::cache_hit(UserDictSearchable *searchable,
                         uint32 *offset, uint32 *length) {
  bool hit = load_miss_cache(searchable);
  if (hit) {
    *offset = 0;
    *length = 0;
    return true;
  }
  hit = load_cache(searchable, offset, length);
  if (hit) {
    return true;
  }
  return false;
}

void UserDict::cache_push(UserDictCacheType type,
                         UserDictSearchable *searchable,
                         uint32 offset, uint32 length) {
  switch (type) {
    case USER_DICT_MISS_CACHE:
      save_miss_cache(searchable);
      break;
    case USER_DICT_CACHE:
      save_cache(searchable, offset, length);
      break;
    default:
      break;
  }
}

#endif

void UserDict::defragment(void) {
#ifdef ___DEBUG_PERF___
  DEBUG_PERF_BEGIN;
#endif
  if (is_valid_state() == false)
    return;
  // Fixup offsets_, set REMOVE flag to lemma's flag if needed
  size_t first_freed = 0;
  size_t first_inuse = 0;
  while (first_freed < dict_info_.lemma_count) {
    // Find first freed offset
    while ((offsets_[first_freed] & kUserDictOffsetFlagRemove) == 0 &&
            first_freed < dict_info_.lemma_count) {
      first_freed++;
    }
    if (first_freed < dict_info_.lemma_count) {
      // Save REMOVE flag to lemma flag
      int off = offsets_[first_freed];
      set_lemma_flag(off, kUserDictLemmaFlagRemove);
    } else {
      break;
    }
    // Find first inuse offse after first_freed
    first_inuse = first_freed + 1;
    while ((offsets_[first_inuse] & kUserDictOffsetFlagRemove) &&
           (first_inuse < dict_info_.lemma_count)) {
      // Save REMOVE flag to lemma flag
      int off = offsets_[first_inuse];
      set_lemma_flag(off, kUserDictLemmaFlagRemove);
      first_inuse++;
    }
    if (first_inuse >= dict_info_.lemma_count) {
      break;
    }
    // Swap offsets_
    int tmp = offsets_[first_inuse];
    offsets_[first_inuse] = offsets_[first_freed];
    offsets_[first_freed] = tmp;
    // Move scores_, no need to swap
    tmp = scores_[first_inuse];
    scores_[first_inuse] = scores_[first_freed];
    scores_[first_freed] = tmp;
    // Swap ids_
    LemmaIdType tmpid = ids_[first_inuse];
    ids_[first_inuse] = ids_[first_freed];
    ids_[first_freed] = tmpid;
    // Go on
    first_freed++;
  }
#ifdef ___PREDICT_ENABLED___
  // Fixup predicts_
  first_freed = 0;
  first_inuse = 0;
  while (first_freed < dict_info_.lemma_count) {
    // Find first freed offset
    while ((predicts_[first_freed] & kUserDictOffsetFlagRemove) == 0 &&
            first_freed < dict_info_.lemma_count) {
      first_freed++;
    }
    if (first_freed >= dict_info_.lemma_count)
      break;
    // Find first inuse offse after first_freed
    first_inuse = first_freed + 1;
    while ((predicts_[first_inuse] & kUserDictOffsetFlagRemove)
           && (first_inuse < dict_info_.lemma_count)) {
      first_inuse++;
    }
    if (first_inuse >= dict_info_.lemma_count) {
      break;
    }
    // Swap offsets_
    int tmp = predicts_[first_inuse];
    predicts_[first_inuse] = predicts_[first_freed];
    predicts_[first_freed] = tmp;
    // Go on
    first_freed++;
  }
#endif
  dict_info_.lemma_count = first_freed;
  // Fixup lemmas_
  size_t begin = 0;
  size_t end = 0;
  size_t dst = 0;
  int total_size = dict_info_.lemma_size + lemma_size_left_;
  int total_count = dict_info_.lemma_count + lemma_count_left_;
  size_t real_size = total_size - lemma_size_left_;
  while (dst < real_size) {
    unsigned char flag = get_lemma_flag(dst);
    unsigned char nchr = get_lemma_nchar(dst);
    if ((flag & kUserDictLemmaFlagRemove) == 0) {
      dst += nchr * 4 + 2;
      continue;
    }
    break;
  }
  if (dst >= real_size)
    return;

  end = dst;
  while (end < real_size) {
    begin = end + get_lemma_nchar(end) * 4 + 2;
 repeat:
    // not used any more
    if (begin >= real_size)
      break;
    unsigned char flag = get_lemma_flag(begin);
    unsigned char nchr = get_lemma_nchar(begin);
    if (flag & kUserDictLemmaFlagRemove) {
      begin += nchr * 4 + 2;
      goto repeat;
    }
    end = begin + nchr * 4 + 2;
    while (end < real_size) {
      unsigned char eflag = get_lemma_flag(end);
      unsigned char enchr = get_lemma_nchar(end);
      if ((eflag & kUserDictLemmaFlagRemove) == 0) {
        end += enchr * 4 + 2;
        continue;
      }
      break;
    }
    memmove(lemmas_ + dst, lemmas_ + begin, end - begin);
    for (size_t j = 0; j < dict_info_.lemma_count; j++) {
      if (offsets_[j] >= begin && offsets_[j] < end) {
        offsets_[j] -= (begin - dst);
        offsets_by_id_[ids_[j] - start_id_] = offsets_[j];
      }
#ifdef ___PREDICT_ENABLED___
      if (predicts_[j] >= begin && predicts_[j] < end) {
        predicts_[j] -= (begin - dst);
      }
#endif
    }
#ifdef ___SYNC_ENABLED___
    for (size_t j = 0; j < dict_info_.sync_count; j++) {
      if (syncs_[j] >= begin && syncs_[j] < end) {
        syncs_[j] -= (begin - dst);
      }
    }
#endif
    dst += (end - begin);
  }

  dict_info_.free_count = 0;
  dict_info_.free_size = 0;
  dict_info_.lemma_size = dst;
  lemma_size_left_ = total_size - dict_info_.lemma_size;
  lemma_count_left_ = total_count - dict_info_.lemma_count;

  // XXX Without following code,
  // offsets_by_id_ is not reordered.
  // That's to say, all removed lemmas' ids are not collected back.
  // There may not be room for addition of new lemmas due to
  // offsests_by_id_ reason, although lemma_size_left_ is fixed.
  // By default, we do want defrag as fast as possible, because
  // during defrag procedure, other peers can not write new lemmas
  // to user dictionary file.
  // XXX If write-back is invoked immediately after
  // this defragment, no need to fix up following in-mem data.
  for (uint32 i = 0; i < dict_info_.lemma_count; i++) {
    ids_[i] = start_id_ + i;
    offsets_by_id_[i] = offsets_[i];
  }

  state_ = USER_DICT_DEFRAGMENTED;

#ifdef ___DEBUG_PERF___
  DEBUG_PERF_END;
  LOGD_PERF("defragment");
#endif
}

#ifdef ___SYNC_ENABLED___
void UserDict::clear_sync_lemmas(unsigned int start, unsigned int end) {
  if (is_valid_state() == false)
    return;
  if (end > dict_info_.sync_count)
    end = dict_info_.sync_count;
  memmove(syncs_ + start, syncs_ + end, (dict_info_.sync_count - end) << 2);
  dict_info_.sync_count -= (end - start);
  if (state_ < USER_DICT_SYNC_DIRTY)
    state_ = USER_DICT_SYNC_DIRTY;
}

int UserDict::get_sync_count() {
  if (is_valid_state() == false)
    return 0;
  return dict_info_.sync_count;
}

LemmaIdType UserDict::put_lemma_no_sync(char16 lemma_str[], uint16 splids[],
                        uint16 lemma_len, uint16 count, uint64 lmt) {
  int again = 0;
 begin:
  LemmaIdType id;
  uint32 * syncs_bak = syncs_;
  syncs_ = NULL;
  id = _put_lemma(lemma_str, splids, lemma_len, count, lmt);
  syncs_ = syncs_bak;
  if (id == 0 && again == 0) {
    if ((dict_info_.limit_lemma_count > 0 &&
        dict_info_.lemma_count >= dict_info_.limit_lemma_count)
        || (dict_info_.limit_lemma_size > 0 &&
            dict_info_.lemma_size + (2 + (lemma_len << 2))
            > dict_info_.limit_lemma_size)) {
      // XXX Always reclaim and defrag in sync code path
      //     sync thread is background thread and ok with heavy work
      reclaim();
      defragment();
      flush_cache();
      again = 1;
      goto begin;
    }
  }
  return id;
}

int UserDict::put_lemmas_no_sync_from_utf16le_string(char16 * lemmas, int len) {
  int newly_added = 0;

  SpellingParser * spl_parser = new SpellingParser();
  if (!spl_parser) {
    return 0;
  }
#ifdef ___DEBUG_PERF___
  DEBUG_PERF_BEGIN;
#endif
  char16 *ptr = lemmas;

  // Extract pinyin,words,frequence,last_mod_time
  char16 * p = ptr, * py16 = ptr;
  char16 * hz16 = NULL;
  int py16_len = 0;
  uint16 splid[kMaxLemmaSize];
  int splid_len = 0;
  int hz16_len = 0;
  char16 * fr16 = NULL;
  int fr16_len = 0;

  while (p - ptr < len) {
    // Pinyin
    py16 = p;
    splid_len = 0;
    while (*p != 0x2c && (p - ptr) < len) {
      if (*p == 0x20)
        splid_len++;
      p++;
    }
    splid_len++;
    if (p - ptr == len)
      break;
    py16_len = p - py16;
    if (kMaxLemmaSize < splid_len) {
      break;
    }
    bool is_pre;
    int splidl = spl_parser->splstr16_to_idxs_f(
        py16, py16_len, splid, NULL, kMaxLemmaSize, is_pre);
    if (splidl != splid_len)
      break;
    // Phrase
    hz16 = ++p;
    while (*p != 0x2c && (p - ptr) < len) {
      p++;
    }
    hz16_len = p - hz16;
    if (hz16_len != splid_len)
      break;
    // Frequency
    fr16 = ++p;
    fr16_len = 0;
    while (*p != 0x2c && (p - ptr) < len) {
      p++;
    }
    fr16_len = p - fr16;
    uint32 intf = (uint32)utf16le_atoll(fr16, fr16_len);
    // Last modified time
    fr16 = ++p;
    fr16_len = 0;
    while (*p != 0x3b && (p - ptr) < len) {
      p++;
    }
    fr16_len = p - fr16;
    uint64 last_mod = utf16le_atoll(fr16, fr16_len);

    put_lemma_no_sync(hz16, splid, splid_len, intf, last_mod);
    newly_added++;

    p++;
  }

#ifdef ___DEBUG_PERF___
  DEBUG_PERF_END;
  LOGD_PERF("put_lemmas_no_sync_from_utf16le_string");
#endif
  return newly_added;
}

int UserDict::get_sync_lemmas_in_utf16le_string_from_beginning(
    char16 * str, int size, int * count) {
  int len = 0;
  *count = 0;

  int left_len = size;

  if (is_valid_state() == false)
    return len;

  SpellingTrie * spl_trie = &SpellingTrie::get_instance();
  if (!spl_trie) {
    return 0;
  }

  uint32 i;
  for (i = 0; i < dict_info_.sync_count; i++) {
    int offset = syncs_[i];
    uint32 nchar = get_lemma_nchar(offset);
    uint16 *spl = get_lemma_spell_ids(offset);
    uint16 *wrd = get_lemma_word(offset);
    int score = _get_lemma_score(wrd, spl, nchar);

    static char score_temp[32], *pscore_temp = score_temp;
    static char16 temp[256], *ptemp = temp;

    pscore_temp = score_temp;
    ptemp = temp;

    uint32 j;
    // Add pinyin
    for (j = 0; j < nchar; j++) {
      int ret_len = spl_trie->get_spelling_str16(
          spl[j], ptemp, temp + sizeof(temp) - ptemp);
      if (ret_len <= 0)
        break;
      ptemp += ret_len;
      if (ptemp < temp + sizeof(temp) - 1) {
        *(ptemp++) = ' ';
      } else {
        j = 0;
        break;
      }
    }
    if (j < nchar) {
      continue;
    }
    ptemp--;
    if (ptemp < temp + sizeof(temp) - 1) {
      *(ptemp++) = ',';
    } else {
      continue;
    }
    // Add phrase
    for (j = 0; j < nchar; j++) {
      if (ptemp < temp + sizeof(temp) - 1) {
        *(ptemp++) = wrd[j];
      } else {
        break;
      }
    }
    if (j < nchar) {
      continue;
    }
    if (ptemp < temp + sizeof(temp) - 1) {
      *(ptemp++) = ',';
    } else {
      continue;
    }
    // Add frequency
    uint32 intf = extract_score_freq(score);
    int ret_len = utf16le_lltoa(intf, ptemp, temp + sizeof(temp) - ptemp);
    if (ret_len <= 0)
      continue;
    ptemp += ret_len;
    if (ptemp < temp + sizeof(temp) - 1) {
      *(ptemp++) = ',';
    } else {
      continue;
    }
    // Add last modified time
    uint64 last_mod = extract_score_lmt(score);
    ret_len = utf16le_lltoa(last_mod, ptemp, temp + sizeof(temp) - ptemp);
    if (ret_len <= 0)
      continue;
    ptemp += ret_len;
    if (ptemp < temp + sizeof(temp) - 1) {
      *(ptemp++) = ';';
    } else {
      continue;
    }

    // Write to string
    int need_len = ptemp - temp;
    if (need_len > left_len)
      break;
    memcpy(str + len, temp, need_len * 2);
    left_len -= need_len;

    len += need_len;
    (*count)++;
  }

  if (len > 0) {
    if (state_ < USER_DICT_SYNC_DIRTY)
      state_ = USER_DICT_SYNC_DIRTY;
  }
  return len;
}

#endif

bool UserDict::state(UserDictStat * stat) {
  if (is_valid_state() == false)
    return false;
  if (!stat)
    return false;
  stat->version = version_;
  stat->file_name = dict_file_;
  stat->load_time.tv_sec = load_time_.tv_sec;
  stat->load_time.tv_usec = load_time_.tv_usec;
  pthread_mutex_lock(&g_mutex_);
  stat->last_update.tv_sec = g_last_update_.tv_sec;
  stat->last_update.tv_usec = g_last_update_.tv_usec;
  pthread_mutex_unlock(&g_mutex_);
  stat->disk_size = get_dict_file_size(&dict_info_);
  stat->lemma_count = dict_info_.lemma_count;
  stat->lemma_size = dict_info_.lemma_size;
  stat->delete_count = dict_info_.free_count;
  stat->delete_size = dict_info_.free_size;
#ifdef ___SYNC_ENABLED___
  stat->sync_count = dict_info_.sync_count;
#endif
  stat->limit_lemma_count = dict_info_.limit_lemma_count;
  stat->limit_lemma_size = dict_info_.limit_lemma_size;
  stat->reclaim_ratio = dict_info_.reclaim_ratio;
  return true;
}

void UserDict::set_limit(uint32 max_lemma_count,
                         uint32 max_lemma_size, uint32 reclaim_ratio) {
  dict_info_.limit_lemma_count = max_lemma_count;
  dict_info_.limit_lemma_size = max_lemma_size;
  if (reclaim_ratio > 100)
    reclaim_ratio = 100;
  dict_info_.reclaim_ratio = reclaim_ratio;
}

void UserDict::reclaim() {
  if (is_valid_state() == false)
    return;

  switch (dict_info_.reclaim_ratio) {
    case 0:
      return;
    case 100:
      // TODO: CLEAR to be implemented
      assert(false);
      return;
    default:
      break;
  }

  // XXX Reclaim is only based on count, not size
  uint32 count = dict_info_.lemma_count;
  int rc = count * dict_info_.reclaim_ratio / 100;

  UserDictScoreOffsetPair * score_offset_pairs = NULL;
  score_offset_pairs = (UserDictScoreOffsetPair *)malloc(
      sizeof(UserDictScoreOffsetPair) * rc);
  if (score_offset_pairs == NULL) {
    return;
  }

  for (int i = 0; i < rc; i++) {
    int s = scores_[i];
    score_offset_pairs[i].score = s;
    score_offset_pairs[i].offset_index = i;
  }

  for (int i = (rc + 1) / 2; i >= 0; i--)
    shift_down(score_offset_pairs, i, rc);

  for (uint32 i = rc; i < dict_info_.lemma_count; i++) {
    int s = scores_[i];
    if (s < score_offset_pairs[0].score) {
      score_offset_pairs[0].score = s;
      score_offset_pairs[0].offset_index = i;
      shift_down(score_offset_pairs, 0, rc);
    }
  }

  for (int i = 0; i < rc; i++) {
    int off = score_offset_pairs[i].offset_index;
    remove_lemma_by_offset_index(off);
  }
  if (rc > 0) {
    if (state_ < USER_DICT_OFFSET_DIRTY)
      state_ = USER_DICT_OFFSET_DIRTY;
  }

  free(score_offset_pairs);
}

inline void UserDict::swap(UserDictScoreOffsetPair * sop, int i, int j) {
  int s = sop[i].score;
  int p = sop[i].offset_index;
  sop[i].score = sop[j].score;
  sop[i].offset_index = sop[j].offset_index;
  sop[j].score = s;
  sop[j].offset_index = p;
}

void UserDict::shift_down(UserDictScoreOffsetPair * sop, int i, int n) {
  int par = i;
  while (par < n) {
    int left = par * 2 + 1;
    int right = left + 1;
    if (left >= n && right >= n)
      break;
    if (right >= n) {
      if (sop[left].score > sop[par].score) {
        swap(sop, left, par);
        par = left;
        continue;
      }
    } else if (sop[left].score > sop[right].score &&
               sop[left].score > sop[par].score) {
      swap(sop, left, par);
      par = left;
      continue;
    } else if (sop[right].score > sop[left].score &&
               sop[right].score > sop[par].score) {
      swap(sop, right, par);
      par = right;
      continue;
    }
    break;
  }
}

LemmaIdType UserDict::put_lemma(char16 lemma_str[], uint16 splids[],
                                uint16 lemma_len, uint16 count) {
  return _put_lemma(lemma_str, splids, lemma_len, count, time(NULL));
}

LemmaIdType UserDict::_put_lemma(char16 lemma_str[], uint16 splids[],
                                 uint16 lemma_len, uint16 count, uint64 lmt) {
#ifdef ___DEBUG_PERF___
  DEBUG_PERF_BEGIN;
#endif
  if (is_valid_state() == false)
    return 0;
  int32 off = locate_in_offsets(lemma_str, splids, lemma_len);
  if (off != -1) {
    int delta_score = count - scores_[off];
    dict_info_.total_nfreq += delta_score;
    scores_[off] = build_score(lmt, count);
    if (state_ < USER_DICT_SCORE_DIRTY)
      state_ = USER_DICT_SCORE_DIRTY;
#ifdef ___DEBUG_PERF___
    DEBUG_PERF_END;
    LOGD_PERF("_put_lemma(update)");
#endif
    return ids_[off];
  } else {
    if ((dict_info_.limit_lemma_count > 0 &&
        dict_info_.lemma_count >= dict_info_.limit_lemma_count)
        || (dict_info_.limit_lemma_size > 0 &&
            dict_info_.lemma_size + (2 + (lemma_len << 2))
            > dict_info_.limit_lemma_size)) {
      // XXX Don't defragment here, it's too time-consuming.
      return 0;
    }
    int flushed = 0;
    if (lemma_count_left_ == 0 ||
        lemma_size_left_ < (size_t)(2 + (lemma_len << 2))) {

      // XXX When there is no space for new lemma, we flush to disk
      // flush_cache() may be called by upper user
      // and better place shoule be found instead of here
      flush_cache();
      flushed = 1;
      // Or simply return and do nothing
      // return 0;
    }
#ifdef ___DEBUG_PERF___
    DEBUG_PERF_END;
    LOGD_PERF(flushed ? "_put_lemma(flush+add)" : "_put_lemma(add)");
#endif
    LemmaIdType id = append_a_lemma(lemma_str, splids, lemma_len, count, lmt);
#ifdef ___SYNC_ENABLED___
    if (syncs_ && id != 0) {
      queue_lemma_for_sync(id);
    }
#endif
    return id;
  }
  return 0;
}

#ifdef ___SYNC_ENABLED___
void UserDict::queue_lemma_for_sync(LemmaIdType id) {
  if (dict_info_.sync_count < sync_count_size_) {
    syncs_[dict_info_.sync_count++] = offsets_by_id_[id - start_id_];
  } else {
    uint32 * syncs = (uint32*)realloc(
        syncs_, (sync_count_size_ + kUserDictPreAlloc) << 2);
    if (syncs) {
      sync_count_size_ += kUserDictPreAlloc;
      syncs_ = syncs;
      syncs_[dict_info_.sync_count++] = offsets_by_id_[id - start_id_];
    }
  }
}
#endif

LemmaIdType UserDict::update_lemma(LemmaIdType lemma_id, int16 delta_count,
                                   bool selected) {
#ifdef ___DEBUG_PERF___
  DEBUG_PERF_BEGIN;
#endif
  if (is_valid_state() == false)
    return 0;
  if (is_valid_lemma_id(lemma_id) == false)
    return 0;
  uint32 offset = offsets_by_id_[lemma_id - start_id_];
  uint8 lemma_len = get_lemma_nchar(offset);
  char16 * lemma_str = get_lemma_word(offset);
  uint16 * splids = get_lemma_spell_ids(offset);

  int32 off = locate_in_offsets(lemma_str, splids, lemma_len);
  if (off != -1) {
    int score = scores_[off];
    int count = extract_score_freq(score);
    uint64 lmt = extract_score_lmt(score);
    if (count + delta_count > kUserDictMaxFrequency ||
        count + delta_count < count) {
      delta_count = kUserDictMaxFrequency - count;
    }
    count += delta_count;
    dict_info_.total_nfreq += delta_count;
    if (selected) {
      lmt = time(NULL);
    }
    scores_[off] = build_score(lmt, count);
    if (state_ < USER_DICT_SCORE_DIRTY)
      state_ = USER_DICT_SCORE_DIRTY;
#ifdef ___DEBUG_PERF___
    DEBUG_PERF_END;
    LOGD_PERF("update_lemma");
#endif
#ifdef ___SYNC_ENABLED___
    queue_lemma_for_sync(ids_[off]);
#endif
    return ids_[off];
  }
  return 0;
}

size_t UserDict::get_total_lemma_count() {
  return dict_info_.total_nfreq;
}

void UserDict::set_total_lemma_count_of_others(size_t count) {
  total_other_nfreq_ = count;
}

LemmaIdType UserDict::append_a_lemma(char16 lemma_str[], uint16 splids[],
                                   uint16 lemma_len, uint16 count, uint64 lmt) {
  LemmaIdType id = get_max_lemma_id() + 1;
  size_t offset = dict_info_.lemma_size;
  if (offset > kUserDictOffsetMask)
    return 0;

  lemmas_[offset] = 0;
  lemmas_[offset + 1] = (uint8)lemma_len;
  for (size_t i = 0; i < lemma_len; i++) {
    *((uint16*)&lemmas_[offset + 2 + (i << 1)]) = splids[i];
    *((char16*)&lemmas_[offset + 2 + (lemma_len << 1) + (i << 1)])
        = lemma_str[i];
  }
  uint32 off = dict_info_.lemma_count;
  offsets_[off] = offset;
  scores_[off] = build_score(lmt, count);
  ids_[off] = id;
#ifdef ___PREDICT_ENABLED___
  predicts_[off] = offset;
#endif

  offsets_by_id_[id - start_id_] = offset;

  dict_info_.lemma_count++;
  dict_info_.lemma_size += (2 + (lemma_len << 2));
  lemma_count_left_--;
  lemma_size_left_ -= (2 + (lemma_len << 2));

  // Sort

  UserDictSearchable searchable;
  prepare_locate(&searchable, splids, lemma_len);

  size_t i = 0;
  while (i < off) {
    offset = offsets_[i];
    uint32 nchar = get_lemma_nchar(offset);
    uint16 * spl = get_lemma_spell_ids(offset);

    if (0 <= fuzzy_compare_spell_id(spl, nchar, &searchable))
      break;
    i++;
  }
  if (i != off) {
    uint32 temp = offsets_[off];
    memmove(offsets_ + i + 1, offsets_ + i, (off - i) << 2);
    offsets_[i] = temp;

    temp = scores_[off];
    memmove(scores_ + i + 1, scores_ + i, (off - i) << 2);
    scores_[i] = temp;

    temp = ids_[off];
    memmove(ids_ + i + 1, ids_ + i, (off - i) << 2);
    ids_[i] = temp;
  }

#ifdef ___PREDICT_ENABLED___
  uint32 j = 0;
  uint16 * words_new = get_lemma_word(predicts_[off]);
  j = locate_where_to_insert_in_predicts(words_new, lemma_len);
  if (j != off) {
    uint32 temp = predicts_[off];
    memmove(predicts_ + j + 1, predicts_ + j, (off - j) << 2);
    predicts_[j] = temp;
  }
#endif

  if (state_ < USER_DICT_LEMMA_DIRTY)
    state_ = USER_DICT_LEMMA_DIRTY;

#ifdef ___CACHE_ENABLED___
  cache_init();
#endif

  dict_info_.total_nfreq += count;
  return id;
}
}
