/*
 * This library is mainly intended to demonstrate how to program a sub
 * library.
 */

#define VERSION		1

#include <xpk/xpksub.h>
#include <exec/memory.h>
#include <pragma/exec_lib.h>

#define SDI_TO_ANSI
#include "SDI_ASM_STD_protos.h"

#ifdef __MAXON__
  #define __asm
#endif

#define SCANBUFLEN 32768

/*
 * Pack a chunk
 */

struct NukeData {
	APTR	inbuf;
	APTR	outbuf;
	APTR	last;
	APTR	next;
	ULONG	cwri;
	ULONG	uwri;
	ULONG	uend;
	ULONG	delpos;
	UWORD	ulen;
	UWORD	clen;
	UWORD	ustop;
	UWORD	ustart;
	UWORD	ucount;
	UWORD	scanrange;
	UWORD	room1;
	UWORD	room2;
	UWORD	room4;
	UWORD	roomN;
	UWORD	data1;
	UWORD	data2;
	ULONG	data4;
	ULONG	dataN;
	ULONG	dest1;
	ULONG	dest2;
	ULONG	dest4;
	ULONG	destN;
	ULONG	dummy;
	ULONG	contbuf;
	ULONG	flags;
};

#ifdef __cplusplus
extern "C" {
#endif

LONG __asm AsmPack  (register __a4 struct NukeData *ND);
STRPTR __asm AsmUnpack(register __a1 STRPTR wread, register __a4 STRPTR bread,
                        register __a0 STRPTR dst, register __a2 STRPTR end);
#ifdef __cplusplus
}
#endif

#define PACKMEM   (65536+SCANBUFLEN)*sizeof(UWORD)+sizeof(struct NukeData)+SCANBUFLEN
#define UNPACKMEM SCANBUFLEN
#define A3000     XPKMF_A3000SPEED

static struct XpkMode NukeMode = {
  NULL,       // Next mode
  100,        // Handles up to
  A3000,      // Flags
  PACKMEM,    // Packing memory
  UNPACKMEM,  // Unpacking memory
  36,         // Packing speed
  630,        // Unpacking speed
  454,        // Compression ratio
  0,          // Reserved
  "normal",   // Description
};

static struct XpkInfo NukeInfo = {
	1,               /* info version */
	VERSION,         /* lib  version */
	0,               /* master vers  */
	1,               /* ModesVersion */
	(STRPTR) "DUKE", /* short name   */
	(STRPTR) "Duke", /* long name    */
	(STRPTR) "NUKE with a delta preprocessor", /* Description */
	0x44554B45,	 /* 'DUKE', 4 letter ID  */
	XPKIF_PK_CHUNK | /* flags        */
	XPKIF_UP_CHUNK,
	30000,           /* max in chunk */
	10,              /* min in chunk */
	30000,           /* def in chunk */
	(STRPTR) "Duking",   /* pk message   */
	(STRPTR) "Unduking", /* up message   */
	(STRPTR) "Duked",    /* pk past msg  */
	(STRPTR) "Unduked",  /* up past msg  */
	50,              /* DefMode      */
	0,               /* Pad          */
	&NukeMode,       /* ModeDesc     */
	0,               /* MinModeDesc  */
	0,               /* MaxModeDesc  */
	0,0,0,0          /* reserved     */
};

void DoDelta(STRPTR dst, STRPTR src, LONG len)
{
  UBYTE a=0, b=0;

  if (len>0) {
    switch (len & 7) {
      do {
    case 0: *dst++ = (UBYTE) ((b=*src++) - a);
    case 7: *dst++ = (UBYTE) ((a=*src++) - b);
    case 6: *dst++ = (UBYTE) ((b=*src++) - a);
    case 5: *dst++ = (UBYTE) ((a=*src++) - b);
    case 4: *dst++ = (UBYTE) ((b=*src++) - a);
    case 3: *dst++ = (UBYTE) ((a=*src++) - b);
    case 2: *dst++ = (UBYTE) ((b=*src++) - a);
    case 1: *dst++ = (UBYTE) ((a=*src++) - b);
      } while ((len-=8)>0);
    }
  }
}

void UndoDelta(STRPTR dst, STRPTR src, LONG len)
{
  UBYTE l = 0;

  if (len > 0) {
    switch (len & 7) {
     do {
    case 0: *dst++ = (l+=*src++);
    case 7: *dst++ = (l+=*src++);
    case 6: *dst++ = (l+=*src++);
    case 5: *dst++ = (l+=*src++);
    case 4: *dst++ = (l+=*src++);
    case 3: *dst++ = (l+=*src++);
    case 2: *dst++ = (l+=*src++);
    case 1: *dst++ = (l+=*src++);
      } while ((len-=8)>0);
    }
  }
}

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

#ifdef __cplusplus
extern "C"
#endif

struct XpkInfo *XpksPackerInfo(void)
{
  return &NukeInfo;
}

#ifdef __cplusplus
extern "C"
#endif

void __asm XpksPackFree(register __a0 struct XpkSubParams *xpar)
{
	struct NukeData  *ND=(struct NukeData *)xpar->xsp_Sub[0];

	if( xpar->xsp_Sub[1] ) FreeMem( (APTR)xpar->xsp_Sub[1], SCANBUFLEN), xpar->xsp_Sub[1]=0;
	if( !ND )      return;
	if( ND->last ) FreeMem( ND->last, 65536     *sizeof(UWORD));
	if( ND->next ) FreeMem( ND->next, SCANBUFLEN*sizeof(UWORD));
	FreeMem(ND, sizeof(struct NukeData));
}


struct NukeData *alloctabs(struct XpkSubParams *xpar)
{
  struct NukeData *ND;

  if ((xpar->xsp_Sub[0] = (LONG) (ND = (struct NukeData*) AllocMem(sizeof(struct NukeData),MEMF_CLEAR))) &&
      (ND->last = AllocMem(65536*sizeof(UWORD),0)) &&
      (ND->next = AllocMem(SCANBUFLEN*sizeof(UWORD),0)) &&
      (xpar->xsp_Sub[1] = (LONG) AllocMem(SCANBUFLEN,0))) {
    return ND;
  }
  else
  {
    XpksPackFree(xpar);
    return 0;
  }
}

#ifdef __cplusplus
extern "C"
#endif

LONG __asm XpksPackChunk(register __a0 struct XpkSubParams *xpar )
{
  struct NukeData *ND=(struct NukeData *)xpar->xsp_Sub[0];

  if (!ND)
    if (!(ND=alloctabs(xpar)))
      return XPKERR_NOMEM;

  DoDelta((STRPTR)xpar->xsp_Sub[1], (STRPTR) xpar->xsp_InBuf, xpar->xsp_InLen);

  ND->inbuf = (APTR) xpar->xsp_Sub[1];
  ND->outbuf= xpar->xsp_OutBuf;
  ND->ulen  = (UWORD) xpar->xsp_InLen;
  ND->clen  = (UWORD) (xpar->xsp_OutBufLen-4);

  memset(ND->last,0,65536*sizeof(UWORD));
  memset(ND->next,0,SCANBUFLEN*sizeof(UWORD));

  if(((LONG)(xpar->xsp_OutLen = AsmPack(ND)))<0 || xpar->xsp_OutLen>xpar->xsp_InLen) {
/*    XpksPackReset(xpar); ist empty function */
    return XPKERR_EXPANSION;
  }
  return 0;
}

#ifdef __cplusplus
extern "C"
#endif

LONG __asm XpksUnpackChunk(register __a0 struct XpkSubParams *xpar)
{
  if(!xpar->xsp_Sub[1] && !(xpar->xsp_Sub[1] = (LONG) AllocMem(SCANBUFLEN,0)))
     return XPKERR_NOMEM;

  AsmUnpack((STRPTR) xpar->xsp_InBuf,  (STRPTR)xpar->xsp_InBuf  + xpar->xsp_InLen,
	        (STRPTR)xpar->xsp_Sub[1], (STRPTR)xpar->xsp_Sub[1] + xpar->xsp_OutLen);

  UndoDelta((STRPTR)xpar->xsp_OutBuf, (STRPTR )xpar->xsp_Sub[1], xpar->xsp_OutLen);   

  return 0;
}

#ifdef __cplusplus
extern "C"
#endif

void __asm XpksUnpackFree(register __a0 struct XpkSubParams *xpar)
{
  if (xpar->xsp_Sub[1]) FreeMem((APTR)xpar->xsp_Sub[1], SCANBUFLEN), xpar->xsp_Sub[1]=0;
}
