/* For free support for VSIDE, please visit www.vsdsp-forum.com */
#include <vo_stdio.h>
#include <volink.h>     // Linker directives like DLLENTRY
#include <apploader.h>  // RunLibraryFunction etc
#include <kernel.h>
#include <string.h>
#include <stdlib.h>
#include <taskandstack.h>
#include <audio.h>
#include <clockspeed.h>
#include <cyclic.h>

u_int16 delayMS = 1000, collectN = 1;
u_int32 oldTimeCount = 0;
u_int16 quiet = 0;
u_int32 tenClockLoops[2] = {0};

struct TaskAndStack *cpuTask = NULL;
u_int16 quitCpuTask = 0;

struct CpuReport {
  u_int16 n, t;
  double effectiveMHz;
} cpuReport[3];

/* C version around 15 clocks/loop, asm exactly 6 clocks/loop */
#define CLOCKS_PER_LOOP 6


/* NOTE!
   Exact number of clock cycles cannot be guaranteed, so this C model
   will generally _not_ give correct numbers. It is just for verifying
   functionality of the function. */
void ReportCpuFree(register struct CyclicNode *cyclicNode) {
  static u_int32 oldTime = 0;
  static u_int32 oldLoops = 0;
  u_int32 newLoops = tenClockLoops[0];
  double cpuMHz = 1.0e-6*clockSpeed.cpuClkHz;
  u_int32 newTime = ReadTimeCount();
  double effectiveMHz;
  u_int32 loops = newLoops-oldLoops;

  if (!oldTime) {
    goto finally;
  }

  /* In case our report came while in the middle of writing the two halves
     of updating tenClockLoops[0] in RunTenClockCounter(). */
  if (tenClockLoops[1] > newLoops) newLoops = tenClockLoops[1];

  effectiveMHz = loops*(1.0e-3*CLOCKS_PER_LOOP)/(newTime-oldTime);

  if (effectiveMHz < cpuReport[0].effectiveMHz) {
    cpuReport[0].effectiveMHz = effectiveMHz;
  }

  if (effectiveMHz > cpuReport[2].effectiveMHz) {
    cpuReport[2].effectiveMHz = effectiveMHz;
  }

  cpuReport[1].n++;
  cpuReport[1].t += (u_int16)(newTime-oldTime);
  cpuReport[1].effectiveMHz += effectiveMHz;

  if (cpuReport[1].n >= collectN) {
    if (!quiet) {
      printf("%6.3fs n=%d: L %5.2fu %5.2ff; M %5.2fu %5.2ff; "
	     "H %5.2fu %5.2ff; T %5.2f MHz\n",
	     1.0e-3*cpuReport[1].t, cpuReport[1].n,
	     cpuMHz-cpuReport[2].effectiveMHz, cpuReport[2].effectiveMHz,
	     cpuMHz-cpuReport[1].effectiveMHz/cpuReport[1].n,
	     cpuReport[1].effectiveMHz/cpuReport[1].n,
	     cpuMHz-cpuReport[0].effectiveMHz, cpuReport[0].effectiveMHz,
	     cpuMHz);
    }
    memset(cpuReport, 0, sizeof(cpuReport));
    cpuReport[0].effectiveMHz = 1e20; /* Impossibly large number */
    if (collectN > 1) newTime = 0; /* Force restart of algorithm. */
  }
finally:
  oldTime = newTime;
  oldLoops = newLoops;
}


struct CyclicNode reportCpuFreeNode =  {{0}, ReportCpuFree};


void RunTenClockCounter(void);

void CpuTask(void) {
  cpuReport[0].effectiveMHz = 1e20; /* Impossibly large number */
  AddCyclic(&reportCpuFreeNode, delayMS, delayMS);
#if 0
  /* Execution time accuracy of C version not guaranteed, so use
     RunTenClockCounter() asm version instead. */
  while (!quitCpuTask) {
    tenClockLoops[0]++;
    tenClockLoops[1]++;
  }
#else
  RunTenClockCounter();
#endif
  DropCyclic(&reportCpuFreeNode);
}

int init(char *parameters) {
  /* NOTE! For bigger tasks a stack of 512 recommended.
     But, 256 is enough for CpuFree as it is so small and never really use
     more than half of that. */
  if (!(cpuTask = CreateTaskAndStack(CpuTask, "CpuFree", 256, -255))) {
    return S_ERROR;
  }

  oldTimeCount = ReadTimeCount();

  return S_OK;
}

void fini(void) {
  if (cpuTask) {
    quitCpuTask = 1;
    while (cpuTask->task.tc_State && cpuTask->task.tc_State != TS_REMOVED) {
      Delay(1);
    }
  }
}

int main(char *parameters) {
  int nParam = RunProgram("ParamSpl", parameters);
  char *p = parameters;
  int res = S_ERROR;
  int flags = 0, verbose = 1;
  int i;

  for (i=0; i<nParam; i++) {
    if (!strcmp(p, "-h")) {
      printf("Usage: CpuFree [-cn] [-h] [x]\n"
	     "-cn\tReport at every n'th round (default: 1)\n"
	     "-h\tShow this help\n"
	     "x\tGather data at x ms intervals (default: 1000, -1 = silent)\n"
	     , collectN, delayMS
	     );
      res = S_OK;
      goto finally;
    } else if (!strncmp(p, "-c", 2)) {
      s_int32 n = strtol(p+2, NULL, 0);
      if (n > 60000) n = 60000;
      if (n < 0) n = 0;
      collectN = (u_int16)n;
    } else {
      s_int32 x = strtol(p, NULL, 0);
      if (x > 60000) x = 60000;
      if (!x) x = 1000;
      if (x < 0) {
	quiet = 1;
	x = 60000;
      } else {
	quiet = 0;
      }
      delayMS = (u_int16)x;
    }
    p += strlen(p)+1;
  }

  reportCpuFreeNode.interval = delayMS;
  reportCpuFreeNode.nextActivation = 1;

  res = S_OK;

 finally:
  return res;
}
