#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>
#include "imagefile.h"
#include "bmp.h"

/*
 MS-BMP file reader
 (c) 2003, I.P.Keller

 aims to decode *all* valid bitmap formats, although some of the
 flavours couldn't be verified due to lack of suitable test-files.
 the following flavours are supported:

	Bit/Pix	Orientation	Compression	Tested?
	  1	top->bottom	n/a		yes
	  1	bottom->top	n/a		yes
	  4	top->bottom	no		yes
	  4	bottom->top	no		yes
	  4	top->bottom	RLE4		yes, but not with displacement
	  8	top->bottom	no		yes
	  8	bottom->top	no		yes
	  8	top->bottom	RLE8		yes, but not with displacement
	 16	top->bottom	no		no
	 16	bottom->top	no		no
	 16	top->bottom	BITMASK		no
	 16	bottom->top	BITMASK		no
	 24	top->bottom	n/a		yes
	 24	bottom->top	n/a		yes
	 32	top->bottom	no		no
	 32	bottom->top	no		no
	 32	top->bottom	BITMASK		no
	 32	bottom->top	BITMASK		no

 OS/2 1.x bmp files are recognised as well, but testing was very limited.

 verifying was done with a number of test files, generated by
 different tools. nevertheless, the tests were in no way exhaustive
 enough to guarantee bug-free decoding. caveat emptor!
*/

static short
r16(Biobuf*b)
{
	short s;

	s = Bgetc(b);
	s |= ((short)Bgetc(b)) << 8;
	return s;
}


static long
r32(Biobuf*b)
{
	long l;

	l = Bgetc(b);
	l |= ((long)Bgetc(b)) << 8;
	l |= ((long)Bgetc(b)) << 16;
	l |= ((long)Bgetc(b)) << 24;
	return l;
}


/* get highest bit set */
static int
msb(ulong x)
{
	int i;
	for(i = 32; i; i--, x <<= 1)
		if(x & 0x80000000L)
			return i;
	return 0;
}

/* Load a 1-Bit encoded BMP file (uncompressed) */
static int
load_1T(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
{
	long ix, iy, i = 0, step_up = 0, padded_width = ((width + 31) / 32) * 32;
	int val = 0, n;

	if(height > 0) {	/* bottom-up */
		i = (height - 1) * width;
		step_up = -2 * width;
	} else
		height = -height;

	for(iy = height; iy; iy--, i += step_up)
		for(ix = 0, n = 0; ix < padded_width; ix++, n--) {
			if(!n) {
				val = Bgetc(b);
				n = 8;
			}
			if(ix < width) {
				buf[i] = clut[val & 0x80 ? 1 : 0];
				i++;
			}
			val <<= 1;
		}
	return 0;
}

/* Load a 4-Bit encoded BMP file (uncompressed) */
static int
load_4T(Biobuf* b, long width, long height, Rgb* buf, Rgb* clut)
{
	long ix, iy, i = 0, step_up = 0, skip = (4 - (((width % 8) + 1) / 2)) & 3;
	uint valH, valL;

	if(height > 0) {	/* bottom-up */
		i = (height - 1) * width;
		step_up = -2 * width;
	} else
		height = -height;

	for(iy = height; iy; iy--, i += step_up) {
		for(ix = 0; ix < width; ) {
			valH = valL = Bgetc(b) & 0xff;
			valH >>= 4;

			buf[i] = clut[valH];
			i++; ix++;

			if(ix < width) {
				valL &= 0xf;
				buf[i] = clut[valL];
				i++; ix++;
			}
		}
		Bseek(b, skip, 1);
	}
	return 0;
}

/* Load a 4-Bit encoded BMP file (RLE4-compressed) */
static int
load_4C(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
{
	long ix, iy = height -1;
	uint val, valS, skip;
	Rgb* p;

	while(iy >= 0) {
		ix = 0;
		while(ix < width) {
			val = (uint)Bgetc(b);

			if(0 != val) {
				valS = (uint)Bgetc(b);
				p = &buf[ix + iy * width];
				while(val--) {
					*p = clut[0xf & (valS >> 4)];
					p++;
					ix++;
					if(val != 0) {
						*p = clut[0xf & valS];
						p++;
						ix++;
						val--;
					}
				}
			} else {
				/* Special modes... */
				val = Bgetc(b);
				switch(val) {
					case 0:	/* End-Of-Line detected */
						ix = width;
						iy--;
						break;
					case 1:	/* End-Of-Picture detected -->> abort */
						ix = width;
						iy = -1;
						break;
					case 2:	/* Position change detected */
						val = (uint)Bgetc(b);
						ix += val;
						val = (uint)Bgetc(b);
						iy -= val;
						break;

					default:/* Transparent data sequence detected */
						p = &buf[ix + iy * width];
						if((1 == (val & 3)) || (2 == (val & 3)))
							skip = 1;
						else 
							skip = 0;

						while(val--) {
							valS = (uint)Bgetc(b);
							*p = clut[0xf & (valS >> 4)];
							p++;
							ix++;
							if(val != 0) {
								*p = clut[0xf & valS];
								p++;
								ix++;
								val--;
							}
						}
						if(skip)
							Bgetc(b);
						break;
				}
			}
		}
	}
	return 0;
}

/* Load a 8-Bit encoded BMP file (uncompressed) */
static int
load_8T(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
{
	long ix, iy, i = 0, step_up = 0, skip = (4 - (width % 4)) & 3;

	if(height > 0) {	/* bottom-up */
		i = (height - 1) * width;
		step_up = -2 * width;
	} else
		height = -height;

	for(iy = height; iy; iy--, i += step_up) {
		for(ix = 0; ix < width; ix++, i++)
			buf[i] = clut[Bgetc(b) & 0xff];
		Bseek(b, skip, 1);
	}
	return 0;
}

/* Load a 8-Bit encoded BMP file (RLE8-compressed) */
static int
load_8C(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
{
	long ix, iy = height -1;
	int val, valS, skip;
	Rgb* p;

	while(iy >= 0) {
		ix = 0;
		while(ix < width) {
			val = Bgetc(b);

			if(0 != val) {
				valS = Bgetc(b);
				p = &buf[ix + iy * width];
				while(val--) {
					*p = clut[valS];
					p++;
					ix++;
				}
			} else {
				/* Special modes... */
				val = Bgetc(b);
				switch(val) {
					case 0: /* End-Of-Line detected */
						ix = width;
						iy--;
						break;
					case 1: /* End-Of-Picture detected */
						ix = width;
						iy = -1;
						break;
					case 2: /* Position change detected */
						val = Bgetc(b);
						ix += val;
						val = Bgetc(b);
						iy -= val;
						break;
					default: /* Transparent (not compressed) sequence detected */
						p = &buf[ix + iy * width];
						if(val & 1)
							skip = 1;
						else 
							skip = 0;

						while(val--) {
							valS = Bgetc(b);
							*p = clut[valS];
							p++;
							ix++;
						}
						if(skip)
							/* Align data stream */
							Bgetc(b);
						break;
				}
			}
		}
	}
	return 0;
}

/* Load a 16-Bit encoded BMP file (uncompressed) */
static int
load_16(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
{
	uchar c[2];
	long ix, iy, i = 0, step_up = 0;

	if(height > 0) {	/* bottom-up */
		i = (height - 1) * width;
		step_up = -2 * width;
	} else
		height = -height;

	if(clut) {
		unsigned mask_blue =  (unsigned)clut[0].blue +
		                     ((unsigned)clut[0].green << 8);
		unsigned mask_green =  (unsigned)clut[1].blue +
		                      ((unsigned)clut[1].green << 8);
		unsigned mask_red =  (unsigned)clut[2].blue +
		                    ((unsigned)clut[2].green << 8);
		int shft_blue = msb((ulong)mask_blue) - 8;
		int shft_green = msb((ulong)mask_green) - 8;
		int shft_red = msb((ulong)mask_red) - 8;

		for(iy = height; iy; iy--, i += step_up)
			for(ix = 0; ix < width; ix++, i++) {
				unsigned val;
				Bread(b, c, sizeof(c));
				val = (unsigned)c[0] + ((unsigned)c[1] << 8);

				buf[i].alpha = 0;
				if(shft_blue >= 0)
					buf[i].blue = (uchar)((val & mask_blue) >> shft_blue);
				else
					buf[i].blue = (uchar)((val & mask_blue) << -shft_blue);
				if(shft_green >= 0)
					buf[i].green = (uchar)((val & mask_green) >> shft_green);
				else
					buf[i].green = (uchar)((val & mask_green) << -shft_green);
				if(shft_red >= 0)
					buf[i].red = (uchar)((val & mask_red) >> shft_red);
				else
					buf[i].red = (uchar)((val & mask_red) << -shft_red);
			}
	} else
		for(iy = height; iy; iy--, i += step_up)
			for(ix = 0; ix < width; ix++, i++) {
				Bread(b, c, sizeof(c));
				buf[i].blue = (uchar)((c[0] << 3) & 0xf8);
				buf[i].green = (uchar)(((((unsigned)c[1] << 6) +
				                        (((unsigned)c[0]) >> 2))) & 0xf8);
				buf[i].red = (uchar)((c[1] << 1) & 0xf8);
			}
	return 0;
}

/* Load a 24-Bit encoded BMP file (uncompressed) */
static int
load_24T(Biobuf* b, long width, long height, Rgb* buf)
{
	long ix, iy, i = 0, step_up = 0, skip = (4 - ((width * 3) % 4)) & 3;

	if(height > 0) {	/* bottom-up */
		i = (height - 1) * width;
		step_up = -2 * width;
	} else
		height = -height;

	for(iy = height; iy; iy--, i += step_up) {
		for(ix = 0; ix < width; ix++, i++) {
			buf[i].alpha = 0;
			buf[i].blue = Bgetc(b);
			buf[i].green = Bgetc(b);
			buf[i].red = Bgetc(b);
		}
		Bseek(b, skip, 1);
	}
	return 0;
}

/* Load a 32-Bit encoded BMP file (uncompressed) */
static int
load_32(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
{
	uchar c[4];
	long ix, iy, i = 0, step_up = 0;

	if(height > 0) {	/* bottom-up */
		i = (height - 1) * width;
		step_up = -2 * width;
	} else
		height = -height;

	if(clut) {
		ulong mask_blue =  (ulong)clut[0].blue +
		                          ((ulong)clut[0].green << 8) +
		                          ((ulong)clut[0].red << 16) +
		                          ((ulong)clut[0].alpha << 24);
		ulong mask_green =  (ulong)clut[1].blue +
		                           ((ulong)clut[1].green << 8) +
		                           ((ulong)clut[1].red << 16) +
		                           ((ulong)clut[1].alpha << 24);
		ulong mask_red =  (ulong)clut[2].blue +
		                         ((ulong)clut[2].green << 8) +
		                         ((ulong)clut[2].red << 16) +
		                         ((ulong)clut[2].alpha << 24);
		int shft_blue = msb(mask_blue) - 8;
		int shft_green = msb(mask_green) - 8;
		int shft_red = msb(mask_red) - 8;

		for(iy = height; iy; iy--, i += step_up)
			for(ix = 0; ix < width; ix++, i++) {
				ulong val;
				Bread(b, c, sizeof(c));
				val =  (ulong)c[0] + ((ulong)c[1] << 8) +
				      ((ulong)c[2] << 16) + ((ulong)c[1] << 24);

				buf[i].alpha = 0;
				if(shft_blue >= 0)
					buf[i].blue = (uchar)((val & mask_blue) >> shft_blue);
				else
					buf[i].blue = (uchar)((val & mask_blue) << -shft_blue);
				if(shft_green >= 0)
					buf[i].green = (uchar)((val & mask_green) >> shft_green);
				else
					buf[i].green = (uchar)((val & mask_green) << -shft_green);
				if(shft_red >= 0)
					buf[i].red = (uchar)((val & mask_red) >> shft_red);
				else
					buf[i].red = (uchar)((val & mask_red) << -shft_red);
			}
	} else
		for(iy = height; iy; iy--, i += step_up)
			for(ix = 0; ix < width; ix++, i++) {
				Bread(b, c, nelem(c));
				buf[i].blue = c[0];
				buf[i].green = c[1];
				buf[i].red = c[2];
			}
	return 0;
}


static Rgb*
ReadBMP(Biobuf *b, int *width, int *height)
{
	int colours, num_coltab = 0;
	Filehdr bmfh;
	Infohdr bmih;
	Rgb clut[256];
	Rgb* buf;

	bmfh.type = r16(b);
	if(bmfh.type != 0x4d42) 	/* signature must be 'BM' */
		sysfatal("bad magic number, not a BMP file");

	bmfh.size = r32(b);
	bmfh.reserved1 = r16(b);
	bmfh.reserved2 = r16(b);
	bmfh.offbits = r32(b);

	memset(&bmih, 0, sizeof(bmih));
	bmih.size = r32(b);

	if(bmih.size == 0x0c) {			/* OS/2 1.x version */
		bmih.width = r16(b);
		bmih.height = r16(b);
		bmih.planes = r16(b);
		bmih.bpp = r16(b);
		bmih.compression = BMP_RGB;
	} else {				/* Windows */
		bmih.width = r32(b);
		bmih.height = r32(b);
		bmih.planes = r16(b);
		bmih.bpp = r16(b);
		bmih.compression = r32(b);
		bmih.imagesize = r32(b);
		bmih.hres = r32(b);
		bmih.vres = r32(b);
		bmih.colours = r32(b);
		bmih.impcolours = r32(b);
	}

	if(bmih.bpp < 16) {
		/* load colour table */
		if(bmih.impcolours)
			num_coltab = (int)bmih.impcolours;
		else
			num_coltab = 1 << bmih.bpp;
	} else if(bmih.compression == BMP_BITFIELDS &&
	          (bmih.bpp == 16 || bmih.bpp == 32))
		/* load bitmasks */
		num_coltab = 3;

	if(num_coltab) {
		int i; 
		Bseek(b, bmih.size + Filehdrsz, 0);

		for(i = 0; i < num_coltab; i++) {
			clut[i].blue  = (uchar)Bgetc(b);
			clut[i].green = (uchar)Bgetc(b);
			clut[i].red   = (uchar)Bgetc(b);
			clut[i].alpha = (uchar)Bgetc(b);
		}
	}

	*width = bmih.width;
	*height = bmih.height;
	colours = bmih.bpp;

	Bseek(b, bmfh.offbits, 0);

	if ((buf = calloc(sizeof(Rgb), *width * abs(*height))) == nil)
		sysfatal("no memory");

	switch(colours) {
		case 1:
			load_1T(b, *width, *height, buf, clut);
			break;
		case 4:
			if(bmih.compression == BMP_RLE4)
				load_4C(b, *width, *height, buf, clut);
			else
				load_4T(b, *width, *height, buf, clut);
			break;
		case 8:
			if(bmih.compression == BMP_RLE8)
				load_8C(b, *width, *height, buf, clut);
			else
				load_8T(b, *width, *height, buf, clut);
			break;
		case 16:
			load_16(b, *width, *height, buf,
			        bmih.compression == BMP_BITFIELDS ? clut : nil);
			break;
		case 24:
			load_24T(b, *width, *height, buf);
			break;
		case 32:
			load_32(b, *width, *height, buf,
			        bmih.compression == BMP_BITFIELDS ? clut : nil);
			break;
	}
	return buf;
}

Rawimage**
Breadbmp(Biobuf *bp, int colourspace)
{
	Rawimage *a, **array;
	int c, width, height;
	uchar *r, *g, *b;
	Rgb *s, *e;
	Rgb *bmp;
	char ebuf[128];

	a = nil;
	bmp = nil;
	array = nil;
	USED(a);
	USED(bmp);
	if (colourspace != CRGB) {
		errstr(ebuf, sizeof ebuf);	/* throw it away */
		werrstr("ReadRGB: unknown colour space %d", colourspace);
		return nil;
	}

	if ((bmp = ReadBMP(bp, &width, &height)) == nil)
		return nil;

	if ((a = calloc(sizeof(Rawimage), 1)) == nil)
		goto Error;

	for (c = 0; c  < 3; c++)
		if ((a->chans[c] = calloc(width, height)) == nil)
			goto Error;

	if ((array = calloc(sizeof(Rawimage *), 2)) == nil)
		goto Error;
	array[0] = a;
	array[1] = nil;

	a->nchans = 3;
	a->chandesc = CRGB;
	a->chanlen = width * height;
	a->r = Rect(0, 0, width, height);

	s = bmp;
	e = s + width * height;
	r = a->chans[0];
	g = a->chans[1];
	b = a->chans[2];

	do {
		*r++ = s->red;
		*g++ = s->green;
		*b++ = s->blue;
	}while(++s < e);

	free(bmp);
	return array;

Error:
	if (a)
		for (c = 0; c < 3; c++)
			if (a->chans[c])
				free(a->chans[c]);
	if (a)
		free(a);
	if (array)
		free(array);
	if (bmp)
		free(bmp);
	return nil;

}

Rawimage**
readbmp(int fd, int colorspace)
{
	Rawimage * *a;
	Biobuf b;

	if (Binit(&b, fd, OREAD) < 0)
		return nil;
	a = Breadbmp(&b, colorspace);
	Bterm(&b);
	return a;
}


