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

int		cflag = 0;
int		dflag = 0;
int		eflag = 0;
int		nineflag = 0;
int		threeflag = 0;
int		output = 0;
ulong	outchan = CMAP8;
Image	**allims;
int		which;
int		defaultcolor = 1;

enum{
	Border	= 2,
	Edge		= 5
};

char	*show(int, char*);

Rectangle
imager(void)
{
	Point p1, p2;

	if(allims==nil || allims[0]==nil)
		return screen->r;

	p1 = addpt(divpt(subpt(allims[0]->r.max, allims[0]->r.min), 2), allims[0]->r.min);
	p2 = addpt(divpt(subpt(screen->clipr.max, screen->clipr.min), 2), screen->clipr.min);
	return rectaddpt(allims[0]->r, subpt(p2, p1));
}

void
eresized(int new)
{
	Rectangle r;

	if(new && getwindow(display, Refnone) < 0){
		fprint(2, "gif: can't reattach to window\n");
		exits("resize");
	}
	if(allims==nil || allims[which]==nil)
		return;
	r = imager();
	border(screen, r, -Border, nil, ZP);
	draw(screen, r, allims[which], nil, allims[which]->r.min);
	flushimage(display, 1);
}

void
main(int argc, char *argv[])
{
	int fd, i;
	char *err;

	ARGBEGIN{
	case '3':		/* produce encoded, compressed, three-color bitmap file; no display by default */
		threeflag++;
		/* fall through */
	case 't':		/* produce encoded, compressed, true-color bitmap file; no display by default */
		cflag++;
		dflag++;
		output++;
		defaultcolor = 0;
		outchan = RGB24;
		break;
	case 'c':		/* produce encoded, compressed, bitmap file; no display by default */
		cflag++;
		dflag++;
		output++;
		if(defaultcolor)
			outchan = CMAP8;
		break;
	case 'd':		/* suppress display of image */
		dflag++;
		break;
	case 'e':		/* disable floyd-steinberg error diffusion */
		eflag++;
		break;
	case 'k':		/* force black and white */
		defaultcolor = 0;
		outchan = GREY8;
		break;
	case 'v':		/* force RGBV */
		defaultcolor = 0;
		outchan = CMAP8;
		break;
	case '9':		/* produce plan 9, uncompressed, bitmap file; no display by default */
		nineflag++;
		dflag++;
		output++;
		if(defaultcolor)
			outchan = CMAP8;
		break;
	default:
		fprint(2, "usage: gif -39cdektv  [file.gif ...]\n");
		exits("usage");
	}ARGEND;

	err = nil;
	if(argc == 0)
		err = show(0, "<stdin>");
	else{
		for(i=0; i<argc; i++){
			fd = open(argv[i], OREAD);
			if(fd < 0){
				fprint(2, "gif: can't open %s: %r\n", argv[i]);
				err = "open";
			}else{
				err = show(fd, argv[i]);
				close(fd);
			}
			if(output && argc>1 && err==nil){
				fprint(2, "gif: exiting after one file\n");
				break;
			}
		}
	}
	exits(err);
}

Image*
transparency(Rawimage *r, char *name)
{
	Image *i;
	int j, index;
	uchar *pic, *mpic, *mask;

	if((r->gifflags&TRANSP) == 0)
		return nil;
	i = allocimage(display, r->r, GREY8, 0, 0);
	if(i == nil){
		fprint(2, "gif: allocimage for mask of %s failed: %r\n", name);
		return nil;
	}
	pic = r->chans[0];
	mask = malloc(r->chanlen);
	if(mask == nil){
		fprint(2, "gif: malloc for mask of %s failed: %r\n", name);
		freeimage(i);
		return nil;
	}
	index = r->giftrindex;
	mpic = mask;
	for(j=0; j<r->chanlen; j++)
		if(*pic++ == index)
			*mpic++ = 0;
		else
			*mpic++ = 0xFF;
	if(loadimage(i, i->r, mask, r->chanlen) < 0){
		fprint(2, "gif: loadimage for mask of %s failed: %r\n", name);
		free(mask);
		freeimage(i);
		return nil;
	}
	free(mask);
	return i;
}

/* interleave alpha values of 0xFF in data stream. alpha value comes first, then b g r */
uchar*
expand(uchar *u, int chanlen, int nchan)
{
	int j, k;
	uchar *v, *up, *vp;

	v = malloc(chanlen*(nchan+1));
	if(v == nil){
		fprint(2, "gif: malloc fails: %r\n");
		exits("malloc");
	}
	up = u;
	vp = v;
	for(j=0; j<chanlen; j++){
		*vp++ = 0xFF;
		for(k=0; k<nchan; k++)
			*vp++ = *up++;
	}
	return v;
}

void
addalpha(Rawimage *i)
{
	char buf[32];

	switch(outchan){
	case CMAP8:
		i->chans[0] = expand(i->chans[0], i->chanlen/1, 1);
		i->chanlen = 2*(i->chanlen/1);
		i->chandesc = CRGBVA16;
		outchan = CHAN2(CMap, 8, CAlpha, 8);
		break;

	case GREY8:
		i->chans[0] = expand(i->chans[0], i->chanlen/1, 1);
		i->chanlen = 2*(i->chanlen/1);
		i->chandesc = CYA16;
		outchan = CHAN2(CGrey, 8, CAlpha, 8);
		break;

	case RGB24:
		i->chans[0] = expand(i->chans[0], i->chanlen/3, 3);
		i->chanlen = 4*(i->chanlen/3);
		i->chandesc = CRGBA32;
		outchan = RGBA32;
		break;

	default:
		chantostr(buf, outchan);
		fprint(2, "gif: can't add alpha to type %s\n", buf);
		exits("err");
	}
}

/*
 * Called only when writing output.  If the output is RGBA32,
 * we must write four bytes per pixel instead of two.
 * There's always at least two: data plus alpha.
 * r is used only for reference; the image is already in c.
 */
void
blackout(Rawimage *r, Rawimage *c)
{
	int i, trindex;
	uchar *rp, *cp;

	rp = r->chans[0];
	cp = c->chans[0];
	trindex = r->giftrindex;
	if(outchan == RGBA32)
		for(i=0; i<r->chanlen; i++){
			if(*rp == trindex){
				*cp++ = 0x00;
				*cp++ = 0x00;
				*cp++ = 0x00;
				*cp++ = 0x00;
			}else{
				*cp++ = 0xFF;
				cp += 3;
			}
			rp++;
		}
	else
		for(i=0; i<r->chanlen; i++){
			if(*rp == trindex){
				*cp++ = 0x00;
				*cp++ = 0x00;
			}else{
				*cp++ = 0xFF;
				cp++;
			}
			rp++;
		}
}

int
init(void)
{
	static int inited;

	if(inited == 0){
		if(initdraw(0, 0, 0) < 0){
			fprint(2, "gif: initdraw failed: %r\n");
			return -1;
		}
		einit(Ekeyboard|Emouse);
		inited++;
	}
	return 1;
}

char*
show(int fd, char *name)
{
	Rawimage **images, **rgbv;
	Image *tmp, *msk, *img, *dst, **ims;
	Rectangle r;
	int j, k, n, ch, nloop, loopcount, dt;
	char *err;
	char buf[32];

	err = nil;
	images = readgif(fd, CRGB, dflag);
	if(images == nil){
		fprint(2, "gif: decode %s failed: %r\n", name);
		return "decode";
	}
	for(n=0; images[n]; n++)
		if(n == 0)
			r = images[n]->r;
		else
			combinerect(&r, images[n]->r);
	tmp = nil;
	ims = malloc((n+1)*sizeof(Image*));
	rgbv = malloc((n+1)*sizeof(Rawimage*));
	if(rgbv==nil || ims==nil){
		fprint(2, "gif: malloc of masks for %s failed: %r\n", name);
		err = "malloc";
		goto Return;
	}
	memset(ims, 0, (n+1)*sizeof(Image*));
	memset(rgbv, 0, (n+1)*sizeof(Rawimage*));
	if(!dflag){
		if(init() < 0){
			err = "initdraw";
			goto Return;
		}
		if(defaultcolor && screen->depth>8)
			outchan = RGB24;
	}

	for(k=0; k<n; k++){
		if(outchan == CMAP8)
			rgbv[k] = torgbv(images[k], !eflag);
		else{
			if(outchan==GREY8 || (images[k]->chandesc==CY && threeflag==0))
				rgbv[k] = totruecolor(images[k], CY);
			else
				rgbv[k] = totruecolor(images[k], CRGB24);
		}
		if(rgbv[k] == nil){
			fprint(2, "gif: converting %s to local format failed: %r\n", name);
			err = "torgbv";
			goto Return;
		}
		if(!dflag){
			msk = transparency(images[k], name);
			if(rgbv[k]->chandesc == CY)
				img = allocimage(display, rgbv[k]->r, GREY8, 0, 0);
			else
				img = allocimage(display, rgbv[k]->r, outchan, 0, 0);
			if(tmp == nil)
				tmp = allocimage(display, r, img->chan, 0, DWhite);
			ims[k]= dst = allocimage(display, r, tmp->chan, 0, DWhite);
			if(tmp == nil || img == nil || dst == nil){
				fprint(2, "gif: allocimage %s failed: %r\n", name);
				err = "allocimage";
				goto Return;
			}
			if(loadimage(img, img->r, rgbv[k]->chans[0], rgbv[k]->chanlen) < 0){
				fprint(2, "gif: loadimage %s failed: %r\n", name);
				err = "loadimage";
				goto Return;
			}
			switch((images[k]->gifflags>>2)&7){
			default:
				draw(tmp, img->r, img, msk, img->r.min);
				draw(dst, tmp->r, tmp, nil, tmp->r.min);
				break;
			case 2:
				draw(tmp, img->r, display->white, msk, img->r.min);
				/* no break */
			case 3:
				draw(dst, tmp->r, tmp, nil, tmp->r.min);
				draw(dst, img->r, img, msk, img->r.min);
				break;
			}
			freeimage(msk);
			freeimage(img);
		}
	}
	if(tmp)
		freeimage(tmp);

	allims = ims;
	loopcount = images[0]->gifloopcount;
	if(!dflag){
		nloop = 0;
		do{
			for(k=0; k<n; k++){
				which = k;
				eresized(0);
				dt = images[k]->gifdelay*10;
				if(dt < 50)
					dt = 50;
				while(n==1 || ecankbd()){
					/* an odd, democratic list */
					if((ch=ekbd())=='q' || ch==Kdel || ch==Keof)
						exits(nil);
					if(ch == '\n')
						goto Out;
				}sleep(dt);
			}
			/* loopcount -1 means no loop (this code's rule), loopcount 0 means loop forever (netscape's rule)*/
		}while(loopcount==0 || ++nloop<loopcount);
		/* loop count has run out */
		ekbd();
    Out:
		draw(screen, screen->clipr, display->white, nil, ZP);
	}
	if(nineflag){
		if(images[0]->gifflags&TRANSP){
			addalpha(rgbv[0]);
			blackout(images[0], rgbv[0]);
		}
		chantostr(buf, outchan);
		print("%11s %11d %11d %11d %11d ", buf,
			rgbv[0]->r.min.x, rgbv[0]->r.min.y, rgbv[0]->r.max.x, rgbv[0]->r.max.y);
		if(write(1, rgbv[0]->chans[0], rgbv[0]->chanlen) != rgbv[0]->chanlen){
			fprint(2, "gif: %s: write error %r\n", name);
			return "write";
		}
	}else if(cflag){
		if(images[0]->gifflags&TRANSP){
			addalpha(rgbv[0]);
			blackout(images[0], rgbv[0]);
		}
		if(writerawimage(1, rgbv[0]) < 0){
			fprint(2, "gif: %s: write error: %r\n", name);
			return "write";
		}
	}

    Return:
	allims = nil;
	for(k=0; images[k]; k++){
		for(j=0; j<images[k]->nchans; j++)
			free(images[k]->chans[j]);
		free(images[k]->cmap);
		if(rgbv[k])
			free(rgbv[k]->chans[0]);
		freeimage(ims[k]);
		free(images[k]);
		free(rgbv[k]);
	}
	free(images);
	free(ims);
	return err;
}
