/*
 *	dpbpe - Byte Pair Encoding compression tool
 *	Copyright (C) 2015 Naoyuki Sawa
 *
 *	* Thu Jan 29 21:20:19 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	- 「/clip/tool/dprc」を元にして作成しました。
 *	* Fri Jan 30 21:08:25 JST 2015 Naoyuki Sawa
 *	- clipbpe.h,clipbpe.c,frambpe.cを変更したので、再コンパイルしました。
 *	* Mon Feb 02 00:23:19 JST 2015 Naoyuki Sawa
 *	- clipbpe.cのBytePairEncodingEncoder_Encode()をマルチスレッドに対応したので、再コンパイルしました。
 *	  BytePairEncodingEncoder_Encode()のマルチスレッド版を使用するために、「プロジェクトの設定」の「ランタイムライブラリ」に「マルチスレッド」を選択しました。
 *	* Mon Feb 02 21:59:02 JST 2015 Naoyuki Sawa
 *	- clipbpe.cのマルチスレッド版のBytePairEncodingEncoder_Encode()が(srcLen=1)の場合に終了しなかったバグを修正したので、再コンパイルしました。
 *	* Sat Nov 14 21:55:38 JST 2015 Naoyuki Sawa
 *	- clipbpe.cモジュールの変更に伴い、当ツールもリビルドしました。
 *	- "-s"オプションを追加しました。エンコード時に、強制的にシングルスレッド版を使用する事が出来ます。
 *	  古いPentium4で、HTによってマルチスレッド版を使用するよりも、強制的にシングルスレッド版を使用する方が早かったので、このオプションを追加する事にしました。
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <math.h>
#include <process.h>			//_beginthread()
#define STRICT
#include <windows.h>			//GetSystemInfo()
#define __CLIP_H__
#include "../../include/stdint.h"	//int64_t
#include "../../clipmisc.h"		//DIE,FIELD_SIZEOF
#include "../../clipmisc.c"		//DIE,FIELD_SIZEOF
#include "../../clipbpe.h"
#include "../../clipbpe.c"
#include "../../frambpe.c"

#define VERSION "20151114"

/////////////////////////////////////////////////////////////////////////////

void usage() {
	printf("dpbpe - Byte Pair Encoding compression tool (%s)\n", VERSION);
	printf("Copyright (C) 2015 Naoyuki Sawa All Rights Reserved.\n");
	printf("Usage: [options] <source file name> [<destination file name>]\n");
	printf("Options:\n");
	printf("  -e : encode source file    (default)\n");
	printf("  -d : decode source file\n");
	printf("  -s : force single threaded (only for encoding)\n");
	exit(1);
}

int mode; /* 'd' or 'e' */
int opt_s;
char srcname[_MAX_PATH];
char dstname[_MAX_PATH];

int srclen;
unsigned char* srcbuf;
int dstlen;
unsigned char* dstbuf;
int verlen;
unsigned char* verbuf;

int main(int argc, char* argv[]) {
	int i;
	char drive[_MAX_DRIVE];
	char dir[_MAX_DIR];
	char fname[_MAX_FNAME];
	char ext[_MAX_EXT];
	FILE* fp;

	/* コマンドライン解析。 */
	for(i = 1; i < argc; i++) {
		switch(argv[i][0]) {
		case '-':
		case '/':
			switch(tolower(argv[i][1])) {
			case 'd':
			case 'e':
				if(!mode) {
					mode = tolower(argv[i][1]);
					break;
				}
				usage();
				break;
			case 's':
				opt_s = 1;
				break;
			default:
				usage();
				break;
			}
			break;
		default:
			if(!srcname[0]) {
				strcpy(srcname, argv[i]);
				break;
			}
			if(!dstname[0]) {
				strcpy(dstname, argv[i]);
				break;
			}
			usage();
			break;
		}
	}
	/* "-d"も"-e"も指定されていなければ、"-e"とします。 */
	if(!mode) {
		mode = 'e';
	}
	/* "-s"は"-e"とだけ併用出来ます。(暗黙の"-e"も含む) */
	if(opt_s && (mode != 'e')) {
		usage();
	}
	/* 入力ファイル名は必須です。 */
	if(!srcname[0]) {
		usage();
	}
	if(mode == 'd') {
		/* 入力ファイル名の拡張子が省略されていたら、".bpe"とします。
		 * - <例>
		 *	入力ファイル名 D:\path\fname -> 入力ファイル名 D:\path\fname.bpe
		 */
		_splitpath(srcname, drive, dir, fname, ext);
		if(!ext[0]) {
			_makepath(srcname, drive, dir, fname, ".bpe");
		}
		/* 出力ファイル名が省略されていた場合、
		 * - 入力ファイル名の拡張子が".bpe"ならば、".bpe"を取り除いたものとします。
		 * - 入力ファイル名の拡張子が".bpe"でなければ、出力ファイル名を推測できないので、エラーとします。
		 * - <例>
		 *	入力ファイル名 D:\path\fname.ext.bpe -> 出力ファイル名 D:\path\fname.ext
		 *	入力ファイル名 D:\path\fname.ext     -> エラー
		 */
		if(!dstname[0]) {
			_splitpath(srcname, drive, dir, fname, ext);
			if(!stricmp(ext, ".bpe")) {
				_makepath(dstname, drive, dir, fname, NULL);
			} else {
				fprintf(stderr, "Error: Destination file could not be supposed.\n");
				exit(1);
			}
		}
	}
	if(mode == 'e') {
		/* 出力ファイル名が省略されていたら、入力ファイル名に拡張子".bpe"を追加したものとします。
		 * - 入力ファイル名が拡張子を持っている場合も、その後ろにもう一つの拡張子".bpe"を追加します。
		 *   その場合、追加した拡張子".bpe"が本当の拡張子になって、元の拡張子はファイル名扱いとなります。
		 *   <例>
		 *	入力ファイル名 D:\path\fname.ext -> 出力ファイル名 D:\path\fname.ext.bpe
		 */
		if(!dstname[0]) {
			_makepath(dstname, NULL, NULL, srcname, ".bpe");
		}
		/* 出力ファイル名の拡張子が省略されていたら、".bpe"とします。
		 * - <例>
		 *	出力ファイル名 D:\path\fname -> 出力ファイル名 D:\path\fname.bpe
		 */
		_splitpath(dstname, drive, dir, fname, ext);
		if(!ext[0]) {
			_makepath(dstname, drive, dir, fname, ".bpe");
		}
	}
	/* 入力ファイルと出力ファイルが同じであってはいけません。 */
	if(!stricmp(srcname, dstname)) {
		fprintf(stderr, "Error: Source file should not be same as destination file.\n");
		exit(1);
	}

	/* 入力ファイルを読み込みます。 */
	fp = fopen(srcname, "rb");
	if(!fp) {
		fprintf(stderr, "Error: Failed to open file %s.\n", srcname);
		exit(1);
	}
	fseek(fp, 0, SEEK_END);
	srclen = ftell(fp);
	if(!srclen && (mode == 'd')) {	//展開('d')ならば、0バイトの圧縮データは有り得ないため、エラーとします。圧縮データは、1バイト以上であるはずです。BytePairEncodingEncoder_Encode()の実装を参照してください。尚、圧縮('e')ならば、0バイトのファイルを圧縮することも可能なので、エラーとはしません。
		fprintf(stderr, "Error: Source file should not be empty.");
		exit(1);
	}
	rewind(fp);
	srcbuf = malloc(srclen);
	if(!srcbuf) {
		fprintf(stderr, "Error: Failed to allocate source buffer.\n");
		exit(1);
	}
	fread(srcbuf, 1, srclen, fp);
	fclose(fp);

	if(mode == 'e') {
		int (*fnEncode)(const void* _src, int srcLen, void* _dst, int dstCap) = BytePairEncodingEncoder_EncodeST;	//仮に、シングルスレッド版としておく。
		if(!opt_s) {													//"-s"が指定されておらず、
			SYSTEM_INFO si;												//
			GetSystemInfo(&si);											//
			if(si.dwNumberOfProcessors >= 2) { fnEncode = BytePairEncodingEncoder_EncodeMT; }			//プロセッサ数が2以上ならば、マルチスレッド版を使用する。
		}
		/* 圧縮。 */
		dstbuf = malloc(srclen/*仮サイズ*/);			//圧縮後の方が小さくなると仮定して1st try
		if(!dstbuf) {
			fprintf(stderr, "Error: Failed to allocate destination buffer.\n");
			exit(1);
		}
		dstlen = (*fnEncode)(srcbuf, srclen, dstbuf, srclen/*仮サイズ*/);
		if(dstlen > srclen) {					//圧縮後の方が大きくなったら…
			dstbuf = realloc(dstbuf, dstlen/*実サイズ*/);	//実際の圧縮後のサイズを割り当てて2nd try
			if(!dstbuf) {
				fprintf(stderr, "Error: Failed to allocate destination buffer.\n");
				exit(1);
			}
			if((*fnEncode)(srcbuf, srclen, dstbuf, dstlen/*実サイズ*/) != dstlen) { DIE(); }	//出力バッファを指定してもしなくても圧縮サイズは同じ結果になるはず。ここで止まったらエンコーダのバグです。
		}
		/* ベリファイ。 */
		verbuf = malloc(srclen);
		if(!verbuf) {
			fprintf(stderr, "Error: Failed to allocate verify buffer.\n");
			exit(1);
		}
		verlen = BytePairEncodingDecoder_Decode(dstbuf, verbuf, srclen);
		if((verlen != srclen) || memcmp(verbuf, srcbuf, srclen)) {
			fprintf(stderr, "Internal error: Verify failed.n");
			exit(1);
		}
	}
	if(mode == 'd') {
		/* 展開。 */
		dstlen = BytePairEncodingDecoder_Decode(srcbuf, NULL, 0);
		dstbuf = malloc(dstlen);
		if(!dstbuf) {
			fprintf(stderr, "Error: Failed to allocate destination buffer.\n");
			exit(1);
		}
		BytePairEncodingDecoder_Decode(srcbuf, dstbuf, dstlen);
	}

	/* 出力ファイルを書き込みます。 */
	fp = fopen(dstname, "wb");
	if(!fp) {
		fprintf(stderr, "Error: Failed to create file %s.\n", dstname);
		exit(1);
	}
	fwrite(dstbuf, 1, dstlen, fp);
	fclose(fp);

	/* レポート表示。 */
	printf("%s (%d) => %s (%d) : %d%%\n",
		srcname, srclen,
		dstname, dstlen,
		(int)ceil((double)dstlen * 100.0 / (double)srclen));

	return 0;
}
