#define NAME     "xBench"
#define VERSION  "2"
#define REVISION "5"

/* Programmheader

        Name:           xBench
        Author:         SDI (before 1.1 Urban Dominik Müller)
        Distribution:   PD
        Description:    xpk benchmark utility
        Compileropts:   -gM
        Linkeropts:     -l xpkmaster amiga -gsi

 1.1   29.11.96 : added version string
 1.2   20.12.96 : added output telling the caller mechanism
 2.0   31.03.97 : now OS 2.0 and up only, rewritten, now an utility to
        allow users making benchmarks
 2.1   01.04.97 : fixed some errors
 2.2   23.04.97 : fixed time calculation
 2.3   10.05.97 : added ALL flag, use with care!
 2.4   08.06.97 : added forgotten TAG_DONE
 2.5   20.06.97 : added Pattern Matching
*/

#include <pragma/dos_lib.h>
#include <pragma/timer_lib.h>
#include <pragma/xpkmaster_lib.h>
#include <pragma/exec_lib.h>
#include <exec/memory.h>
#include <exec/devices.h>
#include "SDI_defines.h"
#define SDI_TO_ANSI
#include "SDI_ASM_STD_protos.h"

/* METHOD's are:
 <mode - upper case, 4 chars>[.<mode - dec number>]
*/

#define PARAMS  "FILENAME/A,PASSWORD/K,METHOD/M,TEST/S,ALL/S,SAVE/K"
#define HEADER  "Type  Num Version Password  CSize  CTime    CSpd  USize  UTime    USpd Rate\n"
#define DATATXT "%s: %3ld %2ld.%ld\t  %8.8s %6ld %3ld.%02ld %7ld %6ld %3ld.%02ld %7ld %2ld.%ld\n"
#define SIZEERR "%s: FileSize false after decrunching %ld != %ld\n"
#define BUFERR  "%s: Decrunched buffer different to source!\n"

struct Args {
  STRPTR        filename;
  STRPTR        password;
  STRPTR *      method;
  ULONG		test;
  ULONG		all;
  STRPTR	save;
};

struct TestData {
  STRPTR	sbuf;
  ULONG		sbufsize;
  STRPTR	pbuf;
  ULONG		pbufsize;
  STRPTR	ubuf;
  ULONG		ubufsize;
  STRPTR	password;
  ULONG		test;
  ULONG		all;
  STRPTR	save;
  struct FileInfoBlock *fib;
  struct XpkPackerInfo *pinfo;
  struct XpkMode *minfo;
};

struct BenchData {
  STRPTR	method;
  ULONG		mode;
  ULONG		version;
  ULONG		revision;
  STRPTR	password;
  ULONG		CSize;
  ULONG		CTime;
  ULONG		CTime100;
  ULONG		CSpd;
  ULONG		USize;
  ULONG		UTime;
  ULONG		UTime100;
  ULONG		USpd;
  ULONG		Rate;
  ULONG		Rate10;
};

struct DosLibrary *     DOSBase;
struct Library *        XpkBase;
struct Library *	TimerBase;

ULONG DoIt(STRPTR name, struct TestData *tdat, STRPTR *method);
LONG GetPackData(struct BenchData *b, struct TestData *t, STRPTR pwd);
void DoTest(struct TestData *data, STRPTR method, ULONG mode);
void ScanMethods(struct TestData *data, STRPTR method);
void ScanPackers(struct TestData *data);

#define PATHNAME_SIZE 256

ULONG start(void) /* not named main, to get error with startup code ! */
{
 ULONG                  error   = RETURN_FAIL;
 struct DosLibrary *    dosbase;
 struct Process *       task;

 /* test for WB and reply startup-message */
 if(!(task = (struct Process *) FindTask(0))->pr_CLI)
 {
  WaitPort(&task->pr_MsgPort);
  Forbid();
  ReplyMsg(GetMsg(&task->pr_MsgPort));
  return RETURN_FAIL;
 }

 if((dosbase = (struct DosLibrary *) OpenLibrary("dos.library", 37)))
 {
  struct Library *xpkbase;

  DOSBase = dosbase; /* set global base */
  if((xpkbase = OpenLibrary("xpkmaster.library", 4)))
  {
   struct RDArgs *rda;
   struct Args args = {0, 0, 0, 0};

   XpkBase = xpkbase;
   if((rda = ReadArgs(PARAMS, (LONG *) &args, 0)))
   {
    struct FileInfoBlock *fib;

    if((fib = (struct FileInfoBlock *) AllocDosObject(DOS_FIB, 0)))
    {
     struct AnchorPath *APath;
     struct TestData tdat;
     ULONG retval;

     tdat.password = args.password;
     tdat.test = args.test;
     tdat.all = args.all;
     tdat.save = args.save;
     tdat.fib = fib;

     if((APath = (struct AnchorPath *) AllocMem(sizeof(struct AnchorPath)+
     PATHNAME_SIZE, MEMF_PUBLIC|MEMF_CLEAR)))
     {
      APath->ap_BreakBits = SIGBREAKF_CTRL_C;
      APath->ap_Strlen = PATHNAME_SIZE;

      for(retval = MatchFirst(args.filename, APath); !retval; retval = MatchNext(APath))
      {
       if(APath->ap_Flags & APF_DIDDIR)
        APath->ap_Flags &= ~APF_DIDDIR;
       else if(APath->ap_Info.fib_DirEntryType > 0)
       {
/*        if(args.deep)
         APath->ap_Flags |= APF_DODIR; */
       }
       else if((error = DoIt(APath->ap_Buf, &tdat, args.method)))
         break;
      }
      MatchEnd(APath);
      FreeMem(APath, sizeof(struct AnchorPath)+PATHNAME_SIZE);
     }
     FreeDosObject(DOS_FIB, fib);
    }
    FreeArgs(rda);
   }
   CloseLibrary(xpkbase);
  }
  if(error && IoErr()) /* print the error */
   PrintFault(IoErr(), 0);
  CloseLibrary((struct Library *) dosbase);
 }

 return error;
}

ULONG DoIt(STRPTR name, struct TestData *tdat, STRPTR *method)
{
  ULONG error = RETURN_FAIL;
  ULONG fh;

  if((fh = Open(name, MODE_OLDFILE)))
  {
   if(ExamineFH(fh, tdat->fib))
   {
    tdat->sbufsize = tdat->fib->fib_Size;
    tdat->ubufsize = tdat->fib->fib_Size + XPK_MARGIN;
    tdat->pbufsize = tdat->fib->fib_Size + (tdat->fib->fib_Size>>5) + (XPK_MARGIN<<1);

    if((tdat->sbuf = (STRPTR) AllocMem(tdat->sbufsize, MEMF_ANY)))
    {
     if((tdat->ubuf = (STRPTR) AllocMem(tdat->ubufsize, MEMF_ANY)))
     {
      if((tdat->pbuf = (STRPTR) AllocMem(tdat->pbufsize, MEMF_ANY)))
      {
       if(Read(fh, tdat->sbuf, tdat->sbufsize) == tdat->sbufsize)
       {
        struct timerequest timerequest = {0};

        if(!OpenDevice("timer.device", UNIT_ECLOCK, (struct IORequest *)
        &timerequest, 0))
	{
	 TimerBase = &timerequest.tr_node.io_Device->dd_Library;
	 if((tdat->pinfo = (struct XpkPackerInfo *)
	 XpkAllocObject(XPKOBJ_PACKERINFO, 0)))
	 {
	  if((tdat->minfo = (struct XpkMode *)
	  XpkAllocObject(XPKOBJ_MODE, 0)))
	  {
           VPrintf("%s\n" HEADER, &name);
           if(method)
           {
            for(;*method && !CTRL_C; ++method)
            {
	     if(!(*method)[0] || !(*method)[1] ||
	     !(*method)[2] || !(*method)[3])
	      XpkPrintFault(XPKERR_MISSINGLIB, *method); 
	     else
	     {
	      (*method)[0] = toupper((*method)[0]);
	      (*method)[1] = toupper((*method)[1]);
	      (*method)[2] = toupper((*method)[2]);
	      (*method)[3] = toupper((*method)[3]);
	      if((*method)[4] == '.')
              {
               (*method)[4] = 0;
               DoTest(tdat, *method, strtoul(*method+5, 0,10));
              }
              else
               ScanMethods(tdat, *method);
             }
            }
           }
           else
            ScanPackers(tdat);
	   error = 0;
	   XpkFreeObject(XPKOBJ_MODE, tdat->minfo);
	  }
	  XpkFreeObject(XPKOBJ_PACKERINFO, tdat->pinfo);
	 }
	 CloseDevice((struct IORequest *) &timerequest);
        }
       }
       FreeMem(tdat->pbuf, tdat->pbufsize);
      }
      FreeMem(tdat->ubuf, tdat->ubufsize);
     }
     FreeMem(tdat->sbuf, tdat->sbufsize);
    }
   }
   Close(fh);
  }
  return error;
}

/* These must be defined false, because this is a benchmark tool ! */
static struct TagItem deftags[] = {
{ XPK_Preferences, FALSE},
{ XPK_UseXfdMaster, FALSE},
{ XPK_UseExternals, FALSE},
{ XPK_PassRequest, FALSE},
{ XPK_ChunkReport, FALSE},
{ TAG_DONE, 0},
};

LONG GetPackData(struct BenchData *b, struct TestData *t, STRPTR pwd)
{
  struct EClockVal eval1, eval2;
  LONG err, d;
  ULONG freq;

  Forbid();
  freq = ReadEClock(&eval1);
  err = XpkPackTags(
	XPK_InBuf,	t->sbuf,
	XPK_InLen,	t->sbufsize,
	XPK_OutBuf,	t->pbuf,
	XPK_OutBufLen,	t->pbufsize,
	XPK_GetOutLen,	&(b->CSize),
	pwd ? XPK_Password : TAG_IGNORE,	pwd,
	XPK_PackMethod,	b->method,
	XPK_PackMode,	b->mode,
	TAG_MORE,	deftags);
  ReadEClock(&eval2);
  Permit();
  b->CTime = eval2.ev_lo - eval1.ev_lo;
  if(err)
    return err;
  Forbid();
  ReadEClock(&eval1);
  err = XpkUnpackTags(
	XPK_InBuf,	t->pbuf,
	XPK_InLen,	t->pbufsize,
	XPK_OutBuf,	t->ubuf,
	XPK_OutBufLen,	t->ubufsize,
	XPK_GetOutLen,	&d,
	pwd ? XPK_Password : TAG_IGNORE, pwd,
	TAG_MORE,	deftags);
  ReadEClock(&eval2);
  Permit();
  if(err)
    return err;
  b->UTime = eval2.ev_lo - eval1.ev_lo;

  if(d != t->sbufsize)
  {
    Printf(SIZEERR, b->method, t->sbufsize, t); return 1;
  }
  if(t->test)
  {
    for(; d && t->sbuf[d-1] == t->ubuf[d-1]; --d)
      ;
    memset(t->ubuf, 0, t->ubufsize);
    if(d)
    {
      VPrintf(BUFERR, &b->method); return 1;
    }
  }

  if(t->save)
  {
    ULONG lock, fh;

    if((lock = Lock(t->save, SHARED_LOCK)))
    {
      lock = CurrentDir(lock);
      if(Examine(lock, t->fib) && t->fib->fib_DirEntryType > 0)
      {
        UBYTE data[10];

        sprintf(data, "%.4s.%03ld", b->method, b->mode);
        if((fh = Open(data, MODE_NEWFILE)))
        {
          Write(fh, t->pbuf, b->CSize);
          Close(fh);
        }
      }
      UnLock(CurrentDir(lock));
    }
  }

  if((d = 1000 - ((1000 * b->CSize)/b->USize)) < 0)
    d = 0;

  b->Rate   = d/10;
  b->Rate10 = d % 10;

  b->CTime100 = (100 * (b->CTime % freq)) / freq;
  b->UTime100 = (100 * (b->UTime % freq)) / freq;
  b->CTime /= freq;
  b->UTime /= freq;

  if(!b->CTime && !b->CTime100)
    b->CTime100 = 1;
  if(!b->UTime && !b->UTime100)
    b->UTime100 = 1;

  b->CSpd = ((100*b->USize) / ((100 * b->CTime) + b->CTime100));
  b->USpd = ((100*b->USize) / ((100 * b->UTime) + b->UTime100));
  return 0;
}

void DoTest(struct TestData *t, STRPTR method, ULONG mode)
{
  struct BenchData b;
  struct Library *xbase;
  UBYTE libname[] = "compressors/xpk____.library";

  LONG err;

  CopyMem(method, libname+15, 4);

  if(!(xbase = OpenLibrary(libname, 0)))
  {
    XpkPrintFault(XPKERR_MISSINGLIB, method);
    return;
  }
  b.version = xbase->lib_Version;
  b.revision = xbase->lib_Revision;
  CloseLibrary(xbase);

  libname[19] = 0;
  b.method = libname + 15;
  if((b.mode = mode) > 100)
    b.mode = 100;
  b.USize = t->sbufsize;

  if((err = XpkQueryTags(
	XPK_PackerQuery,	t->pinfo,
	XPK_PackMethod,		b.method,
	TAG_MORE,		deftags,
	TAG_DONE)))
  {
    XpkPrintFault(err, b.method);
    return;
  }

  if((t->pinfo->xpi_Flags & XPKIF_NEEDPASSWD) && !t->password)
    return;

  if((t->pinfo->xpi_Flags & XPKIF_ENCRYPTION) && t->password)
  {
    b.password = t->password;
    if((err = GetPackData(&b, t, t->password)))
    {
      if(err < 0)
        XpkPrintFault(err, b.method);
      return;
    }
    else
      VPrintf(DATATXT, &b);
  }
  if(!(t->pinfo->xpi_Flags & XPKIF_NEEDPASSWD))
  {
    b.password = "";
    if((err = GetPackData(&b, t, 0)))
    {
      if(err < 0)
        XpkPrintFault(err, b.method);
      return;
    }
    else
      VPrintf(DATATXT, &b);
  }
}

void ScanPackers(struct TestData *t)
{
  LONG err, i;
  struct XpkPackerList *pl;

  if((pl = (struct XpkPackerList *) XpkAllocObject(XPKOBJ_PACKERLIST, 0)))
  {
    if((err = XpkQueryTags(
	XPK_PackersQuery,	pl,
	TAG_MORE,		deftags,
	TAG_DONE)))
    {
      XpkPrintFault(err, 0); return;
    }

    for(i = 0; i < pl->xpl_NumPackers && !CTRL_C; i++)
      ScanMethods(t, pl->xpl_Packer[i]);

    XpkFreeObject(XPKOBJ_PACKERLIST, pl);
  }
}

void ScanMethods(struct TestData *t, STRPTR method)
{
  LONG err;
  ULONG mode;

  if(t->all)
  {
    for(mode = 0; mode <= 100 && !CTRL_C; ++mode)
     DoTest(t, method, mode);
  }
  else
  {
    for(mode = 0; mode < 100 && !CTRL_C; mode = t->minfo->xm_Upto + 1)
    {
      if((err = XpkQueryTags(
	XPK_ModeQuery,	t->minfo,
	XPK_PackMethod,	method,
	XPK_PackMode, 	mode,
	TAG_MORE,	deftags)))
      {
        XpkPrintFault(err, method); return;
      }
      DoTest(t, method, t->minfo->xm_Upto);
    }
  }
}

