/****************************************************************************
 * apps/testing/monkey/monkey_dev.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 <inttypes.h>
#include <nuttx/input/buttons.h>
#include <nuttx/input/touchscreen.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "monkey_assert.h"
#include "monkey_log.h"
#include "monkey_dev.h"
#include "monkey_utils.h"

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

/****************************************************************************
 * Name: utouch_write
 ****************************************************************************/

static void utouch_write(int fd, int x, int y, int touch_down)
{
  struct touch_sample_s sample;

  if (touch_down)
    {
      sample.point[0].x        = x;
      sample.point[0].y        = y;
      sample.point[0].pressure = 42;
      sample.point[0].flags    = TOUCH_DOWN | TOUCH_ID_VALID |
                                 TOUCH_POS_VALID | TOUCH_PRESSURE_VALID;
    }
  else
    {
      sample.point[0].flags = TOUCH_UP | TOUCH_ID_VALID;
    }

  sample.npoints    = 1;
  sample.point[0].h = 1;
  sample.point[0].w = 1;

  write(fd, &sample, sizeof(struct touch_sample_s));
  MONKEY_LOG_INFO("%s at x = %d, y = %d",
                  touch_down ? "PRESS  " : "RELEASE", x, y);
}

/****************************************************************************
 * Name: ubutton_write
 ****************************************************************************/

static void ubutton_write(int fd, uint32_t btn_bits)
{
  btn_buttonset_t buttonset = btn_bits;
  write(fd, &buttonset, sizeof(buttonset));
  MONKEY_LOG_INFO("btn = 0x%08X", btn_bits);
}

/****************************************************************************
 * Name: touch_read
 ****************************************************************************/

static bool touch_read(int fd, FAR int *x, FAR int *y, FAR int *touch_down)
{
  bool retval = false;
  struct touch_sample_s sample;

  int nbytes = read(fd, &sample, sizeof(sample));

  if (nbytes == sizeof(sample))
    {
      retval = true;
      uint8_t touch_flags = sample.point[0].flags;

      if (touch_flags & TOUCH_DOWN || touch_flags & TOUCH_MOVE)
        {
          *x = sample.point[0].x;
          *y = sample.point[0].y;
          *touch_down = true;
        }
      else if (touch_flags & TOUCH_UP)
        {
          *x = 0;
          *y = 0;
          *touch_down = false;
        }
    }

  return retval;
}

/****************************************************************************
 * Name: button_read
 ****************************************************************************/

static bool button_read(int fd, FAR uint32_t *value)
{
  btn_buttonset_t buttonset;

  int nbytes = read(fd, &buttonset, sizeof(buttonset));
  if (nbytes != sizeof(buttonset))
    {
      return false;
    }

  *value = buttonset;
  return true;
}

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

/****************************************************************************
 * Name: monkey_dev_create
 ****************************************************************************/

FAR struct monkey_dev_s *monkey_dev_create(FAR const char *dev_path,
                                           enum monkey_dev_type_e type)
{
  FAR struct monkey_dev_s *dev;
  int oflag;
  int fd;

  MONKEY_ASSERT_NULL(dev_path);

  dev = calloc(1, sizeof(struct monkey_dev_s));
  MONKEY_ASSERT_NULL(dev);

  if (MONKEY_IS_UINPUT_TYPE(type))
    {
      oflag = O_RDWR | O_NONBLOCK;
    }
  else
    {
      oflag = O_RDONLY | O_NONBLOCK;
    }

  fd = open(dev_path, oflag);
  if (fd < 0)
    {
      MONKEY_LOG_ERROR("open %s failed: %d", dev_path, errno);
      goto failed;
    }

  dev->type = type;
  dev->fd = fd;

  MONKEY_LOG_NOTICE("open %s success, fd = %d, type: %s",
                    dev_path, fd, monkey_dev_type2name(type));

  return dev;

failed:
  if (fd > 0)
    {
      close(fd);
    }

  if (dev)
    {
      free(dev);
    }

  return NULL;
}

/****************************************************************************
 * Name: monkey_dev_delete
 ****************************************************************************/

void monkey_dev_delete(FAR struct monkey_dev_s *dev)
{
  MONKEY_ASSERT_NULL(dev);

  if (dev->fd > 0)
    {
      /* Reset input state */

      struct monkey_dev_state_s state;
      memset(&state, 0, sizeof(state));
      state.type = dev->type;
      monkey_dev_set_state(dev, &state);

      MONKEY_LOG_NOTICE("close fd: %d", dev->fd);
      close(dev->fd);
    }

  free(dev);
}

/****************************************************************************
 * Name: monkey_dev_set_state
 ****************************************************************************/

void monkey_dev_set_state(FAR struct monkey_dev_s *dev,
                          FAR const struct monkey_dev_state_s *state)
{
  MONKEY_ASSERT_NULL(dev);

  switch (MONKEY_GET_DEV_TYPE(dev->type))
    {
      case MONKEY_DEV_TYPE_TOUCH:
        utouch_write(dev->fd,
                    state->data.touch.x,
                    state->data.touch.y,
                    state->data.touch.is_pressed);
        break;

      case MONKEY_DEV_TYPE_BUTTON:
        ubutton_write(dev->fd, state->data.button.value);
        break;

      default:
        break;
    }
}

/****************************************************************************
 * Name: monkey_dev_get_state
 ****************************************************************************/

bool monkey_dev_get_state(FAR struct monkey_dev_s *dev,
                          FAR struct monkey_dev_state_s *state)
{
  bool retval;
  MONKEY_ASSERT_NULL(dev);

  retval = false;

  state->type = dev->type;

  switch (dev->type)
    {
      case MONKEY_DEV_TYPE_TOUCH:
        retval = touch_read(dev->fd,
                            &state->data.touch.x,
                            &state->data.touch.y,
                            &state->data.touch.is_pressed);
        break;

      case MONKEY_DEV_TYPE_BUTTON:
        retval = button_read(dev->fd, &state->data.button.value);
        break;

      default:
        break;
    }

  return retval;
}

/****************************************************************************
 * Name: monkey_dev_get_state
 ****************************************************************************/

enum monkey_dev_type_e monkey_dev_get_type(
                       FAR struct monkey_dev_s *dev)
{
  MONKEY_ASSERT_NULL(dev);
  return dev->type;
}

/****************************************************************************
 * Name: monkey_dev_get_available
 ****************************************************************************/

int monkey_dev_get_available(FAR struct monkey_dev_s *devs[], int dev_num)
{
  int i;
  int available = 0;
  struct pollfd fds[MONKEY_DEV_MAX_NUM];
  memset(fds, 0, sizeof(fds));

  for (i = 0; i < dev_num; i++)
    {
      devs[i]->is_available = false;
      fds[i].fd = devs[i]->fd;
      fds[i].events = POLLIN | POLLPRI;
    }

  if (poll(fds, dev_num, -1) < 0)
    {
      MONKEY_LOG_WARN("stop: %d", errno);
      return -1;
    }

  for (i = 0; i < dev_num; i++)
    {
      if (fds[i].revents & (POLLIN | POLLPRI))
        {
          devs[i]->is_available = true;
          available++;
        }
    }

  return available;
}