/* This XAD client is written by Kyzer/CSG <kyzer@4u.net>, the actual
 * decompression code is written by bzip2's author, Julian Seward. bzip2
 * is (C) 1996-2000 Julian R. Seward. Please see some of his files for more
 * information.
 *
 * License:
 * - The modified and unmodified files of libbzip2 are (C) Julian R. Seward
 *   and retain their original license. However, you should not directly
 *   use these modified versions, you should obtain the original bzip2
 *   and libbzip2 from http://sourceware.cygnus.com/bzip2/
 * - This slave and its source code is licensed to Dirk Stoecker for
 *   inclusion in his XAD distribution without further conditions.
 * - For all other people, this slave is licensed under the GNU General
 *   public license, version 2.
 *
 * Although this slave should work perfectly with an unmodifed libbzip2
 * compiled with BZ_NO_STDIO, I have made changes:
 * - all compression-related code is removed. It is unneccessary in a
 *   decompression-only slave.
 * - all high level functionality in bzlib.c is removed, leaving only
 *   low-level decompression code - bzDecompress(Init|%|End)
 * - the decompresser has been modified to try and enter 'small mode' if
 *   it cannot allocate enough memory for 'fast mode'.
 * - 'assertion failures' are turned into error codes, as this slave can't
 *   support the immediate exit() that libbzip2 would like.
 * - the random number table rNums has been made into Int16 instead of Int32
 */

/* bzip2 is actually just a file compressor, and doesn't even include any
 * details about the file itself - perhaps this should be an XFD slave
 * instead? A couple of reasons why not:
 *
 * 1. tar files are archives. gzip'ed tar files are archives too. 99% of
 *    bzip2 compressed files are tar archives. therefore, if gzip is an xad
 *    slave, bzip2 is an xad slave too.
 *
 * 2. bzip2 takes astonishing amounts of memory to unpack - it's far more a
 *    UNIX archive compressor than it is a realtime depacker for demos and
 *    games, which is the ethos of XFD compressors.
 */

/* bzip2 file format:
 * - first, the header: 'BZh1' to 'BZh9'
 *   - the 1-9 in the header is the size of a block / 100000
 *
 * - next, 0 or more compressed data blocks
 *   - the block starts with recogdata: $314159265359 (BCD pi :)
 *   - then comes a 4 byte CRC-so-far
 *   - then the various state tables
 *   - then the sorted randomized huffed RLE data
 *
 * - a final block containing no data is always included
 *   - it's recog is $177245385090, however after the first block
 *     the data is 87.5% likely not to be byte aligned, so you probably
 *     won't see it in a hex editor.
 *   - there's a 4 byte final CRC, but for the reason mentioned above,
 *     it's difficult to check.
 *
 * - minimum size of file is 14 bytes for a 0 byte input file (just a
 *   header and the final block, no data blocks), or 37 bytes for a 1 byte
 *   input file
 *
 * bzip2, by virtue of early recklessness and backwards compatibility on
 * the part of the author, does not contain *any* information whatsoever
 * about the data itself, it can't even know how long a block is without
 * decompressing it. But hey! It beats gzip on compression, so that's got
 * to be good, right?
 */

/* $VER: bzip2.c 1.2 (19.05.00) */

#include <exec/memory.h>
#include <string.h>

#include <libraries/xadmaster.h>
#include <proto/xadmaster.h>

#include "bzlib.h"

#include "SDI_compiler.h"
#define XADBASE  REG(a6, struct xadMasterBase *xadMasterBase)

#ifdef DEBUG
void KPrintF(char *fmt, ...);
#define D(x) x
#define BG KPrintF
#else
#define D(x)
#define BG
#endif


#ifndef XADMASTERFILE
#define bzip2_Client		FirstClient
#define NEXTCLIENT		0
const UBYTE version[] = "$VER: bzip2 1.2 (19.05.00)";
#endif
#define BZIP2_VERSION		1
#define BZIP2_REVISION		0


/*--- memory functions -----------------------------------------------------*/

struct xadMasterBase *xadBase;

struct mh {
  struct mh *next;
};

struct mh *initmem(XADBASE) {
  struct mh *base = (struct mh *) xadAllocVec(sizeof(struct mh), 0);
  xadBase = xadMasterBase;
  if (base) base->next = NULL;
  return base;
}

APTR allocmem(struct mh *base, ULONG size, ULONG flags) {
  struct xadMasterBase *xadMasterBase = xadBase;
  struct mh *mem = (struct mh *) xadAllocVec(size + sizeof(struct mh), flags);
  if (!mem) return NULL;
  mem->next = base->next; /* link into list */
  base->next = mem;
  return (APTR) ++mem;
}

void freemem(struct mh *base, APTR mem) {
  struct xadMasterBase *xadMasterBase = xadBase;
  struct mh *m, *x, *o;

  if (!mem || !base) return;
  m = (struct mh *) mem;  m--; /* get correct address */

  for (o = base; (x = o->next); o = x) {
    if (x == m) {
      o->next = x->next;
      xadFreeObjectA((APTR)x, NULL);
      return;
    }
  }
}

void freeallmem(struct mh *base) {
  struct xadMasterBase *xadMasterBase = xadBase;
  while (base) {
    struct mh *next = base->next;
    xadFreeObjectA((APTR)base, NULL);
    base = next;
  }
}


/*--- xad slave -------------------------------------------------------------*/

ASM(BOOL) bzip2_RecogData(REG(d0, ULONG size), REG(a0, STRPTR data), XADBASE) {

  /* check file header */
  if (data[0] != 'B' || data[1] != 'Z' || data[2] != 'h') return 0;
  if (data[3] <  '1' || data[3] > '9') return 0;

  /* check first block header */

  if (data[4] == 0x31) {
    if (data[5] != 0x41 || data[6] != 0x59) return 0;
    if (data[7] != 0x26 || data[8] != 0x53 || data[9] != 0x59) return 0;
  }
  else {
    if (data[4] != 0x17 || data[5] != 0x72 || data[6] != 0x45) return 0;
    if (data[7] != 0x38 || data[8] != 0x50 || data[9] != 0x90) return 0;
  }  

  /* we're happy with it */
  return 1;
}


ASM(LONG) bzip2_GetInfo(REG(a0, struct xadArchiveInfo *ai), XADBASE) {
  struct TagItem datetags[] = {
    { XAD_DATECURRENTTIME, 1 },
    { XAD_GETDATEXADDATE,  0 },
    { TAG_DONE, 0 }
  };

  /* there's only one file in a bzip archive - the uncompressed data */
  struct xadFileInfo *fi;

  fi = (struct xadFileInfo *) xadAllocObjectA(XADOBJ_FILEINFO, NULL);
  if (!(ai->xai_FileInfo = fi)) return XADERR_NOMEMORY;

  fi->xfi_Flags       = XADFIF_NODATE     | XADFIF_NOUNCRUNCHSIZE 
                      | XADFIF_NOFILENAME | XADFIF_SEEKDATAPOS;
  fi->xfi_EntryNumber = 1;
  fi->xfi_FileName    = xadMasterBase->xmb_DefaultName;
  fi->xfi_CrunchSize  = ai->xai_InSize - 14;
  fi->xfi_Size        = 0;
  fi->xfi_DataPos     = 0;

  /* fill in today's date */
  datetags[1].ti_Data = (ULONG) &fi->xfi_Date;
  xadConvertDatesA(datetags);

  return XADERR_OK;
}


/* bz2lib support functions */
void bz_internal_error() { }
void *bzalloc(void *base, int a, int b) {
  return allocmem((struct mh *)base, a*b, 0);
}
void bzfree(void *base, void *mem) {
  freemem((struct mh *)base, mem);
}


ASM(LONG) bzip2_UnArchive(REG(a0, struct xadArchiveInfo *ai), XADBASE) {
  struct mh *base;
  char *buf_in, *buf_out;
  const int bufsize = 8192;
  int size;
  bz_stream bzs;
  LONG err, bzerr;

  /* ensure we're decrunching our only file! */
  if (!ai || !ai->xai_CurFile || ai->xai_CurFile->xfi_EntryNumber != 1)
    return XADERR_BADPARAMS;

  /* initialise memory system */
  ai->xai_PrivateClient = (APTR) (base = initmem(xadMasterBase));
  bzs.opaque    = base;
  bzs.bzalloc   = bzalloc;
  bzs.bzfree    = bzfree;

  /* allocate some buffers */
  buf_in  = allocmem(base, bufsize, MEMF_PUBLIC);
  buf_out = allocmem(base, bufsize, MEMF_PUBLIC);
  if (!buf_in || !buf_out) return XADERR_NOMEMORY;

 /* force immediate read-in of data on first run only */
  bzs.avail_in = 0;

  /* always keep going - exit is only when demand passes EOF */
  while (1) {
    /* initialise the decompression state */
    if ((bzerr=BZ2_bzDecompressInit(&bzs, 0, 0)) != BZ_OK)
      return XADERR_NOMEMORY;

    /* reset output buffer */
    bzs.avail_out = bufsize;
    bzs.next_out  = buf_out;

    while (bzerr == BZ_OK) {
      /* fill input buffer when empty */
      if (bzs.avail_in == 0) {
        size = ai->xai_InSize - ai->xai_InPos;
        if (size > bufsize) size = bufsize;
        if (size == 0) return XADERR_OK; /** normal exit point = EOF **/
        if((err = xadHookAccess(XADAC_READ, size, buf_in, ai))) return err;
        bzs.next_in  = buf_in;
        bzs.avail_in = size;
      }

      /* do some more decompression */
      bzerr = BZ2_bzDecompress(&bzs);

      /* purge any output */
      if ((size = bufsize - bzs.avail_out) > 0) {
        if((err = xadHookAccess(XADAC_WRITE, size, buf_out, ai))) return err;
        bzs.next_out  = buf_out;
        bzs.avail_out = bufsize;
      }
    }

    switch (bzerr) {
    case BZ_STREAM_END:       BZ2_bzDecompressEnd(&bzs); break;
    case BZ_DATA_ERROR:
    case BZ_DATA_ERROR_MAGIC: return XADERR_ILLEGALDATA;
    case BZ_MEM_ERROR:        return XADERR_NOMEMORY;
    default:                  return XADERR_DECRUNCH;
    }
  }
  return XADERR_DECRUNCH; /* not reached! */
}


ASM(void) bzip2_Free(REG(a0, struct xadArchiveInfo *ai), XADBASE) {
  freeallmem((struct mh *) ai->xai_PrivateClient);
  ai->xai_PrivateClient = NULL;
}


const struct xadClient bzip2_Client = {
  NEXTCLIENT, XADCLIENT_VERSION, 6, BZIP2_VERSION, BZIP2_REVISION,

  14,						/* minimum archive size     */
  XADCF_FILEARCHIVER | XADCF_FREEFILEINFO,	/* type of client and flags */
  0,						/* internal ID (not needed) */
  "BZip2",					/* name of client           */

  /* client functions */
  (BOOL (*)()) bzip2_RecogData,
  (LONG (*)()) bzip2_GetInfo,
  (LONG (*)()) bzip2_UnArchive,
  (void (*)()) bzip2_Free
};
