/*
** xpkGZIP.library using zlib by Jean-loup Gailly and Mark Adler
**
** written by Gunther Nikl (gnikl@informatik.uni-rostock.de)
*/

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

#include <xpk/xpksub.h>
#include <exec/memory.h>
#include "xpkGZIP.h"
#include "zutil.h"

#undef malloc
#undef calloc
#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 (*calloc)(void) = NULL;
void (*free)(void) = NULL;

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 GZIPMODE(x) (struct XpkMode *)(GZIPModes+x)

static const struct XpkMode GZIPModes[] = {
  { GZIPMODE(1),   9, /*XPKMF_A3000SPEED*/ 0, 0, 0, 1607, 4575,   0, 0, "store"     },
  { GZIPMODE(2),  19, /*XPKMF_A3000SPEED*/ 0, 0, 0,  269, 1451, 468, 0, "laziest"   },
  { GZIPMODE(3),  29, /*XPKMF_A3000SPEED*/ 0, 0, 0,  245, 1451, 473, 0, "lazier"    },
  { GZIPMODE(4),  39, /*XPKMF_A3000SPEED*/ 0, 0, 0,  198, 1451, 474, 0, "lazy"      },
  { GZIPMODE(5),  49, /*XPKMF_A3000SPEED*/ 0, 0, 0,  175, 1487, 486, 0, "calm"      },
  { GZIPMODE(6),  59, /*XPKMF_A3000SPEED*/ 0, 0, 0,  137, 1525, 493, 0, "average"   },
  { GZIPMODE(7),  69, /*XPKMF_A3000SPEED*/ 0, 0, 0,   98, 1525, 496, 0, "willing"   },
  { GZIPMODE(8),  79, /*XPKMF_A3000SPEED*/ 0, 0, 0,   86, 1525, 497, 0, "greedy"    },
  { GZIPMODE(9),  89, /*XPKMF_A3000SPEED*/ 0, 0, 0,   62, 1525, 497, 0, "greedier"  },
  { NULL,        100, /*XPKMF_A3000SPEED*/ 0, 0, 0,   52, 1525, 497, 0, "greediest" }
};

static const struct XpkInfo GZIPInfo = {
  1,			/* info version */
  LIBVER,		/* lib  version */
  2,			/* master vers  */
  0,			/* pad          */
  "GZIP",		/* short name   */
  "GZIP Version 1.1",	/* long name    */
  "LZ77 class packer with hashing and dynamic Huffman coding", /* description  */
  0x475A4950,		/* 4 letter ID  */
  XPKIF_PK_CHUNK |	/* flags        */
  XPKIF_UP_CHUNK | XPKIF_MODES,
  16*1024*1024,		/* max in chunk */
  0,			/* min in chunk */
  256*1024,		/* def in chunk */
  "deflating",		/* pk message   */
  "inflating",		/* up message   */
  "deflated",		/* pk past msg  */
  "inflated",		/* up past msg  */
  65,			/* def mode     */
  0,			/* pad          */
  GZIPMODE(0),		/* modes        */
  {0,}			/* reserved     */
};

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

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

/*
** `global data' 
*/

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

/*
** utility functions for ZLIB
*/

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

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

static voidpf zcAlloc(voidpf opaque,unsigned items,unsigned 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 z_streamp init_stream(XPAR *xpar,struct XpkSubBase *xpksLib)
{ z_streamp 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->zalloc = zcAlloc;
      stream->zfree  = zcFree;
      stream->opaque = xpar;
    }
    stream->next_in   = (Bytef *)xpar->xsp_InBuf;
    stream->avail_in  = (uInt   )xpar->xsp_InLen;
    stream->next_out  = (Bytef *)xpar->xsp_OutBuf;
    stream->avail_out = (uInt   )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;
  z_streamp stream;

  if ((stream=init_stream(xpar,xpksLib))) {
    if ((level=xpar->xsp_Mode/10) != 10) {
      if (level < Z_NO_COMPRESSION || level > Z_BEST_COMPRESSION)
        level = Z_DEFAULT_COMPRESSION;
    } else level = Z_BEST_COMPRESSION;
    if (deflateInit2(stream,level,Z_DEFLATED,DEF_WBITS,DEF_MEM_LEVEL,Z_DEFAULT_STRATEGY) == Z_OK) {
      if (deflate(stream,Z_FINISH) == Z_STREAM_END) {
        xpar->xsp_OutLen = stream->total_out; err = XPKERR_OK;
      } else err = XPKERR_UNKNOWN;
      deflateEnd(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=deflateReset(ZStream))!=Z_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;
  z_streamp stream;

  if ((stream=init_stream(xpar,xpksLib))) {
    if (inflateInit2(stream,DEF_WBITS) == Z_OK) {
      if (inflate(stream,Z_FINISH) == Z_STREAM_END) {
        xpar->xsp_OutLen = stream->total_out; err = XPKERR_OK;
      } else err = XPKERR_UNKNOWN;
      inflateEnd(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; }

}
