/*


ngfreq2idngram.c

n-gram頻度リストからをpalmkitのidngramへ変換する

単語リストを単語辞書として読み込む

n-gram頻度リストを読み込み、単語辞書にあるn-gramをid-n-gramとしてバイナリ出力する


Copyright (C) 2013  anausagi


改変・再配布はご自由にどうぞ。


*/


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


#define READ_BUFF_LENGTH 4096
#define MAX_PATH_LENGTH 256

static char tmpdir_usrtmp[] = "/usr/tmp";

// ngfreq2idgram の設定用構造体
typedef struct  {
	char *vocabfile;	// 辞書リストのファイル名
	int ngram_len;		// n-gram (2-gram なら2)
	SLMIDNgramBuffer *NGBuf;		// N-gram用のバッファポインタ
	
	int verbosity;
	int hash_size;
	char *tmpdir;
	
	char *infile, *outfile;
	FILEHANDLE inHANDLE, outHANDLE;
	
	int ascii_output;
	int rev_ngram;
	
} config_ngfreq2idngram;


// hash テーブルポインタ管理のための構造体
typedef struct {
	
    SLMHashTable *vocab_ht;
	SLMWordTuple tuple; 
    SLMWordTuple read_tuple;
    SLMHashTable *ngram_ht;
    SLMHashTableElement *ht_elem;
	
} hash_tabel_ngfreq2idngram;


// config の値を初期化する
void init_config_ngfreq2idngram(config_ngfreq2idngram  *config)
{

	config->ngram_len = 0;
	config->vocabfile = NULL;
	
	config->verbosity = 2;
	
	config->hash_size = 200000;
	config->tmpdir = tmpdir_usrtmp;
	
	config->infile = NULL;
	config->outfile = NULL;	
	
	
	config->NGBuf = New(SLMIDNgramBuffer);
	config->NGBuf->size = 100;  /* MB */
	
	config->ascii_output = 0;
	config->rev_ngram = 0;  //逆順コーパス

}


// 使用法を表示し、終了する
void print_usage(void)
{
	printf("usage : ngfreq2idngram -vocab .vocab -n N-gram-number\n");
	printf("                     [ -hash 200000 ]\n");
	printf("                     [ -buffer 100 ]\n");
	printf("                     [ -verbosity 2 ]\n");
	printf("                     [ -temp /usr/tmp ]\n");
	printf("                     [ -write_ascii ]\n");
	printf("                     [ -rev_ngram ]\n");
	printf("                     [inputfile] [outputfile]\n");
	
	exit(1); // 終了！
}


// コマンドラインからオプションを取り出す
void read_commandline_option(int argc, char *argv[], config_ngfreq2idngram *config)
{
	int i;
	
	for(i=1; i<argc; i++){
		// -vocab
		if(strcmp(argv[i], "-vocab") == 0){
			if (++i >= argc ) break;
			config->vocabfile = argv[i];
			continue;
		}
		
		// -n
		if(strcmp(argv[i], "-n") == 0){
			if (++i >= argc ) break;
			config->ngram_len = atoi(argv[i]);
			continue;
		}
		
		// -verbosity
		if((strcmp(argv[i], "-v")== 0) || (strcmp(argv[i], "-verbosity")== 0) ){
			if (++i >= argc ) break;
			config->verbosity = atoi(argv[i]);
			continue;
		}
		
		// -temp
		if(strcmp(argv[i], "-temp") == 0) {
			if (++i >= argc ) break;
			config->tmpdir = argv[i];
			continue;
		}
		
		// -hash
		if(strcmp(argv[i], "-hash") == 0){
			if (++i >= argc ) break;
			config->hash_size = atoi(argv[i]);
			continue;
		}
		
		// -buffer
		if(strcmp(argv[i], "-buffer") == 0){
			if (++i >= argc ) break;
			config->NGBuf->size = atoi(argv[i]);
			continue;
		}
		
		// -write_ascii
		if(strcmp(argv[i], "-write_ascii") == 0){
			config->ascii_output = 1;
			continue;
		}
		// -rev_ngram
		if(strcmp(argv[i], "-rev_ngram") == 0){
			config->rev_ngram = 1;
			continue;
		}
		// 先頭の文字が-
		// print usage （オプションが間違っている場合など）
		if(argv[i][0] == '-'){
			print_usage();
		}
		
		// 先頭の文字が-でない場合
		// inputfile または outputfileとみなす
		// １番目はinputfile
		// ２番目はoutputfile
		// それ以降にファイル名？がきても無視する
		if(config->infile == NULL){
			config->infile = argv[i];
			continue;
		}
		if(config->outfile == NULL){
			config->outfile = argv[i];
			continue;
		}
		
		
	}
}



// initialze 時のメッセージ
void print_initialize_message(config_ngfreq2idngram *config)
{
	if(config->verbosity < 2)
		return; // verbosity < 2 ならば表示しない
	
	if(config->infile != NULL ) {
		fprintf(stderr,"Input file:      %s\n",config->infile);
	}else {
		fprintf(stderr,"Input file:      stdin\n");
	}
	
	if(config->outfile != NULL ) {
		fprintf(stderr,"Output file:     %s\n",config->outfile);
	}else {
		fprintf(stderr,"Output file:     stdout\n");
	}	
	
	
	fprintf(stderr,"N-gram Buffer:   %dMB\n",config->NGBuf->size);
	fprintf(stderr,"Hash table size: %d\n",config->hash_size);
	fprintf(stderr,"Vocab            %s\n",config->vocabfile);
	fprintf(stderr,"Temp directory:  %s\n",config->tmpdir);
	fprintf(stderr,"N                %d\n",config->ngram_len);	
	
	if(config->rev_ngram)
		fprintf(stderr,"output reverse n-gram\n");
}

// NGBuf の初期化
void init_NGBuf(config_ngfreq2idngram *config)
{
	
	SLMIDNgramBuffer *NGBuf;
	int ngram_len;
	
	NGBuf = config->NGBuf;
	ngram_len = config->ngram_len;
	
	// バッファの確保
    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);
	
}

// 初期化処理
// エラーの時は、usageを出して終了する
void initialize_ngfreq2idngram(config_ngfreq2idngram  *config, hash_tabel_ngfreq2idngram *hash_ptr)
{
	// config 値のエラーチェック
	// ngram_len の確認
	if(config->ngram_len <1) {
		printf("ngfreq2idngram : ngram_len missing\n");
		goto err_end;
	}
	
	// vocabfile の確認
	if(config->vocabfile == NULL) {
		printf("ngfreq2idngram : vocabulary file missing\n");
		goto err_end;
	}
	
	
	// ファイルオープン	
	if (config->infile != NULL) {
		config->inHANDLE = z_open(config->infile, "r");
	} else {
		config->inHANDLE = FILEIO_stdin();// config->infile == NULL の時
	}
	
	if (config->outfile != NULL) {
		config->outHANDLE = z_open(config->outfile, "w");
		
	} else {
		config->outHANDLE = FILEIO_stdout();// config->outfile == NULL の時
	}
	
	
	// ハッシュ、バッファ関連の初期化
	hash_ptr->vocab_ht = read_vocab(config->vocabfile,NULL,0); // vocabの読み込み→ハッシュテーブルへ
	hash_ptr->tuple = SLMNewWordTuple(config->ngram_len); 
    hash_ptr->read_tuple = SLMNewWordTuple(config->ngram_len);
    hash_ptr->ngram_ht = SLMHashCreate(config->hash_size, ngram_hashfunc, ngram_comp); // n-gramハッシュの作成
	init_NGBuf(config); // NGBufの初期化
		
	
	// initialze 時のメッセージ
	print_initialize_message(config); 
	return; // 通常終了
	
err_end:
	print_usage();
	
}


// 終了処理
void finalize_ngfreq2idngram(config_ngfreq2idngram  *config)
{
	z_close(config->inHANDLE);
	z_close(config->outHANDLE);
	
}

// tmp へ NGBuf を書き出す
void output_tmp_ngbuf(config_ngfreq2idngram  *config)
{
	char tmp_name[MAX_PATH_LENGTH];
	FILEHANDLE df;
	
	sprintf(tmp_name,"%s/ngfreq2idn%d.%d.idngram.gz",config->tmpdir,getpid(),merge_list_num); 
	RemoveAfter(tmp_name);
	push_merge_list(tmp_name);
   	
	df = z_open(tmp_name,"w");
	dumpidngram(config->NGBuf,df,0);
	z_close(df);
   	
	if (config->verbosity > 1) {
	    fprintf(stderr,"\nsorted N-gram written into %s\n",tmp_name);
	}
	
}



// ファイルへ出力する
void output_ngram(config_ngfreq2idngram  *config)
{
	char tmp_name[MAX_PATH_LENGTH];
	int i;
	
	// merge_list_num <= 0 つまりtmp を出力していないとき
	if(merge_list_num <= 0) {
		if (verbosity > 1)
	    	fprintf(stderr,"\ndumping ngrams...\n");
		
		dumpidngram(config->NGBuf, config->outHANDLE, config->ascii_output);
		return;
		
	}
	
	// merge_list_num > 0の時。すなわちtmp を出力しているとき
	if (verbosity > 1)
    	fprintf(stderr,"\nmerging ngrams...\n");
   	
	// 残っているNGBufをtmp へ出力
	output_tmp_ngbuf(config);
	
   	// outf へ出力
	SLMMergeIDNgram(config->ngram_len, merge_list,merge_list_num, config->outHANDLE, 0, config->ascii_output);
   	
   	// unlink
	for ( i = 0; i < merge_list_num; i++) {
		sprintf(tmp_name,"%s/ngfreq2idn%d.%d.idngram.gz",config->tmpdir,getpid(),i);
		unlink(tmp_name);
	
	}
}

// n-gram を ngbuf へ追加する
// count は頻度
void add_ngram_ngbuf(config_ngfreq2idngram  *config, hash_tabel_ngfreq2idngram *hash_ptr, unsigned count)
{
	int i;
	//unsigned lower, incr;
	SLMIDNgramBuffer *NGBuf;
	
	NGBuf = config->NGBuf;
	
	hash_ptr->ht_elem = SLMHashSearch(hash_ptr->ngram_ht, hash_ptr->tuple); //ngramのハッシュからtupleを探す
	
	// ht_elem あり
	if(hash_ptr->ht_elem != NULL) {
		incr_ngram(NGBuf,(SLMWordID*)(hash_ptr->ht_elem->valueptr), count);
		return; 
	}
	
	// ht_elem なし
	// 新しいN-gramが発見された
	// buffer へ tupleをコピーする
	for(i=0; i<config->ngram_len; i++){
		NGBuf->buffer[NGBuf->pos+i] = hash_ptr->tuple[i];
	}
	
	// NGBuf->pos は現在の使用バッファの終端データ
	// 2バイトx2が出現数のデータ
	NGBuf->buffer[NGBuf->pos + config->ngram_len+1] = (count & 0xffff);   //(lower & 0xffff);
	NGBuf->buffer[NGBuf->pos + config->ngram_len] = ((count >> 16) & 0xffff); ////((incr+lower) & 0xffff);
	
	// NGBufから、n-gramのハッシュへinsert
	SLMHashInsert(hash_ptr->ngram_ht, (void*)&NGBuf->buffer[NGBuf->pos], (void*)&NGBuf->buffer[NGBuf->pos]);
   	NGBuf->pos += NGBuf->elem_size;
	
	
	if ((NGBuf->pos) <= ((NGBuf->real_size)-(NGBuf->elem_size)))
		return; // きちんと追加できた場合はここで終了
	
	
	// NGBufが不足したとき
	// ファイルに書き出して、n-gramのハッシュ表を作り直している
	output_tmp_ngbuf(config);
	
   	// N-gramのハッシュ表を新しく作る
	SLMHashDestroy(hash_ptr->ngram_ht);
	hash_ptr->ngram_ht = SLMHashCreate(config->hash_size, ngram_hashfunc, ngram_comp);
	NGBuf->pos = 0;
	
}

// ID N-gramの作成
void ngfreq2idngram(config_ngfreq2idngram  *config, hash_tabel_ngfreq2idngram *hash_ptr)
{
	
	int i,IsExistVocabFlag;
	int tuple_number;
	char strbuf[READ_BUFF_LENGTH];
	char linebuf[READ_BUFF_LENGTH];
	char *line_ptr, *str_ptr;
	unsigned  count;
	
	// n-gram の頻度リストからスペースで区切られた単語＆頻度を読み込む
	// 1 行読み込む
	while(z_gets(linebuf, READ_BUFF_LENGTH, config->inHANDLE) != NULL){
		
		line_ptr = &linebuf[0];
		// スペースまたはタブで区切られた単語を抜き出す
		for(i=0; i < config->ngram_len; i++){
			str_ptr = &strbuf[0];
			
			// スペースまたはタブをスキップ
			while(*line_ptr == ' ' || *line_ptr == '\t') {
				line_ptr++;
			}
			// 次のスペースまたはタブまでコピーする
			while(*line_ptr != ' ' && *line_ptr != '\t') {
				if(*line_ptr == '\n')
					break; // 改行の場合
				
				*str_ptr++ = *line_ptr++;
			}
			// 改行の場合は
			if(*line_ptr == '\n') {
				hash_ptr->tuple[i] = 0; // NULLを代入
				continue;
			}			
			
			*str_ptr = '\0'; // 終端にヌル文字
			
			//config->rev_ngram == 1 なら逆順コーパスで登録する
			if(config->rev_ngram == 1){
				tuple_number = config->ngram_len -i -1;
				
			}else{
				tuple_number = i;
			}
			
			hash_ptr->tuple[tuple_number] = SLMIntHashSearch(hash_ptr->vocab_ht,strbuf); // ハッシュに登録があるか？なければNULL
			
		}
		
		// 頻度を得る
		
		str_ptr = &strbuf[0];
		// スペースまたはタブをスキップ
		while(*line_ptr == ' ' || *line_ptr == '\t') {
			line_ptr++; 
		}
		// 次のスペース,タブ,改行までコピーする
		while(*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\n') {
			*str_ptr++ = *line_ptr++;
		}
		*str_ptr = '\0'; // 終端にヌル文字
		
		count = atoi(strbuf); // 頻度
		if(count == 0)
			count++;
		
		
		
		
		// vocab に存在するか？確認
		IsExistVocabFlag = 1;
		for(i=0; i <config->ngram_len; i++){
			if(hash_ptr->tuple[i] == 0)
				IsExistVocabFlag = 0;
		}
		// vocabに存在すれば n-gram をハッシュへ登録する
		// 頻度も一緒に登録する
		if(IsExistVocabFlag){
    		add_ngram_ngbuf(config, hash_ptr, count);
		}
		
	}
}


// メイン関数
int main(int argc, char *argv[])
{
	config_ngfreq2idngram   config; 
	hash_tabel_ngfreq2idngram   hash_table;
	
	init_config_ngfreq2idngram(&config); // config構造体の初期化
	
	read_commandline_option(argc, argv, &config); // コマンドラインオプション読み込み
	
	initialize_ngfreq2idngram(&config, &hash_table); // 初期化処理
	
	TrapAllSignals();
	ngfreq2idngram(&config, &hash_table);  // ID N-gramの作成
	
	output_ngram(&config); // ファイルへ書き出す
	
	finalize_ngfreq2idngram(&config); // 終了処理
	return 0;
}



