/*
 * Small standalone replacement for the EPICS caput command.
 *
 * It uses the distribution-independent MIDAS Channel Access client to write
 * a scalar process variable and then reads the value back. 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-caput MIDAS:DEMAND 25.0
 *   midas-caput -d LONG MIDAS:INTEGER 10
 *   EPICS_CA_ADDR_LIST=127.0.0.1:5075 EPICS_CA_AUTO_ADDR_LIST=NO \
 *      midas-caput MIDAS:DEMAND 30.5
 */

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

#include <unistd.h>

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

namespace {

void usage(FILE *stream)
{
   std::fprintf(stream,
      "Usage: midas-caput [options] <PV name> <PV value> ...\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 the new value only\n"
      "  -d <type>      Write STRING (default), SHORT, FLOAT, ENUM, CHAR,\n"
      "                 LONG, DOUBLE, or the corresponding DBR number 0-6\n");
}

int read_string(chid channel, double timeout, std::string &value)
{
   epics_tool::ScalarValue scalar;
   int status = ca_get(DBR_STRING, channel, scalar.string_value);
   if (status != ECA_NORMAL)
      return status;
   status = ca_pend_io(timeout);
   if (status == ECA_NORMAL)
      value = scalar.string_value;
   return status;
}

void print_value(const char *prefix, const char *pv, const std::string &value)
{
   std::printf("%s%-30s %s\n", prefix, pv, value.c_str());
}

} // 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-caput: 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-caput -h' for help.)\n");
      return 1;
   }
   const char *pv = argv[optind++];
   if (optind == argc) {
      std::fprintf(stderr, "No value specified. ('midas-caput -h' for help.)\n");
      return 1;
   }

   std::string input = argv[optind++];
   while (optind < argc) {
      input += ' ';
      input += argv[optind++];
   }

   epics_tool::ScalarValue write_value;
   std::string error;
   if (!epics_tool::parse_value(type, input, write_value, error)) {
      std::fprintf(stderr, "%s\n", error.c_str());
      return 1;
   }

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

   chid channel = nullptr;
   status = ca_create_channel(pv, nullptr, nullptr, priority, &channel);
   if (status == ECA_NORMAL)
      status = ca_pend_io(timeout);
   if (status != ECA_NORMAL) {
      epics_tool::print_ca_error("Cannot connect to", pv, status);
      ca_task_exit();
      return 1;
   }

   std::string old_value;
   int old_status = read_string(channel, timeout, old_value);
   if (!terse && old_status == ECA_NORMAL)
      print_value("Old : ", pv, old_value);

   status = ca_put(type, channel, write_value.data(type));
   if (status != ECA_NORMAL) {
      epics_tool::print_ca_error("Cannot write", pv, status);
      ca_task_exit();
      return 1;
   }

   std::string new_value;
   status = read_string(channel, timeout, new_value);
   if (status != ECA_NORMAL) {
      epics_tool::print_ca_error("Cannot read new value from", pv, status);
      ca_task_exit();
      return 1;
   }

   if (terse)
      std::puts(new_value.c_str());
   else
      print_value("New : ", pv, new_value);

   ca_task_exit();
   return 0;
}
