/*
** xpkBZP2.library using libbz2 by Julian R Seward
**
** based on xpkGZIP.library by Gunther Nikl (gnikl@informatik.uni-rostock.de)
*/

/* Scramble unwanted alloc prototypes */
#define malloc scram1
#define free scram2

#include <xpk/xpksub.h>
#include <exec/memory.h>
#include <proto/intuition.h>
#include "xpkBZP2.h"
#include "bzlib.h"

#undef malloc
#undef free

/* Reserved library-function and dummy entrypoint */
LONG LIB_Reserved(void)
{
	return 0;
}

struct ExecBase    *SysBase;

ULONG LibVectors[] = {
	FUNCARRAY_32BIT_NATIVE,
	(ULONG) &LIB_Open,
	(ULONG) &LIB_Close,
	(ULONG) &LIB_Expunge,
	(ULONG) &LIB_Reserved,

	(ULONG) &LIBXpksPackerInfo,
	(ULONG) &LIBXpksPackChunk,
	(ULONG) &LIBXpksPackFree,
	(ULONG) &LIBXpksPackReset,
	(ULONG) &LIBXpksUnpackChunk,
	(ULONG) &LIBXpksUnpackFree,
	0xFFFFFFFF
};

struct LibInitStruct
{
	ULONG	LibSize;
	void	*FuncTable;
	void	*DataTable;
	void	(*InitFunc)(void);
};

struct LibInitStruct LibInitStruct = {
	sizeof(struct XpkSubBase),
	LibVectors,
	NULL,
	(void (*)(void)) &LibInit
};

static const char LibVersion[] = "$VER: " LIBID;

static const struct Resident RomTag = {
	RTC_MATCHWORD,
	&RomTag,
	&RomTag + 1,
	RTF_PPC | RTF_AUTOINIT,
	LIBVER,
	NT_LIBRARY,
	0,
	LIBNAME,
	&LibVersion[6],
	&LibInitStruct
};


ULONG __amigappc__ = 1;

/* Kill ANSI alloc functions, they're not used */
void (*malloc)(void) = NULL;
void (*free)(void) = NULL;

void bz_internal_error (int errcode)
{
	/* libbz2 goofed bigtime, let's throw a requester saying so */
	struct IntuitionBase *IntuitionBase;

	if ((IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 36)))
	{
		struct EasyStruct req = {
			sizeof(struct EasyStruct),
			0,
			"xpkBZP2: Internal Error!",
			"An internal error occurred in libbz2: %ld\n\nThis is a bug in libbz2, please report\nit to Julian Seward <jseward@acm.org>!\n\nIf you answer this requester, operation will\nbe resumed, but it's likely that you'll crash.",
			"I'm feeling lucky tonight"
		};

		EasyRequest(NULL, &req, NULL, errcode, NULL);

		CloseLibrary((struct Library *)IntuitionBase);
	}
}

BPTR LibExpunge(struct XpkSubBase *XpkSubBase)
{
	if ((XpkSubBase->xsb_Lib.lib_OpenCnt == 0))
	{
		BPTR seglist;

		seglist = XpkSubBase->xsb_SegList;
		Remove(&XpkSubBase->xsb_Lib.lib_Node);

		FreeMem((APTR)((ULONG)(XpkSubBase) - (ULONG)(XpkSubBase->xsb_Lib.lib_NegSize)),
				XpkSubBase->xsb_Lib.lib_NegSize + XpkSubBase->xsb_Lib.lib_PosSize);

		return seglist;
	} else XpkSubBase->xsb_Lib.lib_Flags |= LIBF_DELEXP;

	return 0L;
}

struct Library *LibInit(struct XpkSubBase *XpkSubBase, BPTR SegList, struct ExecBase *sysbase)
{
	SysBase = sysbase;

	XpkSubBase->xsb_Lib.lib_Revision     = LIBREV;
	XpkSubBase->xsb_SegList              = SegList;
	XpkSubBase->xsb_SysBase              = SysBase;

	return &XpkSubBase->xsb_Lib;
}

struct Library *LIB_Open(void)
{
	struct XpkSubBase *XpkSubBase = (struct XpkSubBase *)REG_A6;

	XpkSubBase->xsb_Lib.lib_Flags &= ~LIBF_DELEXP;
	XpkSubBase->xsb_Lib.lib_OpenCnt++;

	return &XpkSubBase->xsb_Lib;
}

BPTR LIB_Close(void)
{
	struct XpkSubBase *XpkSubBase = (struct XpkSubBase *)REG_A6;

	if (XpkSubBase->xsb_Lib.lib_OpenCnt > 0) XpkSubBase->xsb_Lib.lib_OpenCnt--;

	if ((XpkSubBase->xsb_Lib.lib_OpenCnt == 0) && (XpkSubBase->xsb_Lib.lib_Flags & LIBF_DELEXP))
		return LibExpunge(XpkSubBase);

	return 0L;
}

BPTR LIB_Expunge(void)
{
	struct XpkSubBase *XpkSubBase = (struct XpkSubBase *)REG_A6;

	return LibExpunge(XpkSubBase);
}

/*
** a shortcut
*/

typedef struct XpkSubParams XPAR;

/*
** required information structures
*/

#define BZIPMODE(x) (struct XpkMode *)(BZIPModes+x)

static const struct XpkMode BZIPModes[] = {
  { BZIPMODE(1),  19, /*XPKMF_A3000SPEED*/ 0, 1171*1024,  488*1024, 85, 318, 795, 0, "100K -1" },
  { BZIPMODE(2),  29, /*XPKMF_A3000SPEED*/ 0, 1953*1024,  878*1024, 86, 315, 802, 0, "200K -2" },
  { BZIPMODE(3),  39, /*XPKMF_A3000SPEED*/ 0, 2734*1024, 1269*1024, 85, 313, 804, 0, "300K -3" },
  { BZIPMODE(4),  49, /*XPKMF_A3000SPEED*/ 0, 3515*1024, 1660*1024, 82, 312, 807, 0, "400K -4" },
  { BZIPMODE(5),  59, /*XPKMF_A3000SPEED*/ 0, 4296*1024, 2050*1024, 80, 310, 808, 0, "500K -5" },
  { BZIPMODE(6),  69, /*XPKMF_A3000SPEED*/ 0, 5078*1024, 2441*1024, 79, 309, 808, 0, "600K -6" },
  { BZIPMODE(7),  79, /*XPKMF_A3000SPEED*/ 0, 5957*1024, 2832*1024, 80, 306, 809, 0, "700K -7" },
  { BZIPMODE(8),  89, /*XPKMF_A3000SPEED*/ 0, 6640*1024, 3222*1024, 79, 305, 809, 0, "800K -8" },
  { NULL,        100, /*XPKMF_A3000SPEED*/ 0, 7421*1024, 3613*1024, 79, 302, 807, 0, "900K -9" }
};

static const struct XpkInfo BZP2Info = {
  1,			/* info version */
  LIBVER,		/* lib  version */
  2,			/* master vers  */
  0,			/* pad          */
  "BZP2",		/* short name   */
  "bzip2 1.0.1",	/* long name    */
  "Burrows-Wheeler block-sorting and Huffman coding algorithm", /* description  */
  0x425A5032,		/* 4 letter ID  */
  XPKIF_PK_CHUNK |	/* flags        */
  XPKIF_UP_CHUNK | XPKIF_MODES,
  878*1024,		/* max in chunk */
  0,			/* min in chunk */
  878*1024,		/* def in chunk */
  "Compressing",		/* pk message   */
  "Decompressing",		/* up message   */
  "Compressed",		/* pk past msg  */
  "Decompressed",		/* up past msg  */
  40,			/* def mode     */
  0,			/* pad          */
  BZIPMODE(0),		/* modes        */
  {0,}			/* reserved     */
};

/*
** return an info structure about our packer
*/

const struct XpkInfo *LIBXpksPackerInfo(void)
{
  return &BZP2Info;
}

/*
** `global data' 
*/

#define MemPool *((APTR       *)&xpar->xsp_Sub[1])
#define ZStream *((bz_stream **)&xpar->xsp_Sub[2])

/*
** utility functions for ZLIB
*/

static void zcFree(void *opaque, void *ptr)
{ XPAR *xpar = (XPAR *)opaque;
  ULONG *p = (ULONG *)ptr;

  if (p) FreePooled(MemPool,p-1,*(p-1));
}

static void *zcAlloc(void *opaque, int items, int itemsize)
{ XPAR *xpar = (XPAR *)opaque;
  ULONG size, *mem;

  if ((mem=(ULONG *)AllocPooled(MemPool,size=sizeof(*mem)+items*itemsize)))
    *mem++ = size;
  return mem;
}

/*
** initialize a z_stream (either for compression or decompression)
*/

static bz_stream *init_stream(XPAR *xpar,struct XpkSubBase *xpksLib)
{ bz_stream *stream = ZStream;

  for(;;) {
    if (!stream) {
      if ((MemPool=CreatePool(MEMF_CLEAR,32768,32768)) == NULL)
        break;
      if ((ZStream=stream=zcAlloc(xpar,1,sizeof(*stream))) == NULL)
        break;
      stream->bzalloc = zcAlloc;
      stream->bzfree  = zcFree;
      stream->opaque = xpar;
    }
    stream->next_in   = (char       *)xpar->xsp_InBuf;
    stream->avail_in  = (unsigned int)xpar->xsp_InLen;
    stream->next_out  = (char       *)xpar->xsp_OutBuf;
    stream->avail_out = (unsigned int)xpar->xsp_OutBufLen;
    break;
  }
  return stream;
}

/*
** compress a chunk of memory
*/

LONG LIBXpksPackChunk(void)
{
	XPAR *xpar = (XPAR *)REG_A0;
	struct XpkSubBase *xpksLib = (struct XpkSubBase *)REG_A6;

  LONG level, err = XPKERR_NOMEM;
  bz_stream *stream;

  if ((stream=init_stream(xpar,xpksLib))) {
    if ((level=xpar->xsp_Mode/10) != 10) {
      if (level < 0 || level > 9)
        level = 4;
    } else level = 9;
    if (BZ2_bzCompressInit(stream,level,0,30) == BZ_OK) {
      if (BZ2_bzCompress(stream,BZ_FINISH) == BZ_STREAM_END) {
        xpar->xsp_OutLen = stream->total_out_lo32; err = XPKERR_OK;
      } else err = XPKERR_UNKNOWN;
      BZ2_bzCompressEnd(stream);
    }
  }
  return err;
}

/*
** restore state information after a XPKERR_EXPANSION
*/

LONG LIBXpksPackReset(void)
{
	XPAR *xpar = (XPAR *)REG_A0;
	struct XpkSubBase *xpksLib = (struct XpkSubBase *)REG_A6;

  LONG err;

  if (!ZStream || (err=BZ2_bzCompressEnd(ZStream))!=BZ_OK)
    err = XPKERR_UNKNOWN;
  else
    err = XPKERR_OK;
  return err;
}

/*
** uncompress a chunk of memory
*/

LONG LIBXpksUnpackChunk(void)
{
	XPAR *xpar = (XPAR *)REG_A0;
	struct XpkSubBase *xpksLib = (struct XpkSubBase *)REG_A6;

  LONG err = XPKERR_NOMEM;
  bz_stream *stream;

  if ((stream=init_stream(xpar,xpksLib))) {
    if (BZ2_bzDecompressInit(stream,0,0) == BZ_OK) {
      if (BZ2_bzDecompress(stream) == BZ_STREAM_END) {
        xpar->xsp_OutLen = stream->total_out_lo32; err = XPKERR_OK;
      } else err = XPKERR_UNKNOWN;
      BZ2_bzDecompressEnd(stream);
    }
  }
  return err;
}

/*
** free all allocated stuff (either compression or decompression)
*/

void LIBXpksFree(void)
{
	XPAR *xpar = (XPAR *)REG_A0;
	struct XpkSubBase *xpksLib = (struct XpkSubBase *)REG_A6;

  ZStream = NULL;

  if (MemPool)
    { DeletePool(MemPool); MemPool = NULL; }

}
