/*
 *	dphfm - Canonical Huffman Code compression tool
 *	Copyright (C) 2014 Naoyuki Sawa
 *
 *	* Sun Nov 23 14:28:09 JST 2014 Naoyuki Sawa
 *	- 1st リリース。
 *	- 「/clip/tool/dtinylz」を元にして作成しました。
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#define DIE() abort()
#define __CLIP_H__
#include "../../clipsort.c"	//qsort_r()
#include "../../clipbary.h"	//bitarray_msb1st_get(),bitarray_msb1st_set()
#include "../../clipbary.c"
#include "../../frambary.c"
#include "../../cliphfm.h"
#include "../../cliphfm.c"
#include "../../framhfm.c"

#define VERSION "20141123"

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

void usage() {
	printf("dphfm - Canonical Huffman Code compression tool (%s)\n", VERSION);
	printf("Copyright (C) 2014 Naoyuki Sawa All Rights Reserved.\n");
	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");
	printf("注意:\n");
	printf("  約16MB以上のﾌｧｲﾙを､圧縮･展開することはできません｡\n");
	printf("  cliphfm.cﾓｼﾞｭｰﾙが､clipbary.cﾓｼﾞｭｰﾙを利用しており､\n");
	printf("  clipbary.cがﾋﾞｯﾄ位置を32bitで管理しているためです｡\n");
	printf("  詳細は､clipbary.cの実装を参照してください｡\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') {
		/* 入力ファイル名の拡張子が省略されていたら、".hfm"とします。
		 * - <例>
		 *	入力ファイル名 D:\path\fname -> 入力ファイル名 D:\path\fname.hfm
		 */
		_splitpath(srcname, drive, dir, fname, ext);
		if(!ext[0]) {
			_makepath(srcname, drive, dir, fname, ".hfm");
		}
		/* 出力ファイル名が省略されていた場合、
		 * - 入力ファイル名の拡張子が".hfm"ならば、".hfm"を取り除いたものとします。
		 * - 入力ファイル名の拡張子が".hfm"でなければ、出力ファイル名を推測できないので、エラーとします。
		 * - <例>
		 *	入力ファイル名 D:\path\fname.ext.hfm -> 出力ファイル名 D:\path\fname.ext
		 *	入力ファイル名 D:\path\fname.ext     -> エラー
		 */
		if(!dstname[0]) {
			_splitpath(srcname, drive, dir, fname, ext);
			if(!stricmp(ext, ".hfm")) {
				_makepath(dstname, drive, dir, fname, NULL);
			} else {
				fprintf(stderr, "Error: Destination file could not be supposed.\n");
				exit(1);
			}
		}
	}
	if(mode == 'e') {
		/* 出力ファイル名が省略されていたら、入力ファイル名に拡張子".hfm"を追加したものとします。
		 * - 入力ファイル名が拡張子を持っている場合も、その後ろにもう一つの拡張子".hfm"を追加します。
		 *   その場合、追加した拡張子".hfm"が本当の拡張子になって、元の拡張子はファイル名扱いとなります。
		 *   <例>
		 *	入力ファイル名 D:\path\fname.ext -> 出力ファイル名 D:\path\fname.ext.hfm
		 */
		if(!dstname[0]) {
			_makepath(dstname, NULL, NULL, srcname, ".hfm");
		}
		/* 出力ファイル名の拡張子が省略されていたら、".hfm"とします。
		 * - <例>
		 *	出力ファイル名 D:\path\fname -> 出力ファイル名 D:\path\fname.hfm
		 */
		_splitpath(dstname, drive, dir, fname, ext);
		if(!ext[0]) {
			_makepath(dstname, drive, dir, fname, ".hfm");
		}
	}
	/* 入力ファイルと出力ファイルが同じであってはいけません。 */
	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バイトの圧縮データは有り得ないため、エラーとします。圧縮データは、8バイト以上であるはずです。HuffmanEncoder_Encode()の「データが0バイトの場合の特例処理」を参照してください。尚、圧縮('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') {
		/* 圧縮。 */
		dstlen = HuffmanEncoder_Encode(srcbuf, srclen, NULL, 0);
		dstbuf = malloc(dstlen);
		if(!dstbuf) {
			fprintf(stderr, "Error: Failed to allocate destination buffer.\n");
			exit(1);
		}
		HuffmanEncoder_Encode(srcbuf, srclen, dstbuf, dstlen);
		/* ベリファイ。 */
		verbuf = malloc(srclen);
		if(!verbuf) {
			fprintf(stderr, "Error: Failed to allocate verify buffer.\n");
			exit(1);
		}
		verlen = HuffmanDecoder_Decode(dstbuf, verbuf, srclen);
		if((verlen != srclen) || memcmp(verbuf, srcbuf, srclen)) {
			fprintf(stderr, "Internal error: Verify failed.n");
			exit(1);
		}
	}
	if(mode == 'd') {
		/* 展開。 */
		dstlen = HuffmanDecoder_Decode(srcbuf, NULL, 0);
		dstbuf = malloc(dstlen);
		if(!dstbuf) {
			fprintf(stderr, "Error: Failed to allocate destination buffer.\n");
			exit(1);
		}
		HuffmanDecoder_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;
}
