/****************************************************************************
 * apps/system/uorb/uORB/uORB.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

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

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/ioctl.h>
#include <time.h>
#include <unistd.h>
#include <string.h>

#include <nuttx/streams.h>
#include <uORB/uORB.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define ORB_IOCTL(fd, cmd, arg) \
  do \
    { \
      if (ioctl(fd, cmd, arg) < 0) \
        { \
          return -errno; \
        } \
      \
    } \
  while (0)

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

/****************************************************************************
 * Name: orb_advsub_open
 *
 * Description:
 *   Open device node as advertiser / subscriber, regist node and save meta
 *   in driver for first user, set buffer number for advertisers.
 *
 * Input Parameters:
 *   meta         The uORB metadata (usually from the ORB_ID() macro)
 *   flag         The open flag.
 *   instance     Instance number to open.
 *   queue_size   Maximum number of buffered elements.
 *   wakeup       The wakeup flag.
 *
 * Returned Value:
 *   fd on success, otherwise returns a negated errno value on failure.
 ****************************************************************************/

static int orb_advsub_open(FAR const struct orb_metadata *meta, int flags,
                           int instance, unsigned int queue_size,
                           FAR orb_info_t *info, bool wakeup)
{
  char path[ORB_PATH_MAX];
  int fd;
  int ret;
  int err;

  snprintf(path, ORB_PATH_MAX, ORB_SENSOR_PATH"%s%d", meta->o_name,
           instance);

  /* Check existance before open */

  flags |= O_CLOEXEC;
  fd = open(path, flags);
  if (fd < 0)
    {
      struct sensor_reginfo_s reginfo;
      strlcpy(reginfo.path, path, NAME_MAX);
      reginfo.esize   = meta->o_size;
      reginfo.nbuffer = queue_size;
      reginfo.persist = !!(flags & SENSOR_PERSIST);
      if (info != NULL)
        {
          memcpy(&reginfo.devinfo, info, sizeof(*info));
        }
      else
        {
          memset(&reginfo.devinfo, 0, sizeof(*info));
        }

      fd = open(ORB_USENSOR_PATH, O_WRONLY | O_CLOEXEC);
      if (fd < 0)
        {
          return -errno;
        }

      /* Register new device node */

      ret = ioctl(fd, SNIOC_REGISTER, (unsigned long)(uintptr_t)&reginfo);
      err = errno;
      close(fd);
      if (ret < 0 && err != EEXIST)
        {
          return -errno;
        }

      fd = open(path, flags);
      if (fd < 0)
        {
          return -errno;
        }

      if (err != EEXIST)
        {
          ioctl(fd, SNIOC_SET_USERPRIV, (unsigned long)(uintptr_t)meta);
        }
    }

  /* Only first advertiser can successfully set buffer number */

  if (queue_size)
    {
      ioctl(fd, SNIOC_SET_BUFFER_NUMBER, (unsigned long)queue_size);
    }

  if (wakeup)
    {
      ioctl(fd, SNIOC_SET_WAKEUP, (unsigned long)wakeup);
    }

  return fd;
}

static int
orb_advertise_multi_queue_flags(FAR const struct orb_metadata *meta,
                                FAR const void *data, FAR int *instance,
                                unsigned int queue_size, int flags,
                                FAR orb_info_t *info)
{
  int inst;
  int fd;

  /* Open the node as an advertiser */

  inst = instance ? *instance : orb_group_count(meta);

  fd = orb_advsub_open(meta, flags, inst, queue_size, info, false);
  if (fd < 0)
    {
      uorberr("%s advertise failed (%i)", meta->o_name, fd);
      return -1;
    }

  /* The advertiser may perform an initial publish to initialise the object */

  if (data != NULL)
    {
      int ret;

      ret = orb_publish_multi(fd, data, meta->o_size);
      if (ret != meta->o_size)
        {
          uorberr("%s publish %d, expect %d",
                  meta->o_name, ret, meta->o_size);
          close(fd);
          return -1;
        }
    }

  return fd;
}

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

int orb_open(FAR const char *name, int instance, int flags)
{
  int ret;
  char path[ORB_PATH_MAX];

  snprintf(path, ORB_PATH_MAX, ORB_SENSOR_PATH"%s%d", name, instance);
  ret = open(path, O_CLOEXEC | flags);
  if (ret < 0)
    {
      return -errno;
    }

  return ret;
}

int orb_close(int fd)
{
  int ret = close(fd);
  if (ret < 0)
    {
      return -errno;
    }

  return ret;
}

int orb_unlink_multi(FAR const struct orb_metadata *meta, int instance)
{
  char path[ORB_PATH_MAX];
  int ret;
  int fd;

  snprintf(path, ORB_PATH_MAX, ORB_SENSOR_PATH"%s%d", meta->o_name,
           instance);
  fd = open(ORB_USENSOR_PATH, O_WRONLY | O_CLOEXEC);
  if (fd < 0)
    {
      return -errno;
    }

  ret = ioctl(fd, SNIOC_UNREGISTER, (unsigned long)(uintptr_t)path);
  close(fd);
  if (ret < 0)
    {
      uorberr("topic:%s%d unregister failed! return:%d",
              meta->o_name, instance, ret);
      return -errno;
    }

  return ret;
}

int orb_advertise_multi_queue_info(FAR const struct orb_metadata *meta,
                                   FAR const void *data, FAR int *instance,
                                   unsigned int queue_size,
                                   FAR orb_info_t *info)
{
  return orb_advertise_multi_queue_flags(meta, data, instance,
                                         queue_size, O_WRONLY, info);
}

int
orb_advertise_multi_queue_persist_info(FAR const struct orb_metadata *meta,
                                       FAR const void *data,
                                       FAR int *instance,
                                       unsigned int queue_size,
                                       FAR orb_info_t *info)
{
  return orb_advertise_multi_queue_flags(meta, data, instance, queue_size,
                                         O_WRONLY | SENSOR_PERSIST, info);
}

ssize_t orb_publish_multi(int fd, FAR const void *data, size_t len)
{
  ssize_t ret = write(fd, data, len);
  if (ret < 0)
    {
      return -errno;
    }

  return ret;
}

int orb_subscribe_multi(FAR const struct orb_metadata *meta,
                        unsigned instance)
{
  return orb_advsub_open(meta, O_RDONLY, instance, 0, NULL, false);
}

int orb_subscribe_multi_wakeup(FAR const struct orb_metadata *meta,
                                  unsigned instance)
{
  return orb_advsub_open(meta, O_RDONLY, instance, 0, NULL, true);
}

ssize_t orb_copy_multi(int fd, FAR void *buffer, size_t len)
{
  ssize_t ret = read(fd, buffer, len);
  if (ret < 0)
    {
      return -errno;
    }

  return ret;
}

int orb_get_state(int fd, FAR struct orb_state *state)
{
  struct sensor_state_s tmp;

  if (!state)
    {
      return -EINVAL;
    }

  ORB_IOCTL(fd, SNIOC_GET_STATE, (unsigned long)(uintptr_t)&tmp);
  state->max_frequency      = tmp.min_interval ?
                              1000000 / tmp.min_interval : 0;
  state->min_batch_interval = tmp.min_latency;
  state->queue_size         = tmp.nbuffer;
  state->nsubscribers       = tmp.nsubscribers;
  state->generation         = tmp.generation;
  return 0;
}

int orb_get_events(int fd, FAR unsigned int *events)
{
  if (!events)
    {
      return -EINVAL;
    }

  ORB_IOCTL(fd, SNIOC_GET_EVENTS, (unsigned long)(uintptr_t)events);
  return 0;
}

int orb_check(int fd, FAR bool *updated)
{
  ORB_IOCTL(fd, SNIOC_UPDATED, (unsigned long)(uintptr_t)updated);
  return 0;
}

int orb_ioctl(int fd, int cmd, unsigned long arg)
{
  ORB_IOCTL(fd, cmd, arg);
  return 0;
}

int orb_flush(int fd)
{
  ORB_IOCTL(fd, SNIOC_FLUSH, 0);
  return 0;
}

int orb_set_interval(int fd, unsigned interval)
{
  ORB_IOCTL(fd, SNIOC_SET_INTERVAL, (unsigned long)interval);
  return 0;
}

int orb_get_interval(int fd, FAR unsigned *interval)
{
  struct sensor_ustate_s tmp;

  ORB_IOCTL(fd, SNIOC_GET_USTATE, (unsigned long)(uintptr_t)&tmp);
  *interval = tmp.interval;
  return 0;
}

int orb_get_info(int fd, FAR orb_info_t *info)
{
  ORB_IOCTL(fd, SNIOC_GET_INFO, (unsigned long)(uintptr_t)info);
  return 0;
}

int orb_set_batch_interval(int fd, unsigned batch_interval)
{
  ORB_IOCTL(fd, SNIOC_BATCH, (unsigned long)batch_interval);
  return 0;
}

int orb_get_batch_interval(int fd, FAR unsigned *batch_interval)
{
  struct sensor_ustate_s tmp;

  ORB_IOCTL(fd, SNIOC_GET_USTATE, (unsigned long)(uintptr_t)&tmp);
  *batch_interval = tmp.latency;
  return 0;
}

orb_abstime orb_absolute_time(void)
{
  struct timespec ts;

  clock_gettime(CLOCK_MONOTONIC, &ts);

  return 1000000ull * ts.tv_sec + ts.tv_nsec / 1000;
}

int orb_exists(FAR const struct orb_metadata *meta, int instance)
{
  struct sensor_state_s state;
  char path[ORB_PATH_MAX];
  int ret;
  int fd;

  snprintf(path, ORB_PATH_MAX, ORB_SENSOR_PATH"%s%d", meta->o_name,
           instance);
  fd = open(path, 0);
  if (fd < 0)
    {
      return -errno;
    }

  ret = ioctl(fd, SNIOC_GET_STATE, (unsigned long)(uintptr_t)&state);
  close(fd);
  if (ret < 0)
    {
      return -errno;
    }

  return state.nadvertisers > 0 ? 0 : -1;
}

int orb_group_count(FAR const struct orb_metadata *meta)
{
  unsigned instance = 0;

  while (orb_exists(meta, instance) == 0)
    {
      ++instance;
    }

  return instance;
}

#ifdef CONFIG_DEBUG_UORB
int orb_sscanf(FAR const char *buf, FAR const char *format, FAR void *data)
{
  struct lib_meminstream_s meminstream;
  int lastc;

  lib_meminstream(&meminstream, buf, strlen(buf));
  return lib_oscanf(&meminstream.common, &lastc, format, data);
}

void orb_info(FAR const char *format, FAR const char *name,
              FAR const void *data)
{
  struct lib_stdoutstream_s stdoutstream;
  struct va_format vaf;

  vaf.fmt = format;
  vaf.va  = (va_list *)data;

  lib_stdoutstream(&stdoutstream, stdout);

  flockfile(stdout);
  lib_sprintf(&stdoutstream.common, "%s(now:%" PRIu64 "):%pB\n",
              name, orb_absolute_time(), &vaf);
  funlockfile(stdout);
}

int orb_fprintf(FAR FILE *stream, FAR const char *format,
                FAR const void *data)
{
  struct lib_stdoutstream_s stdoutstream;
  int ret;

  lib_stdoutstream(&stdoutstream, stream);

  flockfile(stream);
  ret = lib_osprintf(&stdoutstream.common, format, data);
  funlockfile(stream);
  return ret;
}
#endif