/*
 *	dtinylz - TinyLZ compression tool
 *	Copyright (C) 2009 Naoyuki Sawa
 *
 *	* Sun Feb 17 14:57:30 JST 2008 Naoyuki Sawa
 *	- 1st リリース。
 *	* Mon Nov 17 11:22:19 JST 2008 Naoyuki Sawa
 *	- 拡張子".tlz"の自動補完ルールを変更しました。
 *	  これまでは、圧縮前のファイル名の拡張子を".tlz"に置き換えてしまっていたので、元の拡張子の情報が失われていました。
 *	  今回の変更で、圧縮前のファイル名の拡張子を残したまま".tlz"を追加するようにしたので、元の拡張子の情報が残ります。
 *	  <例>
 *		圧縮: dtinylz -e fname.ext     (出力ファイル名省略) -> 出力ファイル名は fname.ext.tlz になります。
 *		展開: dtinylz -d fname.ext.tlz (出力ファイル名省略) -> 出力ファイル名は fname.ext     になります。
 *	- 詳細な自動補完ルールは、ソース内のコメントを"2008/11/17"で検索して参照してください。
 *	* Mon Mar 02 22:18:49 JST 2009 Naoyuki Sawa
 *	- cliptlz.cモジュールのTinyLZ_compress()関数の実装変更に伴い、再コンパイルしました。
 *	  本モジュール(main.c)のコードは、変更ありません。
 *	  また、TinyLZ_compress()関数の変更も、出力データはこれまでと変化ありません。
 *	* Thu Aug 06 11:14:39 JST 2009 Naoyuki Sawa
 *	- ファイルサイズが約20MB以上のときに、圧縮率の計算がオーバーフローして、マイナス値を表示してしまっていました。
 *	  間違っていたのはレポート表示だけで、圧縮・展開したファイルそのものは間違っていませんでした。
 *	  今回の版で、上記のバグを修正し、正しい圧縮率をレポート表示するようにしました。
 *	* Wed Aug 26 01:30:42 JST 2009 Naoyuki Sawa
 *	- ベリファイ時に、サイズ比較だけでなく、内容比較も行うよう修正しました。
 *	  本プログラムは既に充分安定しており、ベリファイはさほど重要ではないのですが、念のために修正しておくことにしました。
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#define DIE() abort()
#define __CLIP_H__
#include "../../cliptlz.h"
#include "../../cliptlz.c"

#define VERSION "20090826"

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

void
usage()
{
	printf("dtinylz - TinyLZ compression tool (%s) Naoyuki Sawa\n", VERSION);
	printf("Usage: [-d/-e] <source file name> [<destination file name>]\n");
	printf("Options:\n");
	printf("  -e : encode source file(default)\n");
	printf("  -d : decode source file\n");
	exit(1);
}

int mode; /* 'd' or 'e' */
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;
			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';
	}
	/* 入力ファイル名は必須です。 */
	if(!srcname[0]) {
		usage();
	}
	if(mode == 'd') {
//{{2008/11/17:拡張子".tlz"の自動補完ルールを変更しました。
//		/* 入力ファイル名の拡張子が省略されていたら、".tlz"とします。 */
//		_splitpath(srcname, drive, dir, fname, ext);
//		if(!ext[0]) {
//			_makepath(srcname, drive, dir, fname, ".tlz");
//		}
//		/* 出力ファイル名が省略されていたら、入力ファイル名から拡張子を取り除いたものとします。 */
//		if(!dstname[0]) {
//			_splitpath(srcname, drive, dir, fname, ext);
//			 _makepath(dstname, drive, dir, fname, "");
//		}
//↓2008/11/17:拡張子".tlz"の自動補完ルールを変更しました。
		/* 入力ファイル名の拡張子が省略されていたら、".tlz"とします。
		 * - <例>
		 *	入力ファイル名 D:\path\fname -> 入力ファイル名 D:\path\fname.tlz
		 */
		_splitpath(srcname, drive, dir, fname, ext);
		if(!ext[0]) {
			_makepath(srcname, drive, dir, fname, ".tlz");
		}
		/* 出力ファイル名が省略されていた場合、
		 * - 入力ファイル名の拡張子が".tlz"ならば、".tlz"を取り除いたものとします。
		 * - 入力ファイル名の拡張子が".tlz"でなければ、出力ファイル名を推測できないので、エラーとします。
		 * - <例>
		 *	入力ファイル名 D:\path\fname.ext.tlz -> 出力ファイル名 D:\path\fname.ext
		 *	入力ファイル名 D:\path\fname.ext     -> エラー
		 */
		if(!dstname[0]) {
			_splitpath(srcname, drive, dir, fname, ext);
			if(!stricmp(ext, ".tlz")) {
				_makepath(dstname, drive, dir, fname, NULL);
			} else {
				fprintf(stderr, "Error: Destination file could not be supposed.\n");
				exit(1);
			}
		}
//}}2008/11/17:拡張子".tlz"の自動補完ルールを変更しました。
	}
	if(mode == 'e') {
//{{2008/11/17:拡張子".tlz"の自動補完ルールを変更しました。
//		/* 出力ファイル名が省略されていたら、入力ファイル名の拡張子を".tlz"にしたものとします。 */
//		if(!dstname[0]) {
//			_splitpath(srcname, drive, dir, fname, ext);
//			 _makepath(dstname, drive, dir, fname, ".tlz");
//		}
//		/* 出力ファイル名の拡張子が省略されていたら、".tlz"とします。 */
//		_splitpath(dstname, drive, dir, fname, ext);
//		if(!ext[0]) {
//			_makepath(dstname, drive, dir, fname, ".tlz");
//		}
//↓2008/11/17:拡張子".tlz"の自動補完ルールを変更しました。
		/* 出力ファイル名が省略されていたら、入力ファイル名に拡張子".tlz"を追加したものとします。
		 * - 入力ファイル名が拡張子を持っている場合も、その後ろにもう一つの拡張子".tlz"を追加します。
		 *   その場合、追加した拡張子".tlz"が本当の拡張子になって、元の拡張子はファイル名扱いとなります。
		 *   <例>
		 *	入力ファイル名 D:\path\fname.ext -> 出力ファイル名 D:\path\fname.ext.tlz
		 */
		if(!dstname[0]) {
			_makepath(dstname, NULL, NULL, srcname, ".tlz");
		}
		/* 出力ファイル名の拡張子が省略されていたら、".tlz"とします。
		 * - <例>
		 *	出力ファイル名 D:\path\fname -> 出力ファイル名 D:\path\fname.tlz
		 */
		_splitpath(dstname, drive, dir, fname, ext);
		if(!ext[0]) {
			_makepath(dstname, drive, dir, fname, ".tlz");
		}
//}}2008/11/17:拡張子".tlz"の自動補完ルールを変更しました。
	}
	/* 入力ファイルと出力ファイルが同じであってはいけません。 */
	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) {
		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') {
		/* 圧縮。 */
		dstlen = TinyLZ_compress(NULL, 0, srcbuf, srclen);
		dstbuf = malloc(dstlen);
		if(!dstbuf) {
			fprintf(stderr, "Error: Failed to allocate destination buffer.\n");
			exit(1);
		}
		TinyLZ_compress(dstbuf, dstlen, srcbuf, srclen);
		/* ベリファイ。 */
		verbuf = malloc(srclen);
		if(!verbuf) {
			fprintf(stderr, "Error: Failed to allocate verify buffer.\n");
			exit(1);
		}
		verlen = TinyLZ_uncompress(verbuf, srclen, dstbuf, dstlen);
		//{{2009/08/26:サイズ比較だけでなく、内容比較も行うよう修正
		//if(verlen != srclen) {
		//↓2009/08/26:サイズ比較だけでなく、内容比較も行うよう修正
		if((verlen != srclen) || memcmp(verbuf, srcbuf, srclen)) {
		//}}2009/08/26:サイズ比較だけでなく、内容比較も行うよう修正
			fprintf(stderr, "Internal error: Verify failed.n");
			exit(1);
		}
	}
	if(mode == 'd') {
		/* 展開。 */
		dstlen = TinyLZ_uncompress(NULL, 0, srcbuf, srclen);
		dstbuf = malloc(dstlen);
		if(!dstbuf) {
			fprintf(stderr, "Error: Failed to allocate destination buffer.\n");
			exit(1);
		}
		TinyLZ_uncompress(dstbuf, dstlen, srcbuf, srclen);
	}

	/* 出力ファイルを書き込みます。 */
	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,
		//{{2009/08/06:オーバーフローバグ修正
		//(dstlen * 100 + srclen - 1) / srclen);
		//↓2009/08/06:オーバーフローバグ修正
		(int)ceil((double)dstlen * 100.0 / (double)srclen));
		//}}2009/08/06:オーバーフローバグ修正

	return 0;
}
