/*
 * Copyright (C) 2005 Jonathan Hudson <jh+w1retap@daria.co.uk>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
#include <math.h>
#include <assert.h>
#include <syslog.h>
#include <errno.h>
#include "ownet.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "w1retap.h"

enum W1_sigflag { W1_NOFLAG = 0, W1_RECONF = (1), W1_READALL = (2), W1_SHOWCF = (4) };

static volatile enum W1_sigflag sigme;

#ifndef HAVE_CLOCK_NANOSLEEP
static inline void nanosub(struct timespec *a, struct timespec *b, struct timespec *result) {
  result->tv_sec = a->tv_sec - b->tv_sec;
  result->tv_nsec = a->tv_nsec - b->tv_nsec;
  if (result->tv_nsec < 0) {
    --result->tv_sec;
    result->tv_nsec += 1000000000;
  }
}
#endif

static void sig_hup(int x) { sigme |= W1_RECONF; }

static void sig_usr1(int x) { sigme |= W1_READALL; }

static void sig_usr2(int x) { sigme |= W1_SHOWCF; }

void w1_replog(w1_devlist_t *w1, const char *fmt, ...) {
  static FILE *fp;
  va_list va;
  char *p;
  int nc;

  va_start(va, fmt);
  if ((nc = vasprintf(&p, fmt, va)) != -1) {
    int n;
    w1->lastmsg = p;
    for (n = 0; n < w1->ndll; n++) {
      if (w1->dlls[n].type == 'r' && w1->dlls[n].func) {
        (w1->dlls[n].func)(w1, w1->dlls[n].param);
      }
    }
    w1->lastmsg = NULL;

    if (w1->repfile) {
      if (fp == NULL) {
        if (*w1->repfile != '-') {
          fp = fopen(w1->repfile, "a");
        } else {
          fp = stderr;
        }
      }
      if (fp) {
        char s[64];
        logtimes(w1, w1->logtime, s);
        fputs(s, fp);
        fputc(' ', fp);
        fputs(p, fp);
        fputc('\n', fp);
        fflush(fp);
      }
    }
    free(p);
  }
  va_end(va);
}

static void do_init(w1_devlist_t *w1) {
  int n;
  int done_i = 0;
  void (*func)(w1_devlist_t * w1, char *);

  for (n = 0; n < w1->ndll; n++) {
    if (w1->dlls[n].type == 'c' && done_i == 0) {
      if ((g_module_symbol(w1->dlls[n].handle, "w1_init", (void *)&func))) {
        (func)(w1, w1->dlls[n].param);
        done_i = 1;
      } else {
        perror("init");
        exit(1);
      }
    } else if (w1->dlls[n].type == 'l') {
      if (!(g_module_symbol(w1->dlls[n].handle, "w1_logger", (void *)&w1->dlls[n].func))) {
        perror("logger");
        exit(1);
      }
    } else if (w1->dlls[n].type == 'r') {
      if (!(g_module_symbol(w1->dlls[n].handle, "w1_report", (void *)&w1->dlls[n].func))) {
        fputs("replogger entry point not found\n", stderr);
        exit(1);
      }
    }
  }

  if (done_i == 0) {
    fputs("Init fails: No w1_init() in init= loadable library\n"
          "Please check the init= lines in your configuration file\n"
          "and the manual entry 'Configuring the W1RETAP software'.\n"
          "This is typically a configuration or build error.\n\n"
          "You should also check that the required data driver is\n"
          "present under $prefix/lib/w1retap/, for example, for sqlite,\n"
          "a set of libraries libw1sqlite.so{,.0,.0.0.0} should be present.\n",
          stderr);
    exit(1);
  }
  w1_verify_intervals(w1);
  w1_initialize_couplers(w1);
}

void init_interfaces(w1_devlist_t *w1) {
  if (w1->iface == NULL)
    w1->iface = strdup("DS2490-1");
  if (w1->portnum == -1) {
    if ((w1->portnum = owAcquireEx(w1->iface)) < 0) {
      OWERROR_DUMP(stdout);
      exit(1);
    }
    if (w1->verbose) {
      owShowVersion();
    }
  }
}

void dll_parse(w1_devlist_t *w1, int typ, char *params) {
  char *sep;
  char *sofile = NULL;

  if (w1->ndll == (MAXDLL - 1)) {
    fputs("Too many DLLs\n", stderr);
    exit(1);
  }

  sep = strpbrk(params, ":;=");
  if (sep) {
    *sep++ = 0;
  }

  if (*params == '.' || *params == '/') {
    sofile = strdup(params);
  } else {
    sofile = w1_module_build_path(PACKAGE_LIB_DIR, params);
  }

  if (NULL != (w1->dlls[w1->ndll].handle = g_module_open(sofile, G_MODULE_BIND_LAZY))) {
    w1->dlls[w1->ndll].type = typ;
    if (sep && *sep) {
      w1->dlls[w1->ndll].param = strdup(sep);
    }
    w1->ndll++;
  }
  free(sofile);
}

static void cleanup(int n, void *w) {
  int i;
  w1_devlist_t *w1 = w;

  w1_freeup(w1);
  for (i = 0; i < w1->ndll; i++) {
    if (w1->dlls[i].handle) {
      g_module_close(w1->dlls[i].handle);
      w1->dlls[i].handle = 0;
    }

    if (w1->dlls[i].param) {
      free(w1->dlls[i].param);
      w1->dlls[i].param = NULL;
    }
    w1->dlls[w1->ndll].type = 0;
  }
  w1->ndll = 0;
  free(w1->rcfile);
  w1->rcfile = NULL;
}

/*
static void w1_show_id(uchar *id, char *res)
{
    int i;
    for(i=0; i<8; i++)
    {
        sprintf((res+i*2), "%02X", id[i]);
    }
}
*/

static void w1_show(w1_devlist_t *w1, int forced) {
  if (w1->verbose || forced) {
    int i;

    if (w1->numdev) {
      fputs("Sensors:\n", stderr);
    }

    for (i = 0; i < w1->numdev; i++) {
      int j;

      fprintf(stderr, "%s %s (%ds)%s\n", w1->devs[i].serial, w1->devs[i].devtype, w1->devs[i].intvl,
              (w1->devs[i].stype == W1_INVALID) ? " !" : "");

      if (w1->devs[i].coupler) {
        char *branch = (w1->devs[i].coupler->branch == COUPLER_AUX_ON) ? "aux" : "main";
        fprintf(stderr, "\tVia coupler: %s, %s\n", w1->devs[i].coupler->coupler_device->serial, branch);
      }

      if (w1->devs[i].params && w1->devs[i].stype != W1_COUPLER) {
        int j;
        fputs("\tParameters:", stderr);
        for (j = 0; j < w1->devs[i].params->num; j++) {
          fprintf(stderr, " %f", w1->devs[i].params->values[j]);
        }
        fputc('\n', stderr);
      }

      for (j = 0; j < w1->devs[i].ns; j++) {
        if (w1->devs[i].s[j].abbrv) {
          fprintf(stderr, "\t%d: %s %s %s", j + 1, w1->devs[i].s[j].abbrv, w1->devs[i].s[j].name,
                  (w1->devs[i].s[j].units) ? (w1->devs[i].s[j].units) : "");
          if (w1->devs[i].s[j].flags & W1_ROC) {
            fprintf(stderr, ", %.4f /min", w1->devs[i].s[j].roc);
          }
          if (w1->devs[i].s[j].flags & W1_RMIN) {
            fprintf(stderr, ", min=%.2f", w1->devs[i].s[j].rmin);
          }
          if (w1->devs[i].s[j].flags & W1_RMAX) {
            fprintf(stderr, ", max=%.2f", w1->devs[i].s[j].rmax);
          }
          fputc('\n', stderr);
        }
      }
    }

    if (w1->ndll) {
      fputs("Plugins:\n", stderr);
      for (i = 0; i < w1->ndll; i++) {
        fprintf(stderr, "%2d: %c %s", i, w1->dlls[i].type, g_module_name(w1->dlls[i].handle));
        if (w1->dlls[i].param == NULL) {
          fprintf(stderr, "\n");
        } else {
          fprintf(stderr, " => [");
          if (strcasestr(w1->dlls[i].param, "password=") == NULL) {
            fprintf(stderr, "%s]\n", w1->dlls[i].param);
          } else {
            char *p, *q;
            char *params = strdup(w1->dlls[i].param);
            for (p = params;; p = NULL) {
              char *tok = strtok_r(p, " ", &q);
              if (tok != NULL) {
                if (p == NULL) {
                  fprintf(stderr, " ");
                }
                if (strncmp(tok, "password=", 9) == 0) {
                  fprintf(stderr, "password=?");
                } else {
                  fprintf(stderr, "%s", tok);
                }
              } else {
                fprintf(stderr, "]\n");
                break;
              }
            }
            free(params);
          }
        }
      }
    }
    if (w1->altitude) {
      fprintf(stderr, "Normalising pressure for %dm\n", w1->altitude);
    }
    if (w1->vane_offset) {
      fprintf(stderr, "Vane offset %d\n", w1->vane_offset);
    }
    if (w1->logtmp) {
      fprintf(stderr, "Log file is %s\n", w1->tmpname);
    }
    fprintf(stderr, "Interval %ds, cycle %ds\n", w1->delay, w1->cycle);
    fprintf(stderr, "Release i/face %d\n", w1->release_me);
    if (w1->pres_reduction_temp) {
      fprintf(stderr, "Pressure reduction temp %.2f\n", *w1->pres_reduction_temp);
    }
    if (w1->allow_escape) {
      fputs("Allowing rate limit escapes\n", stderr);
    }
    if (w1->force_utc) {
      fputs("dates logged at UTC\n", stderr);
    }
  }
}

int main(int argc, char **argv) {
  struct timeval now = {0};
  struct sigaction act = {{0}};
  gboolean immed = 0;
  gboolean showvers = 0;
  gboolean once = 0;
  gboolean w1_simul = 0;
  int n;
  char *p;
  GError *error = NULL;
  GOptionContext *context;
  w1_devlist_t *w1 = calloc(1, sizeof(w1_devlist_t));
  GOptionEntry entries[] = {
      {"wait", 'w', 0, G_OPTION_ARG_NONE, &immed, "At startup, wait until next interval", NULL},
      {"once-only", '1', 0, G_OPTION_ARG_NONE, &once, "Read once and exit", NULL},
      {"release-interface", 'R', 0, G_OPTION_ARG_NONE, &w1->release_me, "Release the 1Wire interface between reads", NULL},
      {"daemonise", 'd', 0, G_OPTION_ARG_NONE, &w1->daemonise, "Daemonise (background) application", NULL},
      {"no-tmp-log", 'T', 0, G_OPTION_ARG_NONE, &w1->logtmp, "Disables /tmp/.w1retap.dat logging", NULL},
      {"tmp-log-name", 'l', 0, G_OPTION_ARG_STRING, &w1->tmpname, "Names logging file (/tmp/.w1retap.dat)", "FILE"},
      {"interface", 'i', 0, G_OPTION_ARG_STRING, &w1->iface, "Interface device", "DEVICE"},
      {"cycle-time", 't', 0, G_OPTION_ARG_INT, &w1->delay, "Time (secs) between device readings", "SECS"},
      {"dont-read", 'N', 0, G_OPTION_ARG_NONE, &w1->doread, "Don't read sensors (for debugging)", NULL},
      {"verbose", 'v', 0, G_OPTION_ARG_NONE, &w1->verbose, "Verbose messages", NULL},
      {"vane-offset", 'o', 0, G_OPTION_ARG_INT, &w1->vane_offset, "Value for N for weather vane (0-15)", "VAL"},
      {"version", 'V', 0, G_OPTION_ARG_NONE, &showvers, "Display version number (and exit)", NULL},
      {"simulate", 's', 0, G_OPTION_ARG_NONE, &w1_simul, "Simulate readings (for testing, not yet implemented)", NULL},
      {"use-utc", 'u', 0, G_OPTION_ARG_NONE, &w1->force_utc, "Store dates as UTC (vice localtime)", NULL},
      {"report-log", 'r', 0, G_OPTION_ARG_STRING, &w1->repfile, "Report log file", "FILE"},
      {NULL}};

  openlog("w1retap", LOG_PID, LOG_USER);

  if (!g_module_supported()) {
    exit(2);
  }

  act.sa_flags = SA_RESTART;
  sigemptyset(&(act.sa_mask));

  act.sa_handler = sig_usr1;
  sigaction(SIGUSR1, &act, NULL);

  act.sa_handler = sig_usr2;
  sigaction(SIGUSR2, &act, NULL);

  act.sa_handler = sig_hup;
  sigaction(SIGHUP, &act, NULL);

  w1->logtmp = 1;
  w1->portnum = -1;
  w1->log_delim[0] = ' ';
  w1->delay = w1->cycle = W1_DEFAULT_INTVL;
  w1->temp_scan = 1000;
  w1->pres_reduction_temp = NULL;

  if ((p = getenv("W1RCFILE"))) {
    w1->rcfile = strdup(p);
  }
  read_config(w1);

  context = g_option_context_new("- w1retap");
  g_option_context_set_ignore_unknown_options(context, FALSE);
  g_option_context_add_main_entries(context, entries, NULL);
  if (g_option_context_parse(context, &argc, &argv, &error) == FALSE) {
    if (error != NULL) {
      fprintf(stderr, "%s\n", error->message);
      g_error_free(error);
      exit(1);
    }
  }

  immed ^= 1; // weird JH logic ...

  if (w1->tmpname == NULL) {
    w1->tmpname = "/tmp/.w1retap.dat";
  }

  w1->doread ^= 1;

  if (showvers)
    w1->verbose = 2;

  w1->vane_offset &= 0xf;

  if (w1->verbose) {
    fputs("w1retap v" VERSION " (c) 2005-2023 Jonathan Hudson\n", stderr);
    fputs("Built: " __DATE__ " " __TIME__ " gcc " __VERSION__ "\n", stderr);
    if (w1->verbose == 2) {
      exit(0);
    }
    fprintf(stderr, "cfg: read from %s\n", w1->rcfile);
    fprintf(stderr, "cfg: delay = %d\n", w1->delay);
    fprintf(stderr, "cfg: demonise = %d\n", w1->daemonise);
    fprintf(stderr, "cfg: altitude = %d\n", w1->altitude);
    fprintf(stderr, "cfg: vane_offset = %d\n", w1->vane_offset);
    fprintf(stderr, "cfg: timestamp = %d\n", w1->timestamp);
    fprintf(stderr, "cfg: logtemp = %d\n", w1->logtmp);
    fprintf(stderr, "cfg: temp_scan = %d\n", w1->temp_scan);
  }

  do_init(w1);
  w1_show(w1, 0);

  if (!w1->doread) {
    exit(0);
  }

  init_interfaces(w1);
  w1->logtime = time(NULL);
  w1_replog(w1, "Startup w1retap v" VERSION);

  if (w1->daemonise) {
    // preserve redirected log files unless TTYs
    int iclose;
    iclose = !(isatty(1) && isatty(2));
    assert(0 == daemon(0, iclose));
  }

  if (w1->pidfile) {
    FILE *fp;
    if (NULL != (fp = fopen(w1->pidfile, "w"))) {
      fprintf(fp, "%d\n", getpid());
      fclose(fp);
    }
    free(w1->pidfile);
  }
  w1_all_couplers_off(w1);

  while (1) {
    int nv = 0;
    if (immed) {
      if (w1->verbose)
        fputs("reading ...\n", stderr);
      nv = w1_read_all_sensors(w1, now.tv_sec);
      if (nv) {
        for (n = 0; n < w1->ndll; n++) {
          if (w1->dlls[n].type == 'l' && w1->dlls[n].func) {
            (w1->dlls[n].func)(w1, w1->dlls[n].param);
          }
        }

        if (w1->logtmp && ((now.tv_sec % w1->cycle) == 0)) {
          w1_tmpfilelog(w1);
        }
      }
      if (w1->release_me) {
        owRelease(w1->portnum);
        w1->portnum = -1;
      }
    }

    if (once)
      break;

    if (w1->delay) {
      int ns;
      immed = 1;

      do {
        struct timespec req;
        ns = 0;
        if (sigme & W1_READALL) {
          sigme &= ~W1_READALL;
          nv = w1_read_all_sensors(w1, 0);
          w1_tmpfilelog(w1);
        }

        if (sigme & W1_RECONF) {
          sigme &= ~W1_RECONF;
          cleanup(0, w1);
          read_config(w1);
          do_init(w1);
          w1_show(w1, 0);
          init_interfaces(w1);
        }

        if (sigme & W1_SHOWCF) {
          sigme &= ~W1_SHOWCF;
          w1_show(w1, 1);
        }

        if (w1->verbose)
          fputs("Waiting ... ", stderr);
        gettimeofday(&now, NULL);
#ifdef HAVE_CLOCK_NANOSLEEP
        req.tv_sec = (now.tv_sec / w1->delay) * w1->delay + w1->delay;
        req.tv_nsec = 0;
        ns = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &req, NULL);
#else
        struct timespec then, nnow;
        then.tv_sec = w1->delay * (1 + now.tv_sec / w1->delay);
        then.tv_nsec = 200 * 1000 * 1000; /* ensure tick crossed */
        nnow.tv_sec = now.tv_sec;
        nnow.tv_nsec = now.tv_usec * 1000;
        nanosub(&then, &nnow, &req);
        ns = nanosleep(&req, NULL);
#endif
        if (ns == 0) {
          gettimeofday(&now, NULL);
          w1->logtime = now.tv_sec;
          if (w1->verbose)
            fputs(ctime(&now.tv_sec), stderr);
        }
      } while ((ns == EINTR) || (ns == -1 && errno == EINTR));
    } else {
      break;
    }
  }
  return 0;
}
