#include <u.h>
#include <libc.h>

enum
{
	ERR,
	EOL,
	MAKE,
	TERM,
};

enum
{
	White,
	Black,
};

typedef struct Tab
{
	ushort	run;
	ushort	bits;
	int		code;
} Tab;

Tab	wtab[8192];
Tab	btab[8192];
uchar	bitrev[256];
uchar	bitnonrev[256];

int	readrow(uchar *rev, int*);
void	initwbtab(void);
void	sync(uchar*);
int	readfile(int, char*, char*);

int		nbytes;
uchar	*bytes;
uchar	*pixels;
uchar	*buf;
int		y;
uint		bitoffset;
uint		word24;

enum
{
	Bytes	= 1024*1024,
	Lines	= 1410,	/* 1100 for A4, 1410 for B4 */
	Dots		= 1728,
};

void
error(char *fmt, ...)
{
	char buf[256];
	va_list arg;

	if(fmt){
		va_start(arg, fmt);
		vseprint(buf, buf+sizeof buf, fmt, arg);
		va_end(arg);
		fprint(2, "g3: %s\n", buf);
	}
	exits(fmt);
}

void
usage(void)
{
	fprint(2, "usage: g3p9bit [-gy] file\n");
	exits("usage");
}

void
main(int argc, char **argv)
{
	int y, fd, n, m;
	char *t;
	char *file, err[ERRMAX], tbuf[5*12+1];
	int gray=0;
	int yscale=1;

	ARGBEGIN{
	case 'g':
		/* do simulated 2bit gray to compress x */
		gray++;
		break;
	case 'y':
		/* double each scan line to double the y resolution */
		yscale=2;
		break;
	default:
		usage();
	}ARGEND

	if(argc > 1)
		usage();

	initwbtab();
	buf = malloc(1024*1024);
	t = malloc((Dots/8)*Lines);
	if(buf==nil || t==nil)
		error("malloc failed: %r\n");
	pixels = (uchar*)t;

	file = "<stdin>";
	fd = 0;
	if(argc > 0){
		file = argv[0];
		fd = open(file, OREAD);
		if(fd < 0)
			error("can't open %s", file);
	}
	y = readfile(fd, file, err);
	if(y < 0)
		error(err);
	sprint(tbuf, "%11d %11d %11d %11d %11d ", gray, 0, 0, Dots/(gray+1), y*yscale);
	write(1, tbuf, 5*12);
	n = (Dots/8)*y*yscale;
	/* write in pieces; brazil pipes work badly with huge counts */
	while(n > 0){
		if(yscale > 1)	/* write one scan line */
			m = Dots/8;
		else{	/* write lots */
			m = n;
			if(m > 8192)
				m = 8192;
		}
		for(y=0; y<yscale; y++){
			if(write(1, t, m) != m)
				error("write error");
			n -= m;
		}
		t += m;
	}
	if(err[0])
		error(err);
	error(nil);
}

enum{
	Hvres,
	Hbaud,
	Hwidth,
	Hlength,
	Hcomp,
	HenabECM,
	HenabBFT,
	Hmsperscan,
};

int	defhdr[8] = {
	0,		/* 98 lpi */
	0,		/* 2400 baud */
	0,		/* 1728 pixels in 215mm */
	0,		/* A4, 297mm */
	0,		/* 1-D modified huffman */
	0,		/* disable ECM */
	0,		/* disable BFT */
	3,		/* 10 ms per scan */
};

int
crackhdr(uchar *ap, int *hdr)
{
	char *p, *q;
	int i;

	p = (char*)ap;
	q = p;
	for(i=0; i<8; i++){
		if(*p<'0' || '9'<*p)
			return -1;
		hdr[i] = strtol(p, &q, 0);
		p = q+1;
	}
	return p-(char*)ap;
}

int
readfile(int f, char *file, char *err)
{
	int i, r, lines;
	uchar *rev;
	int hdr[8];

	err[0] = 0;
	memset(pixels, 0, (Dots/8) * Lines);
	nbytes = readn(f, buf, 1024*1024);
	if(nbytes==1024*1024 || nbytes<=100){
    bad:
		sprint(err, "g3: file improper size or format: %s", file);
		return -1;
	}
	bytes = buf;
	if(bytes[0]=='I' && bytes[1]=='I' && bytes[2]=='*'){	/* dumb PC format */
		bytes += 0xf3;
		nbytes -= 0xf3;
		rev = bitrev;
		memmove(hdr, defhdr, sizeof defhdr);
	}else if(bytes[0] == 0 && strcmp((char*)bytes+1, "PC Research, Inc") == 0){	/* digifax format */
		memmove(hdr, defhdr, sizeof defhdr);
		if(bytes[45] == 0x40 && bytes[29] == 1)	/* high resolution */
			hdr[Hvres] = 1;
		else
			hdr[Hvres] = 0;
		/* hdr[26] | (hdr[27]<<8) is page number */

		bytes += 64;
		nbytes -= 64;
		rev = bitnonrev;
	}else{
		while(nbytes > 2){
			if(bytes[0]=='\n'){
				if(strncmp((char*)bytes+1, "FDCS=", 5) == 0){
					i = crackhdr(bytes+6, hdr);
					if(i < 0){
						sprint(err, "g3: bad FDCS in header: %s", file);
						return -1;
					}
					if(hdr[Hwidth] != 0){
						sprint(err, "g3: unsupported width: %s", file);
						return -1;
					}
					if(hdr[Hcomp] != 0){
						sprint(err, "g3: unsupported compression: %s", file);
						return -1;
					}
					bytes += i+1;
					nbytes -= i+1;
					continue;
				}
				if(bytes[1] == '\n'){
					bytes += 2;
					nbytes -= 2;
					break;
				}
			}
			bytes++;
			nbytes--;
		}
		if(nbytes < 2)
			goto bad;
		rev = bitnonrev;
	}
	bitoffset = 24;
	word24 = 0;
	sync(rev);
	lines = Lines;
	if(hdr[Hvres] == 1)
		lines *= 2;
	for(y=0; y<lines; y++){
		r = readrow(rev, hdr);
		if(r < 0)
			break;
		if(r == 0)
			sync(rev);
	}
	if(hdr[Hvres] == 1)
		y /= 2;
//	if(y < 100)
//		goto bad;
	return y;
}

int
readrow(uchar *rev, int *hdr)
{
	int bo, state;
	Tab *tab, *t;
	int x, oldx, x2, oldx2, dx, xx;
	uint w24;
	uchar *p, *q;

	state = White;
	oldx = 0;
	bo = bitoffset;
	w24 = word24;
	x = y;
	if(hdr[Hvres] == 1)	/* high resolution */
		x /= 2;
	p = pixels + x*Dots/8;
	x = 0;

loop:
	if(x > Dots)
		return 0;
	if(state == White)
		tab = wtab;
	else
		tab = btab;
	if(bo > (24-13)) {
		do {
			if(nbytes <= 0)
				return -1;
			w24 = (w24<<8) | rev[*bytes];
			bo -= 8;
			bytes++;
			nbytes--;
		} while(bo >= 8);
	}

	t = tab + ((w24 >> (24-13-bo)) & 8191);
	x += t->run;
	bo += t->bits;
	if(t->code == TERM){
		if(state == White)
			oldx = x;
		else{
			oldx2 = oldx;
			x2 = x;
			xx = oldx2&7;
			q = p+oldx2/8;
			if(x2/8 == oldx2/8)	/* all in one byte, but if((x2&7)==0), do harder case */
				*q |= (0xFF>>xx) & (0xFF<<(8-(x2&7)));
			else{
				dx = x2 - oldx2;
				/* leading edge */
				if(xx){
					*q++ |= 0xFF>>xx;
					dx -= 8-xx;
				}
				/* middle */
				while(dx >= 8){
					*q++ = 0xFF;
					dx -= 8;
				}
				/* trailing edge */
				if(dx)
					*q |= 0xFF<<(8-dx);
			}
		}
		state ^= White^Black;
		goto loop;
	}
	if(t->code == ERR){
		bitoffset = bo;
		word24 = w24;
		return 0;
	}
	if(t->code == EOL){
		bitoffset = bo;
		word24 = w24;
		return 1;
	}
	goto loop;
	return 0;
}


void
sync(uchar *rev)
{
	Tab *t;
	int c;

	c = 0;
loop:
	if(bitoffset > (24-13)) {
		do {
			if(nbytes <= 0)
				return;
			word24 = (word24<<8) | rev[*bytes];
			bitoffset -= 8;
			bytes++;
			nbytes--;
		} while(bitoffset >= 8);
	}
	t = wtab + ((word24 >> (24-13-bitoffset)) & 8191);
	if(t->code != EOL) {
		bitoffset++;
		c++;
		goto loop;
	}
	bitoffset += t->bits;
}

typedef struct File
{
	char	*val;
	int	code;
}File;

File ibtab[] = {
#include "btab"
{nil, 0}
};

File iwtab[] = {
#include "wtab"
{nil, 0}
};

int
binary(char *s)
{
	int n;

	n = 0;
	while(*s)
		n = n*2 + *s++-'0';
	return n;
}

void
tabinit(File *file, Tab *tab)
{
	int i, j, v, r, l;
	char *b;

	for(v=0; v<8192; v++) {
		tab[v].run = 0;
		tab[v].bits = 1;
		tab[v].code = ERR;
	}
	for(i=0; b=file[i].val; i++){
		l = strlen(b);
		v = binary(b);
		r = file[i].code;
		if(l > 13)
			fprint(2, "g3: oops1 l = %d %s\n", l, b);

		v = v<<(13-l);
		for(j=0; j<(1<<((13-l))); j++) {
			if(tab[v].code != ERR)
				fprint(2, "g3: oops2 %d %s\n", r, b);
			tab[v].run = r;
			tab[v].bits = l;
			tab[v].code = TERM;
			if(r < 0) {
				tab[v].run = 0;
				tab[v].code = EOL;
				if(r < -1) {
					tab[v].bits = 1;
					tab[v].code = MAKE;
				}
			}
			if(r >= 64) {
				tab[v].code = MAKE;
			}
			v++;
		}
	}

	for(i=0; i<256; i++)
		for(j=0; j<8; j++)
			if(i & (1<<j))
				bitrev[i] |= 0x80 >> j;
	for(i=0; i<256; i++)
		bitnonrev[i] = i;
}

void
initwbtab(void)
{
	tabinit(iwtab, wtab);
	tabinit(ibtab, btab);
}
