/*
 * Validity Sensors, Inc. VFS5011 Fingerprint Reader driver for libfprint
 * Copyright (C) 2013 Arseniy Lartsev <arseniy@chalmers.se>
 *                    AceLan Kao <acelan.kao@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#define FP_COMPONENT "vfs5011"

#include "drivers_api.h"
#include "vfs5011_proto.h"

/* =================== sync/async USB transfer sequence ==================== */

enum {
  ACTION_SEND,
  ACTION_RECEIVE,
};

struct usb_action
{
  int            type;
  const char    *name;
  int            endpoint;
  int            size;
  unsigned char *data;
  int            correct_reply_size;
};

#define SEND(ENDPOINT, COMMAND) \
  { \
    .type = ACTION_SEND, \
    .endpoint = ENDPOINT, \
    .name = #COMMAND, \
    .size = sizeof (COMMAND), \
    .data = COMMAND \
  },

#define RECV(ENDPOINT, SIZE) \
  { \
    .type = ACTION_RECEIVE, \
    .endpoint = ENDPOINT, \
    .size = SIZE, \
    .data = NULL \
  },

#define RECV_CHECK(ENDPOINT, SIZE, EXPECTED) \
  { \
    .type = ACTION_RECEIVE, \
    .endpoint = ENDPOINT, \
    .size = SIZE, \
    .data = EXPECTED, \
    .correct_reply_size = sizeof (EXPECTED) \
  },

struct usbexchange_data
{
  int                stepcount;
  FpImageDevice     *device;
  struct usb_action *actions;
  void              *receive_buf;
  int                timeout;
};

static void start_scan (FpImageDevice *dev);

static void
async_send_cb (FpiUsbTransfer *transfer, FpDevice *device,
               gpointer user_data, GError *error)
{
  struct usbexchange_data *data = fpi_ssm_get_data (transfer->ssm);
  struct usb_action *action;

  g_assert (!(fpi_ssm_get_cur_state (transfer->ssm) >= data->stepcount));

  action = &data->actions[fpi_ssm_get_cur_state (transfer->ssm)];
  g_assert (!(action->type != ACTION_SEND));

  if (error)
    {
      /* Transfer not completed, return IO error */
      fpi_ssm_mark_failed (transfer->ssm, error);
      return;
    }

  /* success */
  fpi_ssm_next_state (transfer->ssm);
}

static void
async_recv_cb (FpiUsbTransfer *transfer, FpDevice *device,
               gpointer user_data, GError *error)
{
  struct usbexchange_data *data = fpi_ssm_get_data (transfer->ssm);
  struct usb_action *action;

  if (error)
    {
      /* Transfer not completed, return IO error */
      fpi_ssm_mark_failed (transfer->ssm, error);
      return;
    }

  g_assert (!(fpi_ssm_get_cur_state (transfer->ssm) >= data->stepcount));

  action = &data->actions[fpi_ssm_get_cur_state (transfer->ssm)];
  g_assert (!(action->type != ACTION_RECEIVE));

  if (action->data != NULL)
    {
      if (transfer->actual_length != action->correct_reply_size)
        {
          fp_err ("Got %d bytes instead of %d",
                  (gint) transfer->actual_length,
                  action->correct_reply_size);
          fpi_ssm_mark_failed (transfer->ssm, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
          return;
        }
      if (memcmp (transfer->buffer, action->data,
                  action->correct_reply_size) != 0)
        {
          fp_dbg ("Wrong reply:");
          fpi_ssm_mark_failed (transfer->ssm, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
          return;
        }
    }
  else
    {
      fp_dbg ("Got %d bytes out of %d",
              (gint) transfer->actual_length,
              (gint) transfer->length);
    }

  fpi_ssm_next_state (transfer->ssm);
}

static void
usbexchange_loop (FpiSsm *ssm, FpDevice *_dev)
{
  struct usbexchange_data *data = fpi_ssm_get_data (ssm);
  struct usb_action *action = &data->actions[fpi_ssm_get_cur_state (ssm)];
  FpiUsbTransfer *transfer;

  g_assert (fpi_ssm_get_cur_state (ssm) < data->stepcount);

  switch (action->type)
    {
    case ACTION_SEND:
      fp_dbg ("Sending %s", action->name);
      transfer = fpi_usb_transfer_new (_dev);
      fpi_usb_transfer_fill_bulk_full (transfer, action->endpoint,
                                       action->data, action->size,
                                       NULL);
      transfer->ssm = ssm;
      transfer->short_is_error = TRUE;
      fpi_usb_transfer_submit (transfer, data->timeout, NULL,
                               async_send_cb, NULL);
      break;

    case ACTION_RECEIVE:
      fp_dbg ("Receiving %d bytes", action->size);
      transfer = fpi_usb_transfer_new (_dev);
      fpi_usb_transfer_fill_bulk_full (transfer, action->endpoint,
                                       data->receive_buf,
                                       action->size, NULL);
      transfer->ssm = ssm;
      fpi_usb_transfer_submit (transfer, data->timeout, NULL,
                               async_recv_cb, NULL);
      break;

    default:
      fp_err ("Bug detected: invalid action %d", action->type);
      fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
      return;
    }
}

static void
usb_exchange_async (FpiSsm                  *ssm,
                    struct usbexchange_data *data,
                    const char              *exchange_name)
{
  FpiSsm *subsm = fpi_ssm_new_full (FP_DEVICE (data->device),
                                    usbexchange_loop,
                                    data->stepcount,
                                    data->stepcount,
                                    exchange_name);

  fpi_ssm_set_data (subsm, data, NULL);
  fpi_ssm_start_subsm (ssm, subsm);
}

/* ====================== utils ======================= */

/* Calculade squared standand deviation of sum of two lines */
static int
vfs5011_get_deviation2 (struct fpi_line_asmbl_ctx *ctx, GSList *row1, GSList *row2)
{
  unsigned char *buf1, *buf2;
  int res = 0, mean = 0, i;
  const int size = 64;

  buf1 = (unsigned char *) row1->data + 56;
  buf2 = (unsigned char *) row2->data + 168;

  for (i = 0; i < size; i++)
    mean += (int) buf1[i] + (int) buf2[i];

  mean /= size;

  for (i = 0; i < size; i++)
    {
      int dev = (int) buf1[i] + (int) buf2[i] - mean;
      res += dev * dev;
    }

  return res / size;
}

static unsigned char
vfs5011_get_pixel (struct fpi_line_asmbl_ctx *ctx,
                   GSList                    *row,
                   unsigned                   x)
{
  unsigned char *data = (unsigned char *) row->data + 8;

  return data[x];
}

/* ====================== main stuff ======================= */

enum {
  CAPTURE_LINES = 256,
  MAXLINES = 2000,
  MAX_CAPTURE_LINES = 100000,
};

static struct fpi_line_asmbl_ctx assembling_ctx = {
  .line_width = VFS5011_IMAGE_WIDTH,
  .max_height = MAXLINES,
  .resolution = 10,
  .median_filter_size = 25,
  .max_search_offset = 30,
  .get_deviation = vfs5011_get_deviation2,
  .get_pixel = vfs5011_get_pixel,
};

struct _FpDeviceVfs5011
{
  FpImageDevice           parent;

  unsigned char          *total_buffer;
  unsigned char          *capture_buffer;
  unsigned char          *row_buffer;
  unsigned char          *lastline;
  GSList                 *rows;
  int                     lines_captured, lines_recorded, empty_lines;
  int                     max_lines_captured, max_lines_recorded;
  int                     lines_total, lines_total_allocated;
  gboolean                loop_running;
  gboolean                deactivating;
  struct usbexchange_data init_sequence;
};

G_DECLARE_FINAL_TYPE (FpDeviceVfs5011, fpi_device_vfs5011, FPI, DEVICE_VFS5011,
                      FpImageDevice);
G_DEFINE_TYPE (FpDeviceVfs5011, fpi_device_vfs5011, FP_TYPE_IMAGE_DEVICE);

enum {
  DEV_ACTIVATE_REQUEST_FPRINT,
  DEV_ACTIVATE_INIT_COMPLETE,
  DEV_ACTIVATE_READ_DATA,
  DEV_ACTIVATE_DATA_COMPLETE,
  DEV_ACTIVATE_PREPARE_NEXT_CAPTURE,
  DEV_ACTIVATE_NUM_STATES
};

enum {
  DEV_OPEN_START,
  DEV_OPEN_NUM_STATES
};

static void
capture_init (FpDeviceVfs5011 *self, int max_captured,
              int max_recorded)
{
  fp_dbg ("capture_init");
  self->lastline = NULL;
  self->lines_captured = 0;
  self->lines_recorded = 0;
  self->empty_lines = 0;
  self->lines_total = 0;
  self->lines_total_allocated = 0;
  self->total_buffer = NULL;
  self->max_lines_captured = max_captured;
  self->max_lines_recorded = max_recorded;
}

static int
process_chunk (FpDeviceVfs5011 *self, int transferred)
{
  enum {
    DEVIATION_THRESHOLD = 15 * 15,
    DIFFERENCE_THRESHOLD = 600,
    STOP_CHECK_LINES = 50
  };

  fp_dbg ("process_chunk: got %d bytes", transferred);
  int lines_captured = transferred / VFS5011_LINE_SIZE;
  int i;

  for (i = 0; i < lines_captured; i++)
    {
      unsigned char *linebuf = self->capture_buffer
                               + i * VFS5011_LINE_SIZE;

      if (fpi_std_sq_dev (linebuf + 8, VFS5011_IMAGE_WIDTH)
          < DEVIATION_THRESHOLD)
        {
          if (self->lines_captured == 0)
            continue;
          else
            self->empty_lines++;
        }
      else
        {
          self->empty_lines = 0;
        }
      if (self->empty_lines >= STOP_CHECK_LINES)
        {
          fp_dbg ("process_chunk: got %d empty lines, finishing",
                  self->empty_lines);
          return 1;
        }

      self->lines_captured++;
      if (self->lines_captured > self->max_lines_captured)
        {
          fp_dbg ("process_chunk: captured %d lines, finishing",
                  self->lines_captured);
          return 1;
        }

      if ((self->lastline == NULL) ||
          (fpi_mean_sq_diff_norm (self->lastline + 8,
                                  linebuf + 8,
                                  VFS5011_IMAGE_WIDTH) >= DIFFERENCE_THRESHOLD))
        {
          self->lastline = g_malloc (VFS5011_LINE_SIZE);
          self->rows = g_slist_prepend (self->rows,
                                        self->lastline);
          memmove (self->lastline, linebuf, VFS5011_LINE_SIZE);
          self->lines_recorded++;
          if (self->lines_recorded >= self->max_lines_recorded)
            {
              fp_dbg ("process_chunk: recorded %d lines, finishing",
                      self->lines_recorded);
              return 1;
            }
        }
    }
  return 0;
}

static void
submit_image (FpiSsm          *ssm,
              FpDeviceVfs5011 *self,
              FpImageDevice   *dev)
{
  FpImage *img;

  if (self->lines_recorded < VFS5011_IMAGE_WIDTH)
    {
      fpi_image_device_retry_scan (dev, FP_DEVICE_RETRY_TOO_SHORT);
      return;
    }

  g_assert (self->rows != NULL);

  self->rows = g_slist_reverse (self->rows);

  img = fpi_assemble_lines (&assembling_ctx, self->rows,
                            self->lines_recorded);

  g_slist_free_full (self->rows, g_free);
  self->rows = NULL;

  fp_dbg ("Image captured, committing");

  fpi_image_device_image_captured (dev, img);
}

static void
chunk_capture_callback (FpiUsbTransfer *transfer, FpDevice *device,
                        gpointer user_data, GError *error)
{
  FpImageDevice *dev = FP_IMAGE_DEVICE (device);
  FpDeviceVfs5011 *self;

  self = FPI_DEVICE_VFS5011 (dev);

  if (!error ||
      g_error_matches (error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT))
    {
      if (error)
        g_error_free (error);

      if (transfer->actual_length > 0)
        fpi_image_device_report_finger_status (dev, TRUE);

      if (process_chunk (self, transfer->actual_length))
        fpi_ssm_jump_to_state (transfer->ssm,
                               DEV_ACTIVATE_DATA_COMPLETE);
      else
        fpi_ssm_jump_to_state (transfer->ssm,
                               DEV_ACTIVATE_READ_DATA);
    }
  else
    {
      if (!self->deactivating)
        {
          fp_err ("Failed to capture data");
          fpi_ssm_mark_failed (transfer->ssm, error);
        }
      else
        {
          g_error_free (error);
          fpi_ssm_mark_completed (transfer->ssm);
        }
    }
}

static void
capture_chunk_async (FpDeviceVfs5011 *self,
                     GUsbDevice *handle, int nline,
                     int timeout, FpiSsm *ssm)
{
  FpiUsbTransfer *transfer;

  fp_dbg ("capture_chunk_async: capture %d lines, already have %d",
          nline, self->lines_recorded);
  enum {
    DEVIATION_THRESHOLD = 15 * 15,
    DIFFERENCE_THRESHOLD = 600,
    STOP_CHECK_LINES = 50
  };

  transfer = fpi_usb_transfer_new (FP_DEVICE (self));
  fpi_usb_transfer_fill_bulk_full (transfer,
                                   VFS5011_IN_ENDPOINT_DATA,
                                   self->capture_buffer,
                                   nline * VFS5011_LINE_SIZE, NULL);
  transfer->ssm = ssm;
  fpi_usb_transfer_submit (transfer, timeout, fpi_device_get_cancellable (FP_DEVICE (self)),
                           chunk_capture_callback, NULL);
}

/*
 *  Device initialization. Windows driver only does it when the device is
 *  plugged in, but it doesn't harm to do this every time before scanning the
 *  image.
 */
struct usb_action vfs5011_initialization[] = {
  SEND (VFS5011_OUT_ENDPOINT, vfs5011_cmd_01)
  RECV (VFS5011_IN_ENDPOINT_CTRL, 64)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_cmd_19)
  RECV (VFS5011_IN_ENDPOINT_CTRL, 64)
  RECV (VFS5011_IN_ENDPOINT_CTRL, 64)      /* B5C457F9 */

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_00)
  RECV (VFS5011_IN_ENDPOINT_CTRL, 64)      /* 0000FFFFFFFF */

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_01)
  RECV (VFS5011_IN_ENDPOINT_CTRL, 64)      /* 0000FFFFFFFFFF */

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_02)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_cmd_01)
  RECV (VFS5011_IN_ENDPOINT_CTRL, 64)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_cmd_1A)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_03)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_04)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)
  RECV (VFS5011_IN_ENDPOINT_DATA, 256)
  RECV (VFS5011_IN_ENDPOINT_DATA, 64)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_cmd_1A)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_05)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_cmd_01)
  RECV (VFS5011_IN_ENDPOINT_CTRL, 64)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_06)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)
  RECV (VFS5011_IN_ENDPOINT_DATA, 17216)
  RECV (VFS5011_IN_ENDPOINT_DATA, 32)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_07)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)
  RECV (VFS5011_IN_ENDPOINT_DATA, 45056)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_08)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)
  RECV (VFS5011_IN_ENDPOINT_DATA, 16896)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_09)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)
  RECV (VFS5011_IN_ENDPOINT_DATA, 4928)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_10)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)
  RECV (VFS5011_IN_ENDPOINT_DATA, 5632)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_11)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)
  RECV (VFS5011_IN_ENDPOINT_DATA, 5632)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_12)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)
  RECV (VFS5011_IN_ENDPOINT_DATA, 3328)
  RECV (VFS5011_IN_ENDPOINT_DATA, 64)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_13)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_cmd_1A)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_03)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_14)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)
  RECV (VFS5011_IN_ENDPOINT_DATA, 4800)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_cmd_1A)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_02)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_cmd_27)
  RECV (VFS5011_IN_ENDPOINT_CTRL, 64)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_cmd_1A)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_15)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_16)
  RECV (VFS5011_IN_ENDPOINT_CTRL, 2368)
  RECV (VFS5011_IN_ENDPOINT_CTRL, 64)
  RECV (VFS5011_IN_ENDPOINT_DATA, 4800)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_17)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_init_18)
  /* 0000 */
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  /*
   * Windows driver does this and it works
   * But in this driver this call never returns...
   * RECV(VFS5011_IN_ENDPOINT_CTRL2, 8) //00D3054000
   */
};

/* Initiate recording the image */
struct usb_action vfs5011_initiate_capture[] = {
  SEND (VFS5011_OUT_ENDPOINT, vfs5011_cmd_04)
  RECV (VFS5011_IN_ENDPOINT_DATA, 64)
  RECV (VFS5011_IN_ENDPOINT_DATA, 84032)
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_cmd_1A)
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_prepare_00)
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_cmd_1A)
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_prepare_01)
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_prepare_02)
  RECV (VFS5011_IN_ENDPOINT_CTRL, 2368)
  RECV (VFS5011_IN_ENDPOINT_CTRL, 64)
  RECV (VFS5011_IN_ENDPOINT_DATA, 4800)

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_prepare_03)
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 64, VFS5011_NORMAL_CONTROL_REPLY)
  /*
   * Windows driver does this and it works
   * But in this driver this call never returns...
   * RECV(VFS5011_IN_ENDPOINT_CTRL2, 8);
   */

  SEND (VFS5011_OUT_ENDPOINT, vfs5011_prepare_04)
  RECV_CHECK (VFS5011_IN_ENDPOINT_CTRL, 2368, VFS5011_NORMAL_CONTROL_REPLY)

  /*
   * Windows driver does this and it works
   * But in this driver this call never returns...
   * RECV(VFS5011_IN_ENDPOINT_CTRL2, 8);
   */
};

/* ====================== lifprint interface ======================= */

static void
activate_loop (FpiSsm *ssm, FpDevice *_dev)
{
  enum {READ_TIMEOUT = 0};

  FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
  FpDeviceVfs5011 *self;

  self = FPI_DEVICE_VFS5011 (_dev);

  fp_dbg ("main_loop: state %d", fpi_ssm_get_cur_state (ssm));

  if (self->deactivating)
    {
      fp_dbg ("deactivating, marking completed");
      fpi_ssm_mark_completed (ssm);
      return;
    }

  switch (fpi_ssm_get_cur_state (ssm))
    {
    case DEV_ACTIVATE_REQUEST_FPRINT:
      self->init_sequence.stepcount =
        G_N_ELEMENTS (vfs5011_initiate_capture);
      self->init_sequence.actions = vfs5011_initiate_capture;
      self->init_sequence.device = dev;
      if (self->init_sequence.receive_buf == NULL)
        self->init_sequence.receive_buf =
          g_malloc0 (VFS5011_RECEIVE_BUF_SIZE);
      self->init_sequence.timeout = 1000;
      usb_exchange_async (ssm, &self->init_sequence, "ACTIVATE REQUEST");
      break;

    case DEV_ACTIVATE_INIT_COMPLETE:
      if (self->init_sequence.receive_buf != NULL)
        g_free (self->init_sequence.receive_buf);
      self->init_sequence.receive_buf = NULL;
      capture_init (self, MAX_CAPTURE_LINES, MAXLINES);
      fpi_image_device_activate_complete (dev, NULL);
      fpi_ssm_next_state (ssm);
      break;

    case DEV_ACTIVATE_READ_DATA:
      capture_chunk_async (self,
                           fpi_device_get_usb_device (FP_DEVICE (dev)),
                           CAPTURE_LINES,
                           READ_TIMEOUT, ssm);
      break;

    case DEV_ACTIVATE_DATA_COMPLETE:
      fpi_ssm_next_state_delayed (ssm, 1);
      break;

    case DEV_ACTIVATE_PREPARE_NEXT_CAPTURE:
      self->init_sequence.stepcount =
        G_N_ELEMENTS (vfs5011_initiate_capture);
      self->init_sequence.actions = vfs5011_initiate_capture;
      self->init_sequence.device = dev;
      if (self->init_sequence.receive_buf == NULL)
        self->init_sequence.receive_buf =
          g_malloc0 (VFS5011_RECEIVE_BUF_SIZE);
      self->init_sequence.timeout = VFS5011_DEFAULT_WAIT_TIMEOUT;
      usb_exchange_async (ssm, &self->init_sequence, "PREPARE CAPTURE");
      break;

    }
}

static void
activate_loop_complete (FpiSsm *ssm, FpDevice *_dev, GError *error)
{
  FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
  FpDeviceVfs5011 *self;

  self = FPI_DEVICE_VFS5011 (_dev);

  fp_dbg ("finishing");
  if (self->init_sequence.receive_buf != NULL)
    g_free (self->init_sequence.receive_buf);
  self->init_sequence.receive_buf = NULL;
  if (!self->deactivating && !error)
    {
      submit_image (ssm, self, dev);
      fpi_image_device_report_finger_status (dev, FALSE);
    }

  self->loop_running = FALSE;

  if (self->deactivating)
    fpi_image_device_deactivate_complete (dev, error);
  else if (error)
    fpi_image_device_session_error (dev, error);
  else
    start_scan (dev);
}


static void
open_loop (FpiSsm *ssm, FpDevice *_dev)
{
  FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
  FpDeviceVfs5011 *self;

  self = FPI_DEVICE_VFS5011 (_dev);

  switch (fpi_ssm_get_cur_state (ssm))
    {
    case DEV_OPEN_START:
      self->init_sequence.stepcount =
        G_N_ELEMENTS (vfs5011_initialization);
      self->init_sequence.actions = vfs5011_initialization;
      self->init_sequence.device = dev;
      self->init_sequence.receive_buf =
        g_malloc0 (VFS5011_RECEIVE_BUF_SIZE);
      self->init_sequence.timeout = VFS5011_DEFAULT_WAIT_TIMEOUT;
      usb_exchange_async (ssm, &self->init_sequence, "DEVICE OPEN");
      break;
    }
  ;
}

static void
open_loop_complete (FpiSsm *ssm, FpDevice *_dev, GError *error)
{
  FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
  FpDeviceVfs5011 *self;

  self = FPI_DEVICE_VFS5011 (_dev);
  g_free (self->init_sequence.receive_buf);
  self->init_sequence.receive_buf = NULL;

  fpi_image_device_open_complete (dev, error);
}

static void
dev_open (FpImageDevice *dev)
{
  FpiSsm *ssm;
  GError *error = NULL;
  FpDeviceVfs5011 *self;

  self = FPI_DEVICE_VFS5011 (dev);
  self->capture_buffer = g_new0 (unsigned char, CAPTURE_LINES * VFS5011_LINE_SIZE);

  if (!g_usb_device_claim_interface (fpi_device_get_usb_device (FP_DEVICE (dev)), 0, 0, &error))
    {
      fpi_image_device_open_complete (dev, error);
      return;
    }

  ssm = fpi_ssm_new (FP_DEVICE (dev), open_loop, DEV_OPEN_NUM_STATES);
  fpi_ssm_start (ssm, open_loop_complete);
}

static void
dev_close (FpImageDevice *dev)
{
  GError *error = NULL;
  FpDeviceVfs5011 *self = FPI_DEVICE_VFS5011 (dev);

  g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (dev)),
                                  0, 0, &error);

  g_free (self->capture_buffer);
  g_slist_free_full (g_steal_pointer (&self->rows), g_free);

  fpi_image_device_close_complete (dev, error);
}

static void
start_scan (FpImageDevice *dev)
{
  FpDeviceVfs5011 *self;
  FpiSsm *ssm;

  self = FPI_DEVICE_VFS5011 (dev);
  self->loop_running = TRUE;
  fp_dbg ("creating ssm");
  ssm = fpi_ssm_new (FP_DEVICE (dev), activate_loop, DEV_ACTIVATE_NUM_STATES);
  fp_dbg ("starting ssm");
  fpi_ssm_start (ssm, activate_loop_complete);
  fp_dbg ("ssm done, getting out");
}

static void
dev_activate (FpImageDevice *dev)
{
  FpDeviceVfs5011 *self;

  self = FPI_DEVICE_VFS5011 (dev);
  fp_dbg ("device initialized");
  self->deactivating = FALSE;

  start_scan (dev);
}

static void
dev_deactivate (FpImageDevice *dev)
{
  FpDeviceVfs5011 *self;

  self = FPI_DEVICE_VFS5011 (dev);
  if (self->loop_running)
    self->deactivating = TRUE;
  else
    fpi_image_device_deactivate_complete (dev, NULL);
}

static const FpIdEntry id_table[] = {
  { /* Validity device from some Toshiba laptops */ .vid = 0x138a,  .pid = 0x0010, },
  { /* vfs5011 */ .vid = 0x138a,  .pid = 0x0011, },
  { /* Validity device from Lenovo Preferred Pro USB Fingerprint Keyboard KUF1256 */ .vid = 0x138a,  .pid = 0x0015, },
  { /* Validity device from Lenovo T440 laptops */ .vid = 0x138a,  .pid = 0x0017, },
  { /* one more Validity device */ .vid = 0x138a,  .pid = 0x0018, },
  { .vid = 0,  .pid = 0,  .driver_data = 0 },
};

static void
fpi_device_vfs5011_init (FpDeviceVfs5011 *self)
{
}
static void
fpi_device_vfs5011_class_init (FpDeviceVfs5011Class *klass)
{
  FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
  FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS (klass);

  dev_class->id = "vfs5011";
  dev_class->full_name = "Validity VFS5011";
  dev_class->type = FP_DEVICE_TYPE_USB;
  dev_class->id_table = id_table;
  dev_class->scan_type = FP_SCAN_TYPE_SWIPE;

  img_class->img_open = dev_open;
  img_class->img_close = dev_close;
  img_class->activate = dev_activate;
  img_class->deactivate = dev_deactivate;

  img_class->bz3_threshold = 20;

  img_class->img_width = VFS5011_IMAGE_WIDTH;
  img_class->img_height = -1;
}
