/*
 * $Id: oakdecode.c,v 1.27 2007/12/19 23:48:45 rick Exp $
 *
 * Work in progress decoder for Oak Tech. JBIG streams (HP1500)
 *
 * The image data appears to be a bastard little-endian version of JBIG,
 * split into bands of 256 pixels (except for the last).  The data needs
 * to be directly driven into a hacked version of jbig.c.  The -r and -d
 * options are non-functional.
 *
 * In addition, the image data is left/right mirrored.
 */

/*b
 * Copyright (C) 2003-2006  Rick Richardson
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Author: Rick Richardson <rick.richardson@comcast.net>
b*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>

#include "jbig.h"

/*
 * Global option flags
 */
int	Debug = 0;
char	*RawFile;
char	*DecFile;
int	PrintOffset = 0;
int	SupressImage = 0;

int	ImageRec[4];
FILE	*FpDec[4][2];
FILE	*FpRaw[4][2];

void
debug(int level, char *fmt, ...)
{
    va_list ap;

    if (Debug < level)
	return;

    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
}

void
usage(void)
{
    fprintf(stderr,
"Usage:\n"
"	oakdecode [options] < OAKT-file\n"
"\n"
"	Decode an Oak Tech. OAKT printer stream into human readable form.\n"
"\n"
"	OAKT is the printer langauge used by the HP 1500 printers.\n"
"\n"
"Options:\n"
"       -d basename Basename of .pbm file for saving decompressed planes\n"
"       -r basename Basename of .jbg file for saving raw planes\n"
"       -i          Supress display of image records\n"
"       -o          Print file offsets\n"
"       -D lvl      Set Debug level [%d]\n"
    , Debug
    );

    exit(1);
}

#if 0
BIH-style  from foo2zjs/pbmtojbg...

00000000: 00 00 01 00   00 00 26 40   00 00 18 f8   00 00 00 80
00000010: 10 00 03 5c
#endif

#include <inttypes.h>
typedef uint32_t	DWORD;
typedef uint16_t	WORD;
typedef uint8_t		BYTE;

typedef struct
{
    DWORD	opt1;
    DWORD	xd;	// Oak has this little endian
    DWORD	yd;	// Oak has this little endian
    DWORD	l0;	// Oak has this little endian
    DWORD	opt2;
} OAKBIH;

void
iswap32(void *p)
{
    char *cp = (char *) p;
    char tmp;
    tmp = cp[0];
    cp[0] = cp[3];
    cp[3] = tmp;
    tmp = cp[1];
    cp[1] = cp[2];
    cp[2] = tmp;
}

/*
 * This is the standard JBIG-KIT big-endian BIH prettyprinter.
 */
void
print_bih(unsigned char bih[20])
{
    unsigned int xd, yd, l0;

    xd = (bih[4] << 24) | (bih[5] << 16) | (bih[6] << 8) | (bih[7] << 0);
    yd = (bih[8] << 24) | (bih[9] << 16) | (bih[10] << 8) | (bih[11] << 0);
    l0 = (bih[12] << 24) | (bih[13] << 16) | (bih[14] << 8) | (bih[15] << 0);

    printf("		DL = %d, D = %d, P = %d, - = %d, XY = %d x %d\n",
	 bih[0], bih[1], bih[2], bih[3], xd, yd);

    printf("		L0 = %d, MX = %d, MY = %d\n",
	 l0, bih[16], bih[17]);

    printf("		Order   = %d %s%s%s%s%s\n", bih[18],
	bih[18] & JBG_HITOLO ? " HITOLO" : "",
	bih[18] & JBG_SEQ ? " SEQ" : "",
	bih[18] & JBG_ILEAVE ? " ILEAVE" : "",
	bih[18] & JBG_SMID ? " SMID" : "",
	bih[18] & 0xf0 ? " other" : "");

    printf("		Options = %d %s%s%s%s%s%s%s%s\n", bih[19],
	bih[19] & JBG_LRLTWO ? " LRLTWO" : "",
	bih[19] & JBG_VLENGTH ? " VLENGTH" : "",
	bih[19] & JBG_TPDON ? " TPDON" : "",
	bih[19] & JBG_TPBON ? " TPBON" : "",
	bih[19] & JBG_DPON ? " DPON" : "",
	bih[19] & JBG_DPPRIV ? " DPPRIV" : "",
	bih[19] & JBG_DPLAST ? " DPLAST" : "",
	bih[19] & 0x80 ? " other" : "");
    printf("		%u stripes, %d layers, %d planes\n",
	((yd >> bih[1]) +  ((((1UL << bih[1]) - 1) & xd) != 0) + l0 - 1) / l0,
	bih[1] - bih[0], bih[2]);
}

#if 0
00000260: 4f 41 4b 54 40 00 00 00 3c 00 00 00 00 00 01 00 | OAKT@...<....... |
00000270: 60 13 00 00 80 00 00 00 80 00 00 00 20 00 03 58 | `........... ..X |
00000280: 02 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 | ................ |
00000290: 00 00 00 00 00 00 00 00 50 41 44 5f 50 41 44 5f | ........PAD_PAD_ |
000002a0: ff 02 50 41 44 5f 50 41 44 5f 50 41 44 5f 50 41 | ..PAD_PAD_PAD_PA |

000002b0: 4f 41 4b 54 40 00 00 00 3c 00 00 00 00 00 01 00 | OAKT@...<....... |
000002c0: 60 13 00 00 80 00 00 00 80 00 00 00 20 00 03 58 | `........... ..X |
000002d0: 02 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 | ................ |
000002e0: 00 00 00 00 01 00 00 00 50 41 44 5f 50 41 44 5f | ........PAD_PAD_ |
000002f0: ff 02 50 41 44 5f 50 41 44 5f 50 41 44 5f 50 41 | ..PAD_PAD_PAD_PA |

0000ba20: 4f 41 4b 54 40 00 00 00 3c 00 00 00 00 00 01 00 | OAKT@...<....... |
0000ba30: 60 13 00 00 00 01 00 00 00 01 00 00 20 00 03 58 | `........... ..X |
0000ba40: 90 0d 00 00 a0 0d 00 00 00 00 00 00 40 02 00 00 | ............@... |
0000ba50: 00 00 00 00 00 00 00 00 50 41 44 5f 50 41 44 5f | ........PAD_PAD_ |
0000ba60: a6 ab 23 26 a0 78 7e 82 25 30 39 8b 95 16 32 4e | ..#&.x~.%09...2N |
...
0000c7f0: 50 41 44 5f 50 41 44 5f 50 41 44 5f 50 41 44 5f | PAD_PAD_PAD_PAD_ |

0000c800: 4f 41 4b 54 40 00 00 00 3c 00 00 00 00 00 01 00 | OAKT@...<....... |
0000c810: 60 13 00 00 00 01 00 00 00 01 00 00 20 00 03 58 | `........... ..X |
0000c820: 02 00 00 00 10 00 00 00 00 00 00 00 40 02 00 00 | ............@... |
0000c830: 00 00 00 00 01 00 00 00 50 41 44 5f 50 41 44 5f | ........PAD_PAD_ |
0000c840: ff 02 50 41 44 5f 50 41 44 5f 50 41 44 5f 50 41 | ..PAD_PAD_PAD_PA |

000124d0: 4f 41 4b 54 40 00 00 00 3c 00 00 00 00 00 01 00 | OAKT@...<....... |
000124e0: 60 13 00 00 00 01 00 00 00 01 00 00 20 00 03 58 | `........... ..X |
000124f0: fe 16 00 00 10 17 00 00 00 00 00 00 40 02 00 00 | ............@... |
00012500: 03 00 00 00 00 00 00 00 50 41 44 5f 50 41 44 5f | ........PAD_PAD_ |
00012510: 4c 8b 1a 0b 4d 74 19 a8 64 fa 91 c0 02 10 36 8c | L...Mt..d.....6. |
...
00013bf0: 11 f6 d3 e1 ca 98 ed b3 1a c3 2d a1 db 34 a9 db | ..........-..4.. |
00013c00: 04 f4 b8 2e 53 cb d3 be b3 e4 8a 3c ff 02 50 41 | ....S......<..PA |
00013c10: 44 5f 50 41 44 5f 50 41 44 5f 50 41 44 5f 50 41 | D_PAD_PAD_PAD_PA |

00013c20: 4f 41 4b 54 40 00 00 00 3c 00 00 00 00 00 01 00 | OAKT@...<....... |
00013c30: 60 13 00 00 00 01 00 00 00 01 00 00 20 00 03 58 | `........... ..X |
00013c40: 7e 02 00 00 90 02 00 00 00 00 00 00 40 02 00 00 | ~...........@... |
00013c50: 03 00 00 00 01 00 00 00 50 41 44 5f 50 41 44 5f | ........PAD_PAD_ |

#endif

typedef struct
{
    char	magic[4];	
    DWORD	len;
    DWORD	type;
} OAK_HDR;

typedef struct
{
    WORD	unk;
    char	string[64];
    WORD	pad;
} HDR_0D;

typedef struct
{
    char	datetime[32];	// Date/time in string format (with NL)
    DWORD	time_t;		// Time in seconds since the Unix epoch
    WORD	year;		// e.g. 2003
    WORD	tm_mon;		// Month-1
    WORD	tm_mday;	// Day of month (1-31)
    WORD	tm_hour;	// Hour (0-23)
    WORD	tm_min;		// Minute (0-59)
    WORD	tm_sec;		// Second (0-59)
    DWORD	pad;
} HDR_0C;

typedef struct
{
    OAKBIH	bih;
    DWORD	datalen;
    DWORD	padlen;
    DWORD	unk1C;
    DWORD	y;		// Y offset of this chunk
    DWORD	plane;		// 0=, 1=, 2=, 3=K
    DWORD	subplane;	// 0 or 1
    DWORD	pad[2];
} HDR_3C;

typedef struct
{
    DWORD	unk0;
    DWORD	unk1;
    DWORD	w;
    DWORD	h;
    DWORD	resx;
    DWORD	resy;
    DWORD	nbits;
    DWORD	unk7[4];
} HDR_3X;

void
decode(FILE *fp)
{
    OAK_HDR	hdr;
    int		rc;
    int		size;
    int		plane = 0;
    int		subplane;
    int		pageNum = 0;
    int		len;
    int		i, j;
    int		c;
    char	*p;
    int		curOff = 0;
    int		dwords[128];
    short	words[128];
    unsigned char	bytes[128];
    char	buf[512];
    HDR_0D	hdr0d;
    HDR_0C	hdr0c;
    HDR_3C	hdr3c;
    HDR_3X	hdr3x[4];
    int		firstPlane;
    size_t	cnt;
    char	*ibuf;
    struct jbg_dec_state	s[4][2];
    int		height[4][2];
    int		width[4][2];

    for (;;)
    {
	static int	first3c = 1;

	rc = fread(&hdr, 1, len = sizeof(hdr), fp);
	if (rc <=0)
	    break;
	if (rc != len)
	{
	    debug(0, "Expected OAK header, got short read: %d bytes\n", rc);
	    break;
	}

	if (hdr.type == 0x3c && first3c)
	{
	    printf("\t"
		    "\t%8s %4s %4s %4s %8s %5s %5s %3s %4s %s %s\n",
		    "bih0", "w", "h", "l0", "bih5", "dlen", "plen",
		    "unk", "yOff", "P", "subP"
		  );
	    first3c = 0;
	}

	if (hdr.type != 0x3c || !SupressImage || !ImageRec[plane])
	{
	    if (PrintOffset)
		printf("%x:	", curOff);
	    printf("%02x (%d)", hdr.type, hdr.len);
	}

	curOff += len;
	size = hdr.len;
	size -= sizeof(hdr);

	switch (hdr.type)
	{
	case 0x0d:	// first record
	    rc = fread(&hdr0d, len = sizeof(hdr0d), 1, fp);
	    if (rc != 1) goto out;
	    curOff += len;
	    printf(" %x %s", hdr0d.unk, hdr0d.string);
	    break;
	case 0x0c:	// time
	    rc = fread(&hdr0c, len = sizeof(hdr0c), 1, fp);
	    if (rc != 1) goto out;
	    curOff += len;
	    p = strchr(hdr0c.datetime, '\n');
	    if (p) *p = 0;
	    printf(" %s", hdr0c.datetime);
	    printf(", %d", hdr0c.time_t);
	    printf(", %d/%02d/%02d %02d:%02d:%02d",
		    hdr0c.year, hdr0c.tm_mon+1, hdr0c.tm_mday,
		    hdr0c.tm_hour, hdr0c.tm_min, hdr0c.tm_sec);
	    break;
	case 0x0a:	// filename
	    printf(" ");
	    curOff += size;
	    while (size--)
	    {
		c = fgetc(fp);
		if (c == EOF)
		    break;
		else if (c) putchar(c);
		else break;
	    }
	    while (size--)
		fgetc(fp);
	    break;
	case 0x14:
	    printf(" (no args)");
	    ++pageNum;
	    curOff += size;
	    while (size--)
		fgetc(fp);
	    break;
	case 0x28:
	    rc = fread(dwords, len = 1*4, 1, fp);
	    if (rc != 1) goto out;
	    curOff += len;
	    switch (dwords[0])
	    {
	    case 1:	printf(" Source=Tray1"); break;
	    case 4:	printf(" Source=ManualFeed"); break;
	    case 7:	printf(" Source=Auto"); break;
	    default:	printf(" Source=%d", dwords[0]); break;
	    }
	    break;
	case 0x29:
	    rc = fread(bytes, len = 17*4, 1, fp);
	    if (rc != 1) goto out;
	    curOff += len;
	    printf(" PaperType=%d UNK8=%d,%d,%d,%d, blanks(63)",
		    bytes[0],
		    bytes[1], bytes[2], bytes[3], bytes[4]);
	    // PaperType: 0=AutoSelect, 1=Plain, 2=Preprinted, 3=Letterhead
	    // 4=GrayscaleTransparency, 5=Prepunched, 6=Labels, 7=Bond
	    // 8=Recycled, 9=Color, 10=Cardstock, 11=Heavy, 12=Envelope
	    // 13=Light, 14=Tough
	    break;
	case 0x2a:
	    rc = fread(dwords, len = 5*4, 1, fp);
	    if (rc != 1) goto out;
	    curOff += len;
	    printf("	Copies=%x	UNK=%x", dwords[0], dwords[1]);
	    break;
	case 0x2b:
	    rc = fread(dwords, len = 5*4, 1, fp);
	    if (rc != 1) goto out;
	    curOff += len;
	    printf("	papercode=%x", dwords[0]);
	    printf("	xwid=%d", dwords[1]);
	    printf("	ywid=%d", dwords[2]);
	    printf("	UNK=%x", dwords[3]);
	    break;
	case 0x32:
	    firstPlane = 0;
	    goto prplanes;
	case 0x33:
	    firstPlane = 3;
	prplanes:
	    printf("\n\tu0	u1	w	h	resx	resy	nBits");
	    for (i = firstPlane; i < 4; ++i)
	    {
		rc = fread(&hdr3x[i], len = sizeof(HDR_3X), 1, fp);
		if (rc != 1) goto out;
		curOff += len;
		size -= len;
		printf("\n\tx%x\tx%x\t%d\t%d\t%d\t%d\tx%x",
			hdr3x[i].unk0,
			hdr3x[i].unk1,
			hdr3x[i].w,
			hdr3x[i].h,
			hdr3x[i].resx,
			hdr3x[i].resy,
			hdr3x[i].nbits);
	    }
	    curOff += size;
	    while (size-- > 0)
		fgetc(fp);
	    break;
	case 0x15:
	    printf(" (no args)");
	    curOff += size;
	    while (size--)
		fgetc(fp);
	    break;
	case 0x3c:
	    // rc = fread(dwords, len = 48, 1, fp);
	    rc = fread(&hdr3c, len = sizeof(hdr3c), 1, fp);
	    if (rc != 1)
	    {
		debug(0, "Short read of hdr3c\n");
		goto out;
	    }
	    curOff += len;

	    plane = hdr3c.plane;
	    subplane = hdr3c.subplane;
	    if (!SupressImage || !ImageRec[plane])
	    {
		printf(
		    "\t%08x %4d %4d %4d %08x %5d %5d %03x %4d %d %d\n",
			hdr3c.bih.opt1,
			hdr3c.bih.xd,
			hdr3c.bih.yd,
			hdr3c.bih.l0,
			hdr3c.bih.opt2,
			hdr3c.datalen,
			hdr3c.padlen,
			hdr3c.unk1C,
			hdr3c.y,
			hdr3c.plane,
			hdr3c.subplane);
	    }

	    if (RawFile && !FpRaw[plane][subplane])
	    {
		sprintf(buf, "%s-%02d-%d-%d.jbg",
			RawFile, pageNum, plane, subplane);
		FpRaw[plane][subplane] = fopen(buf, "w");
	    }
	    if (DecFile && !FpDec[plane][subplane])
	    {
		height[plane][subplane] = 0;
		sprintf(buf, "%s-%02d-%d-%d.pbm",
			DecFile, pageNum, plane, subplane);
		FpDec[plane][subplane] = fopen(buf, "w");
		fprintf(FpDec[plane][subplane], "P4\n%8d %8d\n", 0, 0);
	    }

	    if (FpDec[plane][subplane])
		jbg_dec_init(&s[plane][subplane]);

	    if (1||hdr3c.subplane == 0)
	    {
		static int testend = 1;
		static char *cp = (char *) &testend;
		static int first_bih = 1;
		// JBIGKIT is bigendian, Oak is little-endian
		// swap the BIH header words before passing it to
		// jbig-kit.  N.B. - is there any issues with endianess
		// inside the compressed stream itself????
		if (*cp == 1)
		{
		    iswap32(&hdr3c.bih.xd);
		    iswap32(&hdr3c.bih.yd);
		    iswap32(&hdr3c.bih.l0);
		}
		if (!SupressImage || !ImageRec[plane])
		    if (first_bih)
		    {
			first_bih = 0;
			print_bih((unsigned char *) &hdr3c);
		    }
		if (0 && *cp == 1)
		{
		    iswap32(&hdr3c.bih.xd);
		    iswap32(&hdr3c.bih.yd);
		    iswap32(&hdr3c.bih.l0);
		}
		if (FpRaw[plane][subplane])
		    fwrite(&hdr3c.bih, 1, 20, FpRaw[plane][subplane]);
		if (FpDec[plane][subplane])
		{
		    rc = jbg_dec_in(&s[plane][subplane],
			    (unsigned char *)&hdr3c.bih, 20, &cnt);
		}
	    }

	    ImageRec[plane]++;

	    // image data
	    if (!hdr3c.padlen)
		break;
	    size = hdr3c.datalen;
	    ibuf = malloc(size);
	    rc = fread(ibuf, 1, size, fp);
	    if (rc <= 0)
		break;
	    curOff += size;
	    if (FpRaw[plane][subplane])
		fwrite(ibuf, 1, size, FpRaw[plane][subplane]);
	    if (FpDec[plane][subplane])
	    {
		unsigned char *image;

		rc = JBG_EAGAIN;
		p = ibuf;
		while (size > 0 &&
			(rc == JBG_EAGAIN || rc == JBG_EOK))
		{
		    rc = jbg_dec_in(&s[plane][subplane],
			(unsigned char *) p, size, &cnt);
		    p += cnt;
		    size -= cnt;
		}
		if (rc)
		    debug(0, "rc= %d size=%d\n", rc, size);

		height[plane][subplane]
		    += jbg_dec_getheight(&s[plane][subplane]);
		width[plane][subplane]
		    = jbg_dec_getwidth(&s[plane][subplane]);
		image = jbg_dec_getimage(&s[plane][subplane], 0);
		if (image)
		    fwrite(image, 1, jbg_dec_getsize(&s[plane][subplane]),
			FpDec[plane][subplane]);
		else
		    debug(0, "Missing image p=%d/%d %dx%d!\n",
			    plane, subplane,
			    jbg_dec_getwidth(&s[plane][subplane]),
			    jbg_dec_getheight(&s[plane][subplane]));
		jbg_dec_free(&s[plane][subplane]);
	    }
	    free(ibuf);

	    size = hdr3c.padlen - hdr3c.datalen;
	    curOff += size;
	    while (size--)
		c = fgetc(fp);
	    continue;
	case 0x17:
	    printf(" (no args)");
	    for (i = 0; i < 4; ++i)
	    {
		for (j = 0; j < 2; ++j)
		{
		    if (FpRaw[i][j])
		    {
			fclose(FpRaw[i][j]);
			FpRaw[i][j] = NULL;
		    }
		    if (FpDec[i][j])
		    {
			fseek(FpDec[i][j], 0, 0);
			fprintf(FpDec[i][j], "P4\n%8d %8d\n",
				width[i][j], height[i][j]);
			fclose(FpDec[i][j]);
			FpDec[i][j] = NULL;
		    }
		}
	    }
	    while (size--)
		fgetc(fp);
	    break;
	case 0x18:
	    rc = fread(words, len = (1+1)*2, 1, fp);
	    if (rc != 1) goto out;
	    curOff += len;
	    printf(" UNK=%x", words[0]);
	    break;
	case 0x0b:
	    printf(" (no args)");
	    curOff += size;
	    while (size--)
		fgetc(fp);
	    break;
	default:
	    curOff += size;
	    while (size--)
		fgetc(fp);
	}

	printf("\n");
    }
out:
    return;
}

int
main(int argc, char *argv[])
{
	extern int	optind;
	extern char	*optarg;
	int		c;

	while ( (c = getopt(argc, argv, "d:ior:D:?h")) != EOF)
		switch (c)
		{
		case 'd': DecFile = optarg; break;
		case 'r': RawFile = optarg; break;
		case 'i': SupressImage = 1; break;
		case 'o': PrintOffset = 1; break;
		case 'D': Debug = atoi(optarg); break;
		default: usage(); exit(1);
		}

	argc -= optind;
	argv += optind;

	decode(stdin);

	exit(0);
}
