/* Derived from: */
/* g3topbm.c - read a Group 3 FAX file and produce a portable bitmap
**
** Copyright (C) 1989 by Paul Haeberli <paul@manray.sgi.com>.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "g3.h"
#include "faxfile.h"

#define TABSIZE(tab) (sizeof(tab)/sizeof(struct tableentry))
#define MAXCOLS LINE_BITS
#define MAXROWS 4300		/* up to two pages long */

typedef struct faxin {
	int	 raw;
	FAXHDR	 faxhdr;
	FAXHDR	 pagehdr;
	int	 endoffile;
	int	 eols;
	int	 rawzeros;
	int	 shdata;
	int	 shbit;
	int	 kludge;
	int	 reversebits;
	int	 stretch;
	unsigned char  *bitp;
	unsigned char	bitval;

} FAXIN;

static FAXIN faxin;

#define WHASHA 3510
#define WHASHB 1178

#define BHASHA 293
#define BHASHB 2695

#define HASHSIZE 1021
static tableentry *whash[HASHSIZE];
static tableentry *bhash[HASHSIZE];

static void addtohash(tableentry *hash[], tableentry* te, int n, int a, int b);
static tableentry *hashfind(tableentry *hash[], int length, int code, int a, int b);
static int getfaxrow(FILE *inf, int row, unsigned char *bitrow);
static void skiptoeol(FILE *file);
static int rawgetbit(FILE *file);

int
memisprint(unsigned char *s, int len)
{
    while (len > 0) {
	if (s[0] && !isprint((s[0])))
	    return 0;
	s++;
	len--;
    }
    return 1;
}

long	/* error code */
faxin_open_fp(FILE *ifp, int dostretch)
{
    int 	    i;

    faxin.stretch = dostretch;

    for (i = 0; i < HASHSIZE; ++i)
	whash[i] = bhash[i] = (tableentry *) 0;
    addtohash(whash, twtable, TABSIZE(twtable), WHASHA, WHASHB);
    addtohash(whash, mwtable, TABSIZE(mwtable), WHASHA, WHASHB);
    addtohash(whash, extable, TABSIZE(extable), WHASHA, WHASHB);
    addtohash(bhash, tbtable, TABSIZE(tbtable), BHASHA, BHASHB);
    addtohash(bhash, mbtable, TABSIZE(mbtable), BHASHA, BHASHB);
    addtohash(bhash, extable, TABSIZE(extable), BHASHA, BHASHB);

#ifdef _DCC
    setvbuf(ifp, NULL, _IOFBF, 16384);
	printf("rbuf %lx rptr %lx wbuf %lx bufsiz %d\n",
	    ifp->sd_RBuf,
	    ifp->sd_RPtr,
	    ifp->sd_WBuf,
	    ifp->sd_BufSiz);
#endif
    /* Attempt to read file/page header */
    if (fread(&faxin.faxhdr, sizeof(faxin.faxhdr), 1, ifp) == 1) {
	fseek(ifp, -sizeof(faxin.faxhdr), SEEK_CUR);
	faxin.raw = !memisprint(&faxin.faxhdr.id.magic[0], sizeof(faxin.faxhdr.id.magic));
	if (faxin.raw) {
	    printf("Assuming raw file\n");
	}
	/*else printf("FAXHDR: %s\n", &faxin.faxhdr.id.magic[1]);*/
#ifdef _DCC
	printf("rbuf %lx rptr %lx wbuf %lx bufsiz %d\n",
	    ifp->sd_RBuf,
	    ifp->sd_RPtr,
	    ifp->sd_WBuf,
	    ifp->sd_BufSiz);
#endif

	return 0;
    }
    fprintf(stderr, "file too short\n");

    return 1;
}

int
faxin_begin_page(FILE *ifp)
{
    if (faxin.raw && ftell(ifp) != 0) {
	/* Raw files contain only one page and no header */
	return 1;
    }

    if (faxin.raw == 0) {
	int		bytes;
	int		byte;

	/* Skip past page header by finding magic id */
    retry:
	bytes = 0;
	/* Shortcut for first byte */
	while ((byte = getc(ifp)) != faxin.faxhdr.id.magic[0]) {
	    if (feof(ifp))
		return 1;
	}
	faxin.pagehdr.id.magic[0] = byte;
	bytes = 1;

	while (bytes < sizeof(faxin.faxhdr.id)) {
	    /* Do we have a matching prefix? */
	    if (memcmp(faxin.pagehdr.id.magic, faxin.faxhdr.id.magic, bytes) == 0) {
		/* Yes, read one more byte and continue */
		byte = getc(ifp);
		if (feof(ifp))
		    return 1;
		faxin.pagehdr.id.magic[bytes] = byte;
		bytes++;
	    } else {
		/* Shift everything down and reduce match length */
		bytes--;
		if (bytes == 0)
		    goto retry;
		memmove(faxin.pagehdr.id.magic, faxin.pagehdr.id.magic + 1, bytes);
	    }
	}

	fread(&faxin.pagehdr.info, sizeof(faxin.pagehdr) - sizeof(faxin.pagehdr.id), 1, ifp);
	if (feof(ifp)) {
	    fprintf(stderr, "Premature EOF after header\n");
	    return 1;
	}

	/* "Normal" bit ordering is MSB first */
	faxin.reversebits = !faxin.pagehdr.info.msbfirst;
    }

    faxin.eols = 0;
    faxin.rawzeros = 0;
    faxin.endoffile = 0;

    if (faxin.raw && feof(ifp))
	return 1;

#ifdef KLUDGE
    if (faxin.kludge) {
	/* Skip extra lines to get in sync. */
	skiptoeol(ifp);
	skiptoeol(ifp);
	skiptoeol(ifp);
    }
#endif
    skiptoeol(ifp);
    if (feof(ifp))
	return 1;

    return 0;
}

static void
addtohash(hash, te, n, a, b)
tableentry     *hash[];
tableentry     *te;
int		n,
		a,
		b;

{
    unsigned int    pos;

    while (n--) {
	pos = ((te->length + a) * (te->code + b)) % HASHSIZE;
	if (hash[pos] != 0)
	    printf("internal error: addtohash fatal hash collision\n");
	hash[pos] = te;
	te++;
    }
}

static tableentry *
hashfind(hash, length, code, a, b)
tableentry     *hash[];
int		length,
		code;
int		a,
		b;

{
    unsigned int    pos;
    tableentry	   *te;

    pos = ((length + a) * (code + b)) % HASHSIZE;
    if (pos >= HASHSIZE)
	printf(
	   "internal error: bad hash position, length %d code %d pos %d\n",
		 length, code, pos);
    te = hash[pos];
    return ((te && te->length == length && te->code == code) ? te : 0);
}

/* Put some black (color 1) bits in IFF bitmap format */
void
putblackbits(int count)
{
    while (count > 0) {
	*faxin.bitp |= faxin.bitval;	/* Set a single bit */
	count--;
	faxin.bitval >>= 1;
	if (faxin.bitval == 0) {
	    faxin.bitval = 0x80;
	    faxin.bitp++;
	    /* While we're byte-aligned, do 8 bits at a time */
	    while (count >= 8) {
		*faxin.bitp++ = 0xFF;
		count -= 8;
	    }
	}
    }
}

/* Put some white (color 0) bits in IFF bitmap format */
void
putwhitebits(int count)
{
    while (count > 0) {
	/* don't set a bit */
	count--;
	faxin.bitval >>= 1;
	if (faxin.bitval == 0) {
	    faxin.bitval = 0x80;
	    faxin.bitp++;
	    /* While we're byte-aligned, do 8 bits at a time */
	    while (count >= 8) {
		faxin.bitp++;
		count -= 8;
	    }
	}
    }
}

int
fromfax(ifp, bitrow)
FILE	       *ifp;
unsigned char *bitrow;
{
    int 	    col;
    int 	    curlen,
		    curcode,
		    nextbit;
    int 	    count,
		    color;
    tableentry	   *te;

    col = 0;
    curlen = 0;
    curcode = 0;
    color = 1;	    /* 1=white, 0=black */
    count = 0;
    faxin.bitp = bitrow;
    faxin.bitval = 0x80;

    while (!faxin.endoffile) {
	if (col >= MAXCOLS) {
	    skiptoeol(ifp);
	    return (col);
	}
	do {
	    if (faxin.rawzeros >= 11) {
		nextbit = rawgetbit(ifp);
		if (nextbit) {
		    if (col == 0)
			/* XXX should be 6 */
			faxin.endoffile = (++faxin.eols == 3);
		    else
			faxin.eols = 0;
#ifndef notdef
		    if (col && col < LINE_BITS)
			printf("warning, row %d short (len %d)\n",
				   -1, col);
#endif				/* notdef */
		    return (col);
		}
		if (feof(ifp)) {
		    faxin.endoffile = 1;
		    return col;
		}
	    } else
		nextbit = rawgetbit(ifp);
	    curcode = (curcode << 1) + nextbit;
	    curlen++;
	} while (curcode <= 0);
	if (curlen > 13) {
	    printf("bad code word at row %d, col %d (len %d code 0x%x), skipping to EOL\n",
		       -1, col, curlen, curcode);
	    skiptoeol(ifp);
	    return (col);
	}
	if (color) {
	    if (curlen < 4)
		continue;
	    te = hashfind(whash, curlen, curcode, WHASHA, WHASHB);
	} else {
	    if (curlen < 2)
		continue;
	    te = hashfind(bhash, curlen, curcode, BHASHA, BHASHB);
	}
	if (!te)
	    continue;
	switch (te->tabid) {
	case TWTABLE:
	case TBTABLE:
	    count += te->count;
	    if (col + count > MAXCOLS)
		count = MAXCOLS - col;
	    if (count > 0) {
		if (color) {
		    putwhitebits(count);
		} else {
		    putblackbits(count);
		}
		col += count;
		count = 0;
	    }
	    curcode = 0;
	    curlen = 0;
	    color = !color;
	    break;
	case MWTABLE:
	case MBTABLE:
	    count += te->count;
	    curcode = 0;
	    curlen = 0;
	    break;
	case EXTABLE:
	    count += te->count;
	    curcode = 0;
	    curlen = 0;
	    break;
	default:
	    printf("internal bad poop\n");
	}
    }
    return -1;	    /* End of page */
}

static void
skiptoeol(ifp)
FILE	       *ifp;
{
    while (faxin.rawzeros < 11)
	(void) rawgetbit(ifp);
    for (;;) {
	if (feof(ifp) || rawgetbit(ifp))
	    break;
    }
}


static int
rawgetbit(ifp)
FILE	       *ifp;
{
    int 	    b;

    if ((faxin.shbit & 0xff) == 0) {
	faxin.shdata = getc(ifp);
	if (faxin.shdata == EOF) {
	    printf("EOF / read error at line %d\n", faxin.eols);
	    faxin.shdata = 0;
	}
	faxin.shbit = faxin.reversebits ? 0x01 : 0x80;
    }
    if (faxin.shdata & faxin.shbit) {
	faxin.rawzeros = 0;
	b = 1;
    } else {
	faxin.rawzeros++;
	b = 0;
    }
    if (faxin.reversebits)
	faxin.shbit <<= 1;
    else
	faxin.shbit >>= 1;
    return b;
}
