/* wasp - Copyright 1991 by Steven Reiz
 * see COPYING and wasp.c for further info
 * readgif.c, 9/12/90 - 30/1/91,
 * 30/4/91 - 2/6/91, 23/6/91, 30/6/91,
 * 8/7/91, 8/12/91, 27/12/91 - 29/12/91
 */

static char *sourcefile=__FILE__;

#include "wasp.h"

static u_short width, height;
static short bitsperpixel, interlaced, codesize;
static u_short *cm=NULL;
static u_short *rgboverflowrow=NULL;
static u_short *gpp;


read_gif(void)
{
    char signature[6], separator;
    short y;

    cread(signature, 6);
    if (strncmp(signature, "GIF87a", 6)) {
        cseek_in(0L, 0);
        return 0;
    }
    if (readscreendescriptor())
        readcolormap();
    cread(&separator, 1);
    if (separator!=',')
	error1(E0_FATAL, E1_GIF, E2_FORMAT, E3_MISS_SEP, ',');
    if (readimagedescriptor())
        readcolormap();
    cread(&separator, 1); codesize=separator;
    printe("GIF input; %d x %d, %d bits/pixel", (int)width, (int)height,
     (int)bitsperpixel);
    if (interlaced)
	printe(", interlaced");
    if (codesize!=bitsperpixel)
	printe(", codesize: %d", (int)codesize);
    pute('\n');
    if (!outfilename)
	exit(0);
    xsz=width;
    ysz=height;
    rgb=Malloc(ysz*sizeof(u_short *));
    for (y=0; y<ysz; ++y)
        rgb[y]=Calloc(xsz*sizeof(u_short));
    readbody();
    return 1;
}


readscreendescriptor(void)
{
    struct sd_t {
        u_char widthlo, widthhi;
        u_char heightlo, heighthi;
        u_char misc, bgindex, reserved;
    } sd;

    cread(&sd, 7);
    bitsperpixel=(sd.misc&7)+1;
    return sd.misc&0x80;
}


readimagedescriptor(void)
{
    struct id_t {
        u_char xoffsetlo, xoffsethi;
        u_char yoffsetlo, yoffsethi;
        u_char widthlo, widthhi;
        u_char heightlo, heighthi;
        u_char misc;
    } id;

    cread(&id, 9);
    width=(id.widthhi<<8)|id.widthlo;
    height=(id.heighthi<<8)|id.heightlo;
    interlaced=(id.misc&0x40 ? 1 : 0);
    if (id.misc&0x80) {
        bitsperpixel=(id.misc&7)+1;
        return 1;
    } else
        return 0;
}


void
readcolormap(void)
{
	char *rgbin, *rgbp;
	int r, g, b, roundin;
	short i, nentries;

	roundin=(gifmaptrunc ? 0 : 8);
	nentries=1<<bitsperpixel;
	rgbin=Malloc(nentries*3);
	if (cm)
		free(cm);
	cread(rgbin, (int)(nentries*3));
	cm=Malloc(nentries*2);
	for (i=0, rgbp=rgbin; i<nentries; ++i) {
		r= *rgbp++ & 0xff;
		g= *rgbp++ & 0xff;
		b= *rgbp++ & 0xff;
		r=(r+roundin)>>4; if (r>15) r=15;
		g=(g+roundin)>>4; if (g>15) g=15;
		b=(b+roundin)>>4; if (b>15) b=15;
		cm[i]=(r<<8)|(g<<4)|b;
	}
	free(rgbin);
}


#define NCODES 6144
static u_short *codes;
static u_short *prefix;
static u_char  *suffix;
static u_char  *stack;
static u_char  *stack_end;
#define NUBYTES 4096
static u_short *ubytes;
#define NBUF 16384
static u_char *buf;
#define NTRANSCHARS 256
static u_char *transchars;
#define NMASKS 64
static u_short *masks;


void
readbody(void)
{
	codes=Malloc(NCODES*sizeof(u_short));
	ubytes=Malloc(NUBYTES);
	buf=Malloc(NBUF);
	transchars=Malloc(NTRANSCHARS);
	masks=Malloc(NMASKS*sizeof(u_short));
	prefix=Malloc(NCODES*sizeof(u_short));
	suffix=Malloc(NCODES);
	stack=Malloc(NCODES);
	stack_end=stack+NCODES;
	fill_transchars();
	decompress();
	free(codes);
	free(ubytes);
	free(buf);
	free(transchars);
	free(masks);
	free(prefix);
	free(suffix);
	free(stack);
}


short
nextrow(int mode)
{
    static int rowsdone= -1;
    static int phase= -1;
    static int currow;

    ++rowsdone;
    counter();
    if (mode) {
        erase_counter(NULL);
        if (rowsdone!=ysz)
		error2(E0_ERROR, E1_GIF, E2_FORMAT, E3_WRONG_NR_ROWS, rowsdone, (int)ysz);
    } else {
        switch (phase) {
        case -1:
            currow=0;
            if (interlaced)
                phase=1;
            else
                phase=0;
        break;
        case 0:
            ++currow;
        break;
        case 1:
            currow+=8;
            if (currow>=ysz) {
                phase=2;
                currow=4;
            }
        break;
        case 2:
            currow+=8;
            if (currow>=ysz) {
                phase=3;
                currow=2;
            }
        break;
        case 3:
            currow+=4;
            if (currow>=ysz) {
                phase=4;
                currow=1;
            }
        break;
        case 4:
            currow+=2;
        break;
        }
        if (currow>=ysz) {
            if (!rgboverflowrow)
                rgboverflowrow=Malloc(xsz*sizeof(u_short));
            gpp=rgboverflowrow;
        } else
            gpp=rgb[currow];
        return xsz-1;
    }
}


void
decompress(void)
{
    u_short *cp; /* code pointer, into codes */
    u_char *sp;  /* stack pointer, into stack */
    u_short *pp; /* pixel pointer, into rgb */
    u_short lastnormal;
    short ncodes;
    short curfree;
    u_char finchar;
    u_short oldcode, curcode, incode;
    short npix;

    init_counter(0, (int)ysz, 10, "reading GIF");
    npix=0;
    lastnormal=(1<<bitsperpixel)-1;
    ncodes=nextcodes();
    if (!ncodes)
        ncodes=nextcodes();
    while (ncodes>0) {
        curfree=(1<<codesize)+2;
        cp=codes;
        sp=stack;
        finchar=oldcode= *cp++;
        if (--npix<0) {
            npix=nextrow(0);
            pp=gpp;
        }
        *pp++ =cm[finchar];
        ncodes-=2;
        do {
            incode= *cp++;
            if (incode>=curfree) {
                curcode=oldcode;
                *sp++ =finchar;
            } else
                curcode=incode;
            while (curcode>lastnormal) {
		if (sp>=stack_end) {
		    error0(E0_ERROR, E1_GIF, E2_FORMAT, E3_INVALID_CODE_TABLE);
		    (void)nextrow(1);
		    return;
		}
                *sp++ =suffix[curcode];
                curcode=prefix[curcode];
            }
            finchar=curcode;
            if (--npix<0) {
                npix=nextrow(0);
                pp=gpp;
            }
            *pp++ =cm[finchar];
            while (sp!=stack) {
                if (--npix<0) {
                    npix=nextrow(0);
                    pp=gpp;
                }
                *pp++ =cm[*--sp];
            }
            prefix[curfree]=oldcode;
            suffix[curfree]=finchar;
            oldcode=incode;
            ++curfree;
        } while (--ncodes>=0);
        ncodes=nextcodes();
    }
    gpp=pp;
    (void)nextrow(1);
}


short
fill_masks(short startbit, short nbits)
{
    u_short *m, *m2;
    short start, length, n, loopc;
    u_long mask;

    m=masks;
    start=startbit;
    length=nbits;
    n=0;
    do {
        if (start+length>16) { /* in two parts */
            mask=1<<(15-start);
            loopc=14-start;
            if (loopc>=0) {
                do {
                    mask|=mask>>1;
                } while (--loopc>=0);
            }
            *m++ = -start;
            *m++ =mask;
            mask=1<<15;
            loopc=length+start-18;
            if (loopc>=0) {
                do {
                    mask|=mask>>1;
                } while (--loopc>=0);
            }
            *m++ =16-start;
            *m++ =mask;
            n+=2;
        } else {               /* in one part */
            mask=1<<(15-start);
            loopc=length-2;
            if (loopc>=0) {
                do {
                    mask|=mask>>1;
                } while (--loopc>=0);
            }
            *m++ = -start;
            *m++ =mask;
            ++n;
        }
        start=(start+length)&15;
    } while (start!=startbit);
    for (start=NMASKS/2/n-2; start>=0; --start) {
        m2=masks;
        loopc=n-1;
        do {
            *m++ = *m2++;
            *m++ = *m2++;
        } while (--loopc>=0);
    }
    n=NMASKS/2/n*n;
    return n;
}


short
nextcodes(void)
{
#define NC_EC_INIT  0
#define NC_EC_CLEAR 1
#define NC_EC_EOF   2
    static int endcode=NC_EC_INIT;
    u_short *ip; static u_short *sip;
    short nushorts; static short snushorts=0;
    static short bitpos=0, csz;
    short tonextcsz;
    static int ok=1;
    short nmasks, inmasks; u_short *m;
    u_short eofcode, clearcode;
    short ncodes; u_short *cp;
    u_short input, output; static u_short sinput;
    short shift;
    u_char *tc;

    if (!ok)
	return 0;
    if (endcode==NC_EC_EOF) {
        ok=0;
        return 0;
    }
    ip=sip;
    nushorts=snushorts;
    input=sinput;
    csz=codesize+1;
    clearcode=1<<codesize;
    eofcode=clearcode+1;
    tonextcsz=(1<<csz)-eofcode-1;
    ncodes=NCODES;
    cp=codes;
    tc=transchars;
    inmasks=fill_masks(bitpos, csz);
    do {
nc_restart:
        m=masks;
        nmasks=inmasks-2;
        shift= *m++;
        if (shift>=0) {
            if (--nushorts<0) {
                nushorts=nextubytes();
                if (nushorts==0) {
                    ok=0;
                    return 0;
                }
                ip=(u_short *)ubytes;
                nushorts=(nushorts+1)/2-1;
            }
            input= *ip++;
            output=(u_long)(input & *m++)>>(u_short)shift;
        } else
            output=(u_long)(input & *m++)<<(u_short)(-shift);
        do {
            shift= *m++;
            if (shift<=0) {
                /* put output into the output stream, this code is repeated below */
                output=(tc[output&0xff]<<8)|tc[((u_long)output>>8)&0xff];
                if (output==eofcode) {
                    endcode=NC_EC_EOF;
                    bitpos= -shift;
                    goto nc_end;
                } 
                if (output==clearcode) {
                    endcode=NC_EC_CLEAR;
                    bitpos= -shift;
                    goto nc_end;
                } 
                if (--tonextcsz<0) {
                    if (csz<12)
                        ++csz;
                    tonextcsz= (1<<(csz-1))-1;
                    if (ncodes<tonextcsz)
                        error0(E0_WARNING, E1_GIF, E2_FORMAT, E3_POT_CODE_OVERFLOW);
                    bitpos= -shift;
                    inmasks=fill_masks(bitpos, csz);
                    *cp++ =output;
                    --ncodes;
                    goto nc_restart;
                }
                *cp++ =output;
                --ncodes;
                /* end of output code */
            } 
            if (shift>=0) {
                if (--nushorts<0) {
                    nushorts=nextubytes();
                    if (nushorts==0) {
                        ok=0;
                        return 0;
                    }
                    ip=(u_short *)ubytes;
                    nushorts=(nushorts+1)/2-1;
                }
                input= *ip++;
            }
            if (shift<=0)
                output=(u_long)(input & *m++)<<(u_short)(-shift);
            else
                output|=(u_long)(input & *m++)>>(u_short)shift;
        } while (--nmasks>=0);
        /* put output into the output stream, this code is repeated above */
        output=(tc[output&0xff]<<8)|tc[((u_long)output>>8)&0xff];
        if (output==eofcode) {
            endcode=NC_EC_EOF;
            goto nc_end;
        } 
        if (output==clearcode) {
            endcode=NC_EC_CLEAR;
            goto nc_end;
        } 
        if (--tonextcsz<0) {
            if (csz<12)
                ++csz;
            tonextcsz= (1<<(csz-1))-1;
            if (ncodes<tonextcsz)
                error0(E0_WARNING, E1_GIF, E2_FORMAT, E3_POT_CODE_OVERFLOW);
            inmasks=fill_masks(bitpos, csz);
        }
        *cp++ =output;
        --ncodes;
        /* end of output code */
    } while (1);
nc_end:
    sip=ip;
    snushorts=nushorts;
    sinput=input;
    return NCODES-ncodes;
}


short
nextubytes(void)
{
    static int ok=1;
    short dec;
    u_char *bp; static u_char *sbp=NULL;
    u_char *up, *tc;
    short inbuf; static short sinbuf;
    short inblock; static short sinblock;
    short inubytes;

    if (!ok)
	return 0;
    bp=sbp;
    up=(u_char *)ubytes;
    tc=transchars;
    inbuf=sinbuf;
    inblock=sinblock;
    inubytes=NUBYTES;
    if (!sbp) {
        dec=0;
        inblock=0;
        inbuf=0;
    } else {
        if (inblock<inbuf)
            dec=inblock;
        else
            dec=inbuf;
        if (inubytes<dec)
            dec=inubytes;
        inblock-=dec;
        inbuf-=dec;
        inubytes-=dec;
    }
    do {
        if (--dec>=0) {
            do {
                *up++ = tc[*bp++];
            } while (--dec>=0);
        }
        if (inblock==0 && inbuf>0) {
            inblock= *bp++;
            --inbuf;
            if (!inblock) { /* zero bytecount encountered, repeated below */
                ok=0;
                break;
            }
        }
        if (inbuf==0) {
	    cread_type=CREAD_VARLEN;
            cread(buf, NBUF);
	    cread_type=CREAD_STRICT;
	    inbuf=cread_result;
	    if (inbuf<=0) {
                ok=0;
                return 0;
            }
            bp=buf;
        }
        if (inblock==0) {
            inblock= *bp++;
            --inbuf;
	    assert(inbuf>0);
            if (!inblock) { /* zero bytecount encountered, repeated above */
                ok=0;
                break;
            }
        }
        if (inubytes==0) {
            break;
        }
        if (inblock<inbuf)
            dec=inblock;
        else
            dec=inbuf;
        if (inubytes<dec)
            dec=inubytes;
        inblock-=dec;
        inbuf-=dec;
        inubytes-=dec;
    } while (1);
    sbp=bp;
    sinbuf=inbuf;
    sinblock=inblock;
    return NUBYTES-inubytes;
}


void
fill_transchars(void)
{
    u_char *tc;
    u_char c, maskout;
    short i, maskin;

    tc=transchars;
    for (i=0; i<NTRANSCHARS; ++i) {
        c=0;
        for (maskout=0x80, maskin=1; maskout; maskin<<=1, maskout=(u_long)maskout>>1) {
            if (i&maskin)
                c|=maskout;
        }
        *tc++ =c;
    }
}
