#include "idngram.h"
#include "remove_tmp.h"
#include <signal.h>


#define PRINT_PROCEED_ADD 0
#define PRINT_PROCEED_END 1
#define PRINT_PROCEED_START 2

int hash_size = 200000;
char *tmpdir = "/usr/tmp";

SLMIDNgramBuffer *NGBuf;
int ascii_output;
int distance = 0;

void usage()
{
    printf("Usage : text2idngram  -vocab .vocab\n");
    printf("                    [ -n 3 ]\n");
    printf("                    [ -d 0 ]\n");
    printf("                    [ -write_ascii ]\n");
    printf("                    [ -hash 200000 ]\n");
    printf("                    [ -buffer 100 ]\n");
    printf("                    [ -verbosity 2 ]\n");
    printf("                    [ -temp /usr/tmp ]\n");
    printf("                    [ .text] [.idngram]\n");
    exit(1);
}

// 進行状況の表示
// mode = 0 processed_ngramを追加し、進行状況を表示
// mode = 1 進行終了の表示
void print_proceed(int mode)
{
	static int processed_ngram = 0; // static 変数の初期化
	
	switch(mode){
	case PRINT_PROCEED_ADD:
		processed_ngram++;  // 読み込んだ行数をカウントしている。
		if (verbosity > 0) {
	    	if (processed_ngram % 20000 == 0) {
				fprintf(stderr,"."); //２万行で．を表示
				if (processed_ngram % 1000000 == 0) {
		    		fprintf(stderr,"\n"); // 100万行で改行
				}
			fflush(stderr);
    		}
		}	
		break; // mode==0 終了
	case PRINT_PROCEED_END:
		if (verbosity > 1)
			fprintf(stderr,"%d N-grams has been processed.\n",processed_ngram);
		break;
	case PRINT_PROCEED_START:
		if (verbosity > 1)
			fprintf(stderr,"20,000 n-grams processed for each \".\", 1,000,000 for each line.\n");
		break;
	default:
		break;
	}

}


void output_ngram(FILEHANDLE outf)
{
	char buf2[256];
	FILEHANDLE df;
	int i;
	// 出力？
    if (merge_list_num > 0) {
    	
		if (verbosity > 1)
	    	fprintf(stderr,"\nmerging ngrams...\n");
    	
		sprintf(buf2,"%s/text2idn%d.%d.idngram.gz",tmpdir,getpid(),merge_list_num);
    	
		RemoveAfter(buf2);
		push_merge_list(buf2);
    	
    	// NGBufを出力
		df = z_open(buf2,"w");
		dumpidngram(NGBuf,df,0);
		z_close(df);
    	
    	// outf へ出力
		SLMMergeIDNgram(ngram_len, merge_list,merge_list_num, outf, 0, ascii_output);
    	
		for ( i = 0; i < merge_list_num; i++) {
			sprintf(buf2,"%s/text2idn%d.%d.idngram.gz",tmpdir,getpid(),i);
			unlink(buf2);
		
		}
    } else {
		if (verbosity > 1)
	    	fprintf(stderr,"\ndumping ngrams...\n");
    	// outf へ出力
		dumpidngram(NGBuf,outf,ascii_output);
    }
	
}



// NGBufはどのように使用されているか？？？調べること
void add_ngram_ngbuf(SLMHashTableElement *ht_elem, SLMHashTable *ngram_ht, SLMWordTuple tuple )
{
	int i;
	char buf2[256];
	FILEHANDLE df;
	
	// ht_elem あり
	if(ht_elem != NULL) {
		// ngram をインクリメントする
	    incr_ngram(NGBuf,(SLMWordID*)ht_elem->valueptr,1);
		return;
	}
	
	// ht_elem == NULL なら新しい n-gram が発見された
	// tuple を NGBuf へコピーする
   	/* newly discovered n-gram */
   	for (i = 0; i < ngram_len; i++)
		NGBuf->buffer[NGBuf->pos+i] = tuple[i];
	
	/* set count to one */
	NGBuf->buffer[NGBuf->pos+ngram_len] = 0;
	NGBuf->buffer[NGBuf->pos+ngram_len+1] = 1;
	
	// NGBufから、n-gramのハッシュへinsert
	SLMHashInsert(ngram_ht, (void*)&NGBuf->buffer[NGBuf->pos], (void*)&NGBuf->buffer[NGBuf->pos]);
   	NGBuf->pos += NGBuf->elem_size;
	
	// NGBufが不足したとき？？？？？？
	// ファイルに書き出して、n-gramのハッシュ表を作り直している?
   	if ((NGBuf->pos) > ((NGBuf->real_size)-(NGBuf->elem_size))) {
		sprintf(buf2,"%s/text2idn%d.%d.idngram.gz",tmpdir,getpid(),merge_list_num); // buf2はファイル名
		RemoveAfter(buf2);
		push_merge_list(buf2);
   		
		df = z_open(buf2,"w");
		dumpidngram(NGBuf,df,0);
		z_close(df);
   		
		if (verbosity > 1) {
		    fprintf(stderr,"\nsorted N-gram written into %s\n",buf2);
		}
   		
   		// N-gramのハッシュ表を新しく作る
		SLMHashDestroy(ngram_ht);
		ngram_ht = SLMHashCreate(hash_size, ngram_hashfunc, ngram_comp);
		NGBuf->pos = 0;
   	}

}


void prepare_NGBuf(void)
{
	
	// バッファの確保
    NGBuf->ngram_len = ngram_len;
    NGBuf->elem_size = ngram_len+2;
    NGBuf->elem_byte = sizeof(SLMWordID)*NGBuf->elem_size;
	NGBuf->real_size = (NGBuf->size)*1024*1024/(NGBuf->elem_byte);
    NGBuf->buffer = New_N(SLMWordID,NGBuf->real_size);
	
}


// ngram_len →コマンドラインから入力、デフォルトは3
// distance を指定する場合、ngram_len = 2 でなければならない
// distance は単語間の距離 distance=1なら「○ × ○」の○同士を評価する

void text2idngram(char *vocabfile, FILEHANDLE inf, FILEHANDLE outf)
{
	
	// SLMWordIDのポインタとして、SLMWordTupleを宣言されている。 SLMWordIDはuint2(unsigned short),またはuint4(unsigned long)
	// つまり SLMWordTuple → (unsigned short *) または (unsigned long *)
	
    SLMHashTable *vocab_ht;
	SLMWordTuple tuple; 
    SLMWordTuple read_tuple;
    SLMHashTable *ngram_ht;
    SLMHashTableElement *ht_elem;
    int i,total_words = 0;
    char buf[256]; //,buf2[256];

    int tuple_len = ngram_len+distance;  

	// Tuple → 順序付けられた対象の並びを表す.複数個のデータを一つ としてまとめたいときなど

	//vocabハッシュ, tuple, read_tuple, ngramハッシュ NGBuf を確保する
    vocab_ht = read_vocab(vocabfile,NULL,0); // vocabの読み込み→ハッシュテーブルへ
    tuple = SLMNewWordTuple(ngram_len); 
    read_tuple = SLMNewWordTuple(tuple_len);
    ngram_ht = SLMHashCreate(hash_size,ngram_hashfunc, ngram_comp); // n-gramハッシュの作成
	prepare_NGBuf(); // NGバッファの初期化
	
	print_proceed(PRINT_PROCEED_START);
	
	// FILEHANDLE inf からbuf へスペースで区切られた「単語」を読み込む 
    while (z_getstr(inf,buf,256) == 0) {
    	
    	print_proceed(PRINT_PROCEED_ADD); // 進行状況の表示
    	
		total_words++; // 読み込んだ単語の数をカウント
    	
		for (i = 0; i < tuple_len-1; i++)
	    	read_tuple[i] = read_tuple[i+1]; // read_tuple を一つ前にずらす
    	
    	// vocab_htのハッシュで一致するものをbuf（単語）から探す
    	// 一致するものがなければ、０(実際にはNULL)あれば（実質には）ハッシュへのポインタ
		read_tuple[tuple_len-1] = SLMIntHashSearch(vocab_ht,buf); // read_tuple の最後に入る
    	
    	// total_words が tuple_len より小さいとき、すなわちループの初回〜数回はwhile ループをやり直し
		if (total_words < tuple_len)
	    	continue; 
    	
    	// tuple をずらし、読み込んだ read_tuple を入れる
    	// tuple には
		if (distance > 0) {
			// ngram_len =2 で、distance が0でないとき、
	    	tuple[0] = read_tuple[0];
	    	tuple[1] = read_tuple[tuple_len-1];
		}else {
			// ngram_len =2 かつdistance が0、または
			// ngram_len が３以上のとき
			// N-gram分のtupleを作る(3-gram なら i=0,1,2)
	    	for (i = 0; i < ngram_len; i++)
				tuple[i] = read_tuple[i];
		}

		ht_elem = SLMHashSearch(ngram_ht, tuple); //ngramのハッシュからtupleを探す
    	add_ngram_ngbuf(ht_elem, ngram_ht,  tuple);
    	
    }
	// while loop end
	
	print_proceed(PRINT_PROCEED_END); //  進行終了の表示
	output_ngram(outf); // ngram のアウトプット

}

int
main(int argc, char *argv[])
{
    char *vocabfile = NULL;
    char *infile = NULL, *outfile = NULL;
    int i;
    FILEHANDLE inf,outf;

    NGBuf = New(SLMIDNgramBuffer);
    NGBuf->size = 100;  /* MB */

    verbosity = 2;
    for (i = 1; i < argc; i++) {
	if (!strcmp(argv[i],"-vocab"))
	    vocabfile = nextarg(argc,argv,i++);
	else if (!strcmp(argv[i],"-n"))
	    ngram_len = atoi(nextarg(argc,argv,i++));
	else if (!strcmp(argv[i],"-d"))
	    distance = atoi(nextarg(argc,argv,i++)); //-d でdistance を指定
	else if (!strcmp(argv[i],"-write_ascii"))
	    ascii_output = 1;
	else if (!strcmp(argv[i],"-temp"))
	    tmpdir = nextarg(argc,argv,i++);
	else if (!strcmp(argv[i],"-hash"))
	    hash_size = atoi(nextarg(argc,argv,i++));
	else if (!strcmp(argv[i],"-buffer"))
	    NGBuf->size = atoi(nextarg(argc,argv,i++));
	else if (!strcmp(argv[i],"-verbosity") || !strcmp(argv[i],"-v"))
	    verbosity = atoi(nextarg(argc,argv,i++));
	else if (argv[i][0] == '-')
	    usage();
	else
	    break;
    }

    if (distance > 0) {
	/* if distance > 0, N should be 2 */
    // distance と N-gram のNの確認
	if (ngram_len != 2) {
	    fprintf(stderr,"text2idngram: -n should be 2 when -d is specified\n");
	    exit(1);
	}
    }

    if (vocabfile == NULL) {
	fprintf(stderr, "text2idngram: vocabulary file missing\n"); // 単語ファイル-vocabの確認
	usage();
    }
    if (i < argc)
	infile = argv[i];
    if (i+1 < argc)
	outfile = argv[i+1];
    if (i+2 < argc)
	usage();

    if (infile) {
	inf = z_open(infile,"r");
	if (verbosity > 1)
	    fprintf(stderr,"Input file:      %s\n",infile);
    }
    else {
	inf = FILEIO_stdin();
	if (verbosity > 2)
	    fprintf(stderr,"Input file:      stdin\n");
    }

    if (outfile) {
	outf = z_open(outfile,"w");
	if (verbosity > 1)
	    fprintf(stderr,"Output file:     %s\n",outfile);
    }
    else {
	outf = FILEIO_stdout();
	if (verbosity > 1)
	    fprintf(stderr,"Output file:     stdout\n");
    }
    if (verbosity > 1) {
	fprintf(stderr,"N-gram Buffer:   %dMB\n",NGBuf->size);
	fprintf(stderr,"Hash table size: %d\n",hash_size);
	fprintf(stderr,"Vocab            %s\n",vocabfile);
	fprintf(stderr,"Temp directory:  %s\n",tmpdir);
	fprintf(stderr,"N                %d\n",ngram_len);
	if (distance > 0) {
	    fprintf(stderr,"Distance:        %d\n",distance);
	}
    }
    TrapAllSignals();
    text2idngram(vocabfile,inf,outf);

    z_close(inf);
    z_close(outf);
    return 0;
}
