/*** randomized.c  -*- C -*- */
/** Randomized 3 SPI module testing */

/*** Ivan Shmakov, 2020 */
/**
 ** To the extent possible under law, the author(s) have dedicated
 ** all copyright and related and neighboring rights to this software
 ** to the public domain worldwide.  This software is distributed
 ** without any warranty.
 **
 ** You should have received a copy of the CC0 Public Domain Dedication
 ** along with this software.  If not, see
 ** <http://creativecommons.org/publicdomain/zero/1.0/>.
 */

/*** History */
/**
 ** 0.6 2020-03-20
 **     Unfortunately, we cannot decide that both amplifiers are
 **     stabilized after receiving just one octet from the device
 **     without keeping track of the state of the other channel as
 **     well; work-around by allowing for amplifier state to change
 **     until the end of the (hard-coded) interval, which is now set
 **     to 25 ms (was: 75 ms.)
 **
 ** 0.5 2020-03-20 04:50:09 +0000
 **     Fixed: read random seed from argv[4] (was: argv[3].)
 **
 ** 0.4 2020-03-20 04:42:36 +0000
 **     Fixed: try inverting 0240 bits in the octet read if amplifier
 **     state is expected to change (was: 040.)  Print A in all the
 **     messages mentioning the octed read if such change is expected.
 **
 ** 0.3 2020-03-20 04:24:26 +0000
 **     Fixed: use %09ld for printing .tv_nsec (was: %ld.)  Also print
 **     current timestamp in all debugging and notice (D:, N:) messages.
 **
 ** 0.2 2020-03-19 10:40:16 +0000
 **     Fixed: there are 12 possible commands than change amplifiers
 **     (was: only 6 covered.)  Use full struct timespec for the time
 **     the amplifier is expected to change state (was: time_t.)
 **     New before_p and timespec_ovf functions.
 **
 ** 0.1 2020-03-19 10:10:48 +0000
 **     Initial revision.
 **/

/*** Code: */
#include <assert.h>             /* for assert */
#include <errno.h>              /* for errno */
#include <error.h>              /* for error */
#include <fcntl.h>
#include <math.h>               /* for modf */
#include <stdbool.h>            /* for bool */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>             /* for rand */
#include <string.h>             /* for memcpy */
#include <time.h>               /* for nanosleep, etc. */
#include <unistd.h>             /* for read, write, etc. */

/** FIXME: allow stopping on signal */
#ifndef CYCLES_DEFAULT
#define CYCLES_DEFAULT          (1024L)
#endif
#ifndef DELAY_DEFAULT
#define DELAY_DEFAULT           (1.23)
#endif

static inline uint_least8_t
parity (uint_least8_t v)
{
  uint_least8_t p = 0;
  /** FIXME: inefficient (though rather general) */
  while (v > 0) {
    p  ^= (1 & v);
    v >>= 1;
  }

  /* . */
  return p;
}

static inline bool
before_p (const struct timespec *a, const struct timespec *b)
{
  /* . */
  return (a->tv_sec < b->tv_sec
          || (a->tv_sec == b->tv_sec && a->tv_nsec < b->tv_nsec));
}

static inline bool
timespec_ovf (struct timespec *a)
{
  if (a->tv_nsec < 1000000000L) {
    /* . */
    return 0;
  }
  /** NB: non-atomic */
  /** FIXME: only works when .tv_nsec is less than 2000000000L */
  a->tv_sec++;
  a->tv_nsec
    -= 1000000000L;
  /* . */
  return 1;
}

static int
p_arg_long (const char *s, long *vp)
{
  char *t;
  long v;

  if ((v = strtol (s, &t, 0)),
      t == s || *t != '\0') {
    /* . */
    return -1;
  }
  if (vp != 0) *vp = v;

  /* . */
  return 0;
}

static int
p_arg_double (const char *s, double *vp)
{
  char *t;
  double v;

  if ((v = strtod (s, &t)),
      t == s || *t != '\0') {
    /* . */
    return -1;
  }
  if (vp != 0) *vp = v;

  /* . */
  return 0;
}

static void
drain_buffer (int fd)
{
  /** FIXME: perhaps not a proper way for draining the buffer */

  /** FIXME: check fcntl return values? */

  int fl
    = fcntl (fd, F_GETFL);
  fcntl (fd, F_SETFL, (O_NONBLOCK | fl));

  char buf[4096];
  ssize_t r
    = read (fd, buf, sizeof (buf));
  if (r < 0 && errno != EAGAIN) {
    error (2, errno, "Flushing fd read buffer failed");
  }

  /** Clear O_NONBLOCK if it was not set */
  if ((O_NONBLOCK & fl) == 0) {
    fcntl (fd, F_SETFL, ((~ O_NONBLOCK) & fcntl (fd, F_GETFL)));
  }

  /* . */
}

static void
usage (int status)
{
  FILE *f
    = (status == 0 ? stdout : stderr);
  fprintf (f, "Usage: %s TTY [[-]DELAY [CYCLES [SEED]]]\n",
           program_invocation_short_name);
  if (status) {
    /* . */
    exit (status);
  }
  /* . */
}

struct stats {
  struct timespec start;
  unsigned long sent, recv, pare, unex;
#if 0
  double amp_sum;
  unsigned long amp_num;
#endif
} glo_st = { 0 };

static void
print_stats (FILE *out, const struct stats *st)
{
  struct timespec stop;
  clock_gettime (CLOCK_MONOTONIC, &stop);
  int r
    = fprintf (out,
               ("Stopped: %lld.%09ld\n"
                "Written: %7ld\n"
                "Read:    %7ld\n"
                "Unexp.:  %7ld\n"
                " parity: %7ld\n"),
               (long long)stop.tv_sec, (long)stop.tv_nsec,
               glo_st.sent, glo_st.recv, 
               glo_st.unex, glo_st.pare);
  assert (r > 0);
  /* . */
}

int
main (int argc, char *argv[])
{
  bool debug_p = 0;
  int tty;
  double delay
    = DELAY_DEFAULT;
  long cycles
    = CYCLES_DEFAULT;

  {
    int r;
    switch (-1 + argc) {
      case 4:
        {
          long seed;
          r = p_arg_long (argv[4], &seed);
          if (r != 0 || seed <= 0) { usage (1); }
          srand (seed);
          r = printf ("Seed:    %ld\n", seed);
          assert (r > 0);
        }
      case 3:
        r = p_arg_long (argv[3], &cycles);
        if (r != 0 || cycles < 0) { usage (1); }
      case 2:
        r = p_arg_double (argv[2], &delay);
        if (delay < 0) { debug_p = 1; delay = - delay; }
        if (r != 0 || delay < 1e-9) { usage (1); }
      case 1:
        if ((tty = open (argv[1], O_RDWR)) < 0) {
          error (2, errno, "%s: Cannot open file", argv[1]);
        }
        break;
      default:
        usage (1);
    }
  }

  double delay_i;
  /** NB: rounds down to an integral number of nanoseconds */
  assert (delay >= 1e-9);
  const struct timespec slp = {
    .tv_nsec
      = (long)(1e9 * modf (delay, &delay_i)),
    .tv_sec
      = (time_t)delay_i
  };

  drain_buffer (tty);

  clock_gettime (CLOCK_MONOTONIC, &(glo_st.start));
  {
    int r
      = printf ("Started: %lld.%09ld\n",
                (long long)glo_st.start.tv_sec,
                (long)glo_st.start.tv_nsec);
    assert (r > 0);
  }

  uint_least8_t ex_ch
    = 0;
  uint_least8_t expect[2]
    = { 0, 0300 };
  struct timespec amp_by = {
    .tv_sec     = glo_st.start.tv_sec,
    .tv_nsec    = glo_st.start.tv_nsec
  };
  for (long left = cycles; left > 0; --left) {
    /** NB: we are interested in some delay here, not a precise one */
    nanosleep (&slp, 0);

    struct timespec now;
    clock_gettime (CLOCK_MONOTONIC, &now);
    bool amp_stable_p
      = ! before_p (&now, &amp_by);

    char *t_s;
    {
      int r
        = asprintf (&t_s, "%lld.%09ld",
                    (long long)now.tv_sec, (long)now.tv_nsec);
      assert (r >= 0);
    }

    /** Send an octet */
    unsigned char wb[1];
    int ra
      = (rand () % (64 + 2 + (amp_stable_p ? 12 : 0)));
    /** Commands:
     ** PC 0AA AAA
     ** PC 111 0MM
     ** PC 111 10M
     ** PC 111 110
     **/
    wb[0]
      = (ra   < 64 ? (((040 & ra) << 1) | (037 & ra))
         : ra < 66 ? (076 | ((1 & ra) ? 0100 : 0))
         : ra < 70 ? (070 | ((4 & ra) ? 0100 : 0) | (3 & ra))
         :           (074 | ((2 & ra) ? 0100 : 0) | (1 & ra)));
    if (parity (wb[0])) {
      wb[0] ^= 0200;
    }
    ssize_t wr
      = write (tty, wb, sizeof (wb));
    assert (wr == sizeof (wb));
    ++glo_st.sent;
    if (debug_p) {
      error (0, 0, "D: @%s  Wrote: %03o", t_s, wb[0]);
    }

    /** Receive an octet */
    unsigned char rb[1];
    ssize_t rd
      = read (tty, rb, sizeof (rb));
    assert (rd == sizeof (rb));
    ++glo_st.recv;
    if (debug_p) {
      error  (0, 0, "D: @%s  Read:  %03o%s", t_s, rb[0],
              (amp_stable_p ? "" : " A"));
    }

    /** Check whether an expected octet was read */
    if (parity (rb[0])) {
      ++glo_st.pare;
      ++glo_st.unex;
      error  (0, 0,
              "N: @%s  Wrote: %03o  Read:  %03o  Exp.:  %03o %c parity!",
              t_s, wb[0], rb[0], expect[ex_ch],
              (amp_stable_p ? ' ' : 'A'));
    } else if (rb[0] == expect[ex_ch]) {
#if 0
      /** FIXME: alas, we must consider both amplifiers here */
      memcpy (&amp_by, &now, sizeof (amp_by));
#endif
    } else if (! amp_stable_p
               && rb[0] == (0240 ^ expect[ex_ch])) {
      /* do nothing */
    } else {
      ++glo_st.unex;
      error  (0, 0,
              "N: @%s  Wrote: %03o  Read:  %03o  Exp.:  %03o%s",
              t_s, wb[0], rb[0], expect[ex_ch],
              (amp_stable_p ? "" : " A"));
    }

    /** Update expected value for the channel(s) reconfigured */
    uint_least8_t v
      = wb[0];
    ex_ch
      = ((v & 0100) ? 1 : 0);
    /** FIXME: amplifier settings change tracking looks unoptimal */
    uint_least8_t amp_old
      = (((000040 & expect[0]) ? 1 : 0)
         | ((0040 & expect[1]) ? 2 : 0));
    if (0 == (040 & v)) {
      expect[ex_ch] &= (~ 037);
      expect[ex_ch] |=   (037 & v);
    } else if (070 == (v & 074)) {
      if ((1 & v)) {
        expect[0] |=   (0040);
      } else {
        expect[0] &= (~ 0040);
      }
      if ((2 & v)) {
        expect[1] |=   (0040);
      } else {
        expect[1] &= (~ 0040);
      }
      if (parity (expect[ex_ch ? 0 : 1])) {
        expect[ex_ch ? 0 : 1] ^= 0200;
      }
    } else if (074 == (v & 076)) {
      if ((1 & v)) {
        expect[ex_ch] |=   (0040);
      } else {
        expect[ex_ch] &= (~ 0040);
      }
    }
    if (parity (expect[ex_ch])) {
      expect[ex_ch] ^= 0200;
    }
    assert ((expect[0] & 0100) == 0);
    assert ((expect[1] & 0100));
    uint_least8_t amp_new
      = (((000040 & expect[0]) ? 1 : 0)
         | ((0040 & expect[1]) ? 2 : 0));
    if (amp_old != amp_new) {
      struct timespec new = {
        .tv_sec     = now.tv_sec,
        .tv_nsec    = now.tv_nsec + 25000000L /* FIXME: hardcoded */
      };
      timespec_ovf (&new);
      if (before_p (&amp_by, &new)) {
        memcpy (&amp_by, &new, sizeof (amp_by));
      }
    }
  }

  print_stats (stdout, &glo_st);

  /* . */
  return 0;
}

/*** 344WI0ZD.c ends here */
