/*
 * Standalone example of the minimal MIDAS EPICS Channel Access server.
 *
 * The program publishes three writable scalar process variables:
 *
 *   MIDAS:TEMPERATURE  double, replaced by a random value once per second
 *   MIDAS:DEMAND       double demand value with direct readback
 *   MIDAS:INTEGER      32-bit integer value with direct readback
 *
 * It listens on the standard CA server port 5064 unless changed with -p.
 * For example, when running it with "-p 5075", clients can use:
 *
 *   EPICS_CA_ADDR_LIST=127.0.0.1:5075 EPICS_CA_AUTO_ADDR_LIST=NO \
 *      midas-caget MIDAS:TEMPERATURE MIDAS:DEMAND MIDAS:INTEGER
 *   EPICS_CA_ADDR_LIST=127.0.0.1:5075 EPICS_CA_AUTO_ADDR_LIST=NO \
 *      midas-caput MIDAS:DEMAND 25.0
 *
 * Successful writes are printed by the server for debugging. This example
 * keeps all values in local variables and passes their addresses to SoftIoc.
 */

#include "epics_ioc.h"

#include <signal.h>
#include <unistd.h>

#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <random>

namespace {

volatile sig_atomic_t stop_requested = 0;

void stop_handler(int)
{
   stop_requested = 1;
}

void usage(FILE *stream)
{
   std::fprintf(stream,
      "Usage: midas-soft-ioc [-p port]\n\n"
      "Publishes three writable scalar PVs:\n"
      "  MIDAS:TEMPERATURE  random double updated once per second\n"
      "  MIDAS:DEMAND       double demand/readback value\n"
      "  MIDAS:INTEGER      32-bit integer value\n");
}

} // namespace

int main(int argc, char **argv)
{
   std::uint16_t port = 5064;
   int option;
   while ((option = getopt(argc, argv, "hp:")) != -1) {
      if (option == 'h') {
         usage(stdout);
         return 0;
      }
      if (option == 'p') {
         char *end = NULL;
         long value = std::strtol(optarg, &end, 10);
         if (end == optarg || *end || value < 1 || value > 65535) {
            std::fprintf(stderr, "Invalid port '%s'\n", optarg);
            return 1;
         }
         port = static_cast<std::uint16_t>(value);
      } else {
         usage(stderr);
         return 1;
      }
   }
   if (optind != argc) {
      usage(stderr);
      return 1;
   }

   double temperature = 22.0;
   double demand = 20.0;
   std::int32_t integer = 0;
   midas::epics::SoftIoc ioc;
   if (!ioc.add_pv("MIDAS:TEMPERATURE", &temperature) ||
       !ioc.add_pv("MIDAS:DEMAND", &demand) ||
       !ioc.add_pv("MIDAS:INTEGER", &integer) || !ioc.start(port)) {
      std::fprintf(stderr, "Cannot start IOC: %s\n", ioc.error().c_str());
      return 1;
   }

   signal(SIGINT, stop_handler);
   signal(SIGTERM, stop_handler);
   std::mt19937 random(static_cast<unsigned int>(
      std::chrono::high_resolution_clock::now().time_since_epoch().count()));
   std::normal_distribution<double> noise(0.0, 0.25);
   std::chrono::steady_clock::time_point next_update =
      std::chrono::steady_clock::now();

   std::printf("MIDAS soft IOC listening on port %u\n", port);
   std::printf("PVs: MIDAS:TEMPERATURE, MIDAS:DEMAND, MIDAS:INTEGER\n");
   std::printf("Set EPICS_CA_ADDR_LIST=127.0.0.1:%u and "
               "EPICS_CA_AUTO_ADDR_LIST=NO\n", port);
   std::fflush(stdout);

   while (!stop_requested) {
      const std::chrono::steady_clock::time_point now =
         std::chrono::steady_clock::now();
      if (now >= next_update) {
         temperature = 22.0 + noise(random);
         next_update = now + std::chrono::seconds(1);
      }
      if (!ioc.poll(100)) {
         std::fprintf(stderr, "IOC error: %s\n", ioc.error().c_str());
         return 1;
      }
   }
   return 0;
}
