/*
 * 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 <errno.h>

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

#include "equations.h"
#include "indequ.h"

double accumSeconds = 0.0;
uint32_t accumCnt = 0;

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


static void evaluatePolynomial(int32_t proc, result *r)
/*     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 polynomial coefficients to use in the evaluation.
 * 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;
  result *bestr;

  // Do the evaluation (see evaluate.c).
  bestr = runMajor(theInductors, r);

  // Open the temporary output file to communicate the results to the parent process.
  fh = fopen(P[proc].fname, "wb");
  ASSERT(fh != NULL);
  
  // Just a binary write of the structure.
  writeResult(bestr, fh);
  fclose(fh);
  freeResult(bestr);
}



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;
  result      *r;
  FILE        *fh;
  int32_t     rv;
  int32_t     pstatus;
  
 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;
      
    }
    r = readResult(fh);
    fclose(fh);
    rv = system(P[proc].rmifname);
    evaluatePolynomial(proc, r);

    // Make sure nothing happened to the range table.
    checkRangeTable();
  }

  // See if the parent has gone away. The -O2 and -Ofast optimizations cause this trouble.
  /*
  waitpid(parent, &pstatus, WNOHANG);
  if (WIFEXITED(pstatus))
  {
    printf("pstatus 0x%08X\n", pstatus);
    fprintf(stderr, "%s (%s:%d) Parent %d of %d has died\n", __FILE__, __func__, __LINE__, parent, proc);
    exit(-1);
  }
  */
  // 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 < S[s_processes].value.iv; ++i)
  {
    // We use this to create a file and check if it exists.
    sprintf(fname, "indequr.%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 indequr.%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, "indequi.%d", i);
    P[i].ifname = strdup(fname);
    sprintf(fname, "rm -f indequi.%d", i);
    P[i].rmifname = strdup(fname);
    rv = system(fname);

    // Now start the remote process, it will wait.
    P[i].startTime = timeus();
    startProcess(i);
  }
}


void terminateProcesses(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;
  
  for (i = 0; i < S[s_processes].value.iv; ++i)
  {
    fh = fopen(P[i].ifname, "wb");
    ASSERT(fh != NULL);
    fputc(0, fh);
    fclose(fh);
  }
}
    

    
// EOF
