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

static char *sourcefile=__FILE__;

#include "wasp.h"
#define WRIFFMAIN
#include "wriff.h"

static int ticolors;
static int mmap_size_known=1;


void
write_iff(void)
{
    long t;

    err=0; err2=0;
    wriff_init();
    decide_mode();
    compute_nregs();
    wrl(id('F', 'O', 'R', 'M'));
    wrl(0L); /* size of the form ilbm, will be patched later */
    wrl(id('I', 'L', 'B', 'M'));
    write_header();
    write_cmap();
    write_mmap();
    write_body();
    write_cmap();
    write_mmap();
    write_body();
    t=cseek_out(0L, 2);
    cseek_out(4L, 0);
    wrl(t-8L);
    printe("IFF file written; %ld bytes, error: %ld, error2: %ld\n", t, err, err2);
}


void
wriff_init(void)
{
	slicedo=0;
	counts=NULL1;
	cm=NULL1;
	curcm=NULL1;
	wf2rgbweight=NULL1;
	cmrgb=NULL1;
	newcol=NULL1;
	error=NULL1;
	error2=NULL1;
	nrestricted=0;
	restrict_changes= -1;
}


void
decide_mode(void)
{
    float sx, sy, st;

    if (directrgb>=0) {
	if (directrgb%3)
		error1(E0_FATAL, E1_IFF_RGB, E2_OPTION, E3_WRONG_NR_PLANES, (int)directrgb);
	nplanes=directrgb;
	printe("IFF output; direct rgb, %d planes\n", nplanes);
	return;
    }
    if ((xmode!=UNSET || ymode!=UNSET) && asc)
	error0(E0_FATAL, E1_IFF, E2_OPTION, E3_ASC);
    if (xmode!=UNSET && hires!=HIRES_OK)
	error0(E0_FATAL, E1_IFF, E2_OPTION, E3_NOHIRES);
    count_pixels(0, (int)ysz-1, 1);
    ticolors=icolors=count_colors(1L);
    if (hires==HIRES_MAYBE)
        hires=(icolors<=16 ? HIRES_OK : HIRES_NOT);
    if (asc && (xsz>scrw || ysz>scrh)) {
        sx=(float)scrw/(float)xsz;
        sy=(float)scrh/(float)ysz;
        if (sy<sx)
            st=sy;
        else
            st=sx;
        ymode=LACE;
        sy=st*2.0;
        if (hires==HIRES_OK) {
            xmode=HIRES;
            sx=st*2.0;
        } else {
            sx=st;
            if (sy>1.0) {
                sx=sx/sy;
                sy=1.0;
            }
        }
        if (sx!=sy || sx<1.0) {
            scalef(1, sy);
            scalef(0, sx);
        }
    }
    if (xmode==UNSET) {
        if (icolors<=32)
            xmode=LORES;
        else
            xmode=HAM;
    }
    slicedo=sliced;
    if (ymode==UNSET)
        ymode=NOLACE;
    if (ymode==LACE && (ysz&1))
	do_enlarge((int)xsz, (int)ysz+1);
    if (distrmeth2!=DISTRMETH_UNSET && xmode!=HAM)
	error0(E0_FATAL, E1_IFF, E2_OPTION, E3_ITMETH);
    if (!inoperation && distrmeth==DISTRMETH_UNSET) {
        if (xmode==EHB)
            distrmeth=DEF_EHB_DISTRMETH;
        else
            distrmeth=DEF_DISTRMETH;
    }
    if (countmeth==COUNTMETH_UNSET)
	countmeth=(xmode==HAM ? DEF_HAM_COUNTMETH : DEF_COUNTMETH);
    if (xmode==EHB && !is_ehb_distr())
	error1(E0_FATAL, E1_IFF, E2_OPTION, E3_EHB_METH, dmethnam(distrmeth));
    printe("IFF output; xmode: %s%s, ymode: %s, cmeth: %s, dmeth: %s",
     mmapnam((int)sliced), xmodenam(xmode), ymodenam(ymode),
     cmethnam(countmeth), dmethnam(distrmeth));
    if (distrmeth2!=DISTRMETH_UNSET)
	printe(", dmeth2: %s", dmethnam(distrmeth2));
    pute('\n');
}


int
is_ehb_distr(void)
{
    return (distrmeth==DISTRMETH_EHB || distrmeth==DISTRMETH_MUE ? 1 : 0);
}


void
compute_nregs(void)
{
    if (directrgb>=0)
	return;
    switch (xmode) {
    case HAM:   nregs=16; nplanes=6; break;
    case EHB:   nregs=64; nplanes=6; break;
    case LORES: nregs=32; nplanes=5; break;
    case HIRES: nregs=16; nplanes=4; break;
    }
    if (slicedo) {
        cm=Malloc((ymode==LACE && sliced!=SLICED_DYN ? (ysz+1)/2 : ysz)*nregs*sizeof(u_short));
	curcm=cm;
        goto compute_npixvals;
    }
    count_pixels(0, (int)ysz-1, 0);
    icolors=count_colors(1L);
    if (icolors>nregs && threshold>1) {
        icolors=count_colors(threshold);
        if (icolors<nregs) {
            u_long mint, curt, maxt;

            printe("%d colors occur at least %ld times, trying to complete to %d\n",
             icolors, threshold, nregs);
            mint=1;
            maxt=threshold;
            while (maxt>mint+1) {
                curt=(mint+maxt)/2;
                icolors=count_colors(curt);
                if (icolors<nregs)
                    maxt=curt-1;
                else
                    mint=curt;
            }
            threshold=(mint+maxt)/2;
            icolors=count_colors(threshold);
            printe("%d colors occur at least %ld times\n", icolors, threshold);
        }
    }
    if (xmode==LORES || xmode==HIRES) {
        if (icolors<nregs)
            nplanes=ceillog2((u_long)icolors);
        else
            nplanes=ceillog2((u_long)nregs);
        nregs=1<<nplanes;
    }
    cm=Malloc(nregs*sizeof(u_short));
    curcm=cm;
compute_npixvals:
    if (distrmeth2!=DISTRMETH_UNSET) {
	cm0=Malloc(nregs*sizeof(u_short));
	cmprev=Malloc(nregs*sizeof(u_short));
    }
    npixvals=1<<nplanes;
    printe("%d colors", ticolors);
    if (icolors!=ticolors)
        printe(", counting %d colors", icolors);
    printe(", %d color registers, %d planes", nregs, nplanes);
    if (nregs-icolors>0)
        printe(", %d registers wasted", nregs-icolors);
    pute('\n');
    if (nregs<2 || xsz<2 || ysz<2)
	error0(E0_FATAL, E1_IFF, E2_FORMAT, E3_2SIMPLE);
    assert(nplanes>0 && nplanes<=MAXNPLANES);
    assert(nregs<=MAXNREGS);
}


void
write_header(void)
{
    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;
    u_long viewmodes;

    wrl(id('B', 'M', 'H', 'D'));
    wrl((long)sizeof(bmhd));
    bmhd.w=xsz;
    bmhd.h=ysz;
    bmhd.x=bmhd.y=0;
    bmhd.nplanes=nplanes;
    bmhd.masking=0;
    bmhd.compression=compression;
    bmhd.pad=0;
    bmhd.transpcol=0;
    bmhd.yaspect=11;
    if (xmode==HIRES && ymode==NOLACE)
        bmhd.xaspect=5;
    else if (xmode!=HIRES && ymode==LACE)
        bmhd.xaspect=20;
    else
        bmhd.xaspect=10;
    bmhd.pagew=bmhd.w;
    bmhd.pageh=bmhd.h;
    cwrite(&bmhd, sizeof(bmhd));
    if (directrgb>=0)
	return;
    wrl(id('C', 'A', 'M', 'G'));
    wrl(4L);
    viewmodes=0;
    if (xmode==HIRES)
        viewmodes|=VM_HIRES;
    else if (xmode==HAM)
        viewmodes|=VM_HAM;
    else if (xmode==EHB)
        viewmodes|=VM_EHB;
    if (ymode==LACE)
        viewmodes|=VM_LACE;
    wrl(viewmodes);
}


void
write_cmap(void)
{
    u_char *cmbuf;
    long t;
    short i, j, nr;
    static long pos= -1;

    if (directrgb>=0)
	return;
    if (xmode==EHB)
        nr=nregs/2;
    else
        nr=nregs;
    t=nr*3;
    if (pos== -1) {
        wrl(id('C', 'M', 'A', 'P'));
        wrl(t);
        pos=cseek_out(0L, 1);
    } else
        cseek_out(pos, 0);
    cmbuf=Malloc(t);
    for (i=0, j=0; i<nr; ++i) {
        cmbuf[j++]=(cm[i]&0x0f00)>>4;
        cmbuf[j++]= cm[i]&0x00f0;
        cmbuf[j++]=(cm[i]&0x000f)<<4;
    }
    cwrite(cmbuf, (int)t);
    free(cmbuf);
}


void
write_mmap(void)
{
	long t;
	static long pos= -1;

	if (!slicedo)
		return;
	if (xmode==EHB)
		error0(E0_FATAL, E1_IFF, E2_EHB, E3_SLICED_EHB);
	t=(ymode==LACE && sliced!=SLICED_DYN ? (ysz+1)/2 : ysz)*nregs*sizeof(u_short);
	if (pos== -1) {
		pos=cseek_out(0L, 2);
		switch (sliced) {
		case SLICED_SHAM: {
			short ver;

			wrl(id('S', 'H', 'A', 'M'));
			wrl(t+2);
			ver=0;
			cwrite(&ver, 2);
			cwrite(cm, (int)t);
		} break;
		case SLICED_DYN:
			wrl(id('C', 'T', 'B', 'L'));
			wrl(t);
			cwrite(cm, (int)t);
		break;
		case SLICED_PCHG:
			mmap_size_known=0;
		break;
		default:
			assert(0);
		break;
		}
	} else {
		switch (sliced) {
		case SLICED_SHAM:
			cseek_out(pos+10, 0);
			cwrite(cm, (int)t);
		break;
		case SLICED_DYN:
			cseek_out(pos+8, 0);
			cwrite(cm, (int)t);
		break;
		case SLICED_PCHG:
			cseek_out(pos, 0);
			write_1pchg();
		break;
		}
		if (nrestricted)
			printe("restricting per line changes to %d made %lu changes unsatisfiable\n",
			 restrict_changes, nrestricted);
	}
}


void
write_1pchg(void)
{
	short Compression, Flags, StartLine, LineCount;
	short ChangedLines, MinReg, MaxReg, MaxChanges;
	long TotalChanges;
	int firstchange, lastchange, nmaps, linemask_size, changes_size;
	u_short *cmptr, *cmptr2, *cmend;
	u_long *linemask;
	u_short *changes, *changesp, *changesp2;
	short i;
	int mapnum, regnum, nchanges, nchanges32;
	long linemask_index;

	assert(nregs<=32);
	nmaps=(ymode==LACE ? (ysz+1)/2 : ysz);
	/* compute firstchange, the mapnr of the first map different
	 * from the first map
	 */
	cmptr=cm;
	for (firstchange=0; firstchange<nmaps; ++firstchange) {
		cmptr2=cm;
		i=nregs-1;
		do {
			if (*cmptr++ != *cmptr2++)
				goto firstchange_found;
		} while (--i>=0);
	}
firstchange_found:
	if (firstchange>=nmaps)
		return;
	/* compute lastchange, the mapnr of the last map different
	 * from the last map, +1
	 */
	cmend=cm+nregs*nmaps;
	cmptr=cmend;
	for (lastchange=nmaps-1; lastchange>=0; --lastchange) {
		cmptr2=cmend;
		i=nregs-1;
		do {
			if (*--cmptr != *--cmptr2)
				goto lastchange_found;
		} while (--i>=0);
	}
lastchange_found:
	++lastchange;
	/* fill in some of the header fields, allocate the linemask */
	Compression=PCHG_COMP_NONE;
	Flags=PCHGF_12BIT;
	StartLine=(ymode==LACE ? 2*firstchange : firstchange);
	LineCount=(ymode==LACE ? 2*lastchange : lastchange)-StartLine+1;
	linemask_size=(LineCount+31)/32*4;
	linemask=Calloc(linemask_size);
	/* compute the other header fields, fill linemask */
	ChangedLines=0;
	MinReg=nregs-1;
	MaxReg=0;
	MaxChanges=0;
	TotalChanges=0;
	cmptr=cm+firstchange*nregs;
	for (mapnum=firstchange; mapnum<=lastchange; ++mapnum) {
		cmptr2=cmptr-nregs;
		nchanges=0;
		for (regnum=0; regnum<nregs; ++regnum) {
			if (cmptr[regnum]!=cmptr2[regnum]) {
				++nchanges;
				if (regnum<MinReg)
					MinReg=regnum;
				if (regnum>MaxReg)
					MaxReg=regnum;
			}
		}
		if (nchanges) {
			++ChangedLines;
			if (nchanges>MaxChanges)
				MaxChanges=nchanges;
			TotalChanges+=nchanges;
			linemask_index=mapnum-firstchange;
			if (ymode==LACE)
				linemask_index<<=1;
			linemask[linemask_index>>5]|=1L<<(31-(linemask_index & 0x1f));
		}
		cmptr+=nregs;
	}
	/* allocate the changes */
	changes_size=(int)ChangedLines*2+TotalChanges*2;
	changes=Malloc(changes_size);
	/* fill changes */
	cmptr=cm+firstchange*nregs;
	changesp=changes;
	for (mapnum=firstchange; mapnum<=lastchange; ++mapnum) {
		cmptr2=cmptr-nregs;
		nchanges=0;
		nchanges32=0;
		changesp2=changesp+1;
		for (regnum=0; regnum<nregs; ++regnum) {
			if (cmptr[regnum]!=cmptr2[regnum]) {
				if (regnum<16)
					++nchanges;
				else
					++nchanges32;
				*changesp2++ =(regnum<<12)|cmptr[regnum];
			}
		}
		if (nchanges+nchanges32) {
			*changesp=(nchanges<<8)|nchanges32;	/* BYTEORDER */
			changesp=changesp2;
		}
		cmptr+=nregs;
	}
	assert(changesp==(u_short *)((long)changes+changes_size));
	/* write the chunk */
	wrl(id('P', 'C', 'H', 'G'));
	wrl(20L+(long)linemask_size+(long)changes_size);
	wrs(Compression);
	wrs(Flags);
	wrs(StartLine);
	wrs(LineCount);
	wrs(ChangedLines);
	wrs(MinReg);
	wrs(MaxReg);
	wrs(MaxChanges);
	wrl(TotalChanges);
	cwrite(linemask, linemask_size);
	cwrite(changes, changes_size);
	/* deallocate data */
	free(linemask);
	free(changes);
}


void
write_body(void)
{
	static long tmpnum= -1;
	static long pos;

	if (tmpnum== -1) {
		if (mmap_size_known) {
			pos=cseek_out(0L, 2);
			write_1body();
			tmpnum= -2;
		} else {
			cout_tmp();
			write_1body();
			cout_default(&tmpnum, &pos); /* pos used as dummy here */
		}
	} else {
		long endpos;

		if (!mmap_size_known) {
			pos=cseek_out(0L, 2);
			ctmp_move(tmpnum);
		}
		endpos=cseek_out(0L, 2);
		cseek_out(pos+4, 0);
		wrl(endpos-pos-8);
	}
}


void
write_1body(void)
{
    int y, step;
    void (*outfunc)(int);

    if (directrgb<0)
	fillconv();
    if (xmode==HAM)
	assert(nplanes==6);
    if (!inoperation) {
        wrl(id('B', 'O', 'D', 'Y'));
        wrl(0L);
    }
    bytesperrow=(xsz+15)/16*2;
    noutrows=MAXOUTBUFSZ/(bytesperrow*nplanes);
    if (noutrows==0)
        noutrows=1;
    outbufsz=noutrows*bytesperrow*nplanes;
    outrows=Malloc(outbufsz);
    if (compression)
	outcrows=Malloc(outbufsz*3/2);
    body_size=0;
    curcm=cm;
    if (xmode==HAM) {
	fill_prgbtab();
        outfunc=ham_row_out;
    } else
        outfunc=row_out;
    if (directrgb>=0) {
	nzero=nplanes/3-4;
	if (nzero<0)
		nzero=0;
	ncolor=nplanes/3;
	if (ncolor>4)
		ncolor=4;
	init_counter(0, (int)ysz, 10, "writing RGB IFF");
	for (y=0; y<ysz; ++y)
		rgb_row_out(y);
    } else if (slicedo) {
        if (ymode==LACE && sliced!=SLICED_DYN)
            step=2;
        else
            step=1;
	init_counter(0, (int)ysz, 5, "writing multimap IFF");
        for (y=0; y<ysz; y+=step) {
            if (y==ysz-1)
                step=1;
            count_pixels(y, y+step-1, 0);
            icolors=count_colors(threshold);
            if (icolors<nregs && threshold>1)
                    printe("in row %d (and %d) only %d colors occur at least %ld times\n",
                     y, y+step-1, icolors, threshold);
            compute_distr(y, y+step-1);
            outfunc(y);
            if (step==2)
                outfunc(y+1);
            curcm+=nregs;
        }
    } else {
        compute_distr(0, (int)ysz-1);
	init_counter(0, (int)ysz, 10, "writing IFF");
        for (y=0; y<ysz; ++y)
            outfunc(y);
    }
    if (!inoperation)
        (void)next_row(1); /* to flush the row buffer */
    erase_counter(NULL);
}


void
fillconv(void)
{
    short i, j, k;
    u_char *p;

    conv=Malloc(npixvals*8*MAXNPLANES);
    p=conv;
    for (i=0; i<npixvals; ++i) {
        for (j=128; j; j>>=1) {
            for (k=0; k<nplanes; ++k) {
                if (i&(1<<k))
                    *p++ =j;
                else
                    *p++ =0;
            }
            p+=MAXNPLANES-nplanes;
        }
    }
}


int
cmpcolregs(short *p1, short *p2)
{
    short c1, c2, i;

    c1= *p1;
    c2= *p2;
    i=((c1>>8) + ((c1&0x00f0)>>4) + (c1&0x000f))
    - ((c2>>8) + ((c2&0x00f0)>>4) + (c2&0x000f));
    if (i)
        return (int)i;
    i=(c1>>8)-(c2>>8);
    if (i)
        return (int)i;
    i=((c1&0x00f0)>>4)-((c2&0x00f0)>>4);
    if (i)
        return (int)i;
    i=(c1&0x000f)-(c2&0x000f);
    return (int)i;
}


void
sort_cm(void)
{
	qsort((char *)curcm, (int)nregs, sizeof(u_short),
	 (int (*)(void *, void *))cmpcolregs);
}


static char *qm="?";

static char *sliceds[]={
    "ok",
    "not",
    "maybe",
    NULL
};

char *
slicednam(int i)
{
    if (i<0 || i>2)
        return qm;
    return sliceds[i];
}


static char *xmodes[]={
    "unset",
    "hires",
    "lores",
    "ehb",
    "ham",
    NULL
};

char *
xmodenam(int i)
{
    if (i<0 || i>4)
        return qm;
    return xmodes[i];
}


static char *ymodes[]={
    "unset",
    "lace",
    "nolace",
    NULL
};

char *
ymodenam(int i)
{
    if (i<0 || i>2)
        return qm;
    return ymodes[i];
}


/* should be in same order as DISTRMETH_ defines in wasp.h */
static char *dmeths[]={
    "unset",
    "mu",
    "wf",
    "ehb",
    "mue",
    "hs",
    "con",
    NULL
};

char *
dmethnam(int i)
{
    if (i<0 || i>6)
        return qm;
    return dmeths[i];
}

int
dmethnum(char *s)
{
	int i;

	for (i=1; dmeths[i]; ++i)
		if (!strcmp(s, dmeths[i]))
			return i;
	return -1;
}


/* should be in same order as COUNTMETH_ defines in wasp.h */
static char *cmeths[]={
    "unset",
    "all1",
    "alldif",
    "allfdif",
    "j1",
    "j21",
    "jdif",
    "jdifsh",
    "jfdif",
    "jfdifsh",
    "hmgs",
    "hmcubic",
    "hm",
    NULL
};

char *
cmethnam(int i)
{
    if (i<0 || i>12)
        return qm;
    return cmeths[i];
}

int
cmethnum(char *s)
{
	int i;

	for (i=1; cmeths[i]; ++i)
		if (!strcmp(s, cmeths[i]))
			return i;
	return -1;
}


static char *mmaps[]={
	"",
	"sliced ",
	"dynamic ",
	"multipalette ",
	NULL
};

char *
mmapnam(int i)
{
	assert(i>=0 && i<4);
	return mmaps[i];
}
