/****************************************************************************
 * drivers/sensors/apds9960.c
 * Character driver for the APDS9960 Gesture Sensor
 *
 *   Copyright (C) 2017 Alan Carvalho de Assis. All rights reserved.
 *   Author: Alan Carvalho de Assis <acassis@gmail.com>
 *
 * This driver is based on APDS-9960 Arduino library developed by
 * Shawn Hymel from SparkFun Electronics and released under public
 * domain.
 *
 * 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 <errno.h>
#include <debug.h>
#include <stdlib.h>

#include <nuttx/kmalloc.h>
#include <nuttx/signal.h>
#include <nuttx/random.h>
#include <nuttx/wqueue.h>
#include <nuttx/fs/fs.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/sensors/apds9960.h>

#if defined(CONFIG_I2C) && defined(CONFIG_SENSORS_APDS9960)

/****************************************************************************
 * Pre-process Definitions
 ****************************************************************************/

#ifndef CONFIG_APDS9960_I2C_FREQUENCY
#  define CONFIG_APDS9960_I2C_FREQUENCY 400000
#endif

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

struct apds9960_dev_s
{
  FAR struct apds9960_config_s *config; /* Hardware Configuration     */
  struct work_s work;                   /* Supports ISR "bottom half" */
  struct gesture_data_s gesture_data;   /* Gesture data container     */
  int gesture_ud_delta;                 /* UP/DOWN delta              */
  int gesture_lr_delta;                 /* LEFT/RIGHT delta           */
  int gesture_ud_count;                 /* UP/DOWN counter            */
  int gesture_lr_count;                 /* LEFT/RIGHT counter         */
  int gesture_near_count;               /* Near distance counter      */
  int gesture_far_count;                /* Far distance counter       */
  int gesture_state;                    /* Gesture machine state      */
  int gesture_motion;                   /* Gesture motion direction   */
  sem_t sample_sem;                     /* Semaphore for sample data  */
};

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

/* Reset gesture values */

static void    apds9960_resetgesture(FAR struct apds9960_dev_s *priv);

/* Setup default initial values */

static int     apds9960_setdefault(FAR struct apds9960_dev_s *priv);

/* Probe function to verify if sensor is present */

static int     apds9960_probe(FAR struct apds9960_dev_s *priv);

/* Work queue */

static void    apds9960_worker(FAR void *arg);

/* Gesture processing/decoding functions */

static int     apds9960_readgesture(FAR struct apds9960_dev_s *priv);
static bool    apds9960_decodegesture(FAR struct apds9960_dev_s *priv);
static bool    apds9960_processgesture(FAR struct apds9960_dev_s *priv);
static bool    apds9960_isgestureavailable(FAR struct apds9960_dev_s *priv);

/* I2C Helpers */

static int     apds9960_i2c_read(FAR struct apds9960_dev_s *priv,
                 uint8_t const regaddr, FAR uint8_t *regval, int len);
static int     apds9960_i2c_read8(FAR struct apds9960_dev_s *priv,
                 uint8_t const regaddr, FAR uint8_t *regval);
static int     apds9960_i2c_write(FAR struct apds9960_dev_s *priv,
                 uint8_t const *data, int len);
static int     apds9960_i2c_write8(FAR struct apds9960_dev_s *priv,
                 uint8_t const regaddr, uint8_t regval);

/* Character driver methods */

static int     apds9960_open(FAR struct file *filep);
static int     apds9960_close(FAR struct file *filep);
static ssize_t apds9960_read(FAR struct file *filep, FAR char *buffer,
                 size_t buflen);
static ssize_t apds9960_write(FAR struct file *filep,
                 FAR const char *buffer, size_t buflen);

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

static const struct file_operations g_apds9960_fops =
{
  apds9960_open,   /* open */
  apds9960_close,  /* close */
  apds9960_read,   /* read */
  apds9960_write,  /* write */
  NULL,            /* seek */
  NULL             /* ioctl */
#ifndef CONFIG_DISABLE_POLL
  , NULL           /* poll */
#endif
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
  , NULL           /* unlink */
#endif
};

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

/****************************************************************************
 * Name: apds9960_worker
 ****************************************************************************/

static void apds9960_worker(FAR void *arg)
{
  FAR struct apds9960_dev_s *priv = (FAR struct apds9960_dev_s *)arg;
  int ret;

  DEBUGASSERT(priv != NULL);

  ret = apds9960_readgesture(priv);
  if (ret != DIR_NONE)
    {
      sninfo("Got a valid gesture!\n");
    }
}

/****************************************************************************
 * Name: apds9960_int_handler
 *
 * Description:
 *   Interrupt handler (ISR) for APDS-99600 INT pin.
 *
 ****************************************************************************/

static int apds9960_int_handler(int irq, FAR void *context, FAR void *arg)
{
  int ret;

  FAR struct apds9960_dev_s *priv = (FAR struct apds9960_dev_s *)arg;

  DEBUGASSERT(priv != NULL);

  /* Transfer processing to the worker thread.  Since APDS-9960 interrupts
   * are disabled while the work is pending, no special action should be
   * required to protect the work queue.
   */

  DEBUGASSERT(priv->work.worker == NULL);
  ret = work_queue(HPWORK, &priv->work, apds9960_worker, priv, 0);
  if (ret != 0)
    {
      snerr("ERROR: Failed to queue work: %d\n", ret);
    }

  return OK;
}

/****************************************************************************
 * Name: apds9960_resetgesture
 *
 * Description:
 *   Reset gesture values
 *
 ****************************************************************************/

static void apds9960_resetgesture(FAR struct apds9960_dev_s *priv)
{
  priv->gesture_data.index = 0;
  priv->gesture_data.total_gestures = 0;

  priv->gesture_ud_delta   = 0;
  priv->gesture_lr_delta   = 0;

  priv->gesture_ud_count   = 0;
  priv->gesture_lr_count   = 0;

  priv->gesture_near_count = 0;
  priv->gesture_far_count  = 0;

  priv->gesture_state      = 0;
}

/****************************************************************************
 * Name: apds9960_setdefault
 *
 * Description:
 *   Verify if sensor is present. Check if ID is 0xAB.
 *
 ****************************************************************************/

static int apds9960_setdefault(FAR struct apds9960_dev_s *priv)
{
  int ret;

  /* Set default values for ambient light and proximity registers */

  ret = apds9960_i2c_write8(priv, APDS9960_ATIME, DEFAULT_ATIME);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_ATIME!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_WTIME, DEFAULT_WTIME);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_WTIME!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_PPULSE, DEFAULT_PPULSE);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_PPULSE!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_POFFSET_UR, DEFAULT_POFFSET_UR);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_POFFSET_UR!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_POFFSET_DL, DEFAULT_POFFSET_DL);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_POFFSET_DL!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_CONFIG1, DEFAULT_CONFIG1);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_CONFIG1!\n");
      return ret;
    }

  /* Set LED driver strength to 100mA, AGAIN 4X and PGAIN 4X */

  ret = apds9960_i2c_write8(priv, APDS9960_CONTROL, DEFAULT_CONTROL);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_CONTROL!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_PILT, DEFAULT_PILT);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_PILT!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_PIHT, DEFAULT_PIHT);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_PIHT!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_AILTL, DEFAULT_AILTL);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_AILTL!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_AILTH, DEFAULT_AILTH);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_AILTH!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_AIHTL, DEFAULT_AIHTL);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_AIHTL!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_AIHTH, DEFAULT_AIHTH);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_AIHTH!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_PERS, DEFAULT_PERS);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_PERS!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_CONFIG2, DEFAULT_CONFIG2);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_CONFIG2!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_CONFIG3, DEFAULT_CONFIG3);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_CONFIG3!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_GPENTH, DEFAULT_GPENTH);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_GPENTH!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_GEXTH, DEFAULT_GEXTH);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_GEXTH!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG1, DEFAULT_GCONFIG1);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_GCONFIG1!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG2, DEFAULT_GCONFIG2);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_GCONFIG2!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_GOFFSET_U, DEFAULT_GOFFSET_U);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_GOFFSET_U!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_GOFFSET_D, DEFAULT_GOFFSET_D);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_GOFFSET_D!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_GOFFSET_L, DEFAULT_GOFFSET_L);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_GOFFSET_L!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_GOFFSET_R, DEFAULT_GOFFSET_R);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_GOFFSET_R!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_GPULSE, DEFAULT_GPULSE);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_GPULSE!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG3, DEFAULT_GCONFIG3);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_GCONFIG3!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG4, DEFAULT_GCONFIG4);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_GCONFIG3!\n");
      return ret;
    }

  ret = apds9960_i2c_write8(priv, APDS9960_WTIME, 0xff);
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_WTIME!\n");
      return ret;
    }

  return OK;
}

/****************************************************************************
 * Name: apds9960_probe
 *
 * Description:
 *   Verify if sensor is present. Check if ID is 0xAB.
 *
 ****************************************************************************/

static int apds9960_probe(FAR struct apds9960_dev_s *priv)
{
  int ret;
  uint8_t id;

  ret = apds9960_i2c_read8(priv, APDS9960_ID, &id);
  if (ret < 0)
    {
      snerr("ERROR: Failed to initialize the APDS9960!\n");
      return ret;
    }

  if (id != APDS9960_ID_VAL)
    {
      return -ENODEV;
    }

  return OK;
}

/****************************************************************************
 * Name: apds9960_i2c_read
 *
 * Description:
 *   Read an arbitrary number of bytes starting at regaddr
 *
 ****************************************************************************/

static int apds9960_i2c_read(FAR struct apds9960_dev_s *priv,
                           uint8_t const regaddr, FAR uint8_t *regval, int len)
{
  struct i2c_config_s config;
  int ret = -1;

  /* Set up the I2C configuration */

  config.frequency = CONFIG_APDS9960_I2C_FREQUENCY;
  config.address   = priv->config->i2c_addr;
  config.addrlen   = 7;

  /* Write the register address to read from */

  ret = i2c_write(priv->config->i2c_dev, &config, &regaddr, 1);
  if (ret < 0)
    {
      snerr ("i2c_write failed: %d\n", ret);
      return ret;
    }

  /* Read "len" bytes from regaddr */

  ret = i2c_read(priv->config->i2c_dev, &config, regval, len);
  if (ret < 0)
    {
      snerr ("i2c_read failed: %d\n", ret);
      return ret;
    }

  return OK;
}

/****************************************************************************
 * Name: apds9960_i2c_read8
 *
 * Description:
 *   Read 8-bit register
 *
 ****************************************************************************/

static int apds9960_i2c_read8(FAR struct apds9960_dev_s *priv,
                            uint8_t const regaddr, FAR uint8_t *regval)
{
  int ret;

  ret = apds9960_i2c_read(priv, regaddr, regval, 1);

  return ret;
}

/****************************************************************************
 * Name: apds9960_i2c_write
 *
 * Description:
 *   Write an arbitrary number of bytes starting at regaddr.
 *
 ****************************************************************************/

static int apds9960_i2c_write(FAR struct apds9960_dev_s *priv,
                             uint8_t const *data, int len)
{
  struct i2c_config_s config;
  int ret;

  /* Set up the I2C configuration */

  config.frequency = CONFIG_APDS9960_I2C_FREQUENCY;
  config.address   = priv->config->i2c_addr;
  config.addrlen   = 7;

  /* Write the data */

  ret = i2c_write(priv->config->i2c_dev, &config, data, len);
  if (ret < 0)
    {
      snerr("ERROR: i2c_write failed: %d\n", ret);
    }

  return ret;
}

/****************************************************************************
 * Name: apds9960_i2c_write8
 *
 * Description:
 *   Write an arbitrary number of bytes starting at regaddr.
 *
 ****************************************************************************/

static int apds9960_i2c_write8(FAR struct apds9960_dev_s *priv,
                             uint8_t const regaddr, uint8_t regval)
{
  int ret;
  uint8_t data[2];

  /* Create the addr:val data */

  data[0] = regaddr;
  data[1] = regval;

  ret = apds9960_i2c_write(priv, data, 2);

  return ret;
}

/****************************************************************************
 * Name: apds9960_isgestureavailable
 *
 * Description:
 *   Return true is gesture data is valid.
 *
 ****************************************************************************/

static bool apds9960_isgestureavailable(FAR struct apds9960_dev_s *priv)
{
  int ret;
  uint8_t val;

  /* Read value from GSTATUS register */

  ret = apds9960_i2c_read8(priv, APDS9960_GSTATUS, &val);
  if (ret < 0)
    {
      snerr("ERROR: Failed to read APDS9960_GSTATUS!\n");
      return ret;
    }

  /* Return true/false based on GVALID bit */

  if ((val & GVALID) == GVALID)
    {
      return true;
    }
  else
    {
      return false;
    }
}

/****************************************************************************
 * Name: apds9960_processgesture
 *
 * Description:
 *   Process the data read from the photodiodes
 *
 ****************************************************************************/

static bool apds9960_processgesture(FAR struct apds9960_dev_s *priv)
{
  uint8_t u_first = 0;
  uint8_t d_first = 0;
  uint8_t l_first = 0;
  uint8_t r_first = 0;
  uint8_t u_last  = 0;
  uint8_t d_last  = 0;
  uint8_t l_last  = 0;
  uint8_t r_last  = 0;
  int ud_ratio_first;
  int lr_ratio_first;
  int ud_ratio_last;
  int lr_ratio_last;
  int ud_delta;
  int lr_delta;
  int i;

  /* If we have less than 4 total gestures, that's not enough */

  if (priv->gesture_data.total_gestures <= 4)
    {
        snerr("ERROR: We don't have enough gesture: %d\n",
              priv->gesture_data.total_gestures);
        return false;
    }

  /* Check to make sure our data isn't out of bounds */

  if ((priv->gesture_data.total_gestures <= 32) && \
      (priv->gesture_data.total_gestures > 0))
    {
      /* Find the first value in U/D/L/R above the threshold */

      for (i = 0; i < priv->gesture_data.total_gestures; i++)
        {
          if ((priv->gesture_data.u_data[i] > GESTURE_THRESHOLD_OUT) && \
              (priv->gesture_data.d_data[i] > GESTURE_THRESHOLD_OUT) && \
              (priv->gesture_data.l_data[i] > GESTURE_THRESHOLD_OUT) && \
              (priv->gesture_data.r_data[i] > GESTURE_THRESHOLD_OUT))
            {
              u_first = priv->gesture_data.u_data[i];
              d_first = priv->gesture_data.d_data[i];
              l_first = priv->gesture_data.l_data[i];
              r_first = priv->gesture_data.r_data[i];
              break;
            }
        }

      /* If one of the _first values is 0, then there is no good data */

      if ((u_first == 0) || (d_first == 0) || \
          (l_first == 0) || (r_first == 0))
        {
          snerr("ERROR: First value is zero! U=%d, D=%d, L=%d, R=%d\n", \
                u_first, d_first, l_first, r_first);
          return false;
        }

      /* Find the last value in U/D/L/R above the threshold */

      for (i = priv->gesture_data.total_gestures - 1; i >= 0; i--)
        {
          sninfo("Finding last: \n");
          sninfo("U: %03d\n", priv->gesture_data.u_data[i]);
          sninfo("D: %03d\n", priv->gesture_data.d_data[i]);
          sninfo("L: %03d\n", priv->gesture_data.l_data[i]);
          sninfo("R: %03d\n", priv->gesture_data.r_data[i]);

          if ((priv->gesture_data.u_data[i] > GESTURE_THRESHOLD_OUT) &&
              (priv->gesture_data.d_data[i] > GESTURE_THRESHOLD_OUT) &&
              (priv->gesture_data.l_data[i] > GESTURE_THRESHOLD_OUT) &&
              (priv->gesture_data.r_data[i] > GESTURE_THRESHOLD_OUT))
            {
              u_last = priv->gesture_data.u_data[i];
              d_last = priv->gesture_data.d_data[i];
              l_last = priv->gesture_data.l_data[i];
              r_last = priv->gesture_data.r_data[i];
              break;
            }
        }
    }

    /* Calculate the first vs. last ratio of up/down and left/right */

    ud_ratio_first = ((u_first - d_first) * 100) / (u_first + d_first);
    lr_ratio_first = ((l_first - r_first) * 100) / (l_first + r_first);
    ud_ratio_last  = ((u_last  - d_last)  * 100) / (u_last  + d_last);
    lr_ratio_last  = ((l_last  - r_last)  * 100) / (l_last  + r_last);

    sninfo("Last Values: \n");
    sninfo("U: %03d\n", u_last);
    sninfo("D: %03d\n", d_last);
    sninfo("L: %03d\n", l_last);
    sninfo("R: %03d\n", r_last);

    sninfo("Ratios: \n");
    sninfo("UD Fi: %03d\n", ud_ratio_first);
    sninfo("UD La: %03d\n", ud_ratio_last);
    sninfo("LR Fi: %03d\n", lr_ratio_first);
    sninfo("LR La: %03d\n", lr_ratio_last);

    /* Determine the difference between the first and last ratios */

    ud_delta = ud_ratio_last - ud_ratio_first;
    lr_delta = lr_ratio_last - lr_ratio_first;

    sninfo("Deltas: \n");
    sninfo("UD: %03d\n", ud_delta);
    sninfo("LR: %03d\n", lr_delta);

    /* Accumulate the UD and LR delta values */

    priv->gesture_ud_delta += ud_delta;
    priv->gesture_lr_delta += lr_delta;

    sninfo("Accumulations: \n");
    sninfo("UD: %03d\n", priv->gesture_ud_delta);
    sninfo("LR: %03d\n", priv->gesture_lr_delta);

    /* Determine U/D gesture */

    if (priv->gesture_ud_delta >= GESTURE_SENSITIVITY_1)
      {
        priv->gesture_ud_count = 1;
      }
    else
      {
        if (priv->gesture_ud_delta <= -GESTURE_SENSITIVITY_1)
          {
            priv->gesture_ud_count = -1;
          }
        else
          {
            priv->gesture_ud_count = 0;
          }
      }

    /* Determine L/R gesture */

    if (priv->gesture_lr_delta >= GESTURE_SENSITIVITY_1)
      {
        priv->gesture_lr_count = 1;
      }
    else
      {
        if (priv->gesture_lr_delta <= -GESTURE_SENSITIVITY_1)
          {
            priv->gesture_lr_count = -1;
          }
        else
          {
            priv->gesture_lr_count = 0;
          }
      }

    /* Determine Near/Far gesture */

    if ((priv->gesture_ud_count == 0) && (priv->gesture_lr_count == 0))
      {
        if ((abs(ud_delta) < GESTURE_SENSITIVITY_2) && \
            (abs(lr_delta) < GESTURE_SENSITIVITY_2))
          {
            if ((ud_delta == 0) && (lr_delta == 0))
              {
                priv->gesture_near_count++;
              }
            else
              {
                if ((ud_delta != 0) || (lr_delta != 0))
                  {
                    priv->gesture_far_count++;
                  }
              }

            if ((priv->gesture_near_count >= 10) && \
                (priv->gesture_far_count >= 2))
              {
                if ((ud_delta == 0) && (lr_delta == 0))
                  {
                    priv->gesture_state = NEAR_STATE;
                  }
                else
                  {
                    if ((ud_delta != 0) && (lr_delta != 0))
                      {
                        priv->gesture_state = FAR_STATE;
                      }
                  }

                return true;
              }
          }
      }
    else
      {
        if ((abs(ud_delta) < GESTURE_SENSITIVITY_2) && \
            (abs(lr_delta) < GESTURE_SENSITIVITY_2))
          {
            if ((ud_delta == 0) && (lr_delta == 0))
              {
                priv->gesture_near_count++;
              }

            if (priv->gesture_near_count >= 10)
              {
                priv->gesture_ud_count = 0;
                priv->gesture_lr_count = 0;
                priv->gesture_ud_delta = 0;
                priv->gesture_lr_delta = 0;
              }
          }
      }

    sninfo(" UD_CT: %03d\n", priv->gesture_ud_count);
    sninfo(" LR_CT: %03d\n", priv->gesture_lr_count);
    sninfo(" NEAR_CT: %03d\n", priv->gesture_near_count);
    sninfo(" FAR_CT:  %03d\n", priv->gesture_far_count);
    sninfo("----------------------\n");

    return false;
}

/****************************************************************************
 * Name: apds9960_decodegesture
 *
 * Description:
 *   Decode the sensor data and return true if there is some valid data
 *
 ****************************************************************************/

static bool apds9960_decodegesture(FAR struct apds9960_dev_s *priv)
{
  /* Return if near or far event is detected */

  if (priv->gesture_state == NEAR_STATE)
    {
      priv->gesture_motion = DIR_NEAR;
      return true;
    }
  else
    {
      if (priv->gesture_state == FAR_STATE)
        {
          priv->gesture_motion = DIR_FAR;
          return true;
        }
    }

  /* Determine swipe direction */

  if ((priv->gesture_ud_count == -1) && (priv->gesture_lr_count == 0))
    {
      priv->gesture_motion = DIR_UP;
    }
  else
    {
      if ((priv->gesture_ud_count == 1) && (priv->gesture_lr_count == 0))
        {
          priv->gesture_motion = DIR_DOWN;
        }
      else
        {
          if ((priv->gesture_ud_count == 0) && (priv->gesture_lr_count == 1))
            {
              priv->gesture_motion = DIR_RIGHT;
            }
          else
            {
              if ((priv->gesture_ud_count == 0) &&
                  (priv->gesture_lr_count == -1))
                {
                  priv->gesture_motion = DIR_LEFT;
                }
              else
                {
                  if ((priv->gesture_ud_count == -1) &&
                      (priv->gesture_lr_count == 1))
                    {
                      if (abs(priv->gesture_ud_delta) > \
                          abs(priv->gesture_lr_delta))
                        {
                          priv->gesture_motion = DIR_UP;
                        }
                      else
                        {
                          priv->gesture_motion = DIR_RIGHT;
                        }
                    }
                  else
                    {
                      if ((priv->gesture_ud_count == 1) && \
                          (priv->gesture_lr_count == -1))
                        {
                          if (abs(priv->gesture_ud_delta) > \
                              abs(priv->gesture_lr_delta))
                            {
                              priv->gesture_motion = DIR_DOWN;
                            }
                          else
                            {
                              priv->gesture_motion = DIR_LEFT;
                            }
                        }
                      else
                        {
                          if ((priv->gesture_ud_count == -1) && \
                              (priv->gesture_lr_count == -1))
                            {
                              if (abs(priv->gesture_ud_delta) > \
                                  abs(priv->gesture_lr_delta))
                                {
                                  priv->gesture_motion = DIR_UP;
                                }
                              else
                                {
                                  priv->gesture_motion = DIR_LEFT;
                                }
                            }
                          else
                            {
                              if ((priv->gesture_ud_count == 1) && \
                                  (priv->gesture_lr_count == 1))
                                {
                                  if (abs(priv->gesture_ud_delta) > \
                                      abs(priv->gesture_lr_delta))
                                    {
                                      priv->gesture_motion = DIR_DOWN;
                                    }
                                  else
                                    {
                                      priv->gesture_motion = DIR_RIGHT;
                                    }
                                }
                              else
                                {
                                  return false;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

  return true;
}

/****************************************************************************
 * Name: apds9960_readgesture
 *
 * Description:
 *   Read the photodiode data, process/decode it and return the guess
 *
 ****************************************************************************/

static int apds9960_readgesture(FAR struct apds9960_dev_s *priv)
{
  uint8_t fifo_level = 0;
  uint8_t bytes_read = 0;
  uint8_t fifo_data[128];
  uint8_t gstatus;
  int motion;
  int ret;
  int i;

  /* Make sure that power and gesture is on and data is valid */

  if (!apds9960_isgestureavailable(priv))
    {
      return DIR_NONE;
    }

  /* Keep looping as long as gesture data is valid */

  while (1)
    {
      /* Wait some time to collect next batch of FIFO data */

      nxsig_usleep(FIFO_PAUSE_TIME);

      /* Get the contents of the STATUS register. Is data still valid? */

      ret = apds9960_i2c_read8(priv, APDS9960_GSTATUS, &gstatus);
      if (ret < 0)
        {
          snerr("ERROR: Failed to read APDS9960_GSTATUS!\n");
          return ret;
        }

      /* If we have valid data, read in FIFO */

      if ((gstatus & GVALID) == GVALID)
        {
          /* Read the current FIFO level */

          ret = apds9960_i2c_read8(priv, APDS9960_GFLVL, &fifo_level);
          if (ret < 0)
            {
              snerr("ERROR: Failed to read APDS9960_GFLVL!\n");
              return ret;
            }

          sninfo("FIFO Level: %d\n", fifo_level);

          /* If there's stuff in the FIFO, read it into our data block */

          if (fifo_level > 0)
            {
              bytes_read = fifo_level * 4;
              ret = apds9960_i2c_read(priv, APDS9960_GFIFO_U,
                                      (uint8_t *) fifo_data, bytes_read);
              if (ret < 0)
                {
                  snerr("ERROR: Failed to read APDS9960_GFIFO_U!\n");
                  return ret;
                }

              sninfo("\nFIFO Dump:\n");
              for (i = 0; i < fifo_level; i++)
                {
                   sninfo("U: %03d | D: %03d | L: %03d | R: %03d\n",
                                               fifo_data[i], fifo_data[i+1],
                                               fifo_data[i+2], fifo_data[i+3]);
                }

              sninfo("\n");

              /* If at least 1 set of data, sort the data into U/D/L/R */

              if (bytes_read >= 4)
                {
                  for (i = 0; i < bytes_read; i += 4)
                    {
                      priv->gesture_data.u_data[priv->gesture_data.index] = fifo_data[i + 0];
                      priv->gesture_data.d_data[priv->gesture_data.index] = fifo_data[i + 1];
                      priv->gesture_data.l_data[priv->gesture_data.index] = fifo_data[i + 2];
                      priv->gesture_data.r_data[priv->gesture_data.index] = fifo_data[i + 3];
                      priv->gesture_data.index++;
                      priv->gesture_data.total_gestures++;
                    }

                  sninfo("Up Data:\n");
                  for (i = 0; i < priv->gesture_data.total_gestures; i++)
                    {
                      sninfo("%03d\n", priv->gesture_data.u_data[i]);
                    }

                  sninfo("\n");

                  /* Filter and process gesture data. Decode near/far state */

                  if (apds9960_processgesture(priv))
                    {
                      if (apds9960_decodegesture(priv))
                        {
                            /* TODO: U-Turn Gestures */
                        }
                    }

                  /* Reset data */

                  priv->gesture_data.index = 0;
                  priv->gesture_data.total_gestures = 0;
                }
            }
        }
      else
        {
          /* Determine best guessed gesture and clean up */

          nxsig_usleep(FIFO_PAUSE_TIME);
          apds9960_decodegesture(priv);
          motion = priv->gesture_motion;

          snwarn("END: %d\n", priv->gesture_motion);

          if (motion == DIR_LEFT)
            {
              snwarn("RESULT = LEFT\n");
            }

          if (motion == DIR_RIGHT)
            {
              snwarn("RESULT = RIGHT\n");
            }

          if (motion == DIR_UP)
            {
              snwarn("RESULT = UP\n");
            }

          if (motion == DIR_DOWN)
            {
              snwarn("RESULT = DOWN\n");
            }

          /* Increase semaphore to indicate new data */

          nxsem_post(&priv->sample_sem);

          apds9960_resetgesture(priv);
          return motion;
        }
    }
}

/****************************************************************************
 * Name: apds9960_open
 *
 * Description:
 *   This function is called whenever the APDS9960 device is opened.
 *
 ****************************************************************************/

static int apds9960_open(FAR struct file *filep)
{
  return OK;
}

/****************************************************************************
 * Name: apds9960_close
 *
 * Description:
 *   This routine is called when the APDS9960 device is closed.
 *
 ****************************************************************************/

static int apds9960_close(FAR struct file *filep)
{
  return OK;
}

/****************************************************************************
 * Name: apds9960_read
 ****************************************************************************/

static ssize_t apds9960_read(FAR struct file *filep, FAR char *buffer,
                             size_t buflen)
{
  FAR struct inode         *inode;
  FAR struct apds9960_dev_s *priv;
  int ret;

  DEBUGASSERT(filep);
  inode = filep->f_inode;

  DEBUGASSERT(inode && inode->i_private);
  priv  = (FAR struct apds9960_dev_s *)inode->i_private;

  /* Check if the user is reading the right size */

  if (buflen < 1)
    {
      snerr("ERROR: You need to read at least 1 byte from this sensor!\n");
      return -EINVAL;
    }

  /* Wait for data available */

  do
    {
      ret = nxsem_wait(&priv->sample_sem);

      /* The only case that an error should occur here is if the wait was
       * awakened by a signal.
       */

      DEBUGASSERT(ret == OK || ret == -EINTR);
    }
  while (ret == -EINTR);

  buffer[0] = (char) priv->gesture_motion;
  buflen    = 1;

  return buflen;
}

/****************************************************************************
 * Name: apds9960_write
 ****************************************************************************/

static ssize_t apds9960_write(FAR struct file *filep,
                              FAR const char *buffer, size_t buflen)
{
  return -ENOSYS;
}

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

/****************************************************************************
 * Name: apds9960_register
 *
 * Description:
 *   Register the APDS9960 character device as 'devpath'
 *
 * Input Parameters:
 *   devpath - The full path to the driver to register. E.g., "/dev/gest0"
 *   i2c - An instance of the I2C interface to use to communicate with APDS9960
 *   addr - The I2C address of the APDS9960.
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno value on failure.
 *
 ****************************************************************************/

int apds9960_register(FAR const char *devpath,
                      FAR struct apds9960_config_s *config)
{
  int ret;

  /* Sanity check */

  DEBUGASSERT(i2c != NULL);

  /* Initialize the APDS9960 device structure */

  FAR struct apds9960_dev_s *priv =
    (FAR struct apds9960_dev_s *)kmm_malloc(sizeof(struct apds9960_dev_s));

  if (priv == NULL)
    {
      snerr("ERROR: Failed to allocate instance\n");
      return -ENOMEM;
    }

  priv->config         = config;
  priv->work.worker    = NULL;
  priv->gesture_motion = DIR_NONE;
  nxsem_init(&priv->sample_sem, 0, 0);

  /* Probe APDS9960 device */

  ret = apds9960_probe(priv);
  if (ret != OK)
    {
      snerr("ERROR: APDS-9960 is not responding!\n");
      return ret;
    }

  /* Turn the device OFF to make it sane */

  ret = apds9960_i2c_write8(priv, APDS9960_ENABLE, 0);
  if (ret < 0)
    {
      snerr("ERROR: Failed to initialize the APDS9960!\n");
      return ret;
    }

  /* Wait 100ms */

  nxsig_usleep(100000);

  /* Initialize the device (leave RESET) */

  ret = apds9960_i2c_write8(priv, APDS9960_ENABLE, PON);
  if (ret < 0)
    {
      snerr("ERROR: Failed to initialize the APDS9960!\n");
      return ret;
    }

  /* Set default initial register values */

  ret = apds9960_setdefault(priv);
  if (ret < 0)
    {
      snerr("ERROR: Failed to initialize the APDS9960!\n");
      return ret;
    }

  /* Reset gesture values */

  apds9960_resetgesture(priv);

  /* Enable the Gesture mode and interruptions */

  ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG4, (GMODE | GIEN));
  if (ret < 0)
    {
      snerr("ERROR: Failed to write APDS9960_GCONFIG4!\n");
      return ret;
    }

  /* Enable the Gesture mode (Proximity mode is needed for gesture mode) */

  ret = apds9960_i2c_write8(priv, APDS9960_ENABLE, PON | PEN | GEN | WEN);
  if (ret < 0)
    {
      snerr("ERROR: Failed to initialize the APDS9960!\n");
      return ret;
    }

  /* Register the character driver */

  ret = register_driver(devpath, &g_apds9960_fops, 0666, priv);
  if (ret < 0)
    {
      snerr("ERROR: Failed to register driver: %d\n", ret);
      kmm_free(priv);
    }

  /* Attach to the interrupt */

  priv->config->irq_attach(priv->config, apds9960_int_handler, priv);

  return ret;
}

#endif /* CONFIG_I2C && CONFIG_SENSORS_APDS9960 */