/*
 *	dpdrh - Canonical Huffman Code compression tool
 *	Copyright (C) 2014 Naoyuki Sawa
 *
 *	* Mon Nov 24 13:19:05 JST 2014 Naoyuki Sawa
 *	- 1st リリース。
 *	- 「/clip/tool/dtinylz」「/clip/tool/dphfm」を元にして作成しました。
 */
#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"
#include "../../clipdrh.h"
#include "../../clipdrh.c"
#include "../../framdrh.c"

#define VERSION "20141124"

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

void usage() {
	printf("dpdrh - 差分圧縮⇒連長圧縮⇒ハフマン圧縮ツール (%s)\n", VERSION);
	printf("Copyright (C) 2014 Naoyuki Sawa All Rights Reserved.\n");
	printf("Usage: [-d/-e] [-bN] <source file name> [<destination file name>]\n");
	printf("Options:\n");
	printf("  -e  : encode source file(default)\n");
	printf("  -d  : decode source file\n");
	printf("  -bN : data bits. N=1...8(default)\n");
	printf("注意:\n");
	printf("  約16MB以上のﾌｧｲﾙを､圧縮･展開することはできません｡\n");
	printf("  clipdrh.cﾓｼﾞｭｰﾙが､cliphfm.cﾓｼﾞｭｰﾙを利用しており､\n");
	printf("  cliphfm.cﾓｼﾞｭｰﾙが､clipbary.cﾓｼﾞｭｰﾙを利用しており､\n");
	printf("  clipbary.cがﾋﾞｯﾄ位置を32bitで管理しているためです｡\n");
	printf("  詳細は､clipbary.cの実装を参照してください｡\n");
	printf("ﾃﾞｰﾀﾋﾞｯﾄ数について:\n");
	printf("  圧縮時に'-bN'ｵﾌﾟｼｮﾝを指定すると､圧縮前に､ﾃﾞｰﾀを(8-N)ﾋﾞｯﾄ左ｼﾌﾄします｡\n");
	printf("  展開時に'-bN'ｵﾌﾟｼｮﾝを指定すると､展開後に､ﾃﾞｰﾀを(8-N)ﾋﾞｯﾄ右ｼﾌﾄします｡\n");
	printf("  たとえば､ﾃﾞｰﾀが0〜31の範囲であることがあらかじめ判っている場合は､\n");
	printf("  '-b5'ｵﾌﾟｼｮﾝを指定することによって､圧縮率を向上することができます｡\n");
	printf("ﾃﾞｰﾀﾋﾞｯﾄ数を指定した場合に検出するｴﾗｰについて:\n");
	printf("  圧縮時と展開時に､同じｵﾌﾟｼｮﾝを指定する必要が有る事に注意してください｡\n");
	printf("  同じｵﾌﾟｼｮﾝを指定しないと､下記のｴﾗｰを検出して､終了することがあります｡\n");
	printf("  圧縮時に'-bN'ｵﾌﾟｼｮﾝが指定されたが､ｼﾌﾄｱｳﾄする上位ﾋﾞｯﾄに､\n");
	printf("  '1'のﾋﾞｯﾄが存在していた場合は､ｴﾗｰを表示して終了します｡\n");
	printf("  展開時に'-bN'ｵﾌﾟｼｮﾝが指定されたが､ｼﾌﾄｱｳﾄする下位ﾋﾞｯﾄに､\n");
	printf("  '1'のﾋﾞｯﾄが存在していた場合は､ｴﾗｰを表示して終了します｡\n");
	exit(1);
}

int mode; /* 'd' or 'e' */
int bits; /* 1,2,3,〜,8 */
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 'b':
				if(!bits) {
					bits = atoi(&argv[i][2]);
					if((bits >= 1) && (bits <= 8)) {
						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';
	}
	/* "-bN"が指定されていなければ、"-b8"とします。 */
	if(!bits) {
		bits = 8;
	}
	/* 入力ファイル名は必須です。 */
	if(!srcname[0]) {
		usage();
	}
	if(mode == 'd') {
		/* 入力ファイル名の拡張子が省略されていたら、".drh"とします。
		 * - <例>
		 *	入力ファイル名 D:\path\fname -> 入力ファイル名 D:\path\fname.drh
		 */
		_splitpath(srcname, drive, dir, fname, ext);
		if(!ext[0]) {
			_makepath(srcname, drive, dir, fname, ".drh");
		}
		/* 出力ファイル名が省略されていた場合、
		 * - 入力ファイル名の拡張子が".drh"ならば、".drh"を取り除いたものとします。
		 * - 入力ファイル名の拡張子が".drh"でなければ、出力ファイル名を推測できないので、エラーとします。
		 * - <例>
		 *	入力ファイル名 D:\path\fname.ext.drh -> 出力ファイル名 D:\path\fname.ext
		 *	入力ファイル名 D:\path\fname.ext     -> エラー
		 */
		if(!dstname[0]) {
			_splitpath(srcname, drive, dir, fname, ext);
			if(!stricmp(ext, ".drh")) {
				_makepath(dstname, drive, dir, fname, NULL);
			} else {
				fprintf(stderr, "Error: Destination file could not be supposed.\n");
				exit(1);
			}
		}
	}
	if(mode == 'e') {
		/* 出力ファイル名が省略されていたら、入力ファイル名に拡張子".drh"を追加したものとします。
		 * - 入力ファイル名が拡張子を持っている場合も、その後ろにもう一つの拡張子".drh"を追加します。
		 *   その場合、追加した拡張子".drh"が本当の拡張子になって、元の拡張子はファイル名扱いとなります。
		 *   <例>
		 *	入力ファイル名 D:\path\fname.ext -> 出力ファイル名 D:\path\fname.ext.drh
		 */
		if(!dstname[0]) {
			_makepath(dstname, NULL, NULL, srcname, ".drh");
		}
		/* 出力ファイル名の拡張子が省略されていたら、".drh"とします。
		 * - <例>
		 *	出力ファイル名 D:\path\fname -> 出力ファイル名 D:\path\fname.drh
		 */
		_splitpath(dstname, drive, dir, fname, ext);
		if(!ext[0]) {
			_makepath(dstname, drive, dir, fname, ".drh");
		}
	}
	/* 入力ファイルと出力ファイルが同じであってはいけません。 */
	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バイトの圧縮データは有り得ないため、エラーとします。圧縮データは、12バイト以上であるはずです。
		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') {
		/* シフト。 */
		for(i = 0; i < srclen; i++) {
			if((((unsigned char)(srcbuf[i] << (8 - bits))) >> (8 - bits)) != srcbuf[i]) {
				fprintf(stderr, "Error: Encoding value (%d) at [%d] is out of range.\n", srcbuf[i], i);
				exit(1);
			}
			srcbuf[i] <<= (8 - bits);
		}
		/* 圧縮。 */
		dstlen = DeltaRunHuffmanEncoder_Encode(srcbuf, srclen, NULL, 0);
		dstbuf = malloc(dstlen);
		if(!dstbuf) {
			fprintf(stderr, "Error: Failed to allocate destination buffer.\n");
			exit(1);
		}
		DeltaRunHuffmanEncoder_Encode(srcbuf, srclen, dstbuf, dstlen);
		/* ベリファイ。 */
		verbuf = malloc(srclen);
		if(!verbuf) {
			fprintf(stderr, "Error: Failed to allocate verify buffer.\n");
			exit(1);
		}
		verlen = DeltaRunHuffmanDecoder_Decode(dstbuf, verbuf, srclen);
		if((verlen != srclen) || memcmp(verbuf, srcbuf, srclen)) {
			fprintf(stderr, "Internal error: Verify failed.n");
			exit(1);
		}
	}
	if(mode == 'd') {
		/* 展開。 */
		dstlen = DeltaRunHuffmanDecoder_Decode(srcbuf, NULL, 0);
		dstbuf = malloc(dstlen);
		if(!dstbuf) {
			fprintf(stderr, "Error: Failed to allocate destination buffer.\n");
			exit(1);
		}
		DeltaRunHuffmanDecoder_Decode(srcbuf, dstbuf, dstlen);
		/* シフト。 */
		for(i = 0; i < dstlen; i++) {
			if((((unsigned char)(dstbuf[i] >> (8 - bits))) << (8 - bits)) != dstbuf[i]) {
				fprintf(stderr, "Error: Decoded value (%d) at [%d] is out of range.\n", dstbuf[i], i);
				exit(1);
			}
			dstbuf[i] >>= (8 - bits);
		}
	}

	/* 出力ファイルを書き込みます。 */
	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;
}
