/*
 * 文節に対して候補のリストを生成する。
 * make_candidates()がcontext管理部から呼ばれる。
 *
 * 候補の生成は次の方法で行う
 * (1)splitterが割り当てた品詞に対してproc_splitter_info()
 *    から候補を生成する
 * (2)ひらがなのみとカタカナのみの候補を生成する
 * (3)最後の文字を助詞と解釈して無理矢理候補を生成する
 */
/*
 * Funded by IPA未踏ソフトウェア創造事業 2001 9/30
 * Copyright (C) 2000-2004 TABATA Yusuke
 * Copyright (C) 2002 UGAWA Tomoharu
 *
 * $Id: compose.c,v 1.22 2002/11/17 14:45:47 yusuke Exp $
 */
/*
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <anthy.h> /* for ANTHY_*_ENCODING */
#include <conf.h>
#include <dic.h>
#include <splitter.h>
#include "main.h"

/* 郵便番号のindexを作る atoiで上位の桁が落ちているので
 * 0を追加して3桁もしくは7桁のindexを作成する
 */
static void
make_zipcode_index(long long num, char *buf)
{
  const char *fmt = "";
  if (num < 10) {
    fmt = "00%d ";
  } else if (num < 100) {
    fmt = "0%d ";
  } else if (num < 1000) {
    fmt = "%d ";
  } else if (num < 10000) {
    fmt = "000%d ";
  } else if (num < 100000) {
    fmt = "00%d ";
  } else if (num < 1000000) {
    fmt = "0%d ";
  } else {
    fmt = "%d ";
  }
  sprintf(buf, fmt, (int)num);
}

static xstr *
search_zipcode_dict(xstr *xs, long long num)
{
  FILE *fp;
  char buf[1000];
  char index[30];
  int len;

  if (xs->len != 3 && xs->len != 7) {
    return NULL;
  }

  if (num > 9999999 || num < 1) {
    return NULL;
  }
  fp = fopen(anthy_conf_get_str("ZIPDICT_EUC"), "r");
  if (!fp) {
    return NULL;
  }
  make_zipcode_index(num, index);
  len = strlen(index);
  while (fgets(buf, 1000, fp)) {
    if (!strncmp(buf, index, len)) {
      char *tmp;
      /* 改行を消す */
      buf[strlen(buf) - 1] = 0;
      /* 一番最後のスペースのあとを地名としてとりだす(<=まずい)*/
      tmp = strrchr(buf, ' ');
      tmp ++;
      fclose(fp);
      return anthy_cstr_to_xstr(tmp, ANTHY_EUC_JP_ENCODING);
    }
  }
  fclose(fp);
  return NULL;
}

static struct cand_ent *
alloc_cand_ent(void)
{
  struct cand_ent *ce;
  ce = (struct cand_ent *)malloc(sizeof(struct cand_ent));
  ce->nr_words = 0;
  ce->elm = 0;
  ce->core_elm_index = -1;
  return ce;
}

/*
 * 候補を複製する
 */
static struct cand_ent *
dup_candidate(struct cand_ent *ce)
{
  struct cand_ent *ce_new;
  int i;
  ce_new = alloc_cand_ent();
  ce_new->nr_words = ce->nr_words;
  ce_new->str.len = ce->str.len;
  ce_new->str.str = anthy_xstr_dup_str(&ce->str);
  ce_new->elm = malloc(sizeof(struct cand_elm)*ce->nr_words);
  ce_new->flag = ce->flag;
  ce_new->si = ce->si;
  ce_new->core_elm_index = ce->core_elm_index;

  for (i = 0 ; i < ce->nr_words ; i++) {
    ce_new->elm[i] = ce->elm[i];
  }
  return ce_new;
}

/** 文節に候補を追加する */
static void
push_back_candidate(struct seg_ent *seg, struct cand_ent *ce)
{
  /* seg_entに候補ceを追加 */
  seg->nr_cands++;
  seg->cands = (struct cand_ent **)
    realloc(seg->cands, sizeof(struct cand_ent *) * seg->nr_cands);
  seg->cands[seg->nr_cands - 1] = ce;
}


static void
push_back_zipcode_candidate(struct seg_ent *seg)
{
  struct cand_ent *ce;
  long long code;
  xstr *str;

  code = anthy_xstrtoll(&seg->str);
  if (code == -1) {
    return ;
  }
  str = search_zipcode_dict(&seg->str, code);
  if (!str) {
    return ;
  }

  ce = alloc_cand_ent();
  ce->str = *str;
  ce->si = 0;
  ce->flag = CEF_SINGLEWORD;
  push_back_candidate(seg, ce);
  free(str);
}

static void
push_back_guessed_candidate(struct seg_ent *seg)
{
  xchar xc;
  xstr *xs;
  struct cand_ent *ce;
  if (seg->str.len < 2) {
    return ;
  }
  /* 最後の文字は助詞か？ */
  xc = seg->str.str[seg->str.len - 1];
  if (!(anthy_get_xchar_type(xc) & XCT_DEP)) {
    return ;
  }
  /* 最後の文字以外をカタカナにしてみる */
  ce = alloc_cand_ent();
  xs = anthy_xstr_hira_to_kata(&seg->str);
  xs->str[xs->len-1] = xc;
  ce->str.str = anthy_xstr_dup_str(xs);
  ce->str.len = xs->len;
  ce->flag = CEF_GUESS;
  ce->si = 0;
  anthy_free_xstr(xs);
  push_back_candidate(seg, ce);
}

/** 再帰で1単語ずつ候補を割当てていく */
static int
enum_candidates(struct seg_ent *seg,
		struct cand_ent *ce,
		int from, int n)
{
  int i, p;
  struct cand_ent *cand;
  int nr_cands = 0;

  if (n == ce->si->nr_word_info) {
    /* 完成形 */
    /* 文節後部の解析しなかった部分を候補文字列に追加 */
    xstr tail;
    tail.len = seg->len - ce->si->info_len;
    tail.str = &seg->str.str[ce->si->info_len];
    anthy_xstrcat(&ce->str, &tail);
    push_back_candidate(seg, dup_candidate(ce));
    return 1;
  }

  p = anthy_get_nr_dic_ents(ce->elm[n].se, &ce->elm[n].str);
  /* 品詞不定の場合には未変換で次の単語へ行く */
  if (anthy_wtype_get_pos(ce->si->word_info[n].wt) == POS_INVAL ||
      p == 0) {
    xstr xs;
    xs.len = ce->si->word_info[n].len;
    xs.str = &seg->str.str[from];
    cand = dup_candidate(ce);
    cand->elm[n].nth = -1;
    cand->elm[n].id = -1;
    anthy_xstrcat(&cand->str, &xs);
    nr_cands = enum_candidates(seg,cand,
			       from + ce->si->word_info[n].len,
			       n + 1);
    anthy_release_cand_ent(cand);
    return nr_cands;
  }

  /* 品詞が割当てられているので、その品詞にマッチするものを割当てる */
  for (i = 0; i < p; i++) {
    wtype_t wt;
    anthy_get_nth_dic_ent_wtype(ce->elm[n].se, &ce->str, i, &wt);
    anthy_wtype_set_ct(&ce->si->word_info[n].wt, CT_NONE);
    if (anthy_wtypecmp(ce->si->word_info[n].wt, wt)) {
      xstr word, yomi;
      yomi.len = ce->si->word_info[n].len;
      yomi.str = &seg->str.str[from];
      cand = dup_candidate(ce);
      anthy_get_nth_dic_ent_str(cand->elm[n].se,
				&yomi, i, &word);
      cand->elm[n].nth = i;
      cand->elm[n].id = anthy_get_nth_dic_ent_id(ce->elm[n].se, i);
      /* 単語の本体 */
      anthy_xstrcat(&cand->str, &word);
      free(word.str);
      /* 自分を再帰呼び出しして続きをわりあてる */
      nr_cands += enum_candidates(seg, cand, 
				  from + ce->si->word_info[n].len,
				  n+1);
      anthy_release_cand_ent(cand);
    }
  }
  return nr_cands;
}

/**
 * 文節全体を含む一単語(単漢字を含む)の候補を生成する
 */
static void
push_back_singleword_candidate(struct seg_ent *seg)
{
  seq_ent_t se;
  struct cand_ent *ce;
  wtype_t wt;
  int i, n;
  xstr xs;

  se = anthy_get_seq_ent_from_xstr(&seg->str);
  n = anthy_get_nr_dic_ents(se, &seg->str);
  /* 辞書の各エントリに対して */
  for (i = 0; i < n; i++) {
    int ct;
    /* 品詞を取り出して */
    anthy_get_nth_dic_ent_wtype(se, &seg->str, i, &wt);
    ct = anthy_wtype_get_ct(wt);
    /* 終止形か活用しないものの原形なら */
    if (ct == CT_SYUSI || ct == CT_NONE) {
      ce = alloc_cand_ent();
      anthy_get_nth_dic_ent_str(se,&seg->str, i, &xs);
      ce->str.str = xs.str;
      ce->str.len = xs.len;
      ce->flag = CEF_SINGLEWORD;
      ce->si = 0;
      push_back_candidate(seg, ce);
    }
  }
}

static void
push_back_noconv_candidate(struct seg_ent *seg)
{
  /* 無変換で片仮名になる候補と平仮名のみになる候補を追加 */
  struct cand_ent *ce;
  xstr *xs;

  /* ひらがなのみ */
  ce = alloc_cand_ent();
  ce->str.str = anthy_xstr_dup_str(&seg->str);
  ce->str.len = seg->str.len;
  ce->si = 0;
  ce->flag = CEF_HIRAGANA;
  push_back_candidate(seg, ce);

  /* 次にカタカナ */
  ce = alloc_cand_ent();
  xs = anthy_xstr_hira_to_kata(&seg->str);
  ce->str.str = anthy_xstr_dup_str(xs);
  ce->str.len = xs->len;
  ce->flag = CEF_KATAKANA;
  ce->si = 0;
  anthy_free_xstr(xs);
  push_back_candidate(seg, ce);
}

/** seginfoの情報を利用して候補を生成する */
static void
make_candidate_from_seginfo(struct seg_ent *se,
			    struct seg_info *si)
{
  /*
   * 各単語の品詞が決定された状態でコミットされる。
   */
  struct cand_ent *ce;
  int from, i;

  /* 複数(1も含む)の単語で構成される文節に単語を割当てていく */
  ce = alloc_cand_ent();
  ce->nr_words = si->nr_word_info;
  ce->str.str = 0;
  ce->str.len = 0;
  ce->elm = malloc(sizeof(struct cand_elm)*ce->nr_words);
  ce->si = si;
  from = 0;

  /* 文節の構造の情報を候補にコピーする */
  for (i = 0; i < si->nr_word_info; i++) {
    xstr core_xs;
    core_xs.str = &se->str.str[from];
    core_xs.len = si->word_info[i].len;
    ce->elm[i].se = anthy_get_seq_ent_from_xstr(&core_xs);
    ce->elm[i].str.str = core_xs.str;
    ce->elm[i].str.len = si->word_info[i].len;
    ce->elm[i].wt = si->word_info[i].wt;
    ce->elm[i].ratio = si->word_info[i].ratio;
    from += si->word_info[i].len;
  }
  ce->core_elm_index = si->core_word_info_index;
  ce->flag = CEF_NONE;
  enum_candidates(se, ce, 0, 0);
  anthy_release_cand_ent(ce);
}

/** splitterの情報を利用して候補を生成する
 */
static void
proc_splitter_info(struct seg_ent *se,
		   struct seg_info *si)
{
  switch (si->type) {
  case SI_NORMAL:
    make_candidate_from_seginfo(se, si);
    break;
  case SI_CAND:
    {
      /* 候補文字列がダイレクトに指定された */
      {
	struct cand_ent *ce;
	ce = alloc_cand_ent();
	ce->str.str = anthy_xstr_dup_str(&si->cand);
	ce->str.len = si->cand.len;
	ce->flag = CEF_OCHAIRE;
	ce->si = si;
	if (si->info_len < se->len) {
	  /* seginfoでカバーされてない領域の文字列を付ける */
	  xstr xs;
	  xs.str = &se->str.str[si->info_len];
	  xs.len = se->len - si->info_len;
	  anthy_xstrcat(&ce->str ,&xs);
	}
	push_back_candidate(se, ce);
      }
      break;
    }
  }
}

/** context.cから呼出されるもっとも大物
 * 一つ以上の候補を必ず生成する
 */
void
anthy_make_candidates(struct seg_ent *se)
{
  int i, limit = 0;

  /* limitよりも低いscoreを持つseginfoからは候補を生成しない */
  if (se->nr_seginfo) {
    int best = se->si[0]->score;
    limit = best / 3;
  }
  for (i = 0; i < se->nr_seginfo; i++) {
    if (se->si[i]->score > limit) {
      /**/
      proc_splitter_info(se, se->si[i]);
    }
  }

  /* 単漢字などの候補 */
  push_back_singleword_candidate(se);
  /* 郵便番号 */
  push_back_zipcode_candidate(se);
  /* ひらがな、カタカナの無変換エントリを作る */
  push_back_noconv_candidate(se);

  /* 候補が二つしか無いときは最後が助詞にで残りが平仮名の候補を作れるか試す */
  push_back_guessed_candidate(se);
}

/** seginfoの配列を作る */
void
anthy_make_seginfo_array(struct anthy_context *ac,
			 struct seg_ent *se)
{
  int i;
  se->si = 0;
  for (i = se->len; i > 0; i--) {
    int j;
    /* 最後に濁点とかがついてたら直前の文字ごと落す */
    if (i < se->len &&
	anthy_get_xchar_type(se->str.str[i]) & XCT_PART) {
      /* FIXME 濁点とかがありえない並びをしてたら */
      i--;
      continue ;
    }
    se->nr_seginfo = anthy_get_nr_seginfo(&ac->split_info, se->from, i);
    if (!se->nr_seginfo) {
      continue ;
    }
    /* seginfoを配列に取り込む */
    se->si = malloc(sizeof(struct seg_info*) * se->nr_seginfo);
    for (j = 0; j < se->nr_seginfo; j++) {
      se->si[j] = anthy_get_nth_seginfo(&ac->split_info, se->from, i, j);
    }
    return ;
  }
}
