/*
 * This is a one-night hack with NO WARRANTY WHATSOEVER.  
 * Jeff Brown, 02/08/1999
 * (If you use this code without giving me credit, you suck.)
 */

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

#define RECSIZE 16


static int get_byte(FILE *input)
{
    int ch, nybble;
    int value;
    int size;
    int in_comment;
            
    size = 0;
    value = 0;
    in_comment = 0;

    while ((ch = fgetc(input)) != EOF) {
        if (in_comment) {
            if (ch == '\n') {
                in_comment = 0;
                if (size > 0) {
                    break;
                }   
            }
            continue;
        } else if (ch == '#') {
            in_comment = 1;
            continue;  
    	} else if (isspace(ch)) {
            if (size > 0) {
                break;
            }
            continue;
        }

		nybble = toupper(ch);
        value <<= 4;
        if ((nybble >= '0') && (nybble <= '9')) {
            value |= nybble - '0';
        } else if ((nybble >= 'A') && (nybble <= 'F')) {
            value |= nybble - 'A' + 10;
        } else {
            fprintf(stderr, "Invalid character ");
            fprintf(stderr, (isprint(ch)) ? "'%c'" : "0x%02x", ch);
            fprintf(stderr, " in input.\n");
            return(-2);
        }

        size++;
        if (size >= 2) {
            break;
	    }
    }

    if (ferror(input)) {
        perror("read error");
        return(-2);
    } else if (feof(input) && (size == 0)) {
        return(-1);
    }

    assert((size > 0) && (size <= 2));
    if (size == 1) {
        fprintf(stderr, "Warning: lone hex digit seen.\n");
    } 
    return(value);
}


static int checksum(const unsigned char *data, int size)
{
    int sum;
    
    sum = 0;    
    for (; size>0; size--) {
        sum += *data++;
        sum &= 0xff;
    }

    if (sum > 0) {
        sum = 0x100 - sum;
    }

    return(sum);    
}


static int asc2hex(FILE *input, FILE *output)
{
    unsigned char outbuf[5 + RECSIZE + 1];
    unsigned char *inbuf;
    int byte;
    int address;
    int size;   
    int i;

    inbuf = outbuf + 5;
    address = 0;
    outbuf[0] = ':';
    
    do {
        size = 0;
        while (size < RECSIZE) {
            byte = get_byte(input);             
            if (byte < 0) {
                if (byte < -1) {
                    return(-1);
                }
                break;
            }
            inbuf[size++] = byte;
        }

        outbuf[1] = size;
        if (size > 0) {
            outbuf[2] = address >> 8;
            outbuf[3] = address & 0xff;
            outbuf[4] = 0;
        } else {
            outbuf[2] = 0;
            outbuf[3] = 0;
            outbuf[4] = 1;
        }
        outbuf[5 + size] = checksum(outbuf + 1, size + 4);

        fputc(outbuf[0], output);
        for (i=1; i<(size+6); i++) {
            fprintf(output, "%02X", outbuf[i]);
        }

        fprintf(output, "\n");
        address += size;        
    } while (!feof(input) || (size > 0));

    return(0);
}


int main(int argc, char *argv[])
{
    FILE *input, *output;

    if ((argc != 1) && (argc != 3)) {
        fprintf(stderr, "usage: %s [asciifile outfile]\n" \
"This reads a sequence of byte values (in hex) and outputs them in the\n" \
"Intel \"HEX\" ROM format, starting at address 0.\n" \
"The comment character '#' causes the rest of a line to be ignored.\n" \
"\n" \
"(02-08-1999 JAB)\n",
            argv[0]); 
        return(1); 
    }

    if (argc == 1) {
        input = stdin; 
        output = stdout;
    } else {
        input = fopen(argv[1], "r");
        if (input == NULL) {
            perror("error opening input file");
            return(1);
        }
        output = fopen(argv[2], "w");
        if (output == NULL) {
            fclose(input);
            perror("error opening output file");
            return(1);
        }
    }

    asc2hex(input, output); 

    if (argc > 1) {
        fclose(input); 
        fclose(output);
    }
    return(0);
}
