#include <proto/exec.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <exec/resident.h>
#include <exec/initializers.h>
#include <xpk/xpksub.h>

#include <emul/emulinterface.h>
#include <emul/emulregs.h>
#include <ppcinline/exec.h>
#include <public/quark/quark.h>
#include <public/proto/quark/syscall_protos.h>

#include "delta.h"
#include "hsc.h"

#define LIBVER	1
#define LIBREV	3
#define LIBDATE	"(19.06.1999)"
#define LIBNAME	"xpkSHSC.library"

#define LIBID "xpkSHSC 1.3 (19.06.1999) [MorphOS]\r\n"


struct XpkSubBase {
	struct Library xsb_Lib;
	UWORD xsb_Pad;

	struct ExecBase *xsb_SysBase;
	BPTR xsb_SegList;
};


struct Library *LibInit(struct XpkSubBase *XpkSubBase, BPTR SegList, struct ExecBase *sysbase);
struct Library *LIB_Open(void);
BPTR LIB_Close(void);
BPTR LIB_Expunge(void);

#define LIBXpksPackReset  LIB_Reserved

const struct XpkInfo *LIBXpksPackerInfo(void);
LONG LIBXpksPackChunk(void);
void LIBXpksPackFree(void);
//LONG LIBXpksPackReset(void);
LONG LIBXpksUnpackChunk(void);
void LIBXpksUnpackFree(void);

/* 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;

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);
}

/************************************************************************/

static const struct XpkMode SHSCModeBest = {
  NULL,       /* Next mode */
  100,        /* Handles up to */
  XPKMF_A3000SPEED, /* Flags */
  0,          /* Packing memory */
  0,          /* Unpacking memory */
  1,          /* Packing speed */
  2,          /* Unpacking speed */
  385,        /* Compression ratio (packed/unpacked) */
  0,          /* Reserved */
  "HSC-best", /* Description */
};

static const struct XpkMode SHSCModeDelta = {
  &SHSCModeBest, /* Next mode */
  66,         /* Handles up to */
  XPKMF_A3000SPEED, /* Flags */
  0,          /* Packing memory */
  0,          /* Unpacking memory */
  2,          /* Packing speed */
  2,          /* Unpacking speed */
  386,        /* Compression ratio (packed/unpacked) */
  0,          /* Reserved */
  "HSC-dlta", /* Description */
};

static const struct XpkMode SHSCModeNorm = {
  &SHSCModeDelta, /* Next mode */
  33,         /* Handles up to */
  XPKMF_A3000SPEED, /* Flags */
  0,          /* Packing memory */
  0,          /* Unpacking memory */
  2,          /* Packing speed */
  2,          /* Unpacking speed */
  387,        /* Compression ratio (packed/unpacked) */
  0,          /* Reserved */
  "HSC-norm", /* Description */
};

static const struct XpkInfo SHSCInfo = {
  1,               /* info version */
  LIBVER,          /* lib  version */
  2,               /* master vers  */
  0,               /* ModesVersion */
  "SHSC"  ,        /* short name   */
  "HSC with delta preprocessor", /* long name    */
  "compressor based on finite context model and arithmetic coder", /* Description  */
  0x53485343,      /* 4 letter ID  */
  XPKIF_PK_CHUNK | /* flags        */
  XPKIF_UP_CHUNK |
  XPKIF_MODES,
  1048576,         /* max in chunk */
  0,               /* min in chunk */
  32768,           /* def in chunk */
  "Crunching",     /* pk message   */
  "Decrunching",   /* up message   */
  "Crunched",      /* pk past msg  */
  "Decrunched",    /* up past msg  */
  0,               /* DefMode      */
  0,               /* Pad          */
  &SHSCModeNorm,   /* ModeDesc     */
  {0,}             /* reserved     */
};

/* returns an info structure about our packer */

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

/* compresssion and decompression stuff */

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

	struct hsc *hsc;
	LONG tst8bit, tst16be, tst16le;
	unsigned char dltatype, bestdlta;
	unsigned char *dltain;
	unsigned char *dltaout;
	ULONG len;

	if (!(hsc = (struct hsc *) xpar->xsp_Sub[0]))
	{
		if (!(hsc = hsc_setup()))
			return XPKERR_NOMEM;
		else
			xpar->xsp_Sub[0] = (ULONG) hsc;
	}

	if (xpar->xsp_Mode <= 33)
	{
		dltatype = 0;
		bestdlta = 0;
	}
	else
	{
		tst8bit = testsamp8bit( xpar->xsp_InBuf, xpar->xsp_InLen );
		tst16be = testsamp16be( xpar->xsp_InBuf, xpar->xsp_InLen );
		tst16le = testsamp16le( xpar->xsp_InBuf, xpar->xsp_InLen );

		if (tst8bit < tst16be)
		{
			if (tst16be < tst16le)
			{
				dltatype = 3;
			}
			else
			{
				dltatype = 2;
			}
		}
		else
		{
			if (tst8bit < tst16le)
			{
				dltatype = 3;
			}
			else
			{
				dltatype = 1;
			}
		}

		if (xpar->xsp_Mode <= 66)
		{
			bestdlta = 0;
		}
		else
		{
			bestdlta = 1;
		}
	}

	if ((dltatype == 0) || (bestdlta != 0))
	{
		((STRPTR)xpar->xsp_OutBuf)[0] = 0;	/* no delta */

		if (!(len = hsc_pack(hsc, xpar->xsp_InBuf, xpar->xsp_InLen, &((STRPTR)xpar->xsp_OutBuf)[1], xpar->xsp_InLen)))
			return XPKERR_NOMEM;

		xpar->xsp_OutLen = (len+1);
	}

	if (dltatype != 0)
	{
		if (!(dltain = (unsigned char *) xpar->xsp_Sub[1]))
		{
			if (!(dltain = AllocVec( xpar->xsp_InLen, 0)))
				return XPKERR_NOMEM;
			else
				xpar->xsp_Sub[1] = (ULONG) dltain;
		}

		if (!(dltaout = (unsigned char *) xpar->xsp_Sub[2]))
		{
			if (!(dltaout = AllocVec( xpar->xsp_OutBufLen, 0)))
				return XPKERR_NOMEM;
			else
				xpar->xsp_Sub[2] = (ULONG) dltaout;
		}

		switch(dltatype)
		{
			case 1:
				dodelta8bit(xpar->xsp_InBuf, dltain, xpar->xsp_InLen);
				break;

			case 2:
				dodelta16be(xpar->xsp_InBuf, dltain, xpar->xsp_InLen);
				break;

			case 3:
				dodelta16le(xpar->xsp_InBuf, dltain, xpar->xsp_InLen);
				break;
		}

		((STRPTR)dltaout)[0] = dltatype;	/* delta */

		if (!(len = hsc_pack(hsc, dltain, xpar->xsp_InLen, &((STRPTR)dltaout)[1], xpar->xsp_InLen)))
			return XPKERR_NOMEM;

		if ((bestdlta == 0) || ((len+1) < xpar->xsp_OutLen))
		{
			xpar->xsp_OutLen = (len+1);

			CopyMem(dltaout,xpar->xsp_OutBuf,xpar->xsp_OutLen);
		}
	}

	if (xpar->xsp_OutLen > xpar->xsp_InLen)
	{
/*		XpksPackReset(xpar); is empty function */
		return XPKERR_EXPANSION;
	}

	return 0;
}

void LIBXpksPackFree(void)
{
	struct XpkSubParams *xpar = (struct XpkSubParams *)REG_A0;
	struct XpkSubBase *XpkSubBase = (struct XpkSubBase *)REG_A6;

	struct hsc *hsc;
	unsigned char *dltain;
	unsigned char *dltaout;

	if (hsc = (struct hsc *) xpar->xsp_Sub[0])
	{
		hsc_cleanup(hsc);

		xpar->xsp_Sub[0] = 0;
	}

	if (dltain = (unsigned char *) xpar->xsp_Sub[1])
	{
		FreeVec( dltain );

		xpar->xsp_Sub[1] = 0;
	}

	if (dltaout = (unsigned char *) xpar->xsp_Sub[2])
	{
		FreeVec( dltaout );

		xpar->xsp_Sub[2] = 0;
	}
}

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

	struct hsc *hsc;
	unsigned char dltatype;
	unsigned char *dltaout;
	ULONG len;

	if (!(hsc = (struct hsc *) xpar->xsp_Sub[0]))
	{
		if (!(hsc = hsc_setup()))
			return XPKERR_NOMEM;
		else
			xpar->xsp_Sub[0] = (ULONG) hsc;
	}

	if (xpar->xsp_InLen > 0)
	{
		dltaout = (unsigned char *) xpar->xsp_Sub[2];

		if (xpar->xsp_Sub[1] < xpar->xsp_OutBufLen)
		{
			if (dltaout)
			{
				FreeVec( dltaout );

				xpar->xsp_Sub[1] = 0;
				xpar->xsp_Sub[2] = 0;
			}

			if (!(dltaout = AllocVec( xpar->xsp_OutBufLen, 0)))
				return XPKERR_NOMEM;
			else
			{
				xpar->xsp_Sub[1] = (ULONG) xpar->xsp_OutBufLen;
				xpar->xsp_Sub[2] = (ULONG) dltaout;
			}
		}

		dltatype = ((STRPTR)xpar->xsp_InBuf)[0];

		len = hsc_unpack(hsc, &((STRPTR)xpar->xsp_InBuf)[1], xpar->xsp_InLen-1, dltaout, xpar->xsp_OutLen);

		if (len != xpar->xsp_OutLen)
			return XPKERR_NOMEM;

		switch(dltatype)
		{
			case 0:
				CopyMem(dltaout, xpar->xsp_OutBuf, xpar->xsp_OutLen);
				break;

			case 1:
				undodelta8bit(dltaout, xpar->xsp_OutBuf, xpar->xsp_OutLen);
				break;

			case 2:
				undodelta16be(dltaout, xpar->xsp_OutBuf, xpar->xsp_OutLen);
				break;

			case 3:
				undodelta16le(dltaout, xpar->xsp_OutBuf, xpar->xsp_OutLen);
				break;

			default:
				return XPKERR_WRONGMODE;
		}
	}

	return 0;
}

void LIBXpksUnpackFree(void)
{
	struct XpkSubParams *xpar = (struct XpkSubParams *)REG_A0;
	struct XpkSubBase *XpkSubBase = (struct XpkSubBase *)REG_A6;

	struct hsc *hsc;
	unsigned char *dltaout;

	if (hsc = (struct hsc *) xpar->xsp_Sub[0])
	{
		hsc_cleanup(hsc);

		xpar->xsp_Sub[0] = 0;
	}

	if (dltaout = (unsigned char *) xpar->xsp_Sub[2])
	{
		FreeVec( dltaout );

		xpar->xsp_Sub[1] = 0;
		xpar->xsp_Sub[2] = 0;
	}
}
