/** g3.c - read a Group 3 FAX file and product a bitmap
 **
 ** Adapted from Paul Haeberli's <paul@manray.sgi.com> G3 to Portable Bitmap 
 ** code.
 **
 ** modified by jimf on 09.18.90 to fail on any load error.  this was done
 ** to cut down on the false positives caused by a lack of any read ID
 ** string.  the old errors are currently ifdef'ed out -- if you want 'em
 ** define ALLOW_G3_ERRORS.
 **/

/* Edit History

04/15/91   8 nazgul	Sanity check line widths
			Doing a zclose on all failures
04/14/91   1 schulert	add <sys/types> for SYSV systems
04/13/91   6 nazgul	Handle reinvocation on the same file
04/13/91   5 nazgul	Bug fix to retry with bitreversed, and do not double allocate on multiple calls
04/12/91   4 nazgul	Spot faxes that do not have a 000 header
			Handle faxes that have the bytes in the wrong order
07/03/90   2 nazgul	Added recovery for premature EOF
*/

#include <sys/types.h>
#include <sys/file.h>

#include "image.h"
#include "g3.h"

/* SUPPRESS 530 */
/* SUPPRESS 558 */
/* SUPPRESS 560 */

/****
 **
 ** Local defines
 **
 ****/

#define BITS_TO_BYTES(bits)	(bits/8)+((bits-((bits/8)*8)?1:0))
#define TABSIZE(tab) (sizeof(tab)/sizeof(struct tableentry))
#ifdef VMS
#define cols vmscols
#endif

/****
 **
 ** Local variables
 **
 ****/

static int g3_eof = 0;
static int g3_eols;
static int g3_rawzeros;
static int g3_Xrawzeros;
static int	maxlinelen;
static int	rows, cols;
static int g3_error = 0;
static int g3_verb;
static int curbit;

#define MAX_ERRORS	20

/****
 **
 ** Local tables
 **
 ****/

tableentry *whash[HASHSIZE];
tableentry *bhash[HASHSIZE];

static
void	g3_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) {
#ifdef ALLOW_G3_ERRORS
			fprintf(stderr, "G3: Hash collision during initialization.\n");
			cleanup(-1);
#else
			++g3_error;
			return;
#endif
			}
		hash[pos] = te;
		te++;
	}
}

int	g3_rawgetbit(fd)
	ZFILE	*fd;
{
	int	b;
	static int	shdata;

	if (curbit >= 8) {
		shdata = zgetc(fd);
		if (shdata == EOF) {
#ifdef ALLOW_G3_ERRORS
		        if (g3_verb) fprintf(stderr, "G3: Premature EOF at line %d.\n", rows);
			g3_eols = 5;
			g3_eof = 1;
			++g3_error;
			return 0;
#else
			return(-1);
#endif
			}
		curbit = 0;
		}
	if (shdata & bmask[curbit]) {
		g3_Xrawzeros = g3_rawzeros;
		g3_rawzeros = 0;
		b = 1;
		}
	else {
		g3_rawzeros++;
		b = 0;
		}
	curbit++;
    return b;
}

static
int	g3_bitson(b, c, n)
	bit	*b;
	int	c, n;
{
	int	i, col;
	bit	*bP;
	static int	bitmask[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };

	bP = b;
	col = c;
	bP+=(c/8);
	i = (c - ((c/8)*8));
	while(col <= (c+n)) { 
		for(;col <= (c+n) && i < 8; i++) {
			*bP |= bitmask[i];
			col++;
			}
		i = 0;
		bP++;
		}
	return(0);
}

tableentry	*g3_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) {
#ifndef ALLOW_G3_ERRORS
		fprintf(stderr, "G3: Bad hash position, length %d code %d pos %d.\n", 
			length, code, pos);
		cleanup(-1);
#else
		++g3_error;
		return(NULL);
#endif
		}
	te = hash[pos];
	return ((te && te->length == length && te->code == code) ? te : 0);
}

int	g3_getfaxrow(fd, bitrow)
	ZFILE	*fd;
	byte	*bitrow;
{
        int col;
	int curlen, curcode, nextbit;
	int count, color;
	tableentry *te;

	/* First make the whole row white... */
	memset((char *) bitrow, 0, maxlinelen);

	col = 0;
	g3_rawzeros = 0;
	curlen = 0;
	curcode = 0;
	color = 1;
	count = 0;
	while (!g3_eof) {
		if (col >= MAXCOLS) {
#ifdef ALLOW_G3_ERRORS
			if (g3_verb) fprintf(stderr, "G3: Input row %d is too long, skipping to EOL.\n", rows);
			g3_skiptoeol(fd);
			++g3_error;
			return (col); 
#else
			return(-1);
#endif
			}
		do {
			if (g3_eof) return 0;
			if (g3_rawzeros >= 11) {
				nextbit = g3_rawgetbit(fd);
				if (nextbit) {
					if ( col == 0 )
						/* 6 consecutive EOLs mean end of document */
						g3_eof = (++g3_eols >= 5);
					else
						g3_eols = 0;

					return (col); 
					}
				}
			else
				nextbit = g3_rawgetbit(fd);

			curcode = (curcode<<1) + nextbit; 
			curlen++;
			} while (curcode <= 0);

		/* No codewords are greater than 13 bytes */
		if (curlen > 13) {
#ifdef ALLOW_G3_ERRORS
			if (g3_verb) fprintf(stderr, "G3: Bad code word at row %d, col %d (len %d code 0x%2.2x), skipping to EOL.\n", rows, col, curlen, curcode );
			g3_skiptoeol(fd);
			++g3_error;
			return (col);
#else
			return(-1);
#endif
			}
		if (color) {
			/* White codewords are at least 4 bits long */
			if (curlen < 4)
				continue;
			te = g3_hashfind(whash, curlen, curcode, WHASHA, WHASHB);
			}
		else {
			/* Black codewords are at least 2 bits long */
			if (curlen < 2)
				continue;
			te = g3_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) {
						col += count;
						count = 0;
						}
					else
						g3_bitson(bitrow, col, count);
					}
				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:
				fprintf(stderr, "G3: Bad table id from table entry.\n");
#ifndef ALLOW_G3_ERRORS
				cleanup(-3);
#else
				++g3_error;
				return(-1);
#endif
			}
		}
	return (0);
}

int	g3_skiptoeol(fd)
	ZFILE	*fd;
{
	while (g3_rawzeros<11 && !g3_eof)
		(void) g3_rawgetbit(fd);
	while(!g3_rawgetbit(fd) && !g3_eof);
	return(0);
}


/* All G3 images begin with a G3 EOL codeword which is eleven binary 0's
 * followed by one binary 1.  There could be up to 15 0' so that the image
 * starts on a char boundary.
 */
/*
 * They are all *supposed* to, but in fact some don't.  In fact pbmtog3 doesn't seem
 * to generate them.  So if that fails, we'll also try reading a line and seeing if
 * we get any errors.  Note that this means we had to move the call to g3_ident
 * to after the hash table init.  -nazgul
 */

int	g3_ident(fd)
	ZFILE	*fd;
{

    int		ret=0, col1=0, col2=0, i;
    byte	*tmpline;
    int		reverse = 0;
    struct cache	*dataptr;
    int			bufptr;
    
    g3_verb = 0;
    tmpline = (byte *) lmalloc(maxlinelen);

    /* In case this got reset by a previous pass through here */
    for (i = 0; i < 8; ++i) {
	bmask[7-i] = 1 << i;
    }

tryagain:
    curbit = 8;
    g3_Xrawzeros = 0;
    g3_eof = g3_eols = rows = cols = 0;
    
    /* If we have the zeros we're off to a good start, otherwise, skip some lines */
    for (g3_rawzeros = 0; !g3_rawgetbit(fd) && !g3_eof;);
    if (g3_Xrawzeros >= 11 && g3_Xrawzeros <= 15) {
	fd->dataptr = fd->data;
	fd->bufptr = 0;
	curbit = 8;
	g3_skiptoeol(fd);
	if (!g3_error) g3_skiptoeol(fd);
	if (!g3_error) g3_skiptoeol(fd);
	if (!g3_error) g3_skiptoeol(fd);
    } else ret = 1;

    /* Now get two lines and make sure they are the same length.  If not give up.
     * Note that it is possible for this to give false positives (value.o on a Sun IPC
     * did) but it's unlikely enough that I think we're okay.
     */
    
    dataptr = fd->dataptr;
    bufptr = fd->bufptr;
    if (!g3_error) col1 = g3_getfaxrow(fd, tmpline);
    if (!g3_error) col2 = g3_getfaxrow(fd, tmpline);
    if (!g3_error && col1 == col2 && col1 != 0) ret = 1;
    else ret = 0;
    /* if (ret) fprintf(stderr, "%d = %d\n", col1, col2); */
    fd->dataptr = dataptr;
    fd->bufptr = bufptr;
    curbit = 8;

    /* This bogus hack is to accomodate some fax modems which apparently use a chip
     * with a different byte order.  We simply try again with the table reversed.
     */
    if (!ret && !reverse) {
	rows = cols = g3_error = 0;
	fd->dataptr = fd->data;
	fd->bufptr = 0;
	g3_Xrawzeros = 0;
	for (i = 0; i < 8; ++i) {
	    bmask[i] = 1 << i;
	}
	reverse = 1;
	goto tryagain;
    }
    g3_eols = rows = cols = 0;
    lfree(tmpline);

    return(ret);
}

Image	*g3Load(fullname, name, verbose)
	char	*fullname, *name;
	unsigned int	verbose;
{

	ZFILE	*fd;
	Image	*image;
	int i, col;
	byte	*currline;
	static int firstTime = 1;

	if ((fd = zopen(fullname)) == NULL)
		return(NULL);

	if (firstTime) {
	    firstTime = 0;
	    
	    /* Initialize and load the hash tables */
	    for ( i = 0; i < HASHSIZE; ++i )
	      whash[i] = bhash[i] = (tableentry *) 0;
	    g3_addtohash(whash, twtable, TABSIZE(twtable), WHASHA, WHASHB);
	    g3_addtohash(whash, mwtable, TABSIZE(mwtable), WHASHA, WHASHB);
	    g3_addtohash(whash, extable, TABSIZE(extable), WHASHA, WHASHB);
	    g3_addtohash(bhash, tbtable, TABSIZE(tbtable), BHASHA, BHASHB);
	    g3_addtohash(bhash, mbtable, TABSIZE(mbtable), BHASHA, BHASHB);
	    g3_addtohash(bhash, extable, TABSIZE(extable), BHASHA, BHASHB);
	}
	
	g3_eof = g3_eols = 0;
	curbit = 8;	/* Reset on multiple reads */

	/* Calulate the number of bytes needed for maximum number of columns 
	 * (bits), create a temprary storage area for it.
	 */
	maxlinelen = BITS_TO_BYTES(MAXCOLS);

	if (!g3_ident(fd)) {
	    zclose(fd);
	    return(NULL);
	}
	g3_verb = verbose;

	image = newBitImage(MAXCOLS, MAXROWS);

	currline = image->data;
	cols = 0;
	for (rows = 0; rows < MAXROWS; ++rows) {
		col = g3_getfaxrow(fd, currline);
#ifndef ALLOW_G3_ERRORS
		if (col < 0) {
		  freeImage(image);
		  zclose(fd);
		  return(NULL);
		}
#else
		if (g3_error > MAX_ERRORS) {
		  freeImage(image);
		  zclose(fd);
		  return(NULL);
	      }		    
#endif
		if (g3_eof)
			break;
		if (col > cols)
			cols = col;
		currline += BITS_TO_BYTES(cols);
		}

	zclose(fd);
	image->title= dupString(name);
	image->width = cols;
	image->height = rows;
	if (!image->width || !image->height) { /* sanity check */
		freeImage(image);
		return(NULL);
	}

	if(verbose)
		fprintf(stderr, "  %s is a %dx%d G3 FAX image.\n", name, image->width, image->height);
    return(image);
}

/* originally this used only g3_ident to determine if it was a G3 image, but
 * it was always getting false positives so now it loads the whole image in
 * to see if it's reasonable.
 */
int	g3Ident(fullname, name)
	char	*fullname, *name;
{
	Image *image;

	g3_verb = 0;
	if ((image = g3Load(fullname, name, 1))) {
		freeImage(image);
		return(1);
	}
	return(0);
}
