#include "unalf.h"
#include "addrs.h"
#include "sanity.h"
#include <time.h>
#include <utime.h>

int bad_checksum, bad_checksum_count = 0;
int new_file = 0;
unsigned int bytes_written = 0;
char *out_filename;

void dpoke(int addr, u16 value) {
	mem[addr] = value & 0xff;
	mem[addr + 1] = value >> 8;
}

u16 dpeek(int addr) {
	return mem[addr] | (mem[addr + 1] << 8);
}

void fix_filename(void) {
	char *p;

	if(!out_filename) return;

	if(strlen(out_filename) > 12) {
		fprintf(stderr, "%s: filename in ALF header not null-terminated, fixing.\n", self);
		out_filename[12] = '\0';
	}

	sanity_check_filename(out_filename);

	if(opts.lowercase) {
		for(p = out_filename; *p; p++)
			*p = tolower(*p);
	}

	if(!opts.keepdot) {
		for(p = out_filename; *p; p++)
			if(p[0] == '.' && !p[1])
				*p = '\0';
	}
}

void set_datetime() {
	u16 t, d;
	struct tm tm;
	time_t time;
	struct utimbuf utb;

	t = dpeek(alf_hdr_time0);

	/* don't set the date/time at all, if it's zero */
	if(!t) {
		fprintf(stderr, "%s: warning: date/time for %s is zero, using current date/time\n", self, out_filename);
		return;
	}

	d = dpeek(alf_hdr_date0);

	tm.tm_isdst = -1;
	tm.tm_sec = (t & 0x1f) << 1;
	tm.tm_hour = t >> 11;
	if(tm.tm_hour == 24) tm.tm_hour = 0;
	tm.tm_min = (t >> 5) & 0x3f;

	tm.tm_year = (d >> 9) + 1980 - 1900;
	tm.tm_mon = ((d >> 5) & 0x0f) - 1;
	tm.tm_mday = (d & 0x1f);

	time = mktime(&tm);

	if(time != (time_t) -1) {
		utb.actime = utb.modtime = time;
		utime(out_filename, &utb); /* ignore errors */
	}
}

void make_backup(const char *fname) {
	/* up to 12-char FILENAME.EXT, plus a ~, plus null terminator = 14 */
	/* has to handle up to 16 chars for -s option: LONGFILE.NAME.ALF */
	char backup[20];

	strncpy(backup, fname, 19);
	strncat(backup, "~", 19);

	/* silently ignore errors! */
	rename(fname, backup);
}

void open_out_file(const char *fname) {
	if(opts.testonly) {
		fname = "/dev/null";
	} else if(!opts.overwrite) {
		make_backup(fname);
	}

	if(!(out_file = fopen(fname, "wb"))) {
		fprintf(stderr, "%s: fatal:  ", self);
		perror(fname);
		exit(1);
	}
}

void truncated_err(void);

void write_split_file(void) {
	int i, c;
	unsigned int len;
	char splitname[20];

	strncpy(splitname, out_filename, 13);
	strncat(splitname, ".ALF", 6);

	if(!opts.quiet) {
		fputs("Writing ", stdout);
		safe_print_filename(splitname, stdout);
		putchar('\n');
	}

	len = getquad(alf_hdr_compsize0);

	open_out_file(splitname);
	fwrite(&mem[alf_header], 1, 29, out_file);
	for(i = 0; i < len; i++) {
		c = fgetc(in_file);
		if(c == EOF) {
			truncated_err();
		}
		fputc(c, out_file);
	}
	fclose(out_file);
}

void extract_alf(void) {
	int files_extracted = 0, skip, file_number = 0;

	/* get ready to call fake 6502 stuff. set up memory like the Atari. */
	dpoke(MEMTOP, 0xbc1f);

	while(read_alf_header()) {
		file_number++;
		skip = 0;

		out_filename = (char *)(mem + alf_hdr_filename);

		fix_filename();

		if(opts.extract_num)
			skip = (opts.extract_num != file_number);
		else
			skip = !file_wanted(out_filename);

		if(skip) {
			if(!opts.quiet)
				printf("Skipping %s\n", out_filename);
			if(fseek(in_file, getquad(alf_hdr_compsize0), SEEK_CUR) != 0) {
				fprintf(stderr, "%s: fatal: seek failed on input!\n", self);
				exit(1);
			}
			out_filename = 0;
			continue;
		}

		files_extracted++;

		if(opts.split) {
			write_split_file();
			continue;
		}

		if(!opts.quiet) {
			printf("%s ", opts.testonly ? "Testing" : "Uncrunching");
			safe_print_filename(out_filename, stdout);
			putchar('\n');
		}

		if(opts.extract_to_stdout) {
			out_file = stdout;
		} else {
			open_out_file(out_filename);
		}

		bad_checksum = bytes_written = 0;
		new_file = 1;
		uncrunch_file();
		if(bad_checksum) bad_checksum_count++;
		if(bytes_written != getquad(alf_hdr_origsize0)) {
			fprintf(stderr, "%s: ", self);
			safe_print_filename(out_filename, stderr);
			fprintf(stderr, " should be %u bytes, but extracted to %u.\n",
					getquad(alf_hdr_origsize0), bytes_written);
		}

		if(!opts.extract_to_stdout) {
			fclose(out_file);
			if(!opts.ignore_datetime)
				set_datetime();
		}
		out_filename = 0;
	}

	if(opts.testonly && !opts.quiet) {
		if(bad_checksum_count)
			printf("%d file%s with bad checksum!\n",
					bad_checksum_count,
					bad_checksum_count == 1 ? "" : "s");
		else
			printf("All files OK.\n");
	}

	if(!opts.quiet && !files_extracted) {
		printf("No files %s!\n", opts.testonly ? "tested" : "extracted");
	}

	if(!opts.quiet)
		puts("Processing complete");
}

void chksum_err(void) {
	bad_checksum = 1;
	fprintf(stderr, "%s: checksum error on ", self);
	safe_print_filename(out_filename, stderr);
	fputc('\n', stderr);
}

void truncated_err(void){
	fprintf(stderr, "%s: fatal: compressed data is truncated, EOF before end marker\n", self);
	exit(1);
}

void stack_underrun(void){
	fprintf(stderr, "%s: fatal: stack underrun\n", self);
	exit(1);
}

void stack_overrun(void){
	fprintf(stderr, "%s: fatal: stack overrun\n", self);
	exit(1);
}
