/* 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 "pbm.h"
#include "g3.h"

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

static bool const checkingShortRows = FALSE;

static int endoffile = 0;
static int eols;
static int rawzeros;
static int shdata;
static int kludge;
static int reversebits;
static int stretch;

#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 ARGS(( tableentry* hash[], tableentry* te, int n, int a, int b ));
static tableentry* hashfind ARGS(( tableentry* hash[], int length, int code, int a, int b ));
static int getfaxrow ARGS(( FILE* inf, int row, bit* bitrow ));
static void skiptoeol ARGS(( FILE* file ));
static int rawgetbit ARGS(( FILE* file ));

static bit* bits[MAXROWS];

int
main( argc, argv )
    int argc;
    char* argv[];
{
    FILE* ifp;
    int argn, rows, cols, row, col, i;
    const char* usage = "[-kludge][-reversebits][-stretch] [g3file]";


    pbm_init( &argc, argv );

    argn = 1;
    kludge = 0;
    reversebits = 0;
    stretch = 0;

    /* Check for flags. */
    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
    {
        if ( pm_keymatch( argv[argn], "-kludge", 2 ) )
            kludge = 1;
        else if ( pm_keymatch( argv[argn], "-reversebits", 2 ) )
            reversebits = 1;
        else if ( pm_keymatch( argv[argn], "-stretch", 2 ) )
            stretch = 1;
        else
            pm_usage( usage );
        argn++;
    }

    if ( argn < argc )
    {
        ifp = pm_openr( argv[argn] );
        argn++;
    }
    else
        ifp = stdin;

    if ( argn != argc )
        pm_usage( usage );

    eols = 0;

    if ( kludge )
    {
        /* Skip extra lines to get in sync. */
        skiptoeol( ifp );
        skiptoeol( ifp );
        skiptoeol( ifp );
    }
    skiptoeol( ifp );
    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 );

    cols = 0;
    for ( rows = 0; rows < MAXROWS; ++rows )
    {
        bits[rows] = pbm_allocrow( MAXCOLS );
        col = getfaxrow( ifp, rows, bits[rows] );
        if ( endoffile )
            break;
        if ( !col)
        {
            --rows;
            continue;
        }
        if ( col > cols )
            cols = col;
        if ( stretch )
        {
            bits[rows + 1] = bits[rows];
            ++rows;
        }
    }

    pm_close( ifp );

    pbm_writepbminit( stdout, cols, rows, 0 );
    for ( row = 0; row < rows; ++row )
        pbm_writepbmrow( stdout, bits[row], cols, 0 );
    pm_close( stdout );

    exit(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)
            pm_error( "internal error: addtohash fatal hash collision" );
        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;
    te = hash[pos];
    return ((te && te->length == length && te->code == code) ? te : 0);
}

static int
getfaxrow( inf, row, bitrow )
    FILE* inf;
    int row;
    bit* bitrow;
{
    int col;
    bit* bP;
    int curlen;  
        /* Number of bits we've read so far for the code we're currently 
           reading
        */
    int curcode; 
        /* What we've assembled so far of the code we're currently reading */
    int nextbit;
        /* The bit we most recently read */
    int count, color;
    tableentry* te;

    for ( col = 0, bP = bitrow; col < MAXCOLS; ++col, ++bP )
        *bP = PBM_WHITE;
    col = 0;
    rawzeros = 0;
    curlen = 0;
    curcode = 0;
    color = 1;
    count = 0;
    while (!endoffile) {
        if (col >= MAXCOLS) {
            skiptoeol(inf);
            return (col); 
        }
        do {
            if (rawzeros >= 11) {
                nextbit = rawgetbit(inf);
                if (nextbit) {
                    /* We have 11 or 12 bits of zero followed by 1 bit of 1.
                       That means EOL.
                    */
                    if (col == 0)
                        /* XXX should be 6 */
                        endoffile = (++eols == 3);
                    else
                        eols = 0;

                    if (checkingShortRows && col > 0 && col < 1728)
                        pm_message("WARNING:  row %d short (len %d)",
                                   row, col);
                    return (col); 
                }
            } else
                nextbit = rawgetbit(inf);
            curcode = (curcode<<1) + nextbit;
            curlen++;
        } while (curcode <= 0);
        if (curlen > 13) {
            /* There are no codewords longer than 13 bits, so we've 
               apparently got garbage.
            */
            pm_message(
                "bad code word at row %d, col %d.  "
                "No prefix of the %d bits 0x%x matches any recognized "
                "code word and no code words longer than 12 bits are "
                "defined.  "
                "Skipping to EOL",
                row, col, curlen, curcode);
            skiptoeol(inf);
            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) {
                    col += count;
                    count = 0;
                } else {
                    for ( ; count > 0; --count, ++col )
                        bitrow[col] = PBM_BLACK;
                }
            }
            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:
            pm_error( "internal bad poop" );
        }
    }
    return (0);
}

static void
skiptoeol( file )
    FILE* file;
{
    while ( rawzeros < 11 )
        (void) rawgetbit( file );
    for ( ; ; )
    {
        if ( rawgetbit( file ) )
            break;
    }
}

static int shbit = 0;

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

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