/*
 * Title: process.c
 * Author: Jed Marti KI7NNP
 * Description: Process control to (hopefully) speed things up. When a process starts, it has the
 *   list of parts it's supposed to do. 
 * Revision History:
 *   Mon Jan 30 15:21:23 2023 - JBM Noise checker.
 *   Wed Dec  6 16:59:09 2023 - JBM parent exit no longer works with optimizer on.
 */


#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>

// For process control.
#include <sys/types.h>
#include <time.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

#include "arrl.h"

double accumSeconds = 0.0;
uint32_t accumCnt = 0;

extern double numl, numh, numinc;
extern double dial, diah, diainc;
extern double lenl, lenh, leninc;

extern double bestgoodness;
extern double bestdia;
extern double bestlen;
extern double bestnum;


// Symbool table.
extern symtab S[];

// The list of measured inductors.
extern anInductor *theInductors;

// This is our parent pid. We periodically check to see if it has gone away and go away ourselves
// if this is so.
static pid_t parent;

// We can run multiple procces per core since some of them might be waiting.
procDetail P[48];

// For a subtask, this is its index into P for the file names and stuff to work with.
int32_t ourProc;

// Command line.
int32_t procCount = 2;


// Useful - should be open coded by -Ofast.
double sqr(double x) { return x * x; }



double calculate(anInductor *l, double num, double dia, double len)
/*     Calculate the inductance with the coefficients and return the precentage error from that
 *   measured.
 * Parameters:
 *   l: A pointer to the inductor to be tested.
 *   num: The numerator coefficient.
 *   dia: The denominator diameter coefficient.
 *   len: The demoninator length coefficient.
 * External References: none
 * Returned values: The square of the percentage error - 0 -> ???
 * Errors detected: none
 */
{
  double den;
  double v;
  den = dia * l->D + len * l->LEN;
  if (den == 0.0) return -1.0;
  v = ((num * sqr(l->D) * sqr(l->N))) / den;
  return sqr((v - l->L) / l->L);
}


double originalARRL(void)
/*     Here's the original ARRL equation. Retuirns the RMS error on the inductors we
 *   read in.
 * Parameters: none
 * External References:
 *   theInductors: A list of the inductors we're checking.
 * Returned values: RMS error against the inductors or -1 if none pass.
 * Errors detected: checking for equation failures causes -1 return.
 */
{
  double     gsum;
  double     v;
  int32_t    cnt;
  anInductor *a;

  for (a = theInductors, cnt=0, gsum = 0.0; a != NULL; a = a->next)
  {
    v = calculate(a, 1.0, 18.0, 40.0);
    if (v != -1.0) { gsum += v; ++cnt; }
  }
  if (cnt > 0) return sqrt(gsum / (double) cnt);
  else return -1.0;
}



static void evaluateDiaLen(int32_t proc, double num)
/*     We have a group of parameters to evaluate. Do so and write the results to a file.
 * Parameters:
 *   proc: Our process number which is in index into the process table P.
 *   r: The numerator coefficient.
 * External References:
 *   theInductors: A list of the measured inductor values.
 *   P[]: The current processes. We look for the comm. file name there.
 * Returned values: none
 * Errors detected: Interprocess file can't be written.
 */
{
  FILE   *fh;
  double dia;
  double len;
  double bestness = 1.0E208;
  double gsum = 0.0;
  int32_t    cnt;
  anInductor *a;
  double     v;
  double     bestdia;
  double     bestlen;

  for (dia = dial; dia <= diah; dia += diainc)
    for(len = lenl; len <= lenh; len += leninc)
    {
      gsum = 0.0;
      cnt = 0;
      for (a = theInductors; a != NULL; a = a->next)
      {
        v = calculate(a, num, dia, len);
        if (v != -1.0) { gsum += v; ++cnt; }
      }
      if (cnt > 0)
      {
        gsum = sqrt(gsum / (double) cnt);
        if (gsum < bestness)
        {
          bestness = gsum;
          bestdia = dia;
          bestlen = len;
        }
      }
    }

  // Open the temporary output file to communicate the results to the parent process.
  fh = fopen(P[proc].fname, "wb");
 
  // Just a binary write of the structure.
  //  printf("%g %g %g %g\n", bestness, num, dia, len);
  fprintf(fh, "%g %g %g %g\n", bestness, num, bestdia, bestlen);
  fclose(fh);
}



static void processMonitor(int32_t proc)
/*     We're a subtask here.  Wait for an input file to appear. If it's size is too small, wait,
 *   then try again. If the file is just a 0, then time to exit.
 * Parameters:
 *   proc: Our process number. We inherit the constant stuff from initialization at startup.
 * External References: 
 *   P[]: The current processes. We look for the comm. file name there.
 * Returned values: none
 * Errors detected: Parent died, time to exit.
 */
{
  struct stat buf;
  FILE        *fh;
  int32_t     rv;
  int32_t     pstatus;
  char        rbuf[80];
  double      num;

 loop:
  // If the input file exists, then read it. 
  if (stat(P[proc].ifname, &buf) == 0)
  {
    if (buf.st_size == 0)
    {
      // Wait a bit, then try again. This assumes that the writing process can complete in
      // 0.01 seconds.
      usleep(10000);
      stat(P[proc].ifname, &buf);
      if (buf.st_size == 1)
      {
        fprintf(stderr, "%s (%s:%d) termination received\n", __FILE__, __func__, __LINE__);
        exit(0);
      }
    }
    if (buf.st_size == 1)
    {
      //      fprintf(stderr, "%s (%s:%d) termination received\n", __FILE__, __func__, __LINE__);
      exit(0);
    }
    fh = fopen(P[proc].ifname, "rb");
    if (fh == NULL)
    {
      //      fprintf(stderr, "%s (%s:%d) can't open %s\n", __FILE__, __func__, __LINE__, P[proc].ifname);
      usleep(10000);
      goto loop;
    }

    // Read the returned value and try again on fail.
    if (fgets(rbuf, 80, fh) == NULL)
    {
      fclose(fh);
      usleep(10000);
      goto loop;
    }
    num = atof(strtok(rbuf, " \n"));
    fclose(fh);
    rv = system(P[proc].rmifname);
    evaluateDiaLen(proc, num);
  }

  // Wait .001 second and try again.
  usleep(1000);
  goto loop;

}



static void startProcess(int32_t proc)
/*     Start a process with the number given. This causes a reentrance here.
 * Parameters:
 *   proc: The process index.
 * External References: none
 * Returned values: none
 * Errors detected: Child wouldn't start, causes halt.
 */
{
  pid_t   child;

  // Start the sub-process. fork returns immediately for both the master and subprocess.
  // Result is 0 if we're the subtask otherwise, the pid of the subtask. We don't really care about
  // the process id - it's off on its own doing things.
  child = fork();

  // If 0 here, we're a subtask. Evaluate the polynomial with minor variances then write the
  // result to the temporary file and exit.
  if (child == 0)
  {
    processMonitor(proc);
    printf("Finished process monitor\n");
    exit(0);
  }

  // Couldn't start. We're the host so if it won't go - go away.
  else if (child == -1)
  {
    fprintf(stderr, "%s (%s:%d) couldn't start process because %s\n", __FILE__, __func__, __LINE__,
            strerror(errno));
    exit(-1);
  }
}



uint8_t hasFinished(int32_t proc)
/*     Returns 1 if the process has finished and waits a while if it hasn't and tries again.
 * Parameters:
 *   proc: The Process index to test.
 * External References:
 *   P[]: The current processes.
 * Returned values: 1 process has finished. 0, process is still running or hasn't started.
 * Errors detected: none
 */
{
  struct stat buf;

  // If the output file exists, then read it.
 tryagain:
  if (stat(P[proc].fname, &buf) == 0)
  {
    if (buf.st_size == 0)
    {
      usleep(10000);
      goto tryagain;
    }
    //    printf("%d has finished size %ld\n", proc, buf.st_size);
    return 1;
  }
  return 0;
}



void initProcs(void)
/*     Build the process table. This is just temporary file names and how to remove them. Gets
 *   called at startup by main process only.
 * Parameters: none
 * External References:
 *   P[]: The process table - up to 48 of these allowed which is enough for most servers (i.e.
 *     dual processor, 12 cores/processor).
 * Returned values: none
 * Errors detected: none
 */
{
  int32_t i;
  int32_t rv;
  char    fname[64];

  // Our pid which will be checked by children.
  parent = getpid();
  
  for (i = 0; i < procCount; ++i)
  {
    // We use this to create a file and check if it exists.
    sprintf(fname, "arrl.%d", i);
    P[i].fname = strdup(fname);

    // Remove it when completed as we use this to test if the process has finished.
    sprintf(fname, "rm -rf arrl.%d", i);
    rv = system(fname);                      // Maybe left over from previous.
    P[i].rmfname = strdup(fname);

    // Input file. Create name and how to remove it.
    sprintf(fname, "arrli.%d", i);
    P[i].ifname = strdup(fname);
    sprintf(fname, "rm -f arrli.%d", i);
    P[i].rmifname = strdup(fname);
    rv = system(fname);

    // Now start the remote process, it will wait.
    startProcess(i);
  }
}


void terminateProcs(void)
/*     Send a 1 byte file to all the processes. This will cause them to terminate when they
 *   get around to reading it.
 * Parameters: none
 * External References:
 *   P[]: Process table.
 * Returned values: none
 * Errors detected: punts if can't open file (should probably just ignore).
 */
{
  int32_t i;
  FILE *fh;

  // Give system time to calm down.
  usleep(10000);

  // Send a 0 to each process - it will halt.
  for (i = 0; i < procCount; ++i)
  {
    fh = fopen(P[i].ifname, "wb");
    fputc(0, fh);
    fclose(fh);
  }
}



void runNum(double num)
/*     We have a numerator value coefficient value (A). Fire up a process to iterate through the
 *   B and C values. Always check to see of a subprocess has completed. If so, read its results,
 *   see if they're the best, and then free it up for use with our new numerator value.
 * Parameters:
 *   num: The numerator coefficient A.
 * External References:
 *   P[]: States of sub processes. The number allowed is set by the command line.
 *   procCount: The number of subprocesses allowed.
 * Returned values: none
 * Errors detected: A process finished but no result file found. Try again.
 */
{
  int32_t i;
  FILE    *tfh;
  char    rbuf[80];
  double  best, bnum, bdia, blen;
  int32_t procsel;
  int32_t rv;
  
 busyWait:
  for (i = 0; i < procCount; ++i)
  {
    if (P[i].status == busy && hasFinished(i))
    {
    tryagain:
      tfh = fopen(P[i].fname, "rb");
      if (tfh == NULL)
      {
        fprintf(stderr, "***** %d: %s\n", i, P[i].fname);
        goto tryagain;
      }

      if (fgets(rbuf, 80, tfh) != NULL)
      {
        best = atof(strtok(rbuf, " \n"));
        bnum = atof(strtok(NULL, " \n"));
        bdia = atof(strtok(NULL, " \n"));
        blen = atof(strtok(NULL, " \n"));
        fclose(tfh);
        rv = system(P[i].rmfname);
        P[i].status = idle;
        if (best < bestgoodness)
          {
            bestgoodness = best;
            bestnum = bnum;
            bestdia = bdia;
            bestlen = blen;
            printf("   %g%%: %g %g %g\n", best * 100.0, bnum, bdia, blen);
          }
      }
    }
  }
    
  // Check for a free proc. If none found, wait for a millisecond and try the whole thing
  // again.
  procsel = -1;
  for (i = 0; i < procCount; ++i) if (P[i].status == idle) procsel = i;
  if (procsel == -1)
  {
    usleep(1000);
    goto busyWait;
  }

  // The task has already started. Write to a file with the numerator that will get it started
  // on the B and C values.
  //  printf("starting task %d with num=%g\n", procsel, num);
  tfh = fopen(P[procsel].ifname, "wb");
  fprintf(tfh, "%g\n", num);
  fclose(tfh);
  P[procsel].status = busy;
}


// EOF
