/*
 * Small standalone replacement for the EPICS caget command.
 *
 * It uses the distribution-independent MIDAS Channel Access client to read
 * one or more scalar process variables. Supported value types are STRING,
 * SHORT, FLOAT, ENUM, CHAR, LONG, and DOUBLE. Channel discovery follows the
 * usual EPICS_CA_ADDR_LIST and EPICS_CA_AUTO_ADDR_LIST environment variables.
 *
 * Examples:
 *
 *   midas-caget MIDAS:TEMPERATURE MIDAS:DEMAND
 *   midas-caget -d DOUBLE -t MIDAS:TEMPERATURE
 *   EPICS_CA_ADDR_LIST=127.0.0.1:5075 EPICS_CA_AUTO_ADDR_LIST=NO \
 *      midas-caget MIDAS:INTEGER
 */

#include "epics_ca.h"
#include "epics_tool.h"

#include <unistd.h>

#include <cstdio>
#include <cstdlib>
#include <string>
#include <vector>

namespace {

void usage(FILE *stream)
{
   std::fprintf(stream,
      "Usage: midas-caget [options] <PV name> ...\n\n"
      "  -h             Print this help\n"
      "  -V             Print the supported CA protocol version\n"
      "  -w <seconds>   CA timeout (default 1.0 or EPICS_CA_TIMEOUT)\n"
      "  -p <priority>  CA priority, 0-99 (default 0)\n"
      "  -t             Terse output: print values only\n"
      "  -d <type>      Request STRING, SHORT, FLOAT, ENUM, CHAR, LONG,\n"
      "                 DOUBLE, or the corresponding DBR number 0-6\n");
}

struct Request {
   const char *name = nullptr;
   chid channel = nullptr;
   int status = ECA_NORMAL;
   epics_tool::ScalarValue value;
};

} // namespace

int main(int argc, char **argv)
{
   setenv("POSIXLY_CORRECT", "1", 0);
   double timeout = epics_tool::timeout_from_environment();
   capri priority = CA_PRIORITY_DEFAULT;
   chtype type = DBR_STRING;
   bool terse = false;

   int option;
   while ((option = getopt(argc, argv, "hVtw:p:d:")) != -1) {
      switch (option) {
      case 'h': usage(stdout); return 0;
      case 'V': std::puts("midas-caget: Channel Access protocol 4.13"); return 0;
      case 't': terse = true; break;
      case 'w':
         if (!epics_tool::parse_double(optarg, timeout) || timeout < 0) {
            std::fprintf(stderr, "Invalid timeout '%s'\n", optarg);
            return 1;
         }
         break;
      case 'p':
         if (!epics_tool::parse_priority(optarg, priority)) {
            std::fprintf(stderr, "Invalid CA priority '%s'\n", optarg);
            return 1;
         }
         break;
      case 'd':
         if (!epics_tool::parse_type(optarg, type)) {
            std::fprintf(stderr, "Invalid DBR type '%s'\n", optarg);
            return 1;
         }
         break;
      default:
         usage(stderr);
         return 1;
      }
   }

   if (optind == argc) {
      std::fprintf(stderr, "No PV name specified. ('midas-caget -h' for help.)\n");
      return 1;
   }

   int status = ca_task_initialize();
   if (status != ECA_NORMAL) {
      epics_tool::print_ca_error("Cannot initialize CA for", "client", status);
      return 1;
   }

   std::vector<Request> requests(static_cast<size_t>(argc - optind));
   bool failed = false;
   for (size_t i = 0; i < requests.size(); ++i) {
      Request &request = requests[i];
      request.name = argv[optind + static_cast<int>(i)];
      request.status = ca_create_channel(request.name, nullptr, nullptr, priority,
                                         &request.channel);
      if (request.status != ECA_NORMAL)
         failed = true;
   }

   status = ca_pend_io(timeout);
   if (status == ECA_TIMEOUT) {
      std::fprintf(stderr, "Channel connection timed out.\n");
      failed = true;
   } else if (status != ECA_NORMAL) {
      epics_tool::print_ca_error("Cannot connect to", "channels", status);
      failed = true;
   }

   bool any_get = false;
   for (Request &request : requests) {
      if (request.status != ECA_NORMAL)
         continue;
      request.status = ca_get(type, request.channel, request.value.data(type));
      if (request.status == ECA_NORMAL)
         any_get = true;
      else
         failed = true;
   }

   if (any_get) {
      status = ca_pend_io(timeout);
      if (status == ECA_TIMEOUT) {
         std::fprintf(stderr, "Read operation timed out.\n");
         for (Request &request : requests) {
            if (request.status == ECA_NORMAL)
               request.status = ECA_TIMEOUT;
         }
         failed = true;
      } else if (status != ECA_NORMAL) {
         epics_tool::print_ca_error("Cannot read", "channels", status);
         failed = true;
      }
   }

   for (const Request &request : requests) {
      if (!terse)
         std::printf("%-30s ", request.name);
      if (request.status == ECA_NORMAL)
         std::puts(epics_tool::format_value(type, request.value).c_str());
      else {
         std::printf("*** CA error %s\n", ca_message(request.status));
         failed = true;
      }
   }

   ca_task_exit();
   return failed ? 1 : 0;
}
