#include "iff.h"

/* WriteILBM.c: Generates 2-24 bit IFF files
        (c) 1991 Dallas J. Hodgson          */

SafeWrite(BPTR fp,void *buf,int length)
{
  return((Write(fp,buf,length)==-1) ? TRUE:FALSE);
}

WriteILBM(char *fspec,struct PicMap *picmap)
{
  BPTR fp=NULL;
  BitMapHeader bmhd;
  Chunk header;
  long id;
  int marker,bodylen,eof,err=TRUE;
  short numCols=1<<picmap->BitMap.Depth;

  if (!(fp=Open(fspec,MODE_NEWFILE))) goto cleanup;

  header.ckID=ID_FORM;
  header.ckSize=0;      /* GETS CORRECTED LATER */

  if (SafeWrite(fp,&header,sizeof(header))) goto cleanup;

  id=ID_ILBM;
  if (SafeWrite(fp,&id,sizeof(id))) goto cleanup;

  header.ckID=ID_BMHD;
  header.ckSize=sizeof(BitMapHeader);
  if (SafeWrite(fp,&header,sizeof(header))) goto cleanup;

  bmhd.w=picmap->BitMap.BytesPerRow*8;
  bmhd.h=picmap->BitMap.Rows;
  bmhd.x=bmhd.y=0;
  bmhd.nPlanes=picmap->BitMap.Depth;
  bmhd.masking=mskNone;
  bmhd.compression=cmpByteRun1;
  bmhd.pad1=0;
  bmhd.transparentColor=0;
  bmhd.xAspect=1;
  bmhd.yAspect=1;
  bmhd.pageWidth=bmhd.w;
  bmhd.pageHeight=bmhd.h;

  if (SafeWrite(fp,&bmhd,sizeof(bmhd))) goto cleanup;

  if (picmap->BitMap.Depth<=8) {

    header.ckID=ID_CMAP;
    header.ckSize=numCols*3;

    if (SafeWrite(fp,&header,sizeof(header))) goto cleanup;

    if (SafeWrite(fp,&picmap->palette,numCols*3))
      goto cleanup;

    if (SafePad(fp,header.ckSize)) goto cleanup;
  }

  header.ckID=ID_CAMG;
  header.ckSize=sizeof(picmap->ViewModes);

  if (SafeWrite(fp,&header,sizeof(header))) goto cleanup;
  if (SafeWrite(fp,&picmap->ViewModes,sizeof(picmap->ViewModes))) goto cleanup;  

  header.ckID=ID_BODY;
  header.ckSize=0;      /* GETS CORRECTED LATER */

  if ((marker=Seek(fp,0,OFFSET_CURRENT))==-1) goto cleanup;

  if (SafeWrite(fp,&header,sizeof(header))) goto cleanup;

  if (!(bodylen=WriteBody(fp,picmap))) goto cleanup;
  if (SafePad(fp,bodylen)) goto cleanup;

  if ((eof=Seek(fp,0,OFFSET_CURRENT))==-1) goto cleanup;

  /* Go back and fix the two size values */

  if (Seek(fp,marker,OFFSET_BEGINNING)==-1) goto cleanup;

  header.ckID=ID_BODY;
  header.ckSize=bodylen;

  if (SafeWrite(fp,&header,sizeof(header))) goto cleanup;

  if (Seek(fp,0,OFFSET_BEGINNING)==-1) goto cleanup;

  header.ckID=ID_FORM;
  header.ckSize=eof-sizeof(header);
  if (SafeWrite(fp,&header,sizeof(header))) goto cleanup;

  err=FALSE;
cleanup:
  if (fp) Close(fp);

  return(err);
}

#define WRITE_SIZE 8192
#define MAX_COMPRESSED_LINE 2048

WriteBody(BPTR fp,struct PicMap *picmap)
{
  unsigned char *rawbuf,*dstPtr,*packbuf=NULL;
  short line,plane,count,bytelen;
  short w=picmap->BitMap.BytesPerRow*8,h=picmap->BitMap.Rows;
  int total=0,len=0;

  if (!(packbuf=AllocMem(WRITE_SIZE+MAX_COMPRESSED_LINE,MEMF_PUBLIC))) {
#ifdef DEBUG
    printf("WriteBody: couldn't allocate packbuf\n");
#endif
    goto cleanup;
  }

  /* roundup width to a multiple of 16 pixels, if necessary; important
     for deinterleaving brushes correctly! */

  if (w%16) w=(((w/16)+1)*16);
  bytelen=w/8;

  /* Read each line from display, 1 plane at a time. Optionally compress
     each bitplane separately, and write each bitplane out. */

  dstPtr=packbuf;

  for (line=0;line<h;line++) {

    for (plane=0;plane<picmap->BitMap.Depth;plane++) {
      rawbuf=picmap->BitMap.Planes[plane]+(picmap->BitMap.BytesPerRow*line);

      /* A worst-case compression would result in the output being TWICE
         the size of the input data. If the compressed version is larger
         than the original, then write the uncompressed version.
       */

      if ((count=PackBits2((char *)rawbuf,(char *)dstPtr,bytelen))<=bytelen) {
        dstPtr+=count;
        len+=count;
        total+=count;
      }
      else {
        count=PackUncompressedBits(rawbuf,dstPtr,bytelen);
        dstPtr+=count;
        len+=count;
        total+=count;
      }

      if (len >= WRITE_SIZE) {
        if (SafeWrite(fp,packbuf,len)) {
          total=0;
          goto cleanup;
        }
        len=0;
        dstPtr=packbuf;
      }
    }
  }

  /* Flush out anything left in the output buffer after the last pass */
  if (len)
    if (SafeWrite(fp,packbuf,len)) total=0;

cleanup:
  if (packbuf) FreeMem(packbuf,WRITE_SIZE+MAX_COMPRESSED_LINE);
  return(total);
}

SafePad(BPTR fp,int len)
{
  static char pad=0;

  if (len & 1)
    if (SafeWrite(fp,&pad,1)) return(TRUE);

  return(FALSE);
}

/*
 * PRIVATE: PackUncompressedBits is an uncompressed stream writer, suitable
 * for those occasions when RLE encoding just doesn't do the job.
 */
  int
PackUncompressedBits(unsigned char *src,unsigned char *dst,int size)
{
  unsigned char *orig=dst;
  int len;

  while(size) {
    if (size<128) len=size; else len=127;
    *dst++ = len-1;
    CopyMem((char *)src,(char *)dst,len);
    src+=len;
    dst+=len;
    size-=len;
  }

  return(dst-orig);
}

#define MAXRUN 127

/*
 * PRIVATE: PackBits2 is derived from the original Macintosh compress
 * routine, but modified so the maximum compressed run is 127 instead
 * of 128 bytes. According to the IFF docs, a compressed run of 128 is
 * illegal, tho' possible if your unpacker routine uses unsigned int
 * (instead of signed char) comparisons for the expansion.
 *
 * Runs longer than 127 bytes are permissible, but will be broken up
 * into smaller runs of 127 bytes or less.
 */
  int
PackBits2(char *src,char *dst,int size)
{
  char c;
  char *sp, *dp, *start;
  int runChar;
  int runCount,nonRunCount;

  /*
   * Initialize.
   */
  sp = src;
  dp = dst;
  start = sp;
  runChar = *sp++;
  --size;
  runCount = 1;
  /*
   * Loop over all input bytes.
   */
  while (size > 0) {
    c = *sp++;
    --size;
    if (c == runChar) {
      ++runCount;
    }
    else {
      /*
       *  This is the end of a run of bytes (possibly a very short run).
       *  Figure out what to do with it.
       */
      if (runCount >= 3) {
        /*
         *  If the run length is greater than three, compress it.  Runs
         *  of smaller than three are treated as non-runs.
         */
        nonRunCount = (sp - 1) - start - runCount;
        /*
         *  First, output any accumulated non-run bytes.
         */
        if (nonRunCount > 0) {
          *dp++ = nonRunCount - 1;
          while (nonRunCount--) {
            *dp++ = *start++;
          }
        }
        /*
         *  Now output the compressed run.  Since the max run length we
         *  can encode is MAXRUN, longer runs must be segmented.
         */
        while (runCount > MAXRUN) {
          /*
           *  Loop to output segments of runs longer that MAXRUN.
           */
           *dp++ = -(MAXRUN - 1);
           *dp++ = runChar;
           runCount -= MAXRUN;
        }
        /*
         *  Output the last (or only) run of length MAXRUN or less.
         */
        *dp++ = -(runCount - 1);
        *dp++ = runChar;
        start = sp - 1;
      }
      /*
       *  Get ready for the next time through this loop.
       */
      runChar = c;
      runCount = 1;
    }
  }
  /*
   *  We've reached the end of the source data.  Now we have to flush
   *  out data we haven't dealt with yet.  This code is almost identical
   *  to the code inside the main loop, above.
   */
  nonRunCount = sp - start;
  if (runCount >= 3) nonRunCount -= runCount;
  else runCount = 0;
  /*
   *  Output non-run data.
   */
  if (nonRunCount) {
    *dp++ = nonRunCount - 1;
    while (nonRunCount--) {
      *dp++ = *start++;
    }
  }
  /*
   *  Output compressed run.
   */
  if (runCount) {
    while (runCount > MAXRUN) {
      *dp++ = -(MAXRUN - 1);
      *dp++ = runChar;
      runCount -= MAXRUN;
    }
    *dp++ = -(runCount - 1);
    *dp++ = runChar;
  }
  /*
   * Clean up and return.
   */

  return(dp-dst);
}
