#include <exec/types.h>
#include <exec/memory.h>
#include <exec/exec.h>
#include <libraries/dos.h>
#include <devices/timer.h>
#include <proto/timer.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/xpk.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define THREE_BUFS	0
#define NOCRC		0

struct timerequest timerequest;
struct Library *TimerBase = NULL;
struct Library *XpkBase = NULL;
BPTR fh = 0;

char errbuf[XPKERRMSGSIZE + 1];	/* +1 to make room for '\n'        */
char *Password = NULL;
char *buf1 = 0, *buf2 = 0, *buf3 = 0;
int bufsize;

void
end (char *str)
{
  if (fh)
    Close (fh);
  if (buf3)
    FreeMem (buf3, bufsize);
  if (buf2)
    FreeMem (buf2, bufsize);
  if (buf1)
    FreeMem (buf1, bufsize);
  if (TimerBase)
    CloseDevice ((struct IORequest *) & timerequest);
  if (XpkBase)
    CloseLibrary (XpkBase);
  if (str)
    printf ("%s\n", str);

  exit (0);
}

void
init (void)
{
  if (OpenDevice ("timer.device", 0, (struct IORequest *) & timerequest, 0))
    end ("Can't open timer.device");

  TimerBase = &timerequest.tr_node.io_Device->dd_Library;

  if (!(XpkBase = OpenLibrary (XPKNAME, 0)))
    end ("Can't open " XPKNAME);
}

void
testfile (char *packer, char *file)
{
  struct EClockVal eval1, eval2;
  struct FileInfoBlock fib;
  ULONG freq, ulen, ulen2, clen, ctime, utime;
  LONG cf;

  if (!(fh = Open (file, MODE_OLDFILE)))
    end ("Cannot open input file");

  if (!(ExamineFH (fh, &fib)))
    end ("Failed to ExamineFH input file");

  bufsize = fib.fib_Size + fib.fib_Size / 32 + 2 * XPK_MARGIN;

  if (!(buf1 = AllocMem (bufsize, MEMF_FAST)))
    end ("Out of memory");

  if (!(buf2 = AllocMem (bufsize, MEMF_FAST)))
    end ("Out of memory");

#if THREE_BUFS
  if (!(buf3 = AllocMem (bufsize, MEMF_FAST)))
    end ("Out of memory");
#endif

  ulen = Read (fh, buf1, fib.fib_Size);

  if (ulen != fib.fib_Size)
    end ("Could not read whole file with one Read()");

  Close (fh);
  fh = 0;

  /*--------------------------------------------------------*/

  Forbid ();
  freq = ReadEClock (&eval1);

  if (XpkPackTags (XPK_InBuf, buf1,
		   XPK_InLen, ulen,
		   XPK_OutBuf, buf2,
		   XPK_OutBufLen, bufsize,
		   XPK_GetOutLen, &clen,
		   XPK_GetError, errbuf,
		   XPK_Password, Password,
		   XPK_FindMethod, packer,
		   TAG_DONE
      )){
    Permit ();
    end (errbuf);
  }

  ReadEClock (&eval2);
  Permit ();
  ctime = eval2.ev_lo - eval1.ev_lo;

  /*--------------------------------------------------------*/

  Forbid ();
  ReadEClock (&eval1);

  if (XpkUnpackTags (
		      XPK_InBuf, buf2,
		      XPK_InLen, clen,
#if THREE_BUFS
		      XPK_OutBuf, buf3,
#else
		      XPK_OutBuf, buf1,
#endif
		      XPK_OutBufLen, bufsize,
		      XPK_GetOutLen, &ulen2,
		      XPK_GetError, errbuf,
#if NOCRC
		      XPK_NoCRC, TRUE,
#endif
		      XPK_Password, Password,
		      TAG_DONE
      )){
    Permit ();
    end (errbuf);
  }

  ReadEClock (&eval2);
  Permit ();
  utime = eval2.ev_lo - eval1.ev_lo;

  /*--------------------------------------------------------*/

  cf = 1000 - 1000 * clen / ulen;

  if (cf < 0)
    cf = 0;

  printf ("%-8s   %6d %6d %2d.%d%    %3d.%02d %7d     %2d.%02d %7d\n",
	  packer,
	  ulen, clen, cf / 10, cf % 10,
	  ctime / freq, (100 * (ctime % freq)) / freq, (int) ((double) freq * (double) ulen / (double) ctime),
	  utime / freq, (100 * (utime % freq)) / freq, (int) ((double) freq * (double) ulen / (double) utime)
    );

  if (ulen != ulen2) {
    printf ("decompressed length is %ld !!!\n", ulen2);
  }

#if THREE_BUFS
  if (buf3) {
    FreeMem (buf3, bufsize);
    buf3 = 0;
  }
#endif
  if (buf2) {
    FreeMem (buf2, bufsize);
    buf2 = 0;
  }
  if (buf1) {
    FreeMem (buf1, bufsize);
    buf1 = 0;
  }
}

main (int argc, char *argv[])
{
  int i = 1;

  if (!strcmp (argv[1], "-p"))
    Password = "testpw", i++;

  init ();

  printf ("Warning: Have to Forbid() task rescheduling for accurate measurements.\n");
  printf ("Packer      UComp   Comp    CF     CTime    CSpd     UTime    USpd\n");
  for (; i < argc - 1; i++)
    testfile (argv[i], argv[argc - 1]);

  end (NULL);
}
