#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#ifndef VERSION
#define VERSION "???"
#endif

#define SELF "xfd2atr"

#define USAGE \
	SELF " v" VERSION " by B. Watson (WTFPL)\n" \
	"Usage: " SELF " -[sd] input.xfd [output.atr]\n"

int main(int argc, char **argv) {
	struct stat st;
	char infile[4096], outfile[4096];
	FILE *in, *out;
	int i, paras, secsize = 0, seccount;

	if(argc < 2 || argc > 4) {
		fprintf(stderr, USAGE);
		exit(1);
	}

	if(argv[1][0] == '-') {
		switch(argv[1][1]) {
			case 's':
				secsize = 128;
				break;

			case 'd':
				secsize = 256;
				break;

			default:
				fprintf(stderr,
					SELF ": invalid option -'%c'\n" USAGE, argv[1][1]);
		}

		argv++; argc--;
	}

	strcpy(infile, argv[1]);
	if(argc == 3) {
		strcpy(outfile, argv[2]);
	} else if(strcmp(infile, "-") == 0) {
		strcpy(outfile, "-");
	} else {
		char *p;
		strcpy(outfile, argv[1]);

		p = strstr(outfile, ".xfd");
		if(!p) p = strstr(outfile, ".XFD");
		if(!p) p = outfile + strlen(outfile);
		strcpy(p, ".atr");
	}

	fprintf(stderr, SELF ": input '%s', output '%s'\n", infile, outfile);

	if(strcmp(infile, "-") == 0) {
		in = stdin;
	} else {
		if( !(in = fopen(infile, "rb")) ) {
			fprintf(stderr, SELF ": (fatal) can't read %s: %s\n",
					infile, strerror(errno));
			exit(1);
		}
	}

	if(fstat(fileno(in), &st)) {
		fprintf(stderr, SELF ": (fatal) can't stat %s: %s\n",
				infile, strerror(errno));
		exit(1);
	}

	/* A few sanity checks... */
	if(st.st_size < 384) {
		fprintf(stderr,
				SELF ": (fatal) %s too small to be an XFD image (<384 bytes)\n",
				infile);
		exit(2);
	}

	if(st.st_size % 128 == 16) {
		fprintf(stderr,
				SELF ": (fatal) %s looks like an ATR image, not an XFD\n",
				infile);
		exit(2);
	}

	if(st.st_size % 128 != 0) {
		fprintf(stderr,
				SELF ": (fatal) %s not a valid XFD image (not an even number "
				"of sectors)\n", infile);
		exit(2);
	}

	if(st.st_size > (65535 * 256)) {
		fprintf(stderr, SELF ": (fatal) %s too large to be an XFD image (>16M)\n",
				infile);
		exit(2);
	}

	if(!secsize) {
		char *type;
		fprintf(stderr, SELF ": guessing type; use -s or -d to set.\n");

		/* Automagically figure out the sector size and count */
		if(st.st_size == 720 * 128) {
			type = "90K SS/SD image";
			secsize = 128;
		} else if(st.st_size == 1040 * 128) {
			type = "130K SS/ED image";
			secsize = 128;
		} else if(st.st_size == 720 * 256 - 128 * 3) {
			type = "180K SS/DD image";
			secsize = 256;
		} else if(st.st_size == 1440 * 256 - 128 * 3) {
			type = "360K DS/DD image";
			secsize = 256;
		} else if(st.st_size < 720 * 128) {
			type = "<90K image, assuming SD";
			secsize = 128;
		} else if(st.st_size > 720 * 128 && st.st_size < 1040 * 128) {
			type = ">90K, <130K image, assuming SD sectors";
			secsize = 128;
		} else if(st.st_size % 256 == 0) {
			fprintf(stderr, SELF ": Non-standard %dK image, assuming SD sectors\n",
					(int)st.st_size / 1024);
			type = "Large floppy or hard disk, SD";
			secsize = 128;
		} else {
			fprintf(stderr, SELF ": Non-standard %dK image, assuming DD sectors\n",
					(int)st.st_size / 1024);
			type = "Large floppy or hard disk, DD";
			secsize = 256;
		}

		fprintf(stderr, SELF ": guessed type: %s\n", type);
	}

	if(secsize == 128) {
		seccount = st.st_size / secsize;
		if(seccount & 1)
			fprintf(stderr, SELF ": odd number of sectors in SD image, might "
					"actually be DD (try with -d?)\n");
	} else {
		seccount = (st.st_size - 384) / secsize + 3;
		if(st.st_size % 256 != 128)
			fprintf(stderr, SELF ": partial sector at end of DD image, might "
					"actually be SD (try with -s?)\n");
	}

	/* One last sanity check */
	if(seccount > 65535) {
		fprintf(stderr,
				SELF ": (fatal) %s too large to be an XFD image "
				"at current density (>65535 sectors)\n",
				infile);
		if(secsize == 128)
			fprintf(stderr, SELF ": Try forcing double density with -d\n");
		exit(2);
	}

	fprintf(stderr, SELF ": sectors: %d, sector size: %d bytes",
			seccount, secsize);
	if(secsize == 256)
		fprintf(stderr, " (first 3 sectors are 128 bytes)");
	fputc('\n', stderr);

	paras = st.st_size / 16;
	fprintf(stderr, SELF ": %d 16-byte paragraphs\n", paras);

	/* Only open the output file after the XFD is known to be good */
	if(strcmp(outfile, "-") == 0) {
		out = stdout;
	} else {
		if( !(out = fopen(outfile, "wb")) ) {
			fprintf(stderr, SELF ": (fatal) can't write %s: %s\n",
					outfile, strerror(errno));
			exit(1);
		}
	}

	/* output ATR header: */
	fputc(0x96, out); /* NICKATARI cksum, lo byte */
	fputc(0x02, out); /* NICKATARI cksum, hi byte */
	fputc(paras & 0xff, out); /* paragraphs, lo byte */
	fputc((paras >> 8) & 0xff, out); /* paragraphs, mid byte */
	fputc(secsize & 0xff, out); /* sector size, lo byte */
	fputc(secsize >> 8, out); /* sector size, hi byte */
	fputc((paras >> 16) & 0xff, out); /* paragraphs, hi byte */

	/* unused ATR header bytes */
	for(i=0; i<9; i++)
		fputc('\0', out);

	/* copy the data */
	while( (i = fgetc(in)) != EOF )
		fputc(i, out);

	/* fgetc() returns EOF on error *or* EOF;
		check for I/O errors, return 1 if so */
	i = 0;

	if(ferror(in)) {
		i = 1;
		fprintf(stderr,
				SELF ": error reading %s: %s\n", infile, strerror(errno));
	}

	if(ferror(out)) {
		i = 1;
		fprintf(stderr,
				SELF ": error writing %s: %s\n", outfile, strerror(errno));
	}

	fclose(in);
	fclose(out);

	return i;
}