/****************************************************************************
 * drivers/serial/uart_bth4.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 <nuttx/fs/fs.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <nuttx/mm/circbuf.h>

#include <fcntl.h>
#include <string.h>
#include <poll.h>

#include <nuttx/wireless/bluetooth/bt_hci.h>
#include <nuttx/wireless/bluetooth/bt_uart.h>
#include <nuttx/wireless/bluetooth/bt_driver.h>

/****************************************************************************
 * Private Types
 ****************************************************************************/

union bt_hdr_u
{
  struct bt_hci_cmd_hdr_s cmd;
  struct bt_hci_acl_hdr_s acl;
  struct bt_hci_evt_hdr_s evt;
  struct bt_hci_iso_hdr_s iso;
};

struct uart_bth4_s
{
  FAR struct bt_driver_s *drv;

  FAR struct circbuf_s    circbuf;

  sem_t                   recvsem;

  uint8_t                 sendbuf[CONFIG_UART_BTH4_TXBUFSIZE];
  size_t                  sendlen;
  mutex_t                 sendlock;

  FAR struct pollfd      *fds[CONFIG_UART_BTH4_NPOLLWAITERS];
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static int     uart_bth4_open(FAR struct file *filep);
static int     uart_bth4_close(FAR struct file *filep);
static ssize_t uart_bth4_read(FAR struct file *filep,
                              FAR char *buffer, size_t buflen);
static ssize_t uart_bth4_write(FAR struct file *filep,
                               FAR const char *buffer, size_t buflen);
static int     uart_bth4_ioctl(FAR struct file *filep,
                               int cmd, unsigned long arg);
static int     uart_bth4_poll(FAR struct file *filep,
                              FAR struct pollfd *fds, bool setup);

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

static const struct file_operations g_uart_bth4_ops =
{
  uart_bth4_open,   /* open */
  uart_bth4_close,  /* close */
  uart_bth4_read,   /* read */
  uart_bth4_write,  /* write */
  NULL,             /* seek */
  uart_bth4_ioctl,  /* ioctl */
  NULL,             /* mmap */
  NULL,             /* truncate */
  uart_bth4_poll    /* poll */
};

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

static inline void uart_bth4_post(FAR sem_t *sem)
{
  int semcount;

  nxsem_get_value(sem, &semcount);
  if (semcount < 1)
    {
      nxsem_post(sem);
    }
}

static void uart_bth4_pollnotify(FAR struct uart_bth4_s *dev,
                                 pollevent_t eventset)
{
  poll_notify(dev->fds, CONFIG_UART_BTH4_NPOLLWAITERS, eventset);

  if ((eventset & POLLIN) != 0)
    {
      uart_bth4_post(&dev->recvsem);
    }
}

static int uart_bth4_receive(FAR struct bt_driver_s *drv,
                             enum bt_buf_type_e type,
                             FAR void *buffer, size_t buflen)
{
  FAR struct uart_bth4_s *dev = drv->priv;
  int ret = buflen;
  irqstate_t flags;
  uint8_t htype;

  flags = enter_critical_section();

  if (circbuf_space(&dev->circbuf) >=
      buflen + H4_HEADER_SIZE)
    {
      if (type == BT_EVT)
        {
          htype = H4_EVT;
        }
      else if (type == BT_ACL_IN)
        {
          htype = H4_ACL;
        }
      else if (type == BT_ISO_IN)
        {
          htype = H4_ISO;
        }
      else
        {
          ret = -EINVAL;
        }

      if (ret >= 0)
        {
          circbuf_write(&dev->circbuf, &htype, H4_HEADER_SIZE);
          circbuf_write(&dev->circbuf, buffer, buflen);
          uart_bth4_pollnotify(dev, POLLIN);
        }
    }
  else
    {
      ret = -ENOMEM;
    }

  leave_critical_section(flags);
  return ret;
}

static int uart_bth4_open(FAR struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct uart_bth4_s *dev = inode->i_private;
  int ret;

  ret = dev->drv->open(dev->drv);
  if (ret < 0)
    {
      return ret;
    }

  dev->sendlen = 0;

  return OK;
}

static int uart_bth4_close(FAR struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct uart_bth4_s *dev = inode->i_private;

  dev->drv->close(dev->drv);

  uart_bth4_pollnotify(dev, POLLIN | POLLOUT);
  return OK;
}

static ssize_t uart_bth4_read(FAR struct file *filep,
                              FAR char *buffer,
                              size_t buflen)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct uart_bth4_s *dev = inode->i_private;
  irqstate_t flags;
  ssize_t nread;

  flags = enter_critical_section();

  for (; ; )
    {
      nread = circbuf_read(&dev->circbuf, buffer, buflen);
      if (nread != 0 || (filep->f_oflags & O_NONBLOCK))
        {
          break;
        }

      while (circbuf_is_empty(&dev->circbuf))
        {
          nxsem_wait_uninterruptible(&dev->recvsem);
        }
    }

  leave_critical_section(flags);
  return nread;
}

static ssize_t uart_bth4_write(FAR struct file *filep,
                               FAR const char *buffer,
                               size_t buflen)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct uart_bth4_s *dev = inode->i_private;
  FAR union bt_hdr_u *hdr;
  enum bt_buf_type_e type;
  size_t reserved;
  uint8_t *data;
  size_t pktlen;
  size_t hdrlen;
  int ret;

  ret = nxmutex_lock(&dev->sendlock);
  if (ret < 0)
    {
      return ret;
    }

  if (dev->drv->head_reserve < H4_HEADER_SIZE)
    {
      reserved = H4_HEADER_SIZE;
    }
  else
    {
      reserved = dev->drv->head_reserve;
    }

  data = dev->sendbuf + reserved;

  if (dev->sendlen + buflen > CONFIG_UART_BTH4_TXBUFSIZE - reserved)
    {
      ret = -E2BIG;
      goto err;
    }

  memcpy(data - H4_HEADER_SIZE + dev->sendlen,
         buffer, buflen);
  dev->sendlen += buflen;

  hdr = (FAR union bt_hdr_u *)data;

  for (; ; )
    {
      switch (*(data - H4_HEADER_SIZE))
        {
          case H4_CMD:
            hdrlen = sizeof(struct bt_hci_cmd_hdr_s);
            pktlen = hdr->cmd.param_len;
            type = BT_CMD;
            break;
          case H4_ACL:
            hdrlen = sizeof(struct bt_hci_acl_hdr_s);
            pktlen = hdr->acl.len;
            type = BT_ACL_OUT;
            break;
          case H4_ISO:
            hdrlen = sizeof(struct bt_hci_iso_hdr_s);
            pktlen = hdr->iso.len;
            type = BT_ISO_OUT;
            break;
          default:
            ret = -EINVAL;
            goto err;
        }

      /* Reassembly is incomplete ? */

      hdrlen += H4_HEADER_SIZE;

      if (dev->sendlen < hdrlen)
        {
          goto out;
        }

      pktlen += hdrlen;
      if (dev->sendlen < pktlen)
        {
          goto out;
        }

      /* Got the full packet, send out */

      ret = dev->drv->send(dev->drv, type,
                           data, pktlen - H4_HEADER_SIZE);
      if (ret < 0)
        {
          goto err;
        }

      dev->sendlen -= pktlen;
      if (dev->sendlen == 0)
        {
          goto out;
        }

      memmove(data - H4_HEADER_SIZE,
              dev->sendbuf + pktlen, dev->sendlen);
    }

err:
  dev->sendlen = 0;
out:
  nxmutex_unlock(&dev->sendlock);
  return ret < 0 ? ret : buflen;
}

static int uart_bth4_ioctl(FAR struct file *filep, int cmd,
                           unsigned long arg)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct uart_bth4_s *dev = inode->i_private;

  if (!dev->drv->ioctl)
    {
      return -ENOTTY;
    }

  return dev->drv->ioctl(dev->drv, cmd, arg);
}

static int uart_bth4_poll(FAR struct file *filep, FAR struct pollfd *fds,
                          bool setup)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct uart_bth4_s *dev = inode->i_private;
  pollevent_t eventset = 0;
  irqstate_t flags;
  int ret = 0;
  int i;

  flags = enter_critical_section();

  if (setup)
    {
      for (i = 0; i < CONFIG_UART_BTH4_NPOLLWAITERS; i++)
        {
          /* Find an available slot */

          if (!dev->fds[i])
            {
              /* Bind the poll structure and this slot */

              dev->fds[i]  = fds;
              fds->priv    = &dev->fds[i];
              break;
            }
        }

      if (i >= CONFIG_UART_BTH4_NPOLLWAITERS)
        {
          fds->priv = NULL;
          ret = -EBUSY;
        }

      if (!circbuf_is_empty(&dev->circbuf))
        {
          eventset |= POLLIN;
        }

      eventset |= POLLOUT;

      uart_bth4_pollnotify(dev, eventset);
    }
  else if (fds->priv != NULL)
    {
      for (i = 0; i < CONFIG_UART_BTH4_NPOLLWAITERS; i++)
        {
          if (fds == dev->fds[i])
            {
              dev->fds[i] = NULL;
              fds->priv = NULL;
              break;
            }
        }
    }

  leave_critical_section(flags);
  return ret;
}

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

int uart_bth4_register(FAR const char *path, FAR struct bt_driver_s *drv)
{
  FAR struct uart_bth4_s *dev;
  int ret;

  dev = (FAR struct uart_bth4_s *)
        kmm_zalloc(sizeof(struct uart_bth4_s));
  if (dev == NULL)
    {
      return -ENOMEM;
    }

  ret = circbuf_init(&dev->circbuf, NULL,
                     CONFIG_UART_BTH4_RXBUFSIZE);
  if (ret < 0)
    {
      kmm_free(dev);
      return -ENOMEM;
    }

  dev->drv     = drv;
  drv->receive = uart_bth4_receive;
  drv->priv    = dev;

  nxmutex_init(&dev->sendlock);
  nxsem_init(&dev->recvsem, 0, 0);

  ret = register_driver(path, &g_uart_bth4_ops, 0666, dev);
  if (ret < 0)
    {
      nxmutex_destroy(&dev->sendlock);
      nxsem_destroy(&dev->recvsem);
      circbuf_uninit(&dev->circbuf);
      kmm_free(dev);
    }

  return ret;
}