/* bingrob2txt.c - Convert a HP48 binary grob to a text one. */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define HPHP48 "HPHP48-"

#define BITS_PER_NIBBLE  4
#define NIBBLES_PER_HPINT 5

typedef unsigned char nibble_t;
typedef unsigned int hpint_t;	/* This should be large enough to hold
				   20 bits */

int
read_hp48_header(FILE* fIn)
{
    char buffer[] = HPHP48;
    if (fread(buffer, 1, strlen(HPHP48), fIn) != strlen(HPHP48)
	|| memcmp(HPHP48, buffer, strlen(HPHP48)) != 0)
	return -1;
    return 0;
}

int
read_hp48_nibble(FILE* fIn, nibble_t* res)
{
    static int unread_nibble;
    static int unread_present = 0;
    int rc = 0;
    if (unread_present) {
	unread_present = ! unread_present;
	*res = unread_nibble;
    } else {
	int c = getc(fIn);
	if (c == EOF)
	    rc = -1;
	else {
	    *res = c & 0xf;
	    unread_nibble = (c & 0xf0) >> BITS_PER_NIBBLE;
	    unread_present = ! unread_present;
	}
    }
    return rc;
}

int
read_hp48_int(FILE* fIn, hpint_t* res)
{
    int rc = 0;
    int i;
    *res = 0;
    for (i = 0; rc == 0 && i < NIBBLES_PER_HPINT; ++i) {
	nibble_t n;
	if (read_hp48_nibble(fIn, &n) < 0)
	    rc = -1;
	else
	    *res |= n << (i * BITS_PER_NIBBLE);
    }
    return rc;
}

int
main(int argc, char** argv)
{
    FILE* fIn = stdin;
    FILE* fOut = stdout;
    hpint_t tag, len, width, height;

    assert(sizeof(hpint_t) > 2);
    /* read the header */
    if (read_hp48_header(fIn) < 0 || getc(fIn) == EOF) {
	perror("Error: not an hp48 binary file\n");
	return EXIT_FAILURE;
    }

    if (read_hp48_int(fIn, &tag) < 0) {
	perror("Error: can't read object tag");
	return EXIT_FAILURE;
    }
    if (tag != 0x02b1e) {
	fputs("Error: this is not a HP grob file.\n", stderr);
	fprintf(stderr, "The object is tagged 0x%05x instead of 0x02b1e\n",
		tag);
	return EXIT_FAILURE;
    }
    if (read_hp48_int(fIn, &len) < 0) {
	perror("Error: can't read object size");
	return EXIT_FAILURE;
    }
    if (read_hp48_int(fIn, &height) < 0) {
	perror("Error: can't read grob height");
	return EXIT_FAILURE;
    }
    if (read_hp48_int(fIn, &width) < 0) {
	perror("Error: can't read grob width");
	return EXIT_FAILURE;
    }

    fprintf(fOut, "GROB %u %u ", width, height);
    len -= NIBBLES_PER_HPINT * 3;
    while (len-- > 0) {
	nibble_t n;
	if (read_hp48_nibble(fIn, &n) < 0) {
	    perror("Error: can't read body");
	    return EXIT_FAILURE;
	}
	fprintf(fOut, "%01X", n);
    }
    putc('\n', fOut);

    return EXIT_SUCCESS;
}
