/*
 * 文節に対する候補をソートする。
 * 将来的には近接する文節も見て、単語の結合による評価をする。
 * ダブった候補の削除もする。
 *
 * Funded by IPA未踏ソフトウェア創造事業 2001 9/22
 * Copyright (C) 2000-2003 TABATA Yusuke
 * Copyright (C) 2001 UGAWA Tomoharu
 *
 * $Id: candsort.c,v 1.27 2002/11/17 14:45:47 yusuke Exp $
 *
 */

#include <stdlib.h>
#include <segment.h>
#include <splitter.h>
#include "sorter.h"
#include "ordering.h"

/* お茶入れ学習による候補 */
#define OCHAIRE_BASE OCHAIRE_SCORE
/* seginfoが十分無理矢理くさいときの、ひらがなカタカナのスコア */
#define NOCONV_WITH_BIAS 900000
/* 普通の候補 */
#define NORMAL_BASE 100
/* 単漢字 */
#define SINGLEWORD_BASE 10
/* ひらがなカタカナのデフォルトのスコア */
#define NOCONV_BASE 1

/* 無理っぽい候補割り当てか判断する */
static int
uncertain_segment_p(struct seg_ent *se)
{
  struct seg_info *si;
  if (se->nr_seginfo == 0) {
    return 0;
  }
  si = se->si[0];
  /* 長さの6割 */
  if (se->len * 3 >= (si->info_len) * 5) {
    return 1;
  }
  return 0;
}

static void
release_redundant_candidate(struct seg_ent *se)
{
  int i, j;
  /* 配列はソートされているのでscoreが0の候補が後ろに並んでいる */
  for (i = 0; i < se->nr_cands && se->cands[i]->score; i++);
  /* iから後ろの候補を解放 */
  if (i < se->nr_cands) {
    for (j = i; j < se->nr_cands; j++) {
      anthy_release_cand_ent(se->cands[j]);
    }
    se->nr_cands = i;
  }
}

/* qsort用の候補比較関数 */
static int
candidate_compare_func(const void *p1, const void *p2)
{
  const struct cand_ent *const *c1 = p1, *const *c2 = p2;
  return (*c2)->score - (*c1)->score;
}

static void
sort_segment(struct seg_ent *se)
{
  qsort(se->cands, se->nr_cands,
	sizeof(struct cand_ent *),
	candidate_compare_func);
}

static void
trim_kana_candidate(struct seg_ent *se)
{
  int i;
  if (se->cands[0]->flag & CEF_KATAKANA) {
    return ;
  }
  for (i = 1; i < se->nr_cands; i++) {
    if (se->cands[i]->flag & CEF_KATAKANA) {
      /* 最低点まで下げる */
      se->cands[i]->score = NOCONV_BASE;
    }
  }
}

static void
check_dupl_candidate(struct seg_ent *se)
{
  int i,j;
  for (i = 0; i < se->nr_cands - 1; i++) {
    for (j = i + 1; j < se->nr_cands; j++) {
      if (!anthy_xstrcmp(&se->cands[i]->str, &se->cands[j]->str)) {
	/* ルールに良くマッチしたものの方を選ぶとかすべき */
	se->cands[j]->score = 0;
	se->cands[i]->flag |= se->cands[j]->flag;
      }
    }
  }
}

/* 品詞割り当てによって生成された候補を評価する */
static void
eval_candidate_by_seginfo(struct seg_ent *seg,
			  struct cand_ent *ce)
{
  int i;
  int score = 0;
  /* まず、単語の頻度によるscoreを加算 */
  for (i = 0; i < ce->nr_words; i++) {
    int pos, div = 1;
    struct cand_elm *elm = &ce->elm[i];
    if (elm->nth < 0) {
      /* 候補割り当ての対象外なのでスキップ */
      continue;
    }
    pos = anthy_wtype_get_pos(elm->wt);
    if (pos == POS_PRE || pos == POS_SUC) {
      div = 8;
    }
    score +=
      anthy_get_nth_dic_ent_freq(elm->se, elm->nth) *
      elm->str.len * elm->str.len *
      elm->ratio / (RATIO_BASE * div);
  }
  score = score *
    (seg->len * seg->len);

  /* 文節構造のスコアの差を強調する */
  if (ce->si) {
    /* その文節でもっとも高いスコアを持つ seg_infoの点数が基準となる */
    int i;
    int ratio = RATIO_BASE * ce->si->score / seg->si[0]->score;
    /* 3乗 */
    for (i = 0; i < 3; i++) {
      score *= ratio;
      score /= RATIO_BASE;
    }
  }
  ce->score = NORMAL_BASE + score;
}

/* 候補を評価する */
static void
eval_candidate(struct seg_ent *seg, struct cand_ent *ce, int uncertain)
{
  if ((ce->flag &
       (CEF_OCHAIRE | CEF_SINGLEWORD | CEF_HIRAGANA |
	CEF_KATAKANA | CEF_GUESS)) == 0) {
    /* splitterからの情報(seginfo)によって生成された候補 */
    eval_candidate_by_seginfo(seg, ce);
  } else if (ce->flag & CEF_OCHAIRE) {
    ce->score = OCHAIRE_BASE;
  } else if (ce->flag & CEF_SINGLEWORD) {
    ce->score = SINGLEWORD_BASE;
  } else if (ce->flag & (CEF_HIRAGANA | CEF_KATAKANA | CEF_GUESS)) {
    if (uncertain) {
      /*
       * この文節は外来語などで、生成した候補よりもひらがなカタカナの
       * 候補を出した方がよい
       */
      ce->score = NOCONV_WITH_BIAS;
      if (CEF_KATAKANA & ce->flag) {
	ce->score ++;
      }
      if (CEF_GUESS & ce->flag) {
	ce->score += 2;
      }
    } else {
      ce->score = NOCONV_BASE;
    }
  }
  ce->score += 1;
}

static void
eval_segment(struct seg_ent *se)
{
  int i;
  int uncertain = uncertain_segment_p(se);
  for (i = 0; i < se->nr_cands; i++) {
    eval_candidate(se, se->cands[i], uncertain);
  }
}

/** 外から呼ばれるエントリポイント */
void
anthy_sort_candidate(struct segment_list *sl, int nth)
{
  int i;
  /* まず評価する */
  for (i = nth; i < sl->nr_segments; i++) {
    eval_segment(anthy_get_nth_segment(sl, i));
  }
  /* つぎにソートする */
  for (i = nth; i < sl->nr_segments; i++) {
    sort_segment(anthy_get_nth_segment(sl, i));
  }
  /* ダブったエントリの点の低い方に0点を付ける */
  for (i = nth; i < sl->nr_segments; i++) {
    check_dupl_candidate(anthy_get_nth_segment(sl, i));
  }
  /* もういちどソートする */
  for ( i = nth ; i < sl->nr_segments ; i++){
    sort_segment(anthy_get_nth_segment(sl, i));
  }
  /* 評価0の候補を解放 */
  for (i = nth ;i < sl->nr_segments ; i++) {
    release_redundant_candidate(anthy_get_nth_segment(sl, i));
  }
  /**/
  anthy_reorder_candidates_by_relation(sl, nth);
  /* 履歴による順序の変更 */
  for (i = nth; i < sl->nr_segments; i++) {
    anthy_reorder_candidates_by_history(anthy_get_nth_segment(sl, i));
  }
  /* 候補の交換 */
  for (i = nth; i < sl->nr_segments; i++) {
    anthy_proc_swap_candidate(anthy_get_nth_segment(sl, i));
  }
  /* またソートする */
  for ( i = nth ; i < sl->nr_segments ; i++){
    sort_segment(anthy_get_nth_segment(sl, i));
  }
  /* カタカナの候補が先頭でなければ最後に回す */
  for (i = nth; i < sl->nr_segments; i++) {
    trim_kana_candidate(anthy_get_nth_segment(sl, i));
  }
  /* またソートする */
  for ( i = nth ; i < sl->nr_segments ; i++){
    sort_segment(anthy_get_nth_segment(sl, i));
  }
}
