/* some of the startup code comes from c:\lcc\lib\wizard\textmode.tpl. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* this may become hex8_16.h someday */
char *programName ="hex8_16";
char *versionName ="0.1";

/* the memory is global, we need 64K chunks*/
#define MAX16 65536
static char memry16[MAX16*2];
static char mem8lo[MAX16];
static char mem8hi[MAX16];

/* splits one hex file into two */
int split(int argc,char *argv[], int firstfile);

/* merges two hex files into one */
int merge(int argc,char *argv[], int firstfile);

/* confirms three files to process */
int threefiles(int argc, char *argv[], int firstfile);

/* read lo-hi, lo-hi, into larger array*/
int lohi(void);

/* this loads an intel hex file into the memory[] array */
/* returns min and max addresses read */
int load_file(char *filename, char memarray[], int maxmem, int *min, int *max);

/* this writes a part of memory[] to an intel hex file */
int save_file(int lowaddr, int hiaddr, char *filename, char memarray[], int maxmem);

/* this is used by load_file to get each line of intel hex */
int parse_hex_line(char *theline, int bytes[], int *addr, int *num, int *code);

/* this does the dirty work of writing an intel hex file */
/* caution, static buffering is used, so it is necessary */
/* to call it with end=1 when finsihed to flush the buffer */
/* and close the file */
void hexout(FILE *fhex, int byte, int memory_location, int end);

int threefiles(int argc, char *argv[], int firstfile)
{
	int i;
	int count = 0;

	for (i=firstfile; i< argc;i++) {
		if (argv[i][0] == '/' || argv[i][0] == '-') return i;
		else count++;
		}
	fprintf(stderr,"first file is %s\n", argv[firstfile]);
	fprintf(stderr,"second file is %s\n", argv[firstfile+1]);
	fprintf(stderr,"third file is %s\n", argv[firstfile+2]);
	return count;
}


void Usage(char *programName)
{
	fprintf(stderr,"%s version %s, usage: \n", programName, versionName);
	fprintf(stderr,"Splits or merges Intel hex record files between 8-bit and 16-bit.\n");
	fprintf(stderr,"-s file16 file8l file8h - split file16 into two files, hi-byte and low-byte.\n");
	fprintf(stderr,"-m file16 file8l file8h - merge two files, hi-byte and low-byte, into one 16-bit file.\n");
	fprintf(stderr,"All files are in Intel hex record format, addresses must be the same for both files.\n");
	fprintf(stderr,"Note filename argument order MATTERS, else you will get reversed order results!\n\n");
}

/* returns the index of the first argument that is not an option; i.e.
   does not start with a dash or a slash
*/
int HandleOptions(int argc,char *argv[], char *Option)
{
	int i,firstnonoption=0;

	*Option='?';
	for (i=1; i< argc;i++) {
		if (argv[i][0] == '/' || argv[i][0] == '-') {
			switch (argv[i][1]) {
				/* An argument -? means help is requested */
				case '?':
					Usage(argv[0]);
					break;
				case 'h':
				case 'H':
					if (!stricmp(argv[i]+1,"help")) {
						Usage(argv[0]);
						break;
					}
					/* If the option -h means anything else
					 * in your application add code here, remove unknown option line
					*/
					fprintf(stderr,"unknown option %s - try -?\n",argv[i]);
					break;
				/* add your option switches here */
				case 's': /* split 16-bit file to two 8-bit files */
				case 'S':
					*Option = 's';
					fprintf(stderr,"-s file16 file8l file8h\n");
					break;

				case 'm': /* merge two 8-bit files to one 16-bit file */
				case 'M':
					*Option = 'm';
					fprintf(stderr,"-m file16 file8l file8h\n");
					break;
				default:
					*Option = '?';
					fprintf(stderr,"unknown option %s - try -?\n",argv[i]);
					break;
			}
		}
		else { /* find non option string */
			firstnonoption = i;
			break;
		}
	}
	return firstnonoption;
}

/* start - Intel HEX read/write functions, Paul Stoffregen, paul@ece.orst.edu */
/* This code is in the public domain.  Please retain my name and */
/* email address in distributed copies, and let me know about any bugs */

/* Intel HEX read/write functions, Paul Stoffregen, paul@ece.orst.edu */
/* parses a line of intel hex code, stores the data in bytes[] */
/* and the beginning address in addr, and returns a 1 if the */
/* line was valid, or a 0 if an error occured.  The variable */
/* num gets the number of bytes that were stored into bytes[] */

int parse_hex_line(theline, bytes, addr, num, code)
char *theline;
int *addr, *num, *code, bytes[];
{
	int sum, len, cksum;
	char *ptr;

	*num = 0;
	if (theline[0] != ':') return 0;
	if (strlen(theline) < 11) return 0;
	ptr = theline+1;
	if (!sscanf(ptr, "%02x", &len)) return 0;
	ptr += 2;
	if ( strlen(theline) < (11 + (len * 2)) ) return 0;
	if (!sscanf(ptr, "%04x", addr)) return 0;
	ptr += 4;
	  /* printf("Line: length=%d Addr=%d\n", len, *addr); */
	if (!sscanf(ptr, "%02x", code)) return 0; /*00=data, 01=address, 02=end*/
	ptr += 2;
	sum = (len & 255) + ((*addr >> 8) & 255) + (*addr & 255) + (*code & 255);
	while(*num != len) {
		if (!sscanf(ptr, "%02x", &bytes[*num])) return 0;
		ptr += 2;
		sum += bytes[*num] & 255;
		(*num)++;
		if (*num >= 256) return 0;
	}
	if (!sscanf(ptr, "%02x", &cksum)) return 0;
	if ( ((sum & 255) + (cksum & 255)) & 255 ) return 0; /* checksum error */
	return 1;
}

/* loads an intel hex file into the memory array of size maxmem */
/* filename is a string of the file to be opened */
/* returns min and max address values */

int load_file(char *filename, char memarray[], int maxmem, int *min, int *max)
{
	char line[1000];
	FILE *fin;
	int addr, n, status, bytes[256];
	int i, total=0, lineno=1;

	if (strlen(filename) == 0) {
		printf("   Can't load a file without the filename.");
		printf("  '?' for help\n");
		return 0;
	}
	fin = fopen(filename, "r");
	if (fin == NULL) {
		printf("   Can't open file '%s' for reading.\n", filename);
		return 2;
	}
	printf("    opened and reading file '%s'\n", filename);
	while (!feof(fin) && !ferror(fin)) {
		line[0] = '\0';
		fgets(line, 1000, fin);
		if (line[strlen(line)-1] == '\n') line[strlen(line)-1] = '\0';
		if (line[strlen(line)-1] == '\r') line[strlen(line)-1] = '\0';
		if (parse_hex_line(line, bytes, &addr, &n, &status)) {
			if (status == 0) {  /* data */
				if (addr < *min) *min = addr;
				for(i=0; i<=(n-1); i++) {
					if (addr>=MAX16) return 3; /* out of 16-bit record bounds */
					if (total>=maxmem) return 4; /* array is overwritten or too big */
					memarray[addr] = (char) bytes[i];
					total++;
					if (addr > *max) *max = addr;
					addr++;
				}
			}
			if (status == 1) {  /* end of file record or EOF*/
				fclose(fin);
				printf("   EOF or end record read\n");
				printf("   Read %d hex lines, ",lineno);
				printf("   Loaded %d bytes between:", total);
				printf(" %04X to %04X\n", *min, *max);
				return 0;
			}
			if (status == 2) { /* begin of file or address code */
				printf("   Intel hex address record, address is %04X\n",addr);
			}
		} else {
			printf("   Hex record Error: '%s', line: %d\n", filename, lineno);
			return 3;
		}
		lineno++;
	}
	return 1;
}


/* "begin" and "end" are the locations to dump to the intel */
/* hex file, specified in hexidecimal. Return 0 on success. */
/* note when merging, may need to overrun 65K address boundary*/
/* if so, write an Intel address record and zero hex record address */
int  save_file(int lowaddr, int hiaddr, char *filename, char memarray[], int maxmem)

{
	int addr;
	char *ptr;
	FILE *fhex;

	if (lowaddr > hiaddr) {
		printf("   Begin address must be less than end address.\n");
		return 4;
	}
	if (hiaddr >= maxmem) {
		printf ("  max address out of array range.\n");
		return 5;
	}
	fhex = fopen(filename, "w");
	if (fhex == NULL) {
		printf("   Can't open '%s' for writing.\n", filename);
		return 6;
	}
	printf("Memory %04X to %04X to be written to '%s'\n", lowaddr, hiaddr, filename);

	if (hiaddr<MAX16) { /* first 64K */
		for (addr=lowaddr; addr <= hiaddr; addr++)
		    hexout(fhex, memarray[addr], addr, 0);
	}
	else { /* upper 64K */
		for (addr=lowaddr; addr < MAX16; addr++)
		    hexout(fhex, memarray[addr], addr, 0);
		printf("   64K rollover found, writing address record\n");
		hexout(fhex, 0, 0x1000, 2); /* flush buffer, address record*/
		for (addr=0; addr <= (hiaddr-MAX16); addr++)
		    hexout(fhex, memarray[addr+MAX16], addr, 0);
	}

	hexout(fhex, 0, 0, 1);
	printf("Memory %04X to %04X written to '%s'\n", lowaddr, hiaddr, filename);
	return 0;
}


/* produce intel hex file output... call this routine with */
/* each byte to output and it's memory location.  The file */
/* pointer fhex must have been opened for writing.  After */
/* all data is written, call with end=1 (normally set to 0) */
/* so it will flush the data from its static buffer */

#define MAXHEXLINE 32	/* the maximum number of bytes to put in one line */

void hexout(fhex, byte, memory_location, end)
FILE *fhex;  /* the file to put intel hex into */
int memory_location, end;
char byte;
{
	static int byte_buffer[MAXHEXLINE];
	static int last_mem, buffer_pos, buffer_addr;
	static int writing_in_progress=0;
	register int i, sum;

	if (!writing_in_progress) {
		/* initial condition setup */
		last_mem = memory_location-1;
		buffer_pos = 0;
		buffer_addr = memory_location;
		writing_in_progress = 1;
		}

	if ( (memory_location != (last_mem+1)) || (buffer_pos >= MAXHEXLINE) \
	 || ((end>0) && (buffer_pos > 0)) ) {
		/* it's time to dump the buffer to a line in the file */
		fprintf(fhex, ":%02X%04X00", buffer_pos, buffer_addr);
		sum = buffer_pos + ((buffer_addr>>8)&255) + (buffer_addr&255);
		for (i=0; i < buffer_pos; i++) {
			fprintf(fhex, "%02X", byte_buffer[i]&255);
			sum += byte_buffer[i]&255;
		}
		fprintf(fhex, "%02X\n", (-sum)&255);
		buffer_addr = memory_location;
		buffer_pos = 0;
	}

	if (end==1) {
		fprintf(fhex, ":00000001FF\n");  /* end of file marker */
		fclose(fhex);
		writing_in_progress = 0;
	}

	if (end==2) {
		fprintf(fhex, ":020000021000EC\n");  /* next 64K address marker */
		writing_in_progress = 0;
	}

	last_mem = memory_location;
	byte_buffer[buffer_pos] = (int)byte;
	buffer_pos++;
}

/* end - Intel HEX read/write functions, Paul Stoffregen, paul@ece.orst.edu */


int split(int argc,char *argv[], int firstfile)
{
	fprintf(stderr,"-s file16 file8l file8h - split file16 into two hex files, low-byte and high-byte\n");
	if (threefiles(argc, argv, firstfile) < 3)	{
		fprintf(stderr,"not enough files named.\n");
		return 1;
	}
	/* first file is 16-bit file to read and split, argv[firstfile]) */
	/* second file is low-byte file to write, argv[firstfile+1]) */
	/* third file is high-byte file to write, argv[firstfile+2]) */

	return 0;
}

/* read lo-hi, lo-hi, into larger array */
/* easier to copy everything, used or not */
int lohi(void)
{
	int i, ilo, ihi,i16;
	for (i=0, i16=0; i<MAX16; i++) {
		memry16[i16]=mem8lo[i]; i16++;
		memry16[i16]=mem8hi[i]; i16++;
	}
	printf("lo-high transfer to memory, i=%04X i16=%04X\n",i, i16);
	return 0;
}

int merge(int argc,char *argv[], int firstfile)
{
	int result = 0;
	int lowaddr =0, hiaddr =0;
	char command[50];
	int minaddr = 0, maxaddr =0;

	fprintf(stderr,"-m file16 file8l file8h - merge two hex files, low-byte and high-byte, into one 16-bit file\n");
	if (threefiles(argc, argv, firstfile) < 3)	{
		fprintf(stderr,"not enough files named.\n");
		return 0;
	}

	/* first file is merged 16-bit file to write, argv[firstfile])*/
	/* second file is low-byte file, argv[firstfile+1])*/
	/* third file is high-byte file, argv[firstfile+2])*/

	/* read low and high byte files */
	result = load_file(argv[firstfile+1], mem8lo, MAX16, &minaddr, &maxaddr); if (result != 0) return result;
	result = load_file(argv[firstfile+2], mem8hi, MAX16, &lowaddr, &hiaddr); if (result != 0) return result;
	fprintf(stderr,"min%i max%i lo%i hi%i\n", minaddr, maxaddr, lowaddr, hiaddr);
	if ((lowaddr != minaddr) || (hiaddr != maxaddr)) {
		fprintf(stderr,"low and high files have different address range\n");

		return 0;
	}

	result = lohi(); if (result != 0) return result;

	/* "lowaddr" and "hiaddr" are the locations to dump to the intel hex file. */
	/* but for merge, high address must be "doubled"*/
	result = save_file(lowaddr, (hiaddr*2)+1, argv[firstfile], memry16, MAX16*2);

	return result;
}


int main(int argc,char *argv[])

{
	char Option = '?';
	int firstfile = 0;
	int result = 0;

	if (argc == 1) {
		/* If no arguments we call the Usage routine and exit */
		Usage(argv[0]);
		result = 1;
	}
	/* handle the program options, return ref to first filename*/
	firstfile = HandleOptions(argc,argv,&Option);
	switch (Option) {
		case 's':
			result = split(argc, argv, firstfile);
			break;
		case 'm':
			result = merge(argc, argv, firstfile);
			break;
		default: break;
	}
	fprintf(stderr,"final result is %i",result);
	return result;
}


