/****************************************************************************
 * apps/graphics/input/generator/input_gen_event.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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/config.h>

#include <debug.h>
#include <stdint.h>
#include <unistd.h>

#include "input_gen_internal.h"

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

#define INPUT_GEN_DEFAULT_PRESSURE       42

#define INPUT_GEN_DEFAULT_PRESS_DURATION 50  /* ms */
#define INPUT_GEN_DEFAULT_HOLD_DURATION  5   /* ms */

#define INPUT_GEN_DEFAULT_CLICK_DURATION 100 /* ms */

#define INPUT_GEN_MAX_FINGERS            2

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

/* Structure to hold parameters for single finger operations, we can support
 * multiple fingers by using multiple instances of this structure.
 */

struct input_gen_single_finger_s
{
  int16_t  x1;
  int16_t  y1;
  int16_t  x2;
  int16_t  y2;
  uint32_t press_duration;
  uint32_t move_duration;
  uint32_t hold_duration;
};

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

/****************************************************************************
 * Name: input_gen_tick_get
 *
 * Description:
 *   Get the current tick count in milliseconds.
 *
 * Returned Value:
 *   The current tick count in milliseconds.
 *
 ****************************************************************************/

static uint32_t input_gen_tick_get(void)
{
  struct timespec ts;
  uint32_t        ms;

  clock_gettime(CLOCK_MONOTONIC, &ts);
  ms = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;

  return ms;
}

/****************************************************************************
 * Name: input_gen_tick_elapsed
 *
 * Description:
 *   Calculate the elapsed time in milliseconds since the last tick.
 *
 * Input Parameters:
 *   act_time   - The current tick count.
 *   prev_tick  - The previous tick count.
 *
 * Returned Value:
 *   The elapsed time in milliseconds.
 *
 ****************************************************************************/

static uint32_t input_gen_tick_elapsed(uint32_t act_time, uint32_t prev_tick)
{
  /* If there is no overflow in sys_time simple subtract */

  if (act_time >= prev_tick)
    {
      prev_tick = act_time - prev_tick;
    }
  else
    {
      prev_tick  = UINT32_MAX - prev_tick + 1;
      prev_tick += act_time;
    }

  return prev_tick;
}

/****************************************************************************
 * Name: input_gen_single_finger
 *
 * Description:
 *   Calculate the position of a single finger based on the elapsed time and
 *   the parameters.
 *
 * Input Parameters:
 *   point   - The touch point structure to fill in.
 *   finger  - The parameters for the single finger operation.
 *   elapsed - The elapsed time since the start of the operation.
 *
 * Returned Value:
 *   True if the operation is finished, false otherwise.
 *
 ****************************************************************************/

static bool
input_gen_single_finger(FAR struct touch_point_s *point,
                        FAR const struct input_gen_single_finger_s *finger,
                        uint32_t elapsed)
{
  if (elapsed < finger->press_duration)
    {
      /* Press */

      input_gen_fill_point(point, finger->x1, finger->y1, TOUCH_DOWN);
      return false;
    }

  elapsed -= finger->press_duration;
  if (elapsed < finger->move_duration)
    {
      int16_t x = ((int64_t)(finger->x2 - finger->x1) * elapsed) /
                  finger->move_duration + finger->x1;
      int16_t y = ((int64_t)(finger->y2 - finger->y1) * elapsed) /
                  finger->move_duration + finger->y1;
      input_gen_fill_point(point, x, y, TOUCH_MOVE);

      return false;
    }

  elapsed -= finger->move_duration;
  if (elapsed < finger->hold_duration)
    {
      /* Hold */

      input_gen_fill_point(point, finger->x2, finger->y2, TOUCH_DOWN);
      return false;
    }

  /* Release */

  input_gen_fill_point(point, finger->x2, finger->y2, TOUCH_UP);
  return true;
}

/****************************************************************************
 * Name: input_gen_write_motion
 *
 * Description:
 *   Write a motion event to the device.
 *
 * Input Parameters:
 *   fd       - The file descriptor of the device.
 *   fingers  - The parameters for the motion operation.
 *   nfingers - The number of fingers.
 *   elapsed  - The elapsed time since the start of the operation.
 *
 * Returned Value:
 *   Zero (OK) is returned on success.  On failure, a negated errno value is
 *   returned.
 *
 ****************************************************************************/

static int input_gen_write_motion(int fd,
                         FAR const struct input_gen_single_finger_s *fingers,
                         size_t nfingers, uint32_t elapsed)
{
  size_t finished = 0;
  size_t i;
  int ret;
  union /* A union with enough space to make compiler / checker happy. */
  {
    struct touch_sample_s sample;
    struct
      {
        uint8_t placeholder[offsetof(struct touch_sample_s, point)];
        struct touch_point_s point[INPUT_GEN_MAX_FINGERS];
      } points;
  } buffer;

  /* Check the number of fingers */

  if (nfingers < 1 || nfingers > INPUT_GEN_MAX_FINGERS)
    {
      return -EINVAL;
    }

  /* Fill the touch sample structure */

  buffer.sample.npoints = nfingers;

  for (i = 0; i < nfingers; i++)
    {
      FAR struct touch_point_s *point = &buffer.points.point[i];

      point->id = i;
      if (input_gen_single_finger(point, &fingers[i], elapsed))
        {
          finished++;
        }

      ginfo("Finger %zu: x = %d, y = %d, flags = %02X\n",
            i, point->x, point->y, point->flags);
    }

  /* Write the sample to the device */

  ret = input_gen_utouch_write(fd, &buffer.sample);
  if (ret < 0)
    {
      return ret;
    }

  return finished == nfingers ? OK : -EAGAIN;
}

/****************************************************************************
 * Name: input_gen_motion
 *
 * Description:
 *   Perform a motion operation.
 *
 * Input Parameters:
 *   ctx      - The input generator context.
 *   fingers  - The parameters for the motion operation.
 *   nfingers - The number of fingers.
 *
 * Returned Value:
 *   Zero (OK) is returned on success.  On failure, a negated errno value is
 *   returned.
 *
 ****************************************************************************/

static int input_gen_motion(input_gen_ctx_t ctx,
                         FAR const struct input_gen_single_finger_s *fingers,
                         size_t nfingers)
{
  FAR struct input_gen_dev_s *dev;
  uint32_t start;
  int ret;

  dev = input_gen_search_dev(ctx, INPUT_GEN_DEV_UTOUCH);
  if (dev == NULL)
    {
      return -ENODEV;
    }

  ret = input_gen_grab(dev);
  if (ret < 0)
    {
      return ret;
    }

  start = input_gen_tick_get();

  do
    {
      uint32_t elapsed = input_gen_tick_elapsed(input_gen_tick_get(), start);
      ret = input_gen_write_motion(dev->fd, fingers, nfingers, elapsed);
      if (ret == -EAGAIN && usleep(8 * 1000) < 0)
        {
          nwarn("WARNING: Maybe interrupted by signal\n");
          input_gen_ungrab(dev);
          return -errno;
        }
    }
  while (ret == -EAGAIN);

  input_gen_ungrab(dev);
  return ret;
}

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

/****************************************************************************
 * Name: input_gen_tap
 *
 * Description:
 *   Perform a tap operation.
 *
 * Input Parameters:
 *   ctx - The input generator context.
 *   x   - The x coordinate.
 *   y   - The y coordinate.
 *
 * Returned Value:
 *   Zero (OK) is returned on success.  On failure, a negated errno value is
 *   returned.
 *
 ****************************************************************************/

int input_gen_tap(input_gen_ctx_t ctx, int16_t x, int16_t y)
{
  struct input_gen_single_finger_s finger =
    {
      .x1 = x,
      .y1 = y,
      .x2 = x,
      .y2 = y,
      .press_duration = INPUT_GEN_DEFAULT_PRESS_DURATION,
      .move_duration  = 0,
      .hold_duration  = 0,
    };

  return input_gen_motion(ctx, &finger, 1);
}

/****************************************************************************
 * Name: input_gen_drag / input_gen_swipe
 *
 * Description:
 *   Perform a drag or swipe operation.
 *
 * Input Parameters:
 *   ctx      - The input generator context.
 *   x1       - The start x coordinate.
 *   y1       - The start y coordinate.
 *   x2       - The end x coordinate.
 *   y2       - The end y coordinate.
 *   duration - The duration of the operation.
 *
 * Returned Value:
 *   Zero (OK) is returned on success.  On failure, a negated errno value is
 *   returned.
 *
 ****************************************************************************/

int input_gen_drag(input_gen_ctx_t ctx, int16_t x1, int16_t y1,
                   int16_t x2, int16_t y2, uint32_t duration)
{
  struct input_gen_single_finger_s finger =
    {
      .x1 = x1,
      .y1 = y1,
      .x2 = x2,
      .y2 = y2,
      .press_duration = INPUT_GEN_DEFAULT_PRESS_DURATION,
      .move_duration  = duration,
      .hold_duration  = INPUT_GEN_DEFAULT_HOLD_DURATION,
    };

  return input_gen_motion(ctx, &finger, 1);
}

int input_gen_swipe(input_gen_ctx_t ctx, int16_t x1, int16_t y1,
                    int16_t x2, int16_t y2, uint32_t duration)
{
  struct input_gen_single_finger_s finger =
    {
      .x1 = x1,
      .y1 = y1,
      .x2 = x2,
      .y2 = y2,
      .press_duration = 0,
      .move_duration  = duration,
      .hold_duration  = 0,
    };

  return input_gen_motion(ctx, &finger, 1);
}

/****************************************************************************
 * Name: input_gen_pinch
 *
 * Description:
 *   Perform a pinch operation.
 *
 * Input Parameters:
 *   ctx      - The input generator context.
 *   x1_start - The start x coordinate of the first finger.
 *   y1_start - The start y coordinate of the first finger.
 *   x2_start - The start x coordinate of the second finger.
 *   y2_start - The start y coordinate of the second finger.
 *   x1_end   - The end x coordinate of the first finger.
 *   y1_end   - The end y coordinate of the first finger.
 *   x2_end   - The end x coordinate of the second finger.
 *   y2_end   - The end y coordinate of the second finger.
 *   duration - The duration of the operation.
 *
 * Returned Value:
 *   Zero (OK) is returned on success.  On failure, a negated errno value is
 *   returned.
 *
 ****************************************************************************/

int input_gen_pinch(input_gen_ctx_t ctx, int16_t x1_start, int16_t y1_start,
                    int16_t x2_start, int16_t y2_start, int16_t x1_end,
                    int16_t y1_end, int16_t x2_end, int16_t y2_end,
                    uint32_t duration)
{
  struct input_gen_single_finger_s fingers[2] =
    {
      {
        .x1 = x1_start,
        .y1 = y1_start,
        .x2 = x1_end,
        .y2 = y1_end,
        .press_duration = INPUT_GEN_DEFAULT_PRESS_DURATION,
        .move_duration  = duration,
        .hold_duration  = INPUT_GEN_DEFAULT_HOLD_DURATION,
      },
      {
        .x1 = x2_start,
        .y1 = y2_start,
        .x2 = x2_end,
        .y2 = y2_end,
        .press_duration = INPUT_GEN_DEFAULT_PRESS_DURATION,
        .move_duration  = duration,
        .hold_duration  = INPUT_GEN_DEFAULT_HOLD_DURATION,
      },
    };

  return input_gen_motion(ctx, fingers, 2);
}

/****************************************************************************
 * Name: input_gen_button_click / input_gen_button_longpress
 *
 * Description:
 *   Perform a button click or long press operation.
 *
 * Input Parameters:
 *   ctx      - The input generator context.
 *   mask     - The button mask.
 *   duration - The duration of the operation.
 *
 * Returned Value:
 *   Zero (OK) is returned on success.  On failure, a negated errno value is
 *   returned.
 *
 ****************************************************************************/

int input_gen_button_click(input_gen_ctx_t ctx, btn_buttonset_t mask)
{
  return input_gen_button_longpress(ctx, mask,
                                    INPUT_GEN_DEFAULT_CLICK_DURATION);
}

int input_gen_button_longpress(input_gen_ctx_t ctx, btn_buttonset_t mask,
                               uint32_t duration)
{
  FAR struct input_gen_dev_s *dev;
  int ret;

  dev = input_gen_search_dev(ctx, INPUT_GEN_DEV_UBUTTON);
  if (dev == NULL)
    {
      return -ENODEV;
    }

  ret = input_gen_grab(dev);
  if (ret < 0)
    {
      return ret;
    }

  ret = input_gen_ubutton_write(dev->fd, mask);
  if (ret < 0)
    {
      goto out;
    }

  usleep(duration * 1000);

  ret = input_gen_ubutton_write(dev->fd, 0);
  if (ret < 0)
    {
      goto out;
    }

out:
  input_gen_ungrab(dev);
  return ret;
}

/****************************************************************************
 * Name: input_gen_mouse_wheel
 *
 * Description:
 *   Perform a mouse wheel operation.
 *
 * Input Parameters:
 *   ctx   - The input generator context.
 *   wheel - The wheel value.
 *
 * Returned Value:
 *   Zero (OK) is returned on success.  On failure, a negated errno value is
 *   returned.
 *
 ****************************************************************************/

#ifdef CONFIG_INPUT_MOUSE_WHEEL
int input_gen_mouse_wheel(input_gen_ctx_t ctx, int16_t wheel)
{
  FAR struct input_gen_dev_s *dev;

  dev = input_gen_search_dev(ctx, INPUT_GEN_DEV_UMOUSE);
  if (dev == NULL)
    {
      return -ENODEV;
    }

  return input_gen_umouse_write(dev->fd, wheel);
}
#endif

/****************************************************************************
 * Name: input_gen_fill_point
 *
 * Description:
 *   Fill the touch point structure.
 *
 * Input Parameters:
 *   point - The touch point structure.
 *   x     - The x coordinate.
 *   y     - The y coordinate.
 *   flags - The TOUCH_DOWN, TOUCH_MOVE, TOUCH_UP flag.
 *
 ****************************************************************************/

void input_gen_fill_point(FAR struct touch_point_s *point,
                          int16_t x, int16_t y, uint8_t flags)
{
  point->x = x;
  point->y = y;
  point->h = 1;
  point->w = 1;

  if (flags & TOUCH_UP)
    {
      point->pressure = 0;
      point->flags    = flags | TOUCH_ID_VALID;
    }
  else
    {
      point->pressure = INPUT_GEN_DEFAULT_PRESSURE;
      point->flags    = flags | TOUCH_ID_VALID | TOUCH_POS_VALID |
                        TOUCH_PRESSURE_VALID;
    }
}