/*************************************************************************
 ***                  ILBM IMAGE READING MODULE                        ***
 *************************************************************************/

#define ILBM_PARENT 1

#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <string.h>
#include <stdio.h>
#include "readILBM.h"

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
#define POW2(a) (1<<(a))

/* READ flags */
#define READ_ERASE   0
#define READ_CMAP    1
#define READ_BODY    (1 << 1)
#define READ_GRAB    (1 << 2)
#define READ_DEST    (1 << 3)
#define READ_SPRT    (1 << 4)
#define READ_CAMG    (1 << 5)
#define READ_CRNG    (1 << 6)
#define READ_CCRT    (1 << 7)
#define READ_BMHD    (1 << 8)

/* WRITE flags */
#define WRITE_ERASE   0
#define WRITE_CMAP    1
#define WRITE_BODY    (1 << 1)
#define WRITE_GRAB    (1 << 2)
#define WRITE_DEST    (1 << 3)
#define WRITE_SPRT    (1 << 4)
#define WRITE_CAMG    (1 << 5)
#define WRITE_CRNG    (1 << 6)
#define WRITE_CCRT    (1 << 7)
#define WRITE_BMHD    (1 << 8)

typedef ULONG chunkpre[2];

FILE            *ILBMfp;
struct BitMap   *theBM;
USHORT          *thecmap;

BitMapHeader     theBMHD;
Point2D          theGRAB;
DestMerge        theDEST;
SpritePrecedence theSPRT;
ILBMViewMode     theCAMG;
CRange           theCRNG;
CycleInfo        theCCRT;

#define MAX_COLORS  32
UWORD            ILBM_Colors[MAX_COLORS];

int     readflags = READ_ERASE;
int     writeflags = WRITE_ERASE;        
int     mask;              /* Added to number of planes to read.
                            * 0 = nomask, 1  = mask plane there. */
char    *ILBMMemBase;
int     bufptr;
int     endbuf;
int     bufsize;

#define NUM_ERRORS  17
char    *errors[NUM_ERRORS] = {
    "ILBM unknown error number!",
    "File not found",
    "File not a FORM",
    "File is a FORM, but not for ILBM",
    "File does not have a BMHD chunk",
    "File image has bad width or height",
    "File image has unknown masking requirements",
    "File image claims to use unknown compression",
    "File image is meant to be a sprite",
    "File hasn't got image data",
    "File has no color information",
    "File DOS error",
    "File BMHD chunk has incorrect length",
    "File BODY chunk has incorrect length",
    "File has a bad row in a scanline",
    "File has a bad unit value in a row",
    "File image depth too large"};

/******************** FUNCTION PREDEFS ***********************************/
char    *ILBMerror();   /* Returns pointer to string describing error #. */
int     getfile();      /* Gets a file pointer. (Will try for .ILBM too) */
int     subreadILBM();  /* Read the file. Just a FORM.ILBM please...     */
int     readbody();     /* Reads the body chunk. Will decompress.        */
int     readILBM();     /* Wrap function around subreadILBM. Added later.*/
int     myread();
int     myseek();
int     readBMHD();
int     readCMAP();
BitMapHeader    *getBMHD();

char    *ILBMerror(er) /*================================================*/
int     er;
{
    if ((er > 0) && (er < NUM_ERRORS))
        return(errors[er]);
    else return(errors[0]);
}

int     getfile(cp) /*===================================================*/
char    *cp;
{
char    *buf;
int     j;

    if (ILBMfp = fopen(cp,"rb")) return(1);
    j = strlen(cp) + 5;
    if ((buf = (char *)AllocMem(j,0)) == NULL) return(0);
    strcpy(buf,cp);
    strcat(buf,".ILBM");
    if (!(ILBMfp = fopen(buf,"rb"))) {
        strcpy(&buf[j-5],".pic");
        ILBMfp = fopen(buf,"rb");}
    FreeMem(buf,j);
    return((ILBMfp == NULL) ? 0:1);
}

int     ucreadbody() /*==================================================*/
{
register int    i,j,w,w2;
register int    k = -theBM->BytesPerRow;
register UBYTE *sp;
int     mymask = mask;
int     jmin;

    w = MIN(theBMHD.w >> 3,theBM->BytesPerRow);
    w2 = (theBMHD.w >> 3) - w;
    jmin = MIN(theBMHD.nPlanes,theBM->Depth);
    if (theBM->Depth < theBMHD.nPlanes) mymask += (theBMHD.nPlanes - theBM->Depth);
    mymask *= (theBMHD.w >> 3);
    for (i = 0; i < MIN(theBMHD.h,theBM->Rows); i++) {
        k += theBM->BytesPerRow;
        for (j = 0; j < jmin; j++) {
            sp = (UBYTE *)(theBM->Planes[j]) + k;
            if (myread((char *)sp,w) < w) return(BODY_BADLENGTH);
            if (w2 > 0) myseek(w2);
        }
        if (mymask) myseek(mymask);
    }
    return(0);
}

int     readbody() /*====================================================*/
{
register int    c,i,j,k;
int             uv,w;
register USHORT *cp,*cp2;
UBYTE   data[9][86];        /* Got an extra line for mask */
    /* NOTE: MUST be an even number in the second dimension! Otherwise
     * cp2 cannot be a valid pointer to odd addresses, and the thing will
     * crash! The reason I want cp, cp2 to point to USHORTs is to increase
     * the copy bandwidth when copying from data[][] to the bitmap. */
char   unit;

    if (!theBMHD.compression) return(ucreadbody());
    w = theBMHD.w >> 3;
    for (i = 0; i < MIN(theBMHD.h,theBM->Rows); i++) { /* for every scanline */
        for (j = 0; j < theBMHD.nPlanes + mask; j++) { /* for every plane */
            c = 0;
            while (c < w) {
                myread(&unit,1);
                uv = (int)unit;
                if ((uv < 0) && (uv > -128)) {
                    uv = (-uv) + 1;
                    if (c + uv > w) return(BODY_BADLINE);
                    myread(&unit,1);
                    for (k = c; k < c + uv; k++)
                        data[j][k] = unit;
                    c += uv;}
                else {
                    if ((uv >= 0) && (uv < 128)) {
                        uv++;
                        if (c + uv > w) return(BODY_BADLINE);
                        if (myread((char *)&data[j][c],uv) < uv)
                            return(BODY_BADLENGTH);
                        c += uv;}
                    else if (uv != -128) return(BODY_BADUNIT);
                } /* end else */
            } /* end while */
        } /* end for (j) -> done planes*/
        /* If I could get rid of this next bit altogether... */
        uv = (i * theBM->BytesPerRow)/2;
        for (j = 0; j < MIN(theBMHD.nPlanes,theBM->Depth); j++) {
            cp = (USHORT *)(theBM->Planes[j]) + uv;
            cp2 = (USHORT *)(&data[j][0]);
            /* Could use more variables to speed up this */
            for (k = 0; k < MIN(theBM->BytesPerRow,w)/2; k++)
                cp[k] = cp2[k];
        }
    } /* end for (i) */
    return(0);      /* If I got this far, then I succeeded! */
}

int     subreadILBM(bm,cmp) /*===========================================*/
struct BitMap   *bm;
USHORT          *cmp;
{
chunkpre    item;
int         loop,j;

    theBM = bm;     thecmap = cmp;
    if (cmp == NULL) readflags = readflags | READ_CMAP;
    if (myread((char *)item,8) <= 0)    return(ILBM_FILE_ERROR);
    if (item[0] != TYPE_FORM)           return(ILBM_NOTFORM);

  /* Set up file buffer. Buffers complete form */
    bufsize = item[1] + 100;
    if (ILBMMemBase = (char *)AllocMem(bufsize,MEMF_CLEAR)) {
        endbuf = fread(ILBMMemBase,1,bufsize,ILBMfp);
        bufptr = 0;}

    if (myread((char *)item,4) <= 0)     return(ILBM_FILE_ERROR);
    if (item[0] != TYPE_ILBM)            return(ILBM_NOTILBMFORM);
    if (myread((char *)item,8) <= 0)     return(ILBM_FILE_ERROR);
    if (item[0] != TYPE_BMHD)            return(ILBM_NOBMHD);
    if (item[1] != sizeof(BitMapHeader)) return(ILBM_BADBMHD);
    if (j = readBMHD()) return(j);

    loop = 1;
    while (loop) {
        if (myread((char *)item,8) <= 0) loop = 0;        /* Assume EOF */
        else switch (item[0]) {
            case (TYPE_CMAP): /* Read color map */
                if (j = readCMAP(item[1])) return(j);
                break;
            case (TYPE_SPRT):       /* Oh No - (its DEVO) Got a sprite ! */
                return(ILBM_SPRITE);
            case (TYPE_BODY):
                if ((j = readbody()) > 0)
                    return(j);
                readflags = readflags | READ_BODY;
                loop = 0;
                break;
            default:if (myseek(item[1]))           /* Skip chunk */
                        return(ILBM_FILE_ERROR);
	}
    }
    if (!(readflags & READ_BODY)) return(ILBM_NOBODY);
    if (!(readflags & READ_CMAP)) return(ILBM_NOCOLORS);
    return(0);
}

int     readILBM(bm,cmp,cp) /*===========================================*/
struct BitMap   *bm;
USHORT          *cmp;
char            *cp;
{
int     r;

    if (!getfile(cp)) return(ILBM_NOFILE);
    ILBMMemBase = NULL;                 /* Moved AllocMem to subreadILBM(),
        * so that the amount allocated could be based on the file size,
        * rather than a fixed amount. */
    r = subreadILBM(bm,cmp);
    fclose(ILBMfp);
    if (ILBMMemBase) FreeMem(ILBMMemBase,bufsize);
    return(r);
}

int     myread(cp,x) /*==================================================*/
char    *cp;         /* Read x characters into cp */
int     x;
{
register int    i,j;

    if (ILBMMemBase) {
        i = bufptr;
        bufptr += x;
        if (bufptr > endbuf) return(0);
            /* Don't want to return error on last byte. Will have read 
             * trash if start bufptr + x was greater than endbuf. */
        for (j = 0; j < x; j++)
            cp[j] = ILBMMemBase[i++];
        return(x);}
    else return(fread(cp,1,x,ILBMfp));
}

int     myseek(x) /*=====================================================*/
int     x;
{
    if (ILBMMemBase) {
        bufptr += x;
        return((bufptr > endbuf)? 1:0);
    }
    else return(fseek(ILBMfp,x,1));
}

int     readBMHD() /*====================================================*/
{
    if (myread((char *)&theBMHD,sizeof(BitMapHeader)) <= 0)
        return(ILBM_FILE_ERROR);

    switch (theBMHD.masking) {
        case (mskNone):                 mask = 0; break;
        case (mskHasMask):              mask = 1; break;
        case (mskHasTransparentColor):  mask = 0; break;
        case (mskLasso):                mask = 0; break;
        default:                        return(ILBM_MASKSET);
    }
    if ((theBMHD.compression < 0) || (theBMHD.compression > 1))
        return(ILBM_COMPUNKNOWN);
    if (theBMHD.w > 640)     return(ILBM_BADIMAGE);
    if (theBMHD.nPlanes > 8) return(ILBM_TOODEEP);
    readflags = readflags | READ_BMHD;
    return(0);
}

int     readCMAP(length) /*==============================================*/
ULONG   length;
{
int     i,j;
ColorRegister   aCR;

    if (thecmap == NULL) {
        if (myseek(length))
            return(ILBM_FILE_ERROR);
        return(0);}
    i = 0;      /* use i to count bytes read in */
    for (j = 0; j < (int)length/3; j++) {
        if (myread((char *)&aCR,3) <= 0)
            return(ILBM_FILE_ERROR);
        i += 3;
        if (j < POW2(theBMHD.nPlanes))
            thecmap[j] = ((aCR.red>>4)<<8)|((aCR.green>>4)<<4)|(aCR.blue>>4);
    }
    if (i < length)
        if (myseek((length-i)))
            return(ILBM_FILE_ERROR);
    for (; j < POW2(theBM->Depth); j++) thecmap[j] = 0x0000;
    readflags = readflags | READ_CMAP;
    return(0);
}

BitMapHeader    *getBMHD(cp) /*==========================================*/
char    *cp;
{
chunkpre    item;

    if (!getfile(cp)) return(NULL);
    if (fread((char *)item,1,8,ILBMfp) != 8) goto ERROR;
    if (item[0] != TYPE_FORM) goto ERROR;
    if (fread((char *)item,1,4,ILBMfp) != 4) goto ERROR;
    if (item[0] != TYPE_ILBM) goto ERROR;
    if (fread((char *)item,1,8,ILBMfp) != 8) goto ERROR;
    if ((item[0] != TYPE_BMHD) || (item[1] != sizeof(BitMapHeader)))
        goto ERROR;
    if (fread((char *)&theBMHD,1,item[1],ILBMfp) != item[1]) goto ERROR;
    fclose(ILBMfp);
    return(&theBMHD);
ERROR:
    fclose(ILBMfp);
    return(NULL);
}
