/* wasp - Copyright 1991 by Steven Reiz
 * see COPYING and wasp.c for further info
 * readiff.c, 4/12/90 - 31/12/90,
 * 5/5/91 - 2/6/91, 23/6/91, 9/7/91, 16/11/91,
 * 8/12/91, 27/12/91
 */

static char *sourcefile=__FILE__;

#include "wasp.h"

static u_long pos;
static short seen_bmhd, seen_cmap, seen_sham, seen_camg, seen_body;
static short laced_sham=0;	/* one sham entry per two lines in lace mode */
static short dynamic=0;		/* dynamic sliced (CTBL iso SHAM chunk) */
static u_long viewmodes=0;
static struct bmhd_t {
    u_short w, h;
    short x, y;
    u_char nplanes, masking, compression, pad;
    u_short transpcol;
    u_char xaspect, yaspect;
    short pagew, pageh;
} bmhd;
#define mskNone			0
#define mskHasMask		1
#define mskHasTransparentColor	2
#define mskLasso		3
static u_short *cmap, *curcmap;
static u_short *sham=NULL;
static short nplanes, ncmap, nregs, nsham, directrrgb=0;
static short mask_plane;


read_iff(void)
{
    u_long id;

    cread(&id, (int)sizeof(id));
    if (!isgroupid(id)) {
        cseek_in(0L, 0);
        return 0;
    }
    pos=sizeof(id);
    seen_bmhd=seen_cmap=seen_sham=seen_camg=seen_body=0;
    do_chunk(id);
    if (!seen_bmhd)
	error0(E0_FATAL, E1_IFF, E2_BMHD, E3_MISS_CHUNK);
    if (!seen_body) {
        if (!seen_camg) {
	    error0(E0_WARNING, E1_IFF, E2_CAMG, E3_MISS_CHUNK);
            viewmodes=(nplanes==6 ? VM_HAM : 0);
            seen_camg=1;
        }
        pos=sizeof(id);
        cseek_in(pos, 0);
        do_chunk(id);
    }
    if (!seen_body)
	error0(E0_FATAL, E1_IFF, E2_BODY, E3_MISS_CHUNK);
    free(cmap);
    return 1;
}


char *
idstr(unsigned long id)
{
    static char ids[5];

    *(u_long *)ids=id;
    ids[4]='\0';
    return ids;
}


void
do_chunk(unsigned long id)
{
    u_long size, t, newpos;

    cread(&size, (int)sizeof(size));
    size=(size+1) & -2;
    pos+=sizeof(size);
    if (isgroupid(id)) {
        cread(&t, (int)sizeof(t));
        pos+=sizeof(t);
        if (size<0)
	    error0(E0_FATAL, E1_IFF, E2_FORMAT, E3_NEGGROUPSIZE);
        newpos=pos+size-sizeof(t);
        while (pos<newpos) {
            u_long pid;
    
            cread(&pid, (int)sizeof(pid));
            pos+=sizeof(pid);
            if (!isvalid(pid))
		error0(E0_FATAL, E1_IFF, E2_FORMAT, E3_INVALID_ID);
            else
                do_chunk(pid);
        }
        if (pos>newpos)
	    error0(E0_FATAL, E1_IFF, E2_FORMAT, E3_GROUP2SMALL);
    } else {
        /* normal chunks */
        if (id==id('B', 'M', 'H', 'D') && !seen_bmhd) {
            seen_bmhd=1;
            if (size!=sizeof(struct bmhd_t))
		error0(E0_ERROR, E1_IFF, E2_BMHD, E3_WRONGCHUNKSZ);
            cread(&bmhd, (int)sizeof(struct bmhd_t));
	    nplanes=bmhd.nplanes;
            pos+=size;
        } else if (id==id('C', 'A', 'M', 'G') && !seen_camg) {
	    int nrmodes;

            seen_camg=1;
            if (size!=sizeof(viewmodes))
		error0(E0_ERROR, E1_IFF, E2_CAMG, E3_WRONGCHUNKSZ);
            cread(&viewmodes, (int)sizeof(viewmodes));
	    nrmodes=0;
	    if (viewmodes & VM_EHB)
		++nrmodes;
	    if (viewmodes & VM_HAM)
		++nrmodes;
	    if (viewmodes & VM_HIRES)
		++nrmodes;
	    if (nrmodes>1)
		error0(E0_FATAL, E1_IFF, E2_CAMG, E3_CAMGMODES);
            pos+=size;
        } else if (id==id('C', 'M', 'A', 'P') && !seen_cmap) {
            seen_cmap=1;
            do_cmap(size);
        } else if (id==id('S', 'H', 'A', 'M') && !seen_sham) {
            seen_sham=1;
            do_sham(size, 0);
        } else if (id==id('C', 'T', 'B', 'L') && !seen_sham) {
            seen_sham=1;
	    dynamic=1;
            do_sham(size, 1);
        } else if (id==id('B', 'O', 'D', 'Y') 
         && !seen_body && seen_bmhd) {
            seen_body=1;
            do_body(size);
	} else if (id==id('A', 'N', 'N', 'O')) {
	    do_anno(size);
        } else { /* skip unknown chunks */
	    error2(E0_WARNING, E1_IFF, E2_FORMAT, E3_UNKNOWN_CHUNK, idstr(id), size);
            cseek_in(size, 1);
            pos+=size;
        }
    }
}


isgroupid(unsigned long id)
{
    return isgroupid2(id, id('F', 'O', 'R', 'M')) ||
           isgroupid2(id, id('L', 'I', 'S', 'T')) ||
           isgroupid2(id, id('C', 'A', 'T', ' ')) ||
           id==id('P', 'R', 'O', 'P');
}


isgroupid2(unsigned long i, unsigned long i2)
{
    char c;

    c=i;
    return i==i2 || ((i&0xffffff00L)==(i2&0xffffff00L) && c>='1' && c<='9');
}


isvalid(unsigned long id)
{
    char *p;
    int i;

    for (p=(char *)&id, i=0; i<4; ++i)
        if (*p<' ' || *p>'~')
            break;
        else
            ++p;
    return i>=4;
}


void
do_anno(unsigned long size)
{
	short i;
	char *anno;

	anno=Malloc(size+1);
	cread(anno, (int)size);
	anno[size]='\0';
	for (i=0; i<size; ++i) {
		if (i==size-1 && !anno[i])
			break;
		if (anno[i]<' ' || anno[i]>'~')
			anno[i]='.';
	}
	printe("ANNO: %s\n", anno);
	free(anno);
	pos+=size;
}


void
do_cmap(unsigned long size)
{
    u_char filergb[3];
    short i;

    if (size%6)
	error0(E0_FATAL, E1_IFF, E2_CMAP, E3_WRONGCHUNKSZ);
    ncmap=size/sizeof(filergb);
    cmap=Malloc(ncmap*sizeof(u_short));
    for (i=0; i<ncmap; ++i) {
        cread(filergb, (int)sizeof(filergb));
        cmap[i]=((u_short)filergb[0]&0xf0)<<4 | filergb[1]&0xf0 | (u_long)filergb[2]>>4;
    }
    pos+=size;
}


void
extend_cmap(void)
{
    u_short *newcmap, rgb;
    u_char r, g, b;
    short i;

    newcmap=Malloc(2*ncmap*sizeof(u_short));
    for (i=0; i<ncmap; ++i) {
        rgb=cmap[i];
        newcmap[i]=rgb;
        r=(rgb&0x0f00)>>9;
        g=(rgb&0x00f0)>>5;
        b=(rgb&0x000f)>>1;
        newcmap[i+ncmap]=(r<<8)|(g<<4)|b;
    }
    free(cmap);
    cmap=newcmap;
    ncmap*=2;
}


void
do_sham(unsigned long size, int is_ctbl)
{
	short i;

	pos+=size;
	if (!is_ctbl) {
		cread(&i, 2);
		if (i)
			error1(E0_WARNING, E1_IFF, E2_SHAM, E3_UNKNOWN_VERSION, (int)i);
		size-=2;
	}
	sham=Malloc(size);
	cread(sham, (int)size);
	nsham=size/2;
	for (i=0; i<nsham; ++i)
		sham[i]&=0x0fff;
}


void
do_body(unsigned long size)
{
    short y;

    xsz=bmhd.w;
    ysz=bmhd.h;
    nregs=1<<nplanes;
    if (viewmodes & VM_HAM)
	nregs=16;
    if (bmhd.masking==mskHasMask) {
	error0(E0_WARNING, E1_IFF, E2_FORMAT, E3_MASKING);
	mask_plane=1;
    } else
	mask_plane=0;
    if (bmhd.compression!=0 && bmhd.compression!=1)
	error1(E0_FATAL, E1_IFF, E2_COMPRESSION, E3_UNKNOWN_VERSION, (int)bmhd.compression);
    if (viewmodes & VM_HAM) {
        if (ncmap!=nregs)
		error1(E0_WARNING, E1_IFF, E2_HAM, E3_WRONG_NR_CREGS, (int)ncmap);
	if (nplanes!=6)
		error1(E0_FATAL, E1_IFF, E2_HAM, E3_WRONG_NR_PLANES, (int)nplanes);
    } else if (viewmodes & VM_EHB) {
	if (nplanes!=6)
		error1(E0_FATAL, E1_IFF, E2_EHB, E3_WRONG_NR_PLANES, (int)nplanes);
        extend_cmap();
    } else if (!seen_cmap) {
	directrrgb=1;
	if (seen_camg)
		error0(E0_ERROR, E1_IFF_RGB, E2_CAMG, E3_UNEXP_CHUNK);
	if (sham)
		error0(E0_ERROR, E1_IFF_RGB, E2_SHAM, E3_UNEXP_CHUNK);
	if (nplanes<12 || nplanes%3)
		error1(E0_FATAL, E1_IFF_RGB, E2_FORMAT, E3_WRONG_NR_PLANES, (int)nplanes);
    } else {
        if (ncmap!=(1<<nplanes))
		error2(E0_FATAL, E1_IFF, E2_FORMAT, E3_CREGS_PLANES, (int)ncmap,
		 (int)nplanes);
    }
    if (sham) {
        int i;

        if (viewmodes & VM_EHB)
		error0(E0_FATAL, E1_IFF, E2_EHB, E3_SLICED_EHB);
	i=ysz;
        if ((viewmodes & VM_LACE) && nregs*i!=nsham) {
            i=(ysz+1)/2;
	    laced_sham=1;
	}
        if (nregs*i!=nsham)
		error2(E0_FATAL, E1_IFF, E2_SHAM, E3_NENTRIES, (int)nsham, nregs*i);
    }
    printe("IFF input; %ld x %ld, %d plane%s, ", xsz, ysz, (int)nplanes,
     (nplanes==1 ? "" : "s"));
    if (viewmodes & VM_LACE)
	printe("interlaced ");
    if (sham)
	printe("%s%s ", (laced_sham ? "lace-" : ""),
	 (dynamic ? "dynamic" : "sliced"));
    if (directrrgb)
	printe("direct rgb\n");
    else if (viewmodes & VM_EHB)
	printe("ehb\n");
    else if (viewmodes & VM_HAM)
	printe("ham\n");
    else if (viewmodes & VM_HIRES)
	printe("hires\n");
    else
	printe("lores\n");
    if (!outfilename)
	exit(0);
    rgb=Malloc(ysz*sizeof(u_short *));
    for (y=0; y<ysz; ++y)
        rgb[y]=Calloc(xsz*sizeof(u_short));
    set_bodylimit(size);
    if (directrrgb)
	read_direct_body();
    else
	read_body();
    check_bodylimit();
    pos+=size;
    erase_counter(NULL);
}


static u_long bodylimit, bodypos;
#define RBBUFSZ 32760
static u_char *conv;

void
set_bodylimit(unsigned long size)
{
    bodylimit=size;
    bodypos=0;
}


void
check_bodylimit(void)
{
    if (bodypos!=bodylimit)
	error0(E0_ERROR, E1_IFF, E2_BODY, E3_UNSPEC);
}


short
nextbody(void *buf)
{
    int toread;

    toread=bodylimit-bodypos;
    if (toread<=0) {
	error1(E0_ERROR, E1_IFF, E2_BODY, E3_UNEXPEND, "chunk");
	return 0;
    }
    if (toread>RBBUFSZ)
        toread=RBBUFSZ;
    cread_type=CREAD_NONFATAL;
    cread(buf, toread);
    cread_type=CREAD_STRICT;
    if (cread_result<=0)
	return 0;
    if (cread_result!=toread)
	toread=cread_result;
    bodypos+=toread;
    return (short)toread;
}



void
read_direct_body(void)
{
    short bufn, x, y, z, planei;
    u_char *buf, *bufp;
    char *row, *q, c;

    init_counter(0, (int)ysz, 10, "reading RGB IFF");
    row=Malloc((nplanes+mask_plane)*((xsz+15)/16)*2+128);
    buf=Malloc(RBBUFSZ);
    bufn=0;
    for (y=0; y<ysz; ++y) {
        counter();
        for (planei=0; planei<nplanes+mask_plane; ++planei) {
            if (bmhd.compression) {
                x=(xsz+15)/16*2;
                q=row+planei*x;
                while (x>0) {
		    if (--bufn<0) {
			bufn=nextbody(buf)-1;
			if (bufn<0)
			    goto quit_read_direct_body;
			bufp=buf;
		    }
		    z= *bufp++;
                    if (z<=127) {
			x-=z;
			--x;
                        while (z>=0) {
			    if (--bufn<0) {
				bufn=nextbody(buf)-1;
				if (bufn<0)
				    goto quit_read_direct_body;
				bufp=buf;
			    }
			    *q++ = *bufp++;
                            --z;
                        }
                    } else if (z==128) {
                        /* do nothing */
                    } else {
			z=257-z;
			x-=z;
			if (--bufn<0) {
			    bufn=nextbody(buf)-1;
			    if (bufn<0)
				goto quit_read_direct_body;
			    bufp=buf;
			}
			c= *bufp++;
                        while (--z>=0)
                            *q++ =c;
                    }
                }
                if (x!=0)
			error0(E0_ERROR, E1_IFF, E2_COMPRESSION, E3_ROWBOUND);
            } else {
                for (x=(xsz+15)/16*2-1, q=row+planei*((xsz+15)/16*2); x>=0; --x) {
		    if (--bufn<0) {
			bufn=nextbody(buf)-1;
			if (bufn<0)
			    goto quit_read_direct_body;
			bufp=buf;
		    }
		    *q++ = *bufp++;
		}
            }
        }
	do_row_direct(y, (u_char *)row);
    }
quit_read_direct_body:
    free(row);
    free(buf);
}


void
read_body(void)
{
    short bufn, x, y;
    long lon;
    short z;
    u_char *buf, *bufp;
    u_long *p, *q, *row;

    init_counter(0, (int)ysz, 20, "reading IFF");
    if (sham)
        curcmap=sham;
    else
        curcmap=cmap;
    row=Malloc(8*(xsz+15)/16*2+128);
    buf=Malloc(RBBUFSZ);
    bufn=0;
    conv=Malloc((nplanes+mask_plane)*256*8);
    fill_conv();
    for (y=0; y<ysz; ++y) {
        counter();
        for (lon=(long)conv; lon<(long)conv+(nplanes+mask_plane)*256*8; lon+=256*8) {
            if (bmhd.compression) {
                x=(xsz+15)/16*2;
                q=row;
                while (x>0) {
		    if (--bufn<0) {
			bufn=nextbody(buf)-1;
			if (bufn<0)
			    goto quit_read_body;
			bufp=buf;
		    }
		    z= *bufp++;
                    if (z<=127) {
			x-=z;
			--x;
                        while (z>=0) {
			    if (--bufn<0) {
				bufn=nextbody(buf)-1;
				if (bufn<0)
				    goto quit_read_body;
				bufp=buf;
			    }
                            p=(u_long *)(lon+(*bufp++ <<3));
                            if (lon==(long)conv) {
                                *q++ = *p++;
                                *q++ = *p;
                            } else {
                                *q++ |= *p++;
                                *q++ |= *p;
                            }
                            --z;
                        }
                    } else if (z==128) {
                        /* do nothing */
                    } else {
			z=257-z;
			x-=z;
			if (--bufn<0) {
			    bufn=nextbody(buf)-1;
			    if (bufn<0)
				goto quit_read_body;
			    bufp=buf;
			}
                        p=(u_long *)(lon+(*bufp++ <<3));
                        if (lon==(long)conv) {
                            while (--z>=0) {
                                *q++ = *p++;
                                *q++ = *p;
                                --p;
                            }
                        } else {
                            while (--z>=0) {
                                *q++ |= *p++;
                                *q++ |= *p;
                                --p;
                            }
                        }
                    }
                }
                if (x!=0)
			error0(E0_ERROR, E1_IFF, E2_COMPRESSION, E3_ROWBOUND);
            } else {
                for (x=(xsz+15)/16*2-1, q=row; x>=0; --x) {
		    if (--bufn<0) {
			bufn=nextbody(buf)-1;
			if (bufn<0)
			    goto quit_read_body;
			bufp=buf;
		    }
                    p=(u_long *)(lon+(*bufp++ <<3));
                    if (lon==(long)conv) {
                        *q++ = *p++;
                        *q++ = *p;
                    } else {
                        *q++ |= *p++;
                        *q++ |= *p;
                    }
                }
            }
        }
        if (viewmodes & VM_HAM)
            do_row_ham(y, (u_char *)row);
        else
            do_row(y, (u_char *)row);
        if (sham && (!laced_sham || !(y&1)))
            curcmap+=nregs;
    }
quit_read_body:
    free(conv);
    free(row);
    free(buf);
}


void
fill_conv(void)
{
    u_char *powtab, *p;
    short x, y;
    char i;
    short j;

    powtab=Malloc(8);
    p=powtab;
    for (i=1; i; i<<=1)
        *p++ =i;
    p=conv;
    for (y=0; y<nplanes; ++y) {
        for (x=0; x<256; ++x) {
            for (j=128; j; j>>=1) {
                if (j&x)
                    *p++ =powtab[y];
                else
                    *p++ =0;
            }
        }
    }
    if (mask_plane) {
        for (x=0; x<256; ++x)
            for (j=128; j; j>>=1)
                *p++ =0;
    }
    free(powtab);
}


void
do_row(short y, unsigned char *parrow)
{
    u_short *rgbrow;
    u_char *row;
    short x;

    rgbrow=rgb[y];
    row=parrow;
    for (x=xsz-1; x>=0; --x)
        *rgbrow++ =curcmap[*row++];
}


void
do_row_ham(short y, unsigned char *parrow)
{
    u_short *rgbrow;
    u_char *row, pixel;
    short x;
    u_short curcolor;

    rgbrow=rgb[y];
    row=parrow;
    curcolor=curcmap[0];
    x=xsz-1;
    do {
        pixel= *row++;
        if (pixel&0x20) {
            if (pixel&0x10) { /* modify green */
                curcolor=(curcolor&0x0f0f)|((pixel&0x0f)<<4);
            } else {          /* modify red */
                curcolor=(curcolor&0x00ff)|((pixel&0x0f)<<8);
            }
        } else {
            if (pixel&0x10) { /* modify blue */
                curcolor=(curcolor&0x0ff0)|(pixel&0x0f);
            } else {          /* use curcmap */
                curcolor=curcmap[pixel];
            }
        }
        *rgbrow++ =curcolor;
    } while (--x>=0);
}


void
do_row_direct(short y, unsigned char *inrow)
{
	u_short color, *dest;
	long x, bytesperscan, plane0;
	u_char *p, pmask, *plane1, *plane2, *plane3;

	dest=rgb[y];
	bytesperscan=(xsz+7)/8;
	plane0=nplanes/3*bytesperscan;
	for (x=0; x<bytesperscan; ++x) {
		plane1=inrow+x+plane0-bytesperscan;
		plane2=plane1+plane0;
		plane3=plane2+plane0;
		for (pmask=128; pmask; pmask=(unsigned long)pmask>>1) {
			color=0;
			p=plane1;
			if (*p & pmask)	color|=0x0800; p-=bytesperscan;
			if (*p & pmask)	color|=0x0400; p-=bytesperscan;
			if (*p & pmask)	color|=0x0200; p-=bytesperscan;
			if (*p & pmask)	color|=0x0100;
			p=plane2;
			if (*p & pmask)	color|=0x0080; p-=bytesperscan;
			if (*p & pmask)	color|=0x0040; p-=bytesperscan;
			if (*p & pmask)	color|=0x0020; p-=bytesperscan;
			if (*p & pmask)	color|=0x0010;
			p=plane3;
			if (*p & pmask)	color|=0x0008; p-=bytesperscan;
			if (*p & pmask)	color|=0x0004; p-=bytesperscan;
			if (*p & pmask)	color|=0x0002; p-=bytesperscan;
			if (*p & pmask)	color|=0x0001;
			*dest++ =color;
		}
	}
}
