#define __DACT_C
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "dact.h"
#include "parse.h"
#include "dendian.h"
#ifdef DACT_OLD_ALGO
#include "algorithms.h"
#endif

extern char *optarg;
extern int optind, opterr, optopt;
uint32_t DACT_BLK_SIZE=4088;

int print_help(int argc, char **argv) {
	printf("DACT %i.%i.%i-%s by Netfuel Design.\n", DACT_VER_MAJOR, \
		DACT_VER_MINOR, DACT_VER_REVISION,
#ifdef DEBUG
		"dev"
#else
		"rel"
#endif
		);
	printf("usage: %s [-dsfh] [file ...]\n",argv[0]);
	printf("  -d          Decompress instead of compressing.\n");
	printf("  -s          Give statistics rather than compress or decompress.\n");
	printf("  -f          Force unsafe things to happen.\n");
	printf("  -h          Give this help.\n");
	printf("  file...     File(s) to (de)compress.  (If none given, use standard input).\n");
	return(0);
}

char *dact_getoutfilename(const char *orig, const int mode) {
	char *ret=NULL;

	switch (mode) {
		case DACT_MODE_COMPR:
			ret=malloc(strlen(orig)+5);
			strcpy(ret,orig);
			strcat(ret,".dct");
			break;
		case DACT_MODE_DECMP:
			if (strcmp(&orig[strlen(orig)-4],".dct")) {
				return(NULL);
			}
			ret=malloc(strlen(orig)-3);
			strncpy(ret,orig,strlen(orig)-4);
			break;
		case DACT_MODE_STAT:
			return(NULL);
			break;
	}
	return(ret);
}



uint32_t dact_select_best_algo(int *algo, char *ret, const char *srcbuf, const uint32_t size, const char *options) {
#ifdef DACT_OLD_ALGO
	char *tmpbuf, *smallbuf=NULL;
	int i, highest_algo=0;
	int smallest_algo;
	uint32_t smallest_size=-1, x;

	if ((tmpbuf=malloc(size*2))==NULL) { PERROR("malloc"); return(0); }

	for (i=0;i<256;i++) {
		if (algorithms[i]!=NULL && algorithms[i]!=DACT_FAILED_ALGO) highest_algo=i;
	}

	for (i=0;i<=highest_algo;i++) {
		if (algorithms[i]!=NULL && algorithms[i]!=DACT_FAILED_ALGO) {
			x=algorithms[i](DACT_MODE_COMPR, NULL, srcbuf, tmpbuf, size);
			if ((x<smallest_size || smallest_size==-1) && x!=-1) {
				smallest_size=x;
				smallest_algo=i;
				if (smallbuf!=NULL) free(smallbuf);
				if ((smallbuf=malloc(smallest_size))==NULL) { PERROR("malloc"); free(tmpbuf); return(0); }
				memcpy(smallbuf, tmpbuf, smallest_size);
			}

			if (options[DACT_OPT_VERB]>1) {
				PRINT_LINE; fprintf(stderr, "dact: \033[%im----| %03i  | %-7i | %s\033[0m\n", (smallest_algo==i)*7 , i, x, algorithm_names[i]);
			}

		}
	}

	free(tmpbuf);
	memcpy(algo, &smallest_algo, sizeof(int));
	memcpy(ret, smallbuf, smallest_size);
	return(smallest_size);
#else
	return(size);
#endif
}

uint32_t dact_process_file(const int src, const int dest, const int mode, const char *options) {
	char *in_buf, *out_buf;
	char version[3]={DACT_VER_MAJOR, DACT_VER_MINOR, DACT_VER_REVISION};
	int algo;
	uint32_t bytes_read, retsize;
	uint32_t filesize=0, blk_cnt=0;

	if (mode==DACT_MODE_COMPR) {
		if (((in_buf=malloc(DACT_BLK_SIZE))==NULL) || \
			((out_buf=malloc(DACT_BLK_SIZE*2))==NULL)) {
				PERROR("malloc");
				return(-1);
		}

/* XXX: WRITE FILE HEADERS FEWLIO */
//		SANITY(write, dest, DACT_MAGIC_NUMBER, 4);
		SHOWVAL(write(dest, "DACT", 4));
		SHOWVAL(write(dest, &version[0], 1));
		SHOWVAL(write(dest, &version[1], 1));
		SHOWVAL(write(dest, &version[2], 1));
		SHOWVAL(write_de(dest, 0, 4)); /* Place holder for ORIG FILE SIZE */
		SHOWVAL(write_de(dest, 0, 4)); /* Place holder for NUM BLOCKS */
		SHOWVAL(write_de(dest, DACT_BLK_SIZE, 4));
		SHOWVAL(write_de(dest, 0, 1)); /* XXX: Option byte... Or not? */
		SHOWVAL(write_de(dest, 0, 4)); /* Place holder for SIZEOF EXTENDED DATA */


		if (options[DACT_OPT_VERB]) {
			PRINTERR("Blk | Algo | Size    | Name");
			PRINTERR("----+------+---------+---------------------------");
		}

		while ( (bytes_read=read(src, in_buf, DACT_BLK_SIZE))>0) {
			filesize+=bytes_read;
			blk_cnt++;
			retsize=dact_select_best_algo(&algo, out_buf, in_buf, DACT_BLK_SIZE, options);
			if (retsize>0) {
				if (options[DACT_OPT_VERB]) {
					if (options[DACT_OPT_VERB]>1) {
						PRINTERR("^^^\\ /^^^^\\ /^^^^^^^\\ /^^^^^^^^^^^^^^^^^^^^^^^^^^");
					}
					PRINT_LINE; fprintf(stderr, "dact: %03i | %03i  | %-7i | %s\n",blk_cnt,algo,retsize,algorithm_names[algo]);
					if (options[DACT_OPT_VERB]>1) {
						PRINTERR("___/ \\____/ \\_______/ \\__________________________");
					}
				}

/* XXX: WRITE BLOCK HEADERS FEWLIO */
				

				if (write(dest, out_buf, retsize)!=retsize) {
					PERROR("write");
					free(in_buf);
					free(out_buf);
					return(0);
				}
			} else {
				PRINTERR("Compression resulted in 0-byte block.");
				free(in_buf);
				free(out_buf);
				return(0);
			}
		}

		if (bytes_read<0) {
			PERROR("read");
		}

		free(in_buf);
		free(out_buf);

		return(filesize);
	}

	if (mode==DACT_MODE_DECMP) {
/* XXX: Make this do something */
		PRINTERR("This does nothing.");
	}

	if (mode==DACT_MODE_STAT) {
/* XXX: Make this do something, too*/
		PRINTERR("This does nothing.");
	}
	return(0);
}

int main(int argc, char **argv) {
	unsigned char options[8]={0,0,0,0,0,0,0,0};
	signed char opt;
	char **in_files, *in_file=NULL, *out_file=NULL;
	int filecnt=0;
	int in_fd, out_fd;
	int mode=DACT_MODE_COMPR;
	uint32_t i;

	while ((opt=getopt(argc,argv,"dfsvcb:h"))!=-1) {
		switch (opt) {
			case 'd':
				mode=DACT_MODE_DECMP;
				break;
			case 'f':
				options[DACT_OPT_FORCE]++;
				break;
			case 's':
				mode=DACT_MODE_STAT;
				break;
			case 'c':
				options[DACT_OPT_STDOUT]=!options[DACT_OPT_STDOUT];
				break;
			case 'b':
				i=atoi2(optarg);
				if (i<DACT_BLK_SIZE_MAX) DACT_BLK_SIZE=i;
				break;
			case 'v':
				options[DACT_OPT_VERB]++;
				break;
			case '?':
			case 'h':
				return(print_help(argc,argv));
		}

	}

	in_files=&argv[optind];

/* Loop through extra parameters (files ...) and setup FDs for them */
	do {
		in_fd=-1;
		out_fd=-1;

		in_file=in_files[filecnt];
		if (in_file!=NULL) {
/* Determine resulting file name */
			out_file=dact_getoutfilename(in_file,mode);
			if ((in_fd=open(in_file,O_RDONLY))==-1) {
				fprintf(stderr, "dact: Can't open %s.\n",in_file);
				PERROR("open");
				continue;
			}
			if (out_file!=NULL) {
				if (access(out_file,F_OK)!=-1 && options[DACT_OPT_FORCE]==0 && options[DACT_OPT_STDOUT]==0) {
					fprintf(stderr, "dact: %s exists.\n",out_file);
					close(in_fd);
					continue;
				}
				if (options[DACT_OPT_STDOUT]) {
					out_fd=STDOUT_FILENO;
				} else {
					if ((out_fd=open(out_file,O_WRONLY|O_CREAT|O_TRUNC,0644))==-1) {
						fprintf(stderr, "dact: Can't open %s for writing.\n",out_file);
						PERROR("open");
						continue;
					}
				}
			}
		}

/* Use STDIN/STDOUT if no files specified ... */
		if (in_file==NULL && filecnt==0) {
/* ... But only if STDOUT isn't a terminal */
			if (isatty(STDOUT_FILENO)) {
				fprintf(stderr, "dact: Refusing to write compressed output to a terminal.\n");
			} else {
				out_fd=STDOUT_FILENO;
				in_fd=STDIN_FILENO;
			}
		}

/* Okay, we're all done, now pass these to something to do the real stuff */
		if (in_fd!=-1 && (out_fd!=-1 || mode==DACT_MODE_STAT)) {
			if (dact_process_file(in_fd, out_fd, mode, options)==0 && mode!=DACT_MODE_STAT) {
				close(in_fd);
				close(out_fd);
				return(-1);
			}
		}
/* Cleanup */
	
		if (out_fd!=-1) close(out_fd);
		if (in_fd!=-1) close(in_fd);

	} while (in_files[filecnt++]!=NULL);

	return(0);
}
