/*
 *         bpack - A Burrows-Wheeler Transform and Huffman Encoding
 *                            Implementation
 *
 *  Copyright (C) 2006      Angelo Arrifano <miknix@gmail.com>
 *  Copyright (C) 2006      Vasco Costa <the_papon@hotmail.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "bpack.h"
#include "bw.h"
#include "mtf.h"
#include "huffman.h"
#include "crc32.h"

/***********************************
   .BPK FILE FORMAT DESCRIPTION
 
BPK Header format
 Pos    |  Len  | Description
 0         24     BPK ID
 24        ..     First block

Block format
 Pos    |  Len  | Description
 0         32     Original Block data
 32        32     Original Block data CRC32 Hash
 64        32     Block data size (bytes): bs
 96        bs     Block data
 96+bs     8      Nr of used bits in last byte
 104+bs    16     Huffman number of codes: hc
 120+bs    hc     Huffman codes
 120+bs+hc 32     Burrows Wheeler Index

Huffman codes format
 Pos    |  Len  | Description
 0         8      Character
 8         8      Code len (bits): cs
 16        32*cs  Byte code

************************************/



void decompress(int usecrc){
unsigned char id[3];
int size1, size2, i, index;
unsigned int crc1, crc2;
unsigned char *block1, *block2;

	fprintf(stderr, "Decompressing..\n");

	//Check header
	fread(id, 1, 3, stdin);
	if(memcmp(id, "BPK", 3) != 0) exit_err(20);

	//Read blocks
	i=0;
	while(!feof(stdin)){
		//Read original block data size
		if(fread(&size2, sizeof(int), 1, stdin) == 0) break;

		//Read CRC32 of original block data
		fread(&crc1, sizeof(int), 1, stdin);

		//Read block data size
		//FIXME: WoW! This is dangerous!
		fread(&size1, sizeof(int), 1, stdin);	

		//FIXME: WoW! This is dangerous!
		if((block1=(unsigned char *)malloc(size1))==NULL) exit_err(10);

		fprintf(stderr, "[Block %2d]. ", i);

		//Read block encoded data
		fread(block1, 1, size1, stdin);
		
		if((block2=(unsigned char *)malloc(size2))==NULL) exit_err(10);

		//Huffman Decoding
		huff_decode(block1, size1, block2, size2);

		fread(&index, sizeof(int), 1, stdin);

		free(block1);
		if((block1=(unsigned char *)malloc(size2))==NULL) exit_err(10);

		fprintf(stderr, ". ");

		//Move to Front Decoding
		mtf_decode(block2, block1, size2);
		
		fprintf(stderr, ". ");

		//Burrows Wheeler Inverse Transform
		bw_decode(block1, block2, size2, index);

		//CRC Check
		if(usecrc && (crc1 != 0)){
			crc2=crc32(block2, size2);
			if(crc2 == crc1)
				fprintf(stderr, "CRC_OK\n");
			else
				exit_err(80);
		}else{
			fprintf(stderr, "OK\n");
		}
	
		//Print data to stdout
		fwrite(block2, 1, size2, stdout);

		free(block1);
		free(block2);
		i++;
	}
	fprintf(stderr, "Done!\n");
}

void compress(int blocksz, int usecrc){
int size1=blocksz*1024;
int size2, size3, i, index;
unsigned int totali, totalo, crc;
unsigned char *block1, *block2;

	fprintf(stderr, "Compressing with block size %d..\n", blocksz);
	if((block1=(unsigned char *)malloc(size1))==NULL) exit_err(10);

	//Write Header
	fwrite((unsigned char *)"BPK", 1, 3, stdout);
		
	i=0;
	totali=0;
	totalo=0;
	do{
		if((size2=fread(block1, 1, size1, stdin)) == 0) break;
	
		//Write original block data size
		fwrite(&size2, sizeof(int), 1, stdout);

		//Write CRC32 of Original data
		//FIXME: This could be calculated
		//during BWEnc Loop to increase efficiency
		if(usecrc)
			crc=crc32(block1, size2);
		else
			crc=0;
		fwrite(&crc, sizeof(int), 1, stdout);
		
		if((block2=(unsigned char *)malloc(size2))==NULL) exit_err(10);
		
		fprintf(stderr, "[Block %2d]. ", i);

		//Burrows Wheeler Transform
		index=bw_encode(block1, block2, size2);		

		fprintf(stderr, ". ");

		//Move to Front Encoding
		mtf_encode(block2, block1, size2);		

		fprintf(stderr, ". ");

		//Huffman Encoding
		size3=huff_encode(block1, size2);

		fwrite(&index, sizeof(int), 1, stdout);
		fprintf(stderr," %d%%\n", 100*size3/size2);
		i++;
		totali+=size2;
		totalo+=size3;	
		free(block2);
	}while(size2 == size1);
	
	fprintf(stderr, "Done!\n");
	fprintf(stderr, "In   : %.2f Kb\n", (float)totali/1024.);
	fprintf(stderr, "Out  : %.2f Kb\n", (float)totalo/1024.);
	fprintf(stderr, "Ratio: %d %%\n", 100*totalo/totali);

	free(block1);
}

void exit_err(int status){
	switch(status){
		case 10:
			fprintf(stderr, "OUT_OF_MEM: Sorry, out of memory!\n");
			break;
		case 20:
			fprintf(stderr, "FORMAT_ERROR: Invalid file format.\n");
			break;
		case 30:
			fprintf(stderr, "ERROR: Encoding tree is damaged!\n");
			break;
		case 40:
			fprintf(stderr, "ERROR: Decoding tree is damaged!\n");
			break;
		case 50:
			fprintf(stderr, "ERROR: Decoding error!\n");
			break;
		case 80:
			fprintf(stderr, "CRC_FAILED\n");
			break;
	}
	exit(status);
}

void help(){
	fprintf(stderr, "./bpack <options>\n");
	fprintf(stderr, "bpack reads from standard input and outputs to standard output.\n");
	fprintf(stderr, "Options:\n");
	fprintf(stderr, "\t-h - this Help.\n");
	fprintf(stderr, "\t-s - Silent! Drop stderr channel.\n");
	fprintf(stderr, "\t-c - enable CRC32 checking.\n");
	fprintf(stderr, "\t-d - Decompress.\n");
	fprintf(stderr, "\t-b <size> - compress with specified Block size in Kb.\n");
	fprintf(stderr, "\t            Sizes could be either 128, 256, 512, 1024 or 2048.\n");
	fprintf(stderr, "\t            Use 128 for low memory systems. 512 is the default.\n");

	exit(0);
}

/************************************
          MAIN EXIT CODES
 0  - SUCCESS
 10 - OUT OF MEMORY
 20 - OPEN: INVALID BPK FILE
 30 - ENC: TREE IS DAMAGED
 40 - DEC: TREE IS DAMAGED
 50 - DEC: ERROR
 80 - CRC ERROR
 ***********************************/
int main(int argc, char **argv){
	int i, options=0, blocksz=512;

	//FIXME: Compressed data is not portable to machines
	//with other sizes of types

	/* OPTIONS Bitfield
	     2  - Decompress
		  4  - Silent mode
		  8  - Enable CRC Checking
		  16 - Block is set
	   BLOCK SIZES
	   128  - 128K Block size compression
	   256  - 256K Block size compression
	  *512  - 512K Block size compression (default)
	  1024  - 1M Block size compression
	  2048  - 2M Block size compression
	*/

	for(i=1; i<argc; i++){
		if(strcmp(argv[i],"-h") == 0) help();
		if((strcmp(argv[i],"-d") == 0) && ((options|16) != options)) options|=2;
		if(strcmp(argv[i],"-s") == 0) options|=4;
		if(strcmp(argv[i],"-c") == 0) options|=8;
		if((strcmp(argv[i],"-b") == 0) && ((options|2) != options)){
			if(++i < argc){
				blocksz=atoi(argv[i]);
				switch(blocksz){
					case 128:
					case 256:
					case 512:
					case 1024:
					case 2048:
						options|=16;
						break;
					default:
						help();
				}
			}else{
				help();
			}
		}
	}	
	
	if((options|4) == options) freopen("/dev/null", "w", stderr);

	if((options|2) == options){
		decompress((options|8) == options);
	}else{
		compress(blocksz, (options|8) == options);
	}

	return(0);
}
