/****************************************************************************
 * examples/alarm/alarm_main.c
 *
 *   Copyright (C) 2016 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/ioctl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sched.h>
#include <errno.h>

#include <nuttx/timers/rtc.h>

/****************************************************************************
 * Private Data
 ****************************************************************************/

static bool g_alarm_daemon_started;
static pid_t g_alarm_daemon_pid;
static bool g_alarm_received[CONFIG_RTC_NALARMS];

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: alarm_handler
 ****************************************************************************/

static void alarm_handler(int signo, FAR siginfo_t *info, FAR void *ucontext)
{
  int almndx = info->si_value.sival_int;
  if (almndx >= 0 && almndx < CONFIG_RTC_NALARMS)
    {
      g_alarm_received[almndx] = true;
    }
}

/****************************************************************************
 * Name: alarm_daemon
 ****************************************************************************/

static int alarm_daemon(int argc, FAR char *argv[])
{
  struct sigaction act;
  sigset_t set;
  int ret;
  int i;

  /* Indicate that we are running */

  g_alarm_daemon_started = true;
  printf("alarm_daemon: Running\n");

  /* Make sure that the alarm signal is unmasked */

  (void)sigemptyset(&set);
  (void)sigaddset(&set, CONFIG_EXAMPLES_ALARM_SIGNO);
  ret = sigprocmask(SIG_UNBLOCK, &set, NULL);
  if (ret != OK)
    {
      int errcode = errno;
      fprintf(stderr, "ERROR: sigprocmask failed: %d\n",
              errcode);
      goto errout;
    }

  /* Register alarm signal handler */

  act.sa_sigaction = alarm_handler;
  act.sa_flags     = SA_SIGINFO;

  (void)sigfillset(&act.sa_mask);
  (void)sigdelset(&act.sa_mask, CONFIG_EXAMPLES_ALARM_SIGNO);

  ret = sigaction(CONFIG_EXAMPLES_ALARM_SIGNO, &act, NULL);
  if (ret < 0)
    {
      int errcode = errno;
      fprintf(stderr, "ERROR: sigaction failed: %d\n",
              errcode);
      goto errout;
    }

  /* Now loop forever, waiting for alarm signals */

  for (; ; )
    {
      /* Check if any alarms fired.
       *
       * NOTE that there are race conditions here... if we missing an alarm,
       * we will just report it a half second late.
       */

      for (i = 0; i < CONFIG_RTC_NALARMS; i++)
        {
          if (g_alarm_received[i])
            {
              printf("alarm_demon: alarm %d received\n", i)  ;
              g_alarm_received[i] = false;
            }
        }

      /* Now wait a little while and poll again.  If a signal is received
       * this should cuase us to awken earlier.
       */

      usleep(500*1000L);
    }

errout:
  g_alarm_daemon_started = false;

  printf("alarm_daemon: Terminating\n");
  return EXIT_FAILURE;
}

/****************************************************************************
 * Name: start_daemon
 ****************************************************************************/

static int start_daemon(void)
{
  if (!g_alarm_daemon_started)
    {
      g_alarm_daemon_pid =
        task_create("alarm_daemon", CONFIG_EXAMPLES_ALARM_PRIORITY,
                    CONFIG_EXAMPLES_ALARM_STACKSIZE, alarm_daemon,
                    NULL);
      if (g_alarm_daemon_pid < 0)
        {
          int errcode = errno;
          fprintf(stderr, "ERROR: Failed to start alarm_daemon: %d\n",
                 errcode);
          return -errcode;
        }

      printf("alarm_daemon started\n");
      usleep(500*1000L);
    }

  return OK;
}

/****************************************************************************
 * Name: show_usage
 ****************************************************************************/

static void show_usage(FAR const char *progname)
{
  fprintf(stderr, "USAGE:\n");
  fprintf(stderr, "\t%s [-a <alarmid>] [-cr] [<seconds>]\n", progname);
  fprintf(stderr, "Where:\n");
  fprintf(stderr, "\t-a <alarmid>\n");
  fprintf(stderr, "\t\t<alarmid> selects the alarm: 0..%d (default: 0)\n",
          CONFIG_RTC_NALARMS - 1);
  fprintf(stderr, "\t-c\tCancel previously set alarm\n");
  fprintf(stderr, "\t-r\tRead previously set alarm\n");
  fprintf(stderr, "\t<seconds>\n");
  fprintf(stderr, "\t\tThe number of seconds until the alarm expires.\n");
  fprintf(stderr, "\t\t(only if no -c or -r option given.)\n");
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * alarm_main
 ****************************************************************************/

#ifdef BUILD_MODULE
int main(int argc, FAR char *argv[])
#else
int alarm_main(int argc, FAR char *argv[])
#endif
{
  unsigned long seconds = 0;
  bool badarg = false;
  bool readmode = false;
  bool cancelmode = false;
  bool setmode;
  int opt;
  int alarmid = 0;
  int fd;
  int ret;

  /* Make sure that the alarm daemon is running */

  ret = start_daemon();
  if (ret < 0)
    {
      return EXIT_FAILURE;
    }

  /* Parse commandline parameters. NOTE: getopt() is not thread safe nor re-entrant.
   * To keep its state proper for the next usage, it is necessary to parse to
   * the end of the line even if an error occurs.  If an error occurs, this
   * logic just sets 'badarg' and continues.
   */

  while ((opt = getopt(argc, argv, ":a:cr")) != ERROR)
    {
      switch (opt)
        {
        case 'a': /* -a: Select alarm id */
          alarmid = strtol(optarg, NULL, 0);
          if (alarmid < 0 || alarmid >= CONFIG_RTC_NALARMS)
            {
              badarg = true;
            }
          break;
        case 'c':
          cancelmode = true;
          break;
        case 'r':
          readmode = true;
          break;
        default:
          fprintf(stderr, "<unknown parameter '-%c'>\n\n", opt);
          /* fall through */
        case '?':
        case ':':
          badarg = true;
        }
    }

  /* Both -r and -c can be given at the same time, setting is exclusive. */

  setmode = !readmode && !cancelmode;

  if (setmode)
    {
      if (optind >= argc)
        {
          badarg = true;
        }
    }
  else if (optind < argc)
    {
       badarg = true;
    }

  if (badarg)
    {
      show_usage(argv[0]);
      return EXIT_FAILURE;
    }

  if (setmode)
    {
      /* Get the number of seconds until the alarm expiration */

      seconds = strtoul(argv[optind], NULL, 10);
      if (seconds < 1)
        {
          fprintf(stderr, "ERROR: Invalid number of seconds: %lu\n", seconds);
          show_usage(argv[0]);
          return EXIT_FAILURE;
        }
    }

  /* Open the RTC driver */

  printf("Opening %s\n", CONFIG_EXAMPLES_ALARM_DEVPATH);

  fd = open(CONFIG_EXAMPLES_ALARM_DEVPATH, O_WRONLY);
  if (fd < 0)
    {
      int errcode = errno;
      fprintf(stderr, "ERROR: Failed to open %s: %d\n",
              CONFIG_EXAMPLES_ALARM_DEVPATH, errcode);
      return EXIT_FAILURE;
    }

  if (readmode)
    {
      struct rtc_rdalarm_s rd = { 0 };
      long timeleft;
      time_t now;

      rd.id = alarmid;
      time(&now);

      ret = ioctl(fd, RTC_RD_ALARM, (unsigned long)((uintptr_t)&rd));
      if (ret < 0)
        {
          int errcode = errno;

          fprintf(stderr, "ERROR: RTC_RD_ALARM ioctl failed: %d\n",
                  errcode);

          (void)close(fd);
          return EXIT_FAILURE;
        }

      /* Some of the NuttX RTC implementations do not support alarms
       * longer than one month. There RTC_RD_ALARM can return partial
       * calendar values without month and year fields.
       * TODO: fix this in lower layers?
       */

      if (rd.time.tm_year > 0)
        {
          /* Normal sane case, assuming we did not actually request an
           * alarm expiring in year 1900.
           */

          timeleft = mktime((struct tm *)&rd.time) - now;
        }
      else
        {
          struct tm now_tm;

          /* Periodic extend "partial" alarms by "unfolding" months,
           * until we get alarm that is in future. Note that mktime()
           * normalizes fields that are out of their valid values,
           * so we don't have to handle carry to tm_year by ourselves.
           */

          gmtime_r(&now, &now_tm);
          rd.time.tm_mon = now_tm.tm_mon;
          rd.time.tm_year = now_tm.tm_year;

          do {
            timeleft = mktime((struct tm *)&rd.time) - now;
            if (timeleft < 0)
              {
                rd.time.tm_mon++;
              }
          } while (timeleft < 0);
        }

      printf("Alarm %d is %s with %ld seconds to expiration\n", alarmid,
             rd.active ? "active" : "inactive", timeleft);
    }

  if (cancelmode)
    {
      ret = ioctl(fd, RTC_CANCEL_ALARM, (unsigned long)alarmid);
      if (ret < 0)
        {
          int errcode = errno;

          fprintf(stderr, "ERROR: RTC_CANCEL_ALARM ioctl failed: %d\n",
                  errcode);

          (void)close(fd);
          return EXIT_FAILURE;
        }

      printf("Alarm %d has been canceled\n", alarmid);
    }

  if (setmode)
    {
      struct rtc_setrelative_s setrel;

      /* Set the alarm */

      setrel.id      = alarmid;
      setrel.pid     = g_alarm_daemon_pid;
      setrel.reltime = (time_t)seconds;

      setrel.event.sigev_notify = SIGEV_SIGNAL;
      setrel.event.sigev_signo  = CONFIG_EXAMPLES_ALARM_SIGNO;
      setrel.event.sigev_value.sival_int = alarmid;

      ret = ioctl(fd, RTC_SET_RELATIVE, (unsigned long)((uintptr_t)&setrel));
      if (ret < 0)
        {
          int errcode = errno;

          fprintf(stderr, "ERROR: RTC_SET_RELATIVE ioctl failed: %d\n",
                  errcode);

          (void)close(fd);
          return EXIT_FAILURE;
        }

      printf("Alarm %d set in %lu seconds\n", alarmid, seconds);
    }

  (void)close(fd);
  return EXIT_SUCCESS;
}