/* Title: caps.c
 * Author: Jed Marti KI7NNP
 * Description: Compute the best set of capacitors and circuit to get a critical value.
 * Revision History: (Created Sun Sep 11 15:23: 248 2022)
 *   Wed Nov  8 08:28:59 2023 - JBM Correct 3c.
 */


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

// Set by readcaps.
extern double *capValues;
uint8_t        ncaps;

// Optimal results here.
Optimal  bestResults[MAXCAPS];

// Macro for doing 2 capacitor solutions. No need to do permutations here as the circuits are
// cummutative. Caqll the solution function, see if it's a better solution, and save the best
// values if so.
// Parameters:
//   fn: function name to call for solution (see solutions.c) - a function name.
//   cn: the circuit name (a string)
//   ca: First capacitor
//   cb: Second capacitor
#define cc2(fn, cn, ca, cb)                        \
  res = fn(ca, cb); \
  if (fabs(res - targetcap) < bestResults[Circuit2].bestgoodness) \
  { bestResults[Circuit2].bestgoodness = fabs(res - targetcap), \
      bestResults[Circuit2].bestperm = perm, \
      bestResults[Circuit2].bestres = res, \
      bestResults[Circuit2].bestcircuit = cn, \
      bestResults[Circuit2].bestcs[0] = ca, \
      bestResults[Circuit2].bestcs[1] = cb; \
  }



// Macro for doing 3 capacitor solutions. As above, but this gets expanded for each possible
// permutation of the 3 values.
// Parameters:
//   fn: function name to call for solution (see solutions.c) - a function name.
//   cn: the circuit name (a string)
//   ca: First capacitor
//   cb: Second capacitor
//   cc: Third capacitor.
#define perm3(fn, cn, xa, xb, xc) \
  res = fn(xa, xb, xc); \
  if (fabs(res - targetcap) < bestResults[Circuit3].bestgoodness) \
  {  bestResults[Circuit3].bestgoodness = fabs(res - targetcap), \
      bestResults[Circuit3].bestres = res, \
      bestResults[Circuit3].bestperm = perm, \
      bestResults[Circuit3].bestcircuit = cn, \
      bestResults[Circuit3].bestcs[0] = xa, \
      bestResults[Circuit3].bestcs[1] = xb, \
      bestResults[Circuit3].bestcs[2] = xc; \
  }



// Macro for doing the 3 capacitor solutions. Does all 6 permutations even though many may
// be redundant. We expand the above once for each permutation.
// Parameters: as above
#define cc3(fn, cn, ca, cb, cc) \
  perm3(fn, cn, ca, cb, cc); \
  perm3(fn, cn, ca, cc, cb); \
  perm3(fn, cn, cb, ca, cc); \
  perm3(fn, cn, cb, cc, ca); \
  perm3(fn, cn, cc, ca, cb); \
  perm3(fn, cn, cc, cb, ca);



// Macro for testing and saving the best results for 4 capacitors.
// Parameters:
//   fn: function name to call from solutions.c - a functio name.
//   cn: The circuit name (a string).
//   xa, xb, xc, xd: The 4 capacitor values.
#define perm4(fn, cn, xa, xb, xc, xd) \
  res = fn(xa, xb, xc, xd); \
  if (fabs(res - targetcap) < bestResults[Circuit4].bestgoodness) \
  { bestResults[Circuit4].bestgoodness = fabs(res - targetcap), \
      bestResults[Circuit4].bestres = res, \
      bestResults[Circuit4].bestperm = perm, \
      bestResults[Circuit4].bestcircuit = cn, \
      bestResults[Circuit4].bestcs[0] = xa, \
      bestResults[Circuit4].bestcs[1] = xb, \
      bestResults[Circuit4].bestcs[2] = xc, \
      bestResults[Circuit4].bestcs[3] = xd; \
  }
  
// Macro for doing the 4 capacitor solutions. The are 24 permutations - expanded by calling the
// above. Same parameters.
#define cc4(fn, cn, ca, cb, cc, cd)  \
  perm4(fn, cn, ca, cb, cc, cd); \
  perm4(fn, cn, ca, cb, cd, cc); \
  perm4(fn, cn, ca, cc, cb, cd); \
  perm4(fn, cn, ca, cc, cd, cb); \
  perm4(fn, cn, ca, cd, cb, cc); \
  perm4(fn, cn, ca, cd, cc, cb); \
  perm4(fn, cn, cb, ca, cc, cd); \
  perm4(fn, cn, cb, ca, cd, cc); \
  perm4(fn, cn, cb, cc, ca, cd); \
  perm4(fn, cn, cb, cc, cd, ca); \
  perm4(fn, cn, cb, cd, ca, cc); \
  perm4(fn, cn, cb, cd, cc, ca); \
  perm4(fn, cn, cc, ca, cb, cd); \
  perm4(fn, cn, cc, ca, cd, cb); \
  perm4(fn, cn, cc, cb, ca, cd); \
  perm4(fn, cn, cc, cb, cd, ca); \
  perm4(fn, cn, cc, cd, ca, cb); \
  perm4(fn, cn, cc, cd, cb, ca); \
  perm4(fn, cn, cd, ca, cb, cc); \
  perm4(fn, cn, cd, ca, cc, cb); \
  perm4(fn, cn, cd, cb, ca, cc); \
  perm4(fn, cn, cd, cb, cc, ca); \
  perm4(fn, cn, cd, cc, ca, cb); \
  perm4(fn, cn, cd, cc, cb, ca);


static void initOptimals(void)
/*     Initialize the optimal results values for each solution.
 * Parameters: none
 * External References:
 *   bestResults[]: A vector of one for each circuit size. (1-4).
 * Returned values: none
 * Errors detected: none
 */
{
  uint32_t i;
  for (i = 0; i < MAXCAPS; ++i)
  {
    bestResults[i].cnt = i + 1,
      bestResults[i].bestgoodness = 1.0e200;
  }
}



static int32_t farad2pf(double f)
/*     Convert a farad measurement into picofarads and round.  Doesn't work for big electrolytics.
 * Parameters:
 *   f: Value in farads.
 * External References: none
 * Returned values: The value in picofarads.
 * Errors detected: none.
 */
{
  return (int32_t) round(f * 1.0e9);
}



static void bestsol(int32_t fnumber, double target)
/*     Write best value and circuit to a file. There are 3 numbers separated by commas (CSV).
 *        target-pf,best-result-pf,circuit index (0-3).
 *   The file name is "res%d" with fnumber changed for each. This was used to generate the
 *   statistics for the "how good was the junkbox?" experiment.
 * Parameters:
 *   fname: A file number to write.
 *   target: Target value in farads.
 * External References:
 *   bestResults[]: The vector of results.
 * Returned values: none
 * Errors detected: couldn't open output file.
 */
{
  FILE    *fh;
  char    fname[32];
  int32_t i;
  double  bestresult;
  int32_t bestindex;

  // Create the file to write results to.
  sprintf(fname, "res%d", fnumber);
  if ((fh = fopen(fname, "wb")) == NULL)
  {
    fprintf(stderr, "Couldn't open %s for output\n", fname);
    exit(-1);
  }

  // Find the single best result.
  bestresult = 1.0E208;
  for (i = 0; i < MAXCAPS; ++i)
    if (bestResults[i].bestgoodness < bestresult)
    {
      bestresult = bestResults[i].bestgoodness,
        bestindex = i;
    }

  // Write the target, best value, the best circuit index - CSV format.
  fprintf(fh, "%d,%d,%d\n", farad2pf(target), farad2pf(bestResults[bestindex].bestres),
          bestindex + 1);
  fclose(fh);
}



static void showOptimal(FILE *fh, int32_t i)
/*     Show the optimal results for result i.
 * Parameters:
 *   fh: File to write to - can be stdout.
 *   i: 0 is 1 cap, 1 is two caps etc.
 * External References:
 *   bestResults[]: A vector of one for each circuit size. (1-4).
 * Returned values: none
 * Errors detected: none
 */
{
  int32_t j;
  fprintf(fh, "%s with capacitance %d PF\n", bestResults[i].bestcircuit,
         farad2pf(bestResults[i].bestres));
  for (j = 0; j < bestResults[i].cnt; ++j)
    fprintf(fh, "    C%d = %d PF\n", j + 1, farad2pf(bestResults[i].bestcs[j]));
  fprintf(fh, "\n");
}

  

int main(int argc, char *argv[])
/*     caps main program. Lots of macro expansions so this can be quite large.
 */
{
  uint64_t maxc;            // The largest permutation number for the number of capacitors.
  uint8_t  bon;             // The number of bits on.

  FILE     *fhperm;
  char     *resultf;
  FILE     *outf;
  
  double   targetcap;       // Target capacitance in farads.

  uint64_t perm;            // The current permutation value.
  char     ibuf[256];       // For reading capacitance values.
  double   res;             // Temporary for capacitance solution.
  double   c1, c2, c3, c4;
  int32_t  i;

  printf("caps KI7NNP V%d.%02d.%03d\n", VERSION, RELEASE, PATCH);
  
  // cmdline is:
  //     caps cap-file-name, perm-file-name resultf targetcap
  if (argc != 5)
  {
    fprintf(stderr, "Usage: caps cap-file-name perm-file-name resultf target-PF\n");
    fprintf(stderr, "   cap-file-name   junk box capacitor file.\n");
    fprintf(stderr, "   perm-file-name  Permutation file (64caps.dat).\n");
    fprintf(stderr, "   resultf         - output to stdout, integer - best result to file,\n\
                   other - output to file.\n");
    fprintf(stderr, "   target-PF       Target capacitance in PF\n");

    exit(-1);
  }

  // Read the capacitor file.
  ncaps = readCapacitorFile(argv[1]);

  // The largest value of permutation allowed.
  #ifdef WINDOWS
    maxc = (1ll << (uint64_t) ncaps) - 1ll;
  #else
    maxc = (1L << ncaps) - 1;
  #endif
  //  for (i = 0; i < ncaps; ++i) printf("  %d: %d\n", i, farad2pf(capValues[i]));

  // Open the permutation file. We could generate the permutation on the fly with some more
  // rigorous code but that would actually take work. This was (not so) quick and dirty. 2^64 - 1
  // is a big number.
  if ((fhperm = fopen(argv[2], "r")) == NULL)
  {
    fprintf(stderr, "Couldn't open %s for permutation input\n", argv[2]);
    exit(-1);
  }

  // Where we might put the result. Could be a file name to write to, - for stdout, or a
  // number to indicate output to a file res??? with just the best result.
  resultf = argv[3];

  // The target capcitance - what we're working towards. Must be in PD which we convert to
  // farads. 
  targetcap = ((double) atoi(argv[4])) / 1.0E9;

  // Initialize the best results.
  initOptimals();

  for (;;)
  {
    // Read a permutation line from the file.
    if (fgets(ibuf, 256, fhperm) == NULL) break;
    bon = atoi(strtok(ibuf, " "));      // Number of bits on.
    perm = atoll(strtok(NULL, " \n"));  // The permutation value. A bit on indicates which cap.

    // Don't sample beyond the number of capacitors we have.
    if (perm > maxc) break;

    // The number of bits on (precomputed) tells us which kinds of circuit to test.
    // Note: Since we converted the count from ASCII, it's probably slower to do it this way
    // Note: than the bitson function in utils.c which is really cute and fast.
    switch (bon) {

    // Single capacitor does the job. Circuit1.
    case 1:
      res = circuit1(capValues[bitIndex(perm, 1)]);;
      if (fabs(res - targetcap) < bestResults[Circuit1].bestgoodness)
      {
        bestResults[Circuit1].bestgoodness = fabs(res - targetcap),
          bestResults[Circuit1].bestres = res,
          bestResults[Circuit1].bestperm = perm,
          bestResults[Circuit1].bestcircuit = "Circuit 1",
          bestResults[Circuit1].bestcs[0] = capValues[bitIndex(perm, 1)];
      }
      break;

    // Two in series or parallel. Circuits 2a, 2b.
    case 2:
      c1 = capValues[bitIndex(perm, 1)];
      c2 = capValues[bitIndex(perm, 2)];

      cc2(circuit2a, "Circuit 2a", c1, c2);
      cc2(circuit2b, "Circuit 2b", c1, c2);
      break;

    // 4 different solutions here. Circuits 3a, 3b, 3c, and 3d.
    case 3:
      c1 = capValues[bitIndex(perm, 1)];
      c2 = capValues[bitIndex(perm, 2)];
      c3 = capValues[bitIndex(perm, 3)];
      cc3(circuit3a, "Circuit 3a", c1, c2, c3);
      cc3(circuit3b, "Circuit 3b", c1, c2, c3);
      cc3(circuit3c, "Circuit 3c", c1, c2, c3);
      cc3(circuit3d, "Circuit 3d", c1, c2, c3);
      break;

    // 9 different solutions with 4 capacitors. Circuits 4a - 4j.
    case 4:
      c1 = capValues[bitIndex(perm, 1)];
      c2 = capValues[bitIndex(perm, 2)];
      c3 = capValues[bitIndex(perm, 3)];
      c4 = capValues[bitIndex(perm, 4)];

      cc4(circuit4a, "Circuit 4a", c1, c2, c3, c4);
      cc4(circuit4b, "Circuit 4b", c1, c2, c3, c4);
      cc4(circuit4c, "Circuit 4c", c1, c2, c3, c4);
      cc4(circuit4d, "Circuit 4d", c1, c2, c3, c4);
      cc4(circuit4e, "Circuit 4e", c1, c2, c3, c4);
      cc4(circuit4f, "Circuit 4f", c1, c2, c3, c4);
      cc4(circuit4g, "Circuit 4g", c1, c2, c3, c4);
      cc4(circuit4h, "Circuit 4h", c1, c2, c3, c4);
      cc4(circuit4i, "Circuit 4i", c1, c2, c3, c4);
      cc4(circuit4j, "Circuit 4j", c1, c2, c3, c4);
      break;
      
   // Should never get here.
   default:
      break;
    }
  }

  // Don't need this anymore.
  fclose(fhperm);

  // Show results on standard out.
  if (strcmp(resultf, "-") == 0)  
    for (i = 0; i < MAXCAPS; ++i) showOptimal(stdout, i);

  // Write the single best solution to a file.
  else if (isInt(resultf)) bestsol(atoi(resultf), targetcap);

  // Write single result to a file and put the logo on the top.
  else
  {
    if ((outf = fopen(resultf, "wb")) == NULL)
    {
      fprintf(stderr, "Couldn't open %s for output\n", resultf);
      exit(-1);
    }
    fprintf(outf, "caps KI7NNP V%d.%02d.%03d\n", VERSION, RELEASE, PATCH);
    fprintf(outf, "Capacitor file %s with %d values\n", argv[1], ncaps);
    for (i = 0; i < MAXCAPS; ++i) showOptimal(outf, i);
    fclose(outf);
  }
  exit(0);
}



// EOF
