/* 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.
*/
/* $Id: fromfax.c,v 1.5 1993/10/25 02:14:27 Rhialto Exp $
 * $Log: fromfax.c,v $
 * Revision 1.5  1993/10/25  02:14:27  Rhialto
 * Make +FBOR flexible; fix RTC detection bug.
 *
 * Revision 1.4  1993/09/18  22:41:27  Rhialto
 * Improve on bit order terminology: use msb/lsb first.
 *
 * Revision 1.3  1993/08/20  03:45:27  Rhialto
 * Add hack for auto-detecting fax bit order.
 *
 * Revision 1.2  1993/06/11  16:33:37  Rhialto
 * First real RCS checkin
 *
 */

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

#ifdef DEBUG
#define debug(x)    printf x
#else
#define debug(x)
#endif

#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	 row;
	int	 endoffile;
	int	 eols;
	int	 rawzeros;
	int	 shdata;
	int	 shbit;
	int	 kludge;
	int	 reversebits;
	int	 stretch;
	unsigned char  *bitp;
	unsigned char	bitval;

} FAXIN;

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);
extern int verbose;

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

int
try_line(FILE *ifp)
{
    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;

    while (!faxin.endoffile) {
	if (col >= MAXCOLS) {
	    skiptoeol(ifp);
	    return col;
	}
	do {
	    if (faxin.rawzeros >= 11) {
		nextbit = rawgetbit(ifp);
		if (nextbit) {
		    if (col && col < LINE_BITS)
			return -1;
		    return (col);
		}
		if (feof(ifp)) {
		    return -1;
		}
	    } else
		nextbit = rawgetbit(ifp);
	    curcode = (curcode << 1) + nextbit;
	    curlen++;
	} while (curcode <= 0);
	if (curlen > 13) {
	    skiptoeol(ifp);
	    return -1;
	}
	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) {
		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:
	    fprintf(stderr, "internal bad poop\n");
	}
    }
}

int
infer_bitorder(FILE *f)
{
    int 	    normerrors = 0;
    int 	    reverrors = 0;
    int 	    i;
    long	    pos;

    pos = ftell(f);

    if (verbose)
	printf("try MSB first...");
    faxin.reversebits = 0;
    faxin.shbit = 0;
    skiptoeol(f);
    for (i = 0; i < 10; i++)
	if (try_line(f) == -1)
	    normerrors++;
    fseek(f, pos, SEEK_SET);
    if (verbose)
	printf(" %d errors\n", normerrors);

    if (verbose)
	printf("try LSB first...");
    faxin.reversebits = 1;
    faxin.shbit = 0;
    skiptoeol(f);
    for (i = 0; i < 10; i++)
	if (try_line(f) == -1)
	    reverrors++;
    fseek(f, pos, SEEK_SET);
    if (verbose)
	printf(" %d errors\n", reverrors);

    if (normerrors < reverrors) {
	return faxin.reversebits = 0;
    } else {
	fprintf(stderr, "Warning: this file has its bits the wrong way!\n");
	return faxin.reversebits = 1;
    }
}

/*
 * Prepare for reading a faxfile.
 * dostretch (ignored) indicates we must repeat each scan line.
 * bitorder indicates bit order: 0=normal: lsb first, 1=reversed: msb first.
 *				 -1: auto-detect.
 *	For understandable reasons, the designer of this decoder had
 *	the opposite idea of what was normal and reversed, hence
 *	some confusion.
 */

long	/* error code */
faxin_open_fp(FILE *ifp, int dostretch, int bitorder)
{
    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);
#endif
    /* Attempt to read file/page header */
    if (fread(&faxin.faxhdr, sizeof(faxin.faxhdr), 1, ifp) == 1) {
	fseek(ifp, -sizeof(faxin.faxhdr), SEEK_CUR);
#if 0
	faxin.raw = 1;
#else
	faxin.raw = !memisprint(&faxin.faxhdr.id.magic[0], sizeof(faxin.faxhdr.id.magic));
	debug(("assume faxin.raw = %d\n", faxin.raw));
#endif
	/* If fax is raw, try to infer bit order */
	if (faxin.raw) {
	    faxin.reversebits = !bitorder;	/* swap 0 and 1 */
	    if (bitorder < 0)
		bitorder = infer_bitorder(ifp);
	    if (bitorder < 0) {
		debug(("fail on bit order\n"));
		return 1;
	    }
	}

	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;
    faxin.row = 0;
    faxin.shbit = 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)
	    fprintf(stderr, "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)
	fprintf(stderr,
	   "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;
    faxin.row++;

    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);
			if (verbose) {
			    fprintf(stderr, "empty row %d: found %d so far, eof=%d\n",
				faxin.row, faxin.eols, faxin.endoffile);
			}
		    }
#ifndef notdef
		    if (col && col < LINE_BITS)
			fprintf(stderr, "warning, row %d short (len %d)\n",
				   faxin.row, 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 (col != 0)
	    faxin.eols = 0;
	if (curlen > 13) {
	    fprintf(stderr, "bad code word at row %d, col %d (len %d code 0x%x), skipping to EOL\n",
		       faxin.row, 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:
	    fprintf(stderr, "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) {
	    fprintf(stderr, "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;
}
