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

#include "get_address.h"
#include "xex.h"

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

#define SELF "blob2xex"

#define DEFAULT_SIZE 0xffff

static FILE *outfh = 0;

/* get_offset() is patterned on get_address(), but isn't limited
	to 64K. TODO: it won't handle offsets >2GB. */
int get_offset(const char *arg) {
	unsigned int got;

	if(sscanf(arg, "0x%x", &got) != 1)
		if(sscanf(arg, "$%x", &got) != 1)
			if(sscanf(arg, "%d", &got) != 1) {
				fprintf(stderr, "Invalid offset '%s'\n", arg);
				return -1;
			}

	return (int)got;
}

int write_segment(
		const char *infile,
		const char *outfile,
		int loadaddr,
		int initaddr,
		int offset,
		int size)
{
	FILE *infh;
	unsigned char buffer[65536], *p = buffer;
	xex_segment seg;
	int c;

	if(size < 1) {
		fprintf(stderr, SELF ": fatal: invalid size %d (must be >= 1).\n", size);
		return(0);
	}

	if(strcmp(infile, "-") == 0) {
		infh = stdin;
		infile = "(standard input)";
	} else {
		infh = fopen(infile, "rb");
		if(!infh) {
			fprintf(stderr, SELF ": %s: %s\n", infile, strerror(errno));
			return(0);
		}
	}

	if(outfh) {
		seg.has_ff_header = 0;
	} else {
		seg.has_ff_header = 1;
		if(strcmp(outfile, "-") == 0) {
			outfh = stdout;
		} else {
			outfh = fopen(outfile, "wb");
			if(!outfh) {
				fprintf(stderr, SELF ": fatal: %s: %s\n", outfile, strerror(errno));
				return(0);
			}
		}
	}

	if(isatty(fileno(outfh))) {
		fprintf(stderr,
				SELF ": fatal: standard output is a terminal; not writing binary data.\n");
		return 0;
	}

	seg.object = buffer;
	seg.len = 0;
	seg.start_addr = loadaddr;

	/* skip <offset> bytes in input. don't seek, input might be stdin.
	   not very efficient to read 1 byte at a time, but we're dealing
	   with very small files, by modern standards. */
	while(offset) {
		c = getc(infh);
		if(c < 0) {
			fprintf(stderr, SELF ": fatal: offset extends past EOF on file %s\n", infile);
			return(0);
		}
		offset--;
	}

	/* make sure we don't wrap the Atari's address space. */
	if(size + loadaddr > 0xffff) {
		size = (size - loadaddr) + 1;
	}

	/* read <size> bytes, or until EOF (which is not an error) */
	while(size) {
		c = getc(infh);
		if(c < 0) break;
		*p++ = c;
		seg.len++;
		size--;
	}

	if(seg.len == 0) {
		fprintf(stderr, SELF ": fatal: read 0 bytes from %s, xex files cannot contain empty segments.\n", infile);
		return(0);
	}

	seg.end_addr = seg.start_addr + seg.len - 1;

	/* if we start/end in ROM, warn, but it's not an error. */
	if(seg.start_addr >= 0xc000)
		fprintf(stderr, SELF ": warning: %s: start address $%04x loads into ROM.\n", infile, seg.start_addr);

	if(seg.end_addr >= 0xc000)
		fprintf(stderr, SELF ": warning: %s: end address $%04x loads into ROM.\n",
				infile, seg.end_addr);

	xex_fwrite_seg(&seg, outfh);

	if(initaddr >= 0) {
		xex_init_seg(&seg, buffer, initaddr);
		xex_fwrite_seg(&seg, outfh);
	}

	fclose(infh);
	return(seg.len);
}

void usage() {
	printf(SELF ": Usage:\n  "
			SELF " outfile [-v] [-r runaddr]\n"
			"           [-l loadaddr ] [-i initaddr] "
			"[-o offset] [-s size] infile] ..."
			"\nSee man page for details.\n");
}

int main(int argc, char **argv) {
	char *outfile = 0, *infile = 0;
	int i, loadaddr = -1, runaddr = -1, initaddr = -1, offset = 0, size = DEFAULT_SIZE, *param = 0;
	int bytes = 0, was_file = 0;
	int segcount = 0, incount = 0;

	outfile = argv[1];
	if(!outfile || strcmp(outfile, "--help") == 0 || strcmp(outfile, "-h") == 0) {
		usage();
		exit(0);
	}

	if(strcmp(outfile, "--version") == 0 || strcmp(outfile, "-V") == 0) {
		printf(SELF " " VERSION "\n");
		exit(0);
	}

	if(outfile[0] == '-' && outfile[1] != '\0') {
		fprintf(stderr, SELF ": output file must come before any options.\n");
		exit(1);
	}

	for(i = 2; i < argc; i++) {
		char *arg = argv[i];

		if(!arg[0]) continue; /* skip empty args */

		if(param) { /* previous option needs an argument */
			was_file = 0;
			if(param == &offset) {
				if( (offset = get_offset(arg) ) < 0 )
					exit(1);
			} else {
				if( (*param = get_address(SELF, arg) ) < 0 )
					exit(1);
			}
			param = 0;
		} else if(arg[0] == '-' && arg[1] != '\0') {
			was_file = 0;
			infile = 0;
			switch(arg[1]) {
				case 'l': param = &loadaddr; break;
				case 'r': param = &runaddr;  break;
				case 'i': param = &initaddr; break;
				case 'o': param = &offset;   break;
				case 's': param = &size;     break;
				case 'v': xex_verbose = 1;   break;
				default:
					fprintf(stderr, SELF ": unknown option '-%c'\n", arg[1]);
					usage();
					exit(1);
					break;
				}
		} else {
			was_file = 1;
			if(loadaddr == -1) {
				fprintf(stderr, SELF ": at least one load address (-l) is required.\n");
				exit(1);
			}
			infile = arg;
			incount++;
			if( (bytes = write_segment(infile, outfile, loadaddr, initaddr, offset, size)) ) {
				segcount++;
				loadaddr += bytes;
				loadaddr &= 0xffff;
			} else {
				segcount = 0;
				break;
			}
			infile = 0;
			initaddr = -1; offset = 0; size = DEFAULT_SIZE;
		}
	}

	if(incount) {
		if(segcount && ((param || !was_file || (initaddr >= 0)) ||
				(offset != 0) || (size != DEFAULT_SIZE)))
		{
			fprintf(stderr, SELF ": "
					"warning: extra arguments after last input file ignored.\n");
		}
	} else {
		fprintf(stderr, SELF ": no input files!\n");
	}

	if(outfh) {
		if(runaddr >= 0) {
			xex_segment seg;
			unsigned char buf[10];

			xex_run_seg(&seg, buf, runaddr);
			xex_fwrite_seg(&seg, outfh);
		}
		fclose(outfh);
	}

	if(segcount) {
		if(xex_verbose)
			fprintf(stderr, SELF ": read %d input files, wrote %d segments to %s.\n",
					incount, segcount, outfile);
	} else {
		if(xex_verbose)
			fprintf(stderr, SELF ": no output file created, due to fatal error.\n");
		unlink(outfile);
		return 1;
	}

	return 0;
}