#include "stdinc.h"
#include "dat.h"
#include "fns.h"

enum
{
	IDATSIZE	= 20000,
	FilterNone = 0
};

typedef struct ZlibR ZlibR;
typedef struct ZlibW ZlibW;

struct ZlibR
{
	uchar *data;
	int width;
	int dx;
	int dy;
	int x;
	int y;
	int pixwid;
};

struct ZlibW
{
	Hio *io;
	uchar *buf;
	uchar *b;
	uchar *e;
};

static ulong *crctab;
static uchar PNGmagic[] = { 137, 'P', 'N', 'G', '\r', '\n', 26, '\n'};

static void
put4(uchar *a, ulong v)
{
	a[0] = v>>24;
	a[1] = v>>16;
	a[2] = v>>8;
	a[3] = v;
}

static void
chunk(Hio *io, char *type, uchar *d, int n)
{
	uchar buf[4];
	ulong crc = 0;

	if(strlen(type) != 4)
		return;
	put4(buf, n);
	hwrite(io, buf, 4);
	hwrite(io, type, 4);
	hwrite(io, d, n);
	crc = blockcrc(crctab, crc, type, 4);
	crc = blockcrc(crctab, crc, d, n);
	put4(buf, crc);
	hwrite(io, buf, 4);
}

static int
zread(void *va, void *buf, int n)
{
	int a, i, pixels, pixwid;
	uchar *b, *e, *img;
	ZlibR *z;

	z = va;
	pixwid = z->pixwid;
	b = buf;
	e = b+n;
	while(b+pixwid <= e){
		if(z->y >= z->dy)
			break;
		if(z->x == 0)
			*b++ = FilterNone;
		pixels = (e-b)/pixwid;
		if(pixels > z->dx - z->x)
			pixels = z->dx - z->x;
		img = z->data + z->width*z->y + pixwid*z->x;
		memmove(b, img, pixwid*pixels);
		if(pixwid == 4){
			/*
			 * Convert to non-premultiplied alpha.
			 */
			for(i=0; i<pixels; i++, b+=4){
				a = b[3];
				if(a != 0 && a != 255){
					if(b[0] >= a)
						b[0] = a;
					b[0] = (b[0]*255)/a;
					if(b[1] >= a)
						b[1] = a;
					b[1] = (b[1]*255)/a;
					if(b[2] >= a)
						b[2] = a;
					b[2] = (b[2]*255)/a;
				}
			}
		}else	
			b += pixwid*pixels;

		z->x += pixels;
		if(z->x >= z->dx){
			z->x = 0;
			z->y++;
		}
	}
	return b - (uchar*)buf;
}

static void
IDAT(ZlibW *z)
{
	chunk(z->io, "IDAT", z->buf, z->b - z->buf);
	z->b = z->buf;
}

static int
zwrite(void *va, void *buf, int n)
{
	int m;
	uchar *b, *e;
	ZlibW *z;

	z = va;
	b = buf;
	e = b+n;

	while(b < e){
		m = z->e - z->b;
		if(m > e - b)
			m = e - b;
		memmove(z->b, b, m);
		z->b += m;
		b += m;
		if(z->b >= z->e)
			IDAT(z);
	}
	return n;
}

static Memimage*
memRGBA(Memimage *i)
{
	Memimage *ni;
	char buf[32];
	ulong dst;
	
	/*
	 * [A]BGR because we want R,G,B,[A] in big-endian order.  Sigh.
	 */
	chantostr(buf, i->chan);
	if(strchr(buf, 'a'))
		dst = ABGR32;
	else
		dst = BGR24;
		
	if(i->chan == dst)
		return i;

	qlock(&memdrawlock);
	ni = allocmemimage(i->r, dst);
	if(ni)
		memimagedraw(ni, ni->r, i, i->r.min, nil, i->r.min, S);
	qunlock(&memdrawlock);
	return ni;
}

int
writepng(Hio *io, Memimage *m)
{
	static int first = 1;
	static QLock lk;
	uchar buf[200], *h;
	Memimage *rgb;
	ZlibR zr;
	ZlibW zw;

	if(first){
		qlock(&lk);
		if(first){
			deflateinit();
			crctab = mkcrctab(0xedb88320);
			first = 0;
		}
		qunlock(&lk);
	}

	rgb = memRGBA(m);
	if(rgb == nil)
		return -1;

	hwrite(io, PNGmagic, sizeof PNGmagic);
	
	/* IHDR chunk */
	h = buf;
	put4(h, Dx(m->r)); h += 4;
	put4(h, Dy(m->r)); h += 4;
	*h++ = 8;	/* 8 bits per channel */
	if(rgb->chan == BGR24)
		*h++ = 2;		/* RGB */
	else
		*h++ = 6;		/* RGBA */
	*h++ = 0;	/* compression - deflate */
	*h++ = 0;	/* filter - none */
	*h++ = 0;	/* interlace - none */
	chunk(io, "IHDR", buf, h-buf);

	/* image data */
	zr.dx = Dx(m->r);
	zr.dy = Dy(m->r);
	zr.width = rgb->width * sizeof(ulong);
	zr.data = rgb->data->bdata;
	zr.x = 0;
	zr.y = 0;
	zr.pixwid = chantodepth(rgb->chan)/8;
	zw.io = io;
	zw.buf = vtmalloc(IDATSIZE);
	zw.b = zw.buf;
	zw.e = zw.b + IDATSIZE;
	if(deflatezlib(&zw, zwrite, &zr, zread, 6, 0) < 0){
		free(zw.buf);
		return -1;
	}
	if(zw.b > zw.buf)
		IDAT(&zw);
	free(zw.buf);
	chunk(io, "IEND", nil, 0);

	if(m != rgb){
		qlock(&memdrawlock);
		freememimage(rgb);
		qunlock(&memdrawlock);
	}
	return 0;
}
