/* ライブラリの関数呼び出しのテスト
 *
 * デフォルトでは、test.txtから1行ずつ読み込んで変換を行う。
 * 変換前の文字列と変換を行った結果をtest.expから探し、
 * 変換結果が合っているかをカウントして最後に出力する。
 *
 * ./anthy --from 1 --to 10 のように実行するとtest.txtの最初の10個の
 *  行の変換テストが行われます。
 *
 * --askオプションを付けて実行すると、結果が合っているかの判断を
 * 設定するモードになるので、表示された結果に対する判断を
 * 標準入力から'y', 'n', 'd', 'q'で入力してください。
 * 'd' dont care, 'q' quit
 * 判断できない場合はその他の文字を入力してください。
 *
 * Copyright (C) 2000-2006 TABATA Yusuke
 * Copyright (C) 2004-2006 YOSHIDA Yuichi
 * Copyright (C) 2001-2002 TAKAI Kosuke
 *
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#include <anthy/anthy.h>
#include <anthy/convdb.h>
#include <config.h>

/* Makefile の $(srcdir) (静的データファイルの基準ディレクトリ) */
#ifndef SRCDIR
# define SRCDIR "."
#endif
/* ビルド時のカレントディレクトリ (ここに .anthy を作る) */
#ifndef TEST_HOME
# define TEST_HOME "."		/* FIXME: 実際は相対パスだと誤動作する */
#endif

/* テストデータとなる変換前の文字列 */
#define TESTDATA "test.txt"
const char *testdata = SRCDIR "/" TESTDATA;

/* 変換後の文字列が妥当かどうかをチェックするためのデータ */
#define EXPDATA "test.exp"
const char *expdata = SRCDIR "/" EXPDATA;

struct input {
  char *str;
  int serial;
};

/* テストを行う条件 */
struct condition {
  /* conversion condition */
  int serial;
  int from;
  int to;
  /* operation */
  int ask;
  int quiet;
  int miss_only;
  int use_utf8;
};

static int
read_file(FILE *fp, struct input *in)
{
  char buf[256];
  while(fgets(buf, 256, fp)) {
    switch(buf[0]){
    case '#':
    case ':':
    case '-':
      break;
    case '*':
      if (in->str) {
	free(in->str);
	in->str = 0;
      }
      buf[strlen(buf)-1] = 0;
      in->str = strdup(&buf[1]);
      in->serial ++;
      return 0;
      break;
    }
  }
  return -1;
}

static int
check_cond(struct condition *cond, struct input *in)
{
  if (in->serial == cond->serial) {
    return 1;
  }
  if (in->serial <= cond->to && in->serial >= cond->from) {
    return 1;
  }
  return 0;
}

static void
log_print(int lv, const char *msg)
{
  printf("log:%d:%s\n", lv, msg);
}

static anthy_context_t
init_lib(int use_utf8)
{
  anthy_context_t ac;
  /* 既にインストールされているファイルの影響を受けないようにする */
  anthy_conf_override("CONFFILE", "../anthy-conf");
  anthy_conf_override("HOME", TEST_HOME);
  anthy_conf_override("DIC_FILE", "../mkanthydic/anthy.dic");
  anthy_set_logger(log_print, 0);
  if (anthy_init()) {
    printf("failed to init anthy\n");
    exit(0);
  }
  anthy_set_personality("");

  ac = anthy_create_context();
  if (use_utf8) {
    anthy_context_set_encoding(ac, ANTHY_UTF8_ENCODING);
  } else {
    anthy_context_set_encoding(ac, ANTHY_EUC_JP_ENCODING);
  }
  return ac;
}

static void
print_usage(void)
{
  printf("Anthy "VERSION"\n"
	 "./anthy [test-id]\n"
	 " For example.\n"
	 "  ./anthy 1\n"
	 "  ./anthy --to 100\n"
	 "  ./anthy --from 10 --to 100\n"
	 "  ./anthy --all --print-miss-only --ask\n"
	 "  ./anthy --ll 1\n\n");
  exit(0);
}

static void
parse_args(struct condition *cond, int argc, char **argv)
{
  int i;
  char *arg;
  for (i = 1; i < argc; i++) {
    arg = argv[i];
    if (!strncmp(arg, "--", 2)) {
      arg = &arg[2];
      if (!strcmp(arg, "help") || !strcmp(arg, "version")) {
	print_usage();
      }
      if (!strcmp(arg, "all")) {
	cond->from = 0;
	cond->to = 100000000;
      } else if (!strcmp(arg, "quiet")) {
	cond->quiet = 1;
      } else if (!strcmp(arg, "ask") ||
		 !strcmp(arg, "query")) {
	cond->ask = 1;
      } else if (!strcmp(arg, "print-miss-only")) {
	cond->miss_only = 1;
      } else if (!strcmp(arg, "utf8")) {
	cond->use_utf8 = 1;
      }

      if (i + 1 < argc) {
	if (!strcmp(arg, "from")){
	  cond->from = atoi(argv[i+1]);
	  i++;
	}else if (!strcmp(arg, "to")){
	  cond->to = atoi(argv[i+1]);
	  i++;
	}else if (!strcmp(arg, "ll")) {
	  anthy_set_logger(NULL, atoi(argv[i+1]));
	  i++;
	}
      }
    } else {
      int num = atoi(arg);
      if (num) {
	cond->serial = num;
      } else {
	char *buf = alloca(strlen(SRCDIR)+strlen(arg) + 10);
	sprintf(buf, SRCDIR "/%s.txt", arg);
	testdata = strdup(buf);
      }
    }
  }
}

static void
print_run_env(void)
{
  time_t t;
  const char *env;
  env = getenv("ANTHY_ENABLE_DEBUG_PRINT");
  if (!env) {
    env = "";
  }
  printf("ANTHY_ENABLE_DEBUG_PRINT=(%s)\n", env);
  env = getenv("ANTHY_SPLITTER_PRINT");
  if (!env) {
    env = "";
  }
  printf("ANTHY_SPLITTER_PRINT=(%s)\n", env);
  printf("SRCDIR=(%s)\n", SRCDIR);
  t = time(&t);
  printf(PACKAGE "-" VERSION " %s", ctime(&t));
}

static void
sum_up(struct res_db *db, struct conv_res *cr)
{
  int is_split;
  struct res_stat *rs;
  cr->used = 1;
  db->total ++;
  if (cr->res_str[0] == '|') {
    rs = &db->split;
    is_split = 1;
  } else {
    rs = &db->res;
    is_split = 0;
  }
  if (cr->check == CHK_OK) {
    rs->ok ++;
  } else if (cr->check == CHK_MISS) {
    rs->miss ++;
  } else if (cr->check == CHK_DONTCARE) {
    rs->dontcare ++;
  } else {
    rs->unknown ++;
  }
}

static void
set_string(struct condition *cond, struct res_db *db,
	   struct input *in, anthy_context_t ac)
{
  struct conv_res *cr1, *cr2;
  int pr;

  anthy_set_string(ac, in->str);

  /* result */
  cr1 = find_conv_res(db, ac, in->str, 1);
  sum_up(db, cr1);
  /* split */
  cr2 = find_conv_res(db, ac, in->str, 0);
  sum_up(db, cr2);

  /**/
  pr = 0;
  if (cond->miss_only) {
    if (cr1->check == CHK_MISS ||
	cr2->check == CHK_MISS) {
      pr = 1;
    }
  } else if (!cond->quiet) {
    pr = 1;
  }

  if (pr) {
    printf("%d:(%s)\n", in->serial, in->str);
    anthy_print_context(ac);
  }
  anthy_reset_context(ac);
}


static void
dump_res(FILE *fp, struct conv_res *r)
{
  fprintf(fp, "%s %s ", r->src_str, r->res_str);
  if (r->check == CHK_MISS) {
    fprintf(fp, "X");
  } else if (r->check == CHK_OK) {
    fprintf(fp, "OK");
  } else if (r->check == CHK_DONTCARE) {
    fprintf(fp, "*");
  } else {
    fprintf(fp, "?");
  }
  fprintf(fp, "\n");
}

static void
save_db(const char *fn, struct res_db *db)
{
  FILE *fp = fopen(fn, "w");
  struct conv_res *cr;
  if (!fp) {
    printf("failed to open (%s) to write\n", fn);
    return ;
  }
  for (cr = db->res_list.next; cr; cr = cr->next) {
    dump_res(fp, cr);
  }
}

static void
ask_results(struct res_db *db)
{
  struct conv_res *cr;
  for (cr = db->res_list.next; cr; cr = cr->next) {
    if (cr->check == CHK_UNKNOWN && cr->used == 1) {
      char buf[256];
      printf("%s -> %s (y/n/d/q)\n", cr->src_str, cr->res_str);
      fgets(buf, 256, stdin);
      if (buf[0] == 'y') {
	cr->check = CHK_OK;
      } else if (buf[0] == 'n') {
	cr->check = CHK_MISS;
      } else if (buf[0] == 'd') {
	cr->check = CHK_DONTCARE;
      } else if (buf[0] == 'q') {
	return ;
      }
    }
  }
}

static void
show_stat(struct res_db *db)
{
  struct res_stat *rs;
  int i;
  /**/
  printf("%d items\n", db->total);
  for (i = 0; i < 2; i++) {
    if (i == 0) {
      printf("conversion result\n");
      rs = &db->res;
    } else {
      printf("split result\n");
      rs = &db->split;
    }
    printf("ok : %d\n", rs->ok);
    printf("miss : %d\n", rs->miss);
    printf("unknown : %d\n", rs->unknown);
    printf("\n");
  }
}

static void
init_condition(struct condition *cond)
{
  cond->serial = 0;
  cond->from = 0;
  cond->to = 0;
  /**/
  cond->quiet = 0;
  cond->ask = 0;
  cond->miss_only = 0;
  cond->use_utf8 = 1;
}

int
main(int argc,char **argv)
{
  anthy_context_t ac;
  FILE *fp;
  struct input cur_input;
  struct res_db *db;
  struct condition cond;

  cur_input.serial = 0;
  cur_input.str = 0;
  init_condition(&cond);

  parse_args(&cond, argc, argv);
  db = create_db();
  read_db(db, expdata);

  printf("./test_anthy --help to print usage.\n");

  print_run_env();

  fp = fopen(testdata, "r");
  if (!fp) {
    printf("failed to open %s.\n", testdata);
    return 0;
  }
  
  ac = init_lib(cond.use_utf8);

  /* ファイルを読んでいくループ */
  while (!read_file(fp, &cur_input)) {
    if (check_cond(&cond, &cur_input)) {
      set_string(&cond, db, &cur_input, ac);
    }
  }

  anthy_release_context(ac);
  anthy_quit();

  if (cond.ask) {
    /* ユーザに聞く */
    ask_results(db);
  }

  show_stat(db);
  save_db(expdata, db);

  return 0;
}
