/*
 * TGA is a fairly dead standard, however in the video industry
 * it is still used a little for test patterns and the like.
 *
 * Thus we ignore any alpha channels.
 */

#include <u.h>
#include <lib9.h>
#include <bio.h>
#include <draw.h>
#include <ctype.h>
#include "imagefile.h"

enum {
	HdrLen = 18,
};

typedef struct {
	int idlen;			/* length of string after header */
	int cmaptype;		/* 1 => datatype = 1 => colourmapped */
	int datatype;		/* see below */
	int cmaporigin;		/* index of first entry in colour map */
	int cmaplen;		/* length of colour map */
	int cmapbpp;		/* bits per pixel of colour map: 16, 24, or 32 */
	int xorigin;		/* source image origin */
	int yorigin;
	int width;
	int height;
	int bpp;			/* bits per pixel of image: 16, 24, or 32 */
	int descriptor;
	unsigned char *cmap;		/* colour map (optional) */
} Tga;

/*
 * descriptor:
 * d0-3 = number of attribute bits per pixel
 * d4 	= reserved, always zero
 * d6-7	= origin: 0=lower left, 1=upper left, 2=lower right, 3=upper right
 * d8-9 = interleave: 0=progressive, 1=2 way, 3=4 way, 4=reserved.
 */

char *datatype[] = {
	[0]		"No image data",
	[1]		"Color-mapped",
	[2]		"RGB",
	[3]		"B&W",
	[9]		"RLE color-mapped",
	[10]	"RLE RGB",
	[11]	"RLE B&W",
	[32]	"Compressed color",
	[33]	"Quadtree compressed color",
};

static int
Bgeti(Biobuf *bp)
{
	int x, y;

	if((x = Bgetc(bp)) < 0)
		return -1;
	if((y = Bgetc(bp)) < 0)
		return -1;
	return (y<<8)|x;
}

static int
fixcmap(unsigned char *cmap, int *cmapbpp, int cmaplen)
{
	int i;
	unsigned short x;
	unsigned char tmp;

	switch(*cmapbpp){
	case 32:
		/* swap B with R */
		for(i = 0; i < cmaplen; i++){
			tmp = cmap[4*i+0];
			cmap[4*i+0] = cmap[4*i+2];
			cmap[4*i+2] = tmp;
		}
		break;
	case 24:
		/* swap B with R */
		for(i = 0; i < cmaplen; i++){
			tmp = cmap[3*i+0];
			cmap[3*i+0] = cmap[3*i+2];
			cmap[3*i+2] = tmp;
		}
		break;
	case 16:
	case 15:
		/* convert to 24-bit colormap */
		if((cmap = realloc(cmap, 3*cmaplen)) == nil)
			return -1;
		for(i = cmaplen-1; i >= 0; i--){
			x = (cmap[2*i+1]<<8) | cmap[2*i+0];
			tmp = (x>>0)&0x1f;
			cmap[3*i+2] = (tmp<<3) | (tmp>>2);
			tmp = (x>>5)&0x1f;
			cmap[3*i+1] = (tmp<<3) | (tmp>>2);
			tmp = (x>>10)&0x1f;
			cmap[3*i+0] = (tmp<<3) | (tmp>>2);
		}
		*cmapbpp = 24;
		break;
	default:
		break;
	}

	return 0;
}

static Tga *
rdhdr(Biobuf *bp)
{
	int n;
	Tga *h;

	if((h = malloc(sizeof(Tga))) == nil)
		return nil;
	if((h->idlen = Bgetc(bp)) == -1)
		return nil;
	if((h->cmaptype = Bgetc(bp)) == -1)
		return nil;
	if((h->datatype = Bgetc(bp)) == -1)
		return nil;
	if((h->cmaporigin = Bgeti(bp)) == -1)
		return nil;
	if((h->cmaplen = Bgeti(bp)) == -1)
		return nil;
	if((h->cmapbpp = Bgetc(bp)) == -1)
		return nil;
	if((h->xorigin = Bgeti(bp)) == -1)
		return nil;
	if((h->yorigin = Bgeti(bp)) == -1)
		return nil;
	if((h->width = Bgeti(bp)) == -1)
		return nil;
	if((h->height = Bgeti(bp)) == -1)
		return nil;
	if((h->bpp = Bgetc(bp)) == -1)
		return nil;
	if((h->descriptor = Bgetc(bp)) == -1)
		return nil;

	/* skip over ID, usually empty anyway */
	if(Bseek(bp, h->idlen, 1) < 0){
		free(h);
		return nil;
	}

	if(h->cmaptype == 0){
		h->cmap = 0;
		return h;
	}

	/* skip over unused color map data */
	n = (h->cmapbpp/8)*h->cmaporigin;
	if(Bseek(bp, n, 1) < 0){
		free(h);
		return nil;
	}
	h->cmaplen -= h->cmaporigin;

	n = (h->cmapbpp/8)*h->cmaplen;
	if((h->cmap = malloc(n)) == nil){
		free(h);
		return nil;
	}
	if(Bread(bp, h->cmap, n) != n){
		free(h);
		free(h->cmap);
		return nil;
	}
	if(fixcmap(h->cmap, &h->cmapbpp, h->cmaplen) != 0){
		free(h);
		free(h->cmap);
		return nil;
	}
	return h;
}

static int
cmap(Biobuf *bp, unsigned char *l, int num)
{
	return Bread(bp, l, num);
}

static int
luma(Biobuf *bp, int bpp, unsigned char *l, int num)
{
	char tmp[2];
	int got;

	if(bpp == 8){
		got = Bread(bp, l, num);
	}
	else{
		for(got = 0; got < num; got++){
			if(Bread(bp, tmp, 2) != 2)
				break;
			*l++ = tmp[0];
		}
	}
	return got;
}

static int
luma_rle(Biobuf *bp, int bpp, unsigned char *l, int num)
{
	unsigned char len, p;
	int got;

	for(got = 0; got < num;){
		if(Bread(bp, &len, 1) != 1)
			break;
		if(len & 0x80){
			len &= 0x7f;
			if(luma(bp, bpp, &p, 1) != 1)
				break;
			for(len++; len > 0 && got < num; len--, got++)
				*l++ = p;
		}
		else{
			for(len++; len > 0 && got < num; len--, got++)
				if(luma(bp, bpp, l++, 1) != 1)
					return got;
		}
	}
	return got;
}

static int
cmap_rle(Biobuf *bp, unsigned char *l, int num)
{
	return luma_rle(bp, 8, l, num);
}

static int
rgba(Biobuf *bp, int bpp, unsigned char *r, unsigned char *g, unsigned char *b, int num)
{
	int i;
	unsigned char buf[4], tmp;
	unsigned short x;

	switch(bpp){
	case 16:
	case 15:
		for(i = 0; i < num; i++){
			if(Bread(bp, buf, 2) != 2)
				break;
			x = (buf[1]<<8) | buf[0];
			tmp = (x>>0)&0x1f;
			*b++ = (tmp<<3) | (tmp>>2);
			tmp = (x>>5)&0x1f;
			*g++ = (tmp<<3) | (tmp>>2);
			tmp = (x>>10)&0x1f;
			*r++ = (tmp<<3) | (tmp>>2);
		}
		break;
	case 24:
		for(i = 0; i < num; i++){
			if(Bread(bp, buf, 3) != 3)
				break;
			*b++ = buf[0];
			*g++ = buf[1];
			*r++ = buf[2];
		}
		break;
	case 32:
		for(i = 0; i < num; i++){
			if(Bread(bp, buf, 4) != 4)
				break;
			*b++ = buf[0];
			*g++ = buf[1];
			*r++ = buf[2];
		}
		break;
	default:
		i = 0;
		break;
	}
	return i;
}

static int
rgba_rle(Biobuf *bp, int bpp, unsigned char *r, unsigned char *g, unsigned char *b, int num)
{
	unsigned char len;
	int i, got;

	for(got = 0; got < num; got += len){
		if(Bread(bp, &len, 1) != 1)
			break;
		if(len & 0x80){
			len &= 0x7f;
			len += 1;	/* run of zero is meaningless */
			if(rgba(bp, bpp, r, g, b, 1) != 1)
				break;
			for(i = 1; i < len && got+i < num; i++){
				r[i] = *r;
				g[i] = *g;
				b[i] = *b;
			}
			len = i;
		}
		else{
			len += 1;	/* raw block of zero is meaningless */
			if(rgba(bp, bpp, r, g, b, len) != len)
				break;
		}
		r += len;
		g += len;
		b += len;
	}
	return got;
}

int
flip(Rawimage *ar)
{
	int w, h, c, l;
	unsigned char *t, *s, *d;

	w = Dx(ar->r);
	h = Dy(ar->r);
	if((t = malloc(w)) == nil){
		werrstr("ReadTGA: no memory - %r\n");
		return -1;
	}

	for(c = 0; c < ar->nchans; c++){
		s = ar->chans[c];
		d = ar->chans[c] + ar->chanlen - w;
		for(l = 0; l < (h/2); l++){
			memcpy(t, s, w);
			memcpy(s, d, w);
			memcpy(d, t, w);
			s += w;
			d -= w;
		}
	}
	free(t);
	return 0;
}

int
reflect(Rawimage *ar)
{
	int w, h, c, l, p;
	unsigned char t, *sol, *eol, *s, *d;

	w = Dx(ar->r);
	h = Dy(ar->r);

	for(c = 0; c < ar->nchans; c++){
		sol = ar->chans[c];
		eol = ar->chans[c] +w -1;
		for(l = 0; l < h; l++){
			s = sol;
			d = eol;
			for(p = 0; p < w/2; p++){
				t = *s;
				*s = *d;
				*d = t;
				s++;
				d--;
			}
			sol += w;
			eol += w;
		}
	}
	return 0;
}


Rawimage**
Breadtga(Biobuf *bp)
{
	Tga *h;
	int n, c, num;
	unsigned char *r, *g, *b;
	Rawimage *ar, **array;

	if((h = rdhdr(bp)) == nil){
		werrstr("ReadTGA: bad header %r");
		return nil;
	}

	if(0){
		fprint(2, "idlen=%d\n", h->idlen);
		fprint(2, "cmaptype=%d\n", h->cmaptype);
		fprint(2, "datatype=%s\n", datatype[h->datatype]);
		fprint(2, "cmaporigin=%d\n", h->cmaporigin);
		fprint(2, "cmaplen=%d\n", h->cmaplen);
		fprint(2, "cmapbpp=%d\n", h->cmapbpp);
		fprint(2, "xorigin=%d\n", h->xorigin);
		fprint(2, "yorigin=%d\n", h->yorigin);
		fprint(2, "width=%d\n", h->width);
		fprint(2, "height=%d\n", h->height);
		fprint(2, "bpp=%d\n", h->bpp);
		fprint(2, "descriptor=%d\n", h->descriptor);
	}

	array = nil;
	if((ar = calloc(1, sizeof(Rawimage))) == nil){
		werrstr("ReadTGA: no memory - %r\n");
		goto Error;
	}

	if((array = calloc(2, sizeof(Rawimage *))) == nil){
		werrstr("ReadTGA: no memory - %r\n");
		goto Error;
	}
	array[0] = ar;
	array[1] = nil;

	if(h->datatype == 3 || h->datatype == 11){
		ar->nchans = 1;
		ar->chandesc = CY;
	}
	else if(h->datatype == 1){
		ar->nchans = 1;
		ar->chandesc = CRGB1;
	}
	else if(h->datatype == 9){
		ar->nchans = 1;
		ar->chandesc = (h->cmapbpp == 32) ? CRGBV : CRGB1;
	}
	else{
		ar->nchans = 3;
		ar->chandesc = CRGB;
	}

	ar->cmap = h->cmap;
	ar->cmaplen = (h->cmapbpp/8)*h->cmaplen;
	ar->chanlen = h->width*h->height;
	ar->r = Rect(0, 0, h->width, h->height);
	for(c = 0; c < ar->nchans; c++)
		if((ar->chans[c] = malloc(h->width*h->height)) == nil){
			werrstr("ReadTGA: no memory - %r\n");
			goto Error;
		}
	r = ar->chans[0];
	g = ar->chans[1];
	b = ar->chans[2];

	num = h->width*h->height;
	switch(h->datatype){
	case 1:
		n = cmap(bp, r, num);
		break;
	case 2:
		n = rgba(bp, h->bpp, r, g, b, num);
		break;
	case 3:
		n = luma(bp, h->bpp, r, num);
		break;
	case 9:
		n = cmap_rle(bp, r, num);
		break;
	case 10:
		n = rgba_rle(bp, h->bpp, r, g, b, num);
		break;
	case 11:
		n = luma_rle(bp, h->bpp, r, num);
		break;
	default:
		werrstr("ReadTGA: type=%d (%s) unsupported\n", h->datatype, datatype[h->datatype]);
		goto Error;
 	}

	if(n != num){
		werrstr("ReadTGA: decode fail (%d!=%d) - %r\n", n, num);
		goto Error;
	}
	if((h->descriptor&(1<<4)) != 0)
		reflect(ar);
	if((h->descriptor&(1<<5)) == 0)
		flip(ar);

	free(h);
	return array;
Error:

	if(ar)
		for (c = 0; c < ar->nchans; c++)
			free(ar->chans[c]);
	free(ar);
	free(array);
	free(h->cmap);
	free(h);
	return nil;
}

Rawimage**
readtga(int fd)
{
	Rawimage * *a;
	Biobuf b;

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


