/* sane - Scanner Access Now Easy.

   Copyright (C) 2006 Gerard Klaver  <gerard at gkall dot hobby dot nl>
   The teco2 and gl646 backend (Frank Zago) are used as a template for 
   this backend.
   
   For the sunplusfps_add_text routine the add_text routine and font_6x11.h file 
   are taken from the webcam.c file, part of xawtv program,
   (c) 1998-2002 Gerd Knorr (GNU GPL license 2).

   For the init of the device, parts of the following programs are used:

   This file is part of the SANE package.
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.
   
   This program 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
   General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
   MA 02111-1307, USA.
   
*/

/*  $Id: sunplusfps.c,v 1.4 2006/09/18 19:49:06 gerard Exp $*/
/*
   sunplusfps fingerprint  driver Gerard Klaver
*/

/*SANE FLOW DIAGRAM

   - sane_init() : initialize backend, attach fingerprints
   . - sane_get_devices() : query list of fingerprint devices
   . - sane_open() : open a particular fingerprint device
   . . - sane_set_io_mode : set blocking mode
   . . - sane_get_select_fd : get fingerprint fd
   . . - sane_get_option_descriptor() : get option information
   . . - sane_control_option() : change option values
   . .
   . . - sane_start() : start image acquisition
   . .   - sane_get_parameters() : returns actual scan parameters
   . .   - sane_read() : read image data (from pipe)
   . .     (sane_read called multiple times; 
   . .      after sane_read returns EOF)
   . .     go back to sane_start() if more frames desired
   . . - sane_cancel() : cancel operation
   . - sane_close() : close opened fingerprint device
   - sane_exit() : terminate use of backend
*/
/*--------------------------------------------------------------------------*/

#define BUILD 1			/* 2006/11/25 */
#define BACKEND_NAME sunplusfps
#define SUNPLUSFPS_CONFIG_FILE "sunplusfps.conf"

/* --------------------- SANE INTERNATIONALISATION ------------------------ */

/* must be first include */
#include "../include/sane/config.h"

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "../include/sane/sane.h"
#include "../include/sane/sanei.h"
#include "../include/sane/saneopts.h"
#include "../include/sane/sanei_usb.h"
#include "../include/sane/sanei_debug.h"
#include "../include/sane/sanei_backend.h"
#include "../include/sane/sanei_config.h"
#include "../include/lassert.h"

/* for add-text routine           */
#include <time.h>
#include "../include/font_6x11.h"
/*--------------------------------*/

/* #include "sunplusfps_finger850k.c" */
#include "sunplusfps.h"

#define TIMEOUT 1000

/*--------------------------------------------------------------------------*/
/* Lists of possible scan modes. */
static SANE_String_Const scan_mode_list[] = {
  GRAYSCALE_RAW_STR,
  GRAYSCALE_TEXT_STR,
  SANE_VALUE_SCAN_MODE_GRAY,
  NULL
};

/*-----------------------------------------minium, maximum, quantization----*/
static const SANE_Range brightness_range = { -128, 128, 1 };

static const SANE_Range red_level_range = { -32, 32, 1 };

static const SANE_Range white_level_range = { -32, 32, 1 };

/*--------------------------------------------------------------------------*/

static const struct dpi_color_adjust sunplusfps_dpi_color_adjust[] = {

  /*dpi, y, x, color sequence R G or B */
  {192, 480, 0, 1, 2},		/* 30x           finger835k??? */
  {192, 160, 0, 1, 2},		/* 10x           test??/ */
  {192, 16, 0, 1, 2},		/* singel field  test??? */
  /* must be the last entry */
  {0, 0, 0, 0, 0}
};

static const struct fingerprint_hardware fingerprints[] = {

  {0x04fc, 0x0232, USB_CLASS_VENDOR_SPEC, Sunplusfps_Finger835K,
   "Sunplus", "Finger835K",
   sunplusfps_dpi_color_adjust},
  
};

/* List of fingerprints attached. */
static Sunplusfps_Fingerprint *first_dev = NULL;
static int num_devices = 0;
/* used by sane_get_devices */
static const SANE_Device **devlist = NULL;

/*----------------------------------------------------------- */

/* Local functions. */

/* Display a buffer in the log. Display by lines of 16 bytes. */
static void
hexdump (int level, const char *comment, unsigned char *buf, const int length)
{
  int i;
  char line[128];
  char *ptr;
  char asc_buf[17];
  char *asc_ptr;

  DBG (level, "  %s\n", comment);

  i = 0;
  goto start;

  do
    {
      if (i < length)
	{
	  ptr += sprintf (ptr, " %2.2x", *buf);

	  if (*buf >= 32 && *buf <= 127)
	    {
	      asc_ptr += sprintf (asc_ptr, "%c", *buf);
	    }
	  else
	    {
	      asc_ptr += sprintf (asc_ptr, ".");
	    }
	}
      else
	{
	  /* After the length; do nothing. */
	  ptr += sprintf (ptr, "   ");
	}

      i++;
      buf++;

      if ((i % 16) == 0)
	{
	  /* It's a new line */
	  DBG (level, "  %s    %s\n", line, asc_buf);

	start:
	  ptr = line;
	  *ptr = '\0';
	  asc_ptr = asc_buf;
	  *asc_ptr = '\0';

	  ptr += sprintf (ptr, "  %3.3d:", i);
	}
    }
  while (i < ((length + 15) & ~15));
}

/* Returns the length of the longest string, including the terminating
 * character. */
static size_t
max_string_size (SANE_String_Const strings[])
{
  size_t size, max_size = 0;
  int i;

  for (i = 0; strings[i]; ++i)
    {
   size = strlen (strings[i]) + 1;
      if (size > max_size)
	{
	  max_size = size;
	}
    }
  return max_size;
}

/* Initialize a fingerprint entry. Return an allocated fingerprint with some
 *  */
static Sunplusfps_Fingerprint *
sunplusfps_init (void)
{
  Sunplusfps_Fingerprint *dev;

  DBG (DBG_proc, "sunplusfps_init: enter\n");

  /* Allocate a new fingerprint entry. */
  dev = malloc (sizeof (Sunplusfps_Fingerprint));
  if (dev == NULL)
    {
      return NULL;
    }
  memset (dev, 0, sizeof (Sunplusfps_Fingerprint));

/* Allocate the windoww buffer*/
  dev->windoww_size = 0x100;
  dev->windoww = malloc (dev->windoww_size);
  if (dev->windoww == NULL)
    {
      free (dev);
      return NULL;
    }

/* Allocate the windowr buffer*/
  dev->windowr_size = 0x100;
  dev->windowr = malloc (dev->windowr_size);
  if (dev->windowr == NULL)
    {
      free (dev);
      free (dev->windoww);
      return NULL;
    }

  dev->fd = -1;

  DBG (DBG_proc, "sunplusfps_init: exit\n");

  return (dev);
}

static SANE_Status
sunplusfps_init_2 (Sunplusfps_Fingerprint * dev)
{
  SANE_Status status;

  DBG (DBG_proc, "sunplusfps_init_2: enter\n");

  /* Allocate the buffer used to transfer the USB data */
  /* Check for max. format image size so buffer size can
   * be adjusted  */
  dev->buffer_size = 192 * 480;	/* for max size, data = 4bit??, specification? */

  dev->buffer = malloc (dev->buffer_size);

  if (dev->buffer == NULL)
    {
      free (dev);
      free (dev->windowr);
      free (dev->windoww);
      return SANE_STATUS_NO_MEM;
    }

  /* Allocate the output buffer used for conversion */
  dev->output_size = 2 * dev->buffer_size;

  dev->output = malloc (dev->output_size);
  if (dev->output == NULL)
    {
      free (dev);
      free (dev->windowr);
      free (dev->windoww);
      free (dev->buffer);

      return SANE_STATUS_NO_MEM;
    }
  dev->image_size = 2 * dev->buffer_size;

  dev->image = malloc (dev->image_size);
  if (dev->image == NULL)
    {
      free (dev);
      free (dev->windowr);
      free (dev->windoww);
      free (dev->buffer);
      free (dev->output);

      return SANE_STATUS_NO_MEM;
    }

  DBG (DBG_proc, "sunplusfps_init_2: exit\n");
  status = SANE_STATUS_GOOD;
  return status;
}

/* Closes an open fingerprints. */
static void
sunplusfps_close (Sunplusfps_Fingerprint * dev)
{
  DBG (DBG_proc, "sunplusfps_close: enter \n");

  if (dev->fd != -1)
    {

      DBG (DBG_proc, "sunplusfps_close: fd !=-1 \n");
      sanei_usb_close (dev->fd);
      dev->fd = -1;
    }

  DBG (DBG_proc, "sunplusfps_close: exit\n");
}

/* Frees the memory used by a fingerprint. */
static void
sunplusfps_free (Sunplusfps_Fingerprint * dev)
{
  int i;

  DBG (DBG_proc, "sunplusfps_free: enter\n");

  if (dev == NULL)
    return;

  sunplusfps_close (dev);
  if (dev->devicename)
    {
      free (dev->devicename);
    }
  if (dev->buffer)
    {
      free (dev->buffer);
    }
  if (dev->output)
    {
      free (dev->output);
    }
  if (dev->image)
    {
      free (dev->image);
    }
  if (dev->windoww)
    {
      free (dev->windoww);
    }
  if (dev->windowr)
    {
      free (dev->windowr);
    }
  for (i = 1; i < OPT_NUM_OPTIONS; i++)
    {
      if (dev->opt[i].type == SANE_TYPE_STRING && dev->val[i].s)
	{
	  free (dev->val[i].s);
	}
    }
  if (dev->resolutions_list)
    free (dev->resolutions_list);

  free (dev);

  DBG (DBG_proc, "sunplusfps_free: exit\n");
}

/* Reset fingerprint */
static SANE_Status
sunplusfps_reset_fingerprint (Sunplusfps_Fingerprint * dev)
{
  SANE_Status status;
  size_t sizew;			/* significant size of window */
  size_t sizer;

  DBG (DBG_proc, "sunplusfps_reset_fingerprint: enter\n");

  sizew = dev->windoww_size;
  sizer = dev->windowr_size;

  memset (dev->windoww, 0, sizew);
  memset (dev->windowr, 0, sizer);

  status = SANE_STATUS_GOOD;
  DBG (DBG_proc, "sunplusfps_reset_fingerprint: exit\n");

  return status;
}

/* Inquiry a device and returns TRUE if is supported. */
static int
sunplusfps_identify_fingerprint (Sunplusfps_Fingerprint * dev)
{
  SANE_Status status;
  SANE_Word vendor;
  SANE_Word product;
  int i;

  DBG (DBG_info, "sunplusfps_identify_fingerprint: open\n");

  status = sanei_usb_get_vendor_product (dev->fd, &vendor, &product);

  /* Loop through our list to make sure this scanner is supported. */
  for (i = 0; i < NELEMS (fingerprints); i++)
    {
      if (fingerprints[i].vendor == vendor
	  && fingerprints[i].product == product)
	{

	  DBG (DBG_info,
	       "sunplusfps_identify_fingerprint: fingerprint 0x%x:0x%x is in list\n",
	       vendor, product);

	  dev->hw = &(fingerprints[i]);

	  DBG (DBG_info,
	       "sunplusfps_identify_fingerprint: exit fingerprint supported\n");
	  return SANE_TRUE;
	}
    }
  /* TODO, place some commands to read some ident string from device if supported */
  DBG (DBG_error,
       "sunplusfps_identify_fingerprint: exit this is not a SUNPLUSFPS exit\n");
  return SANE_FALSE;
}

static SANE_Status
sunplusfps_fingerprint_init (Sunplusfps_Fingerprint * dev)
{
  SANE_Status status;
  size_t sizer;
  size_t sizew;
  size_t size;
  int i;
  int num_entries;
  DBG (DBG_proc, "sunplusfps_fingerprint_init: open\n");

  sizew = dev->windoww_size;
  sizer = dev->windowr_size;

  memset (dev->windoww, 0, sizew);
  memset (dev->windowr, 0, sizer);

  usleep (5000);

  switch (dev->hw->sunplusfpsref)
    {
    case Sunplusfps_Finger835K:

      status = SANE_STATUS_GOOD;

      break;

    default:

      status = SANE_STATUS_GOOD;

    }
  return status;
}

/* Attach a fingerprint to this backend. */
static SANE_Status
attach_fingerprint (SANE_String_Const devicename,
		    Sunplusfps_Fingerprint ** devp)
{
  Sunplusfps_Fingerprint *dev;
  int fd;
  SANE_Status status;

  DBG (DBG_proc, "attach_fingerprint: %s\n", devicename);

  if (devp)
    *devp = NULL;

  /* Check if we know this device name. */
  for (dev = first_dev; dev; dev = dev->next)
    {
      if (strcmp (dev->sane.name, devicename) == 0)
	{
	  if (devp)
	    {
	      *devp = dev;
	    }
	  DBG (DBG_info, "device is already known\n");
	  return SANE_STATUS_GOOD;
	}
    }

  /* Allocate a new fingerprint entry. */
  dev = sunplusfps_init ();
  if (dev == NULL)
    {
      DBG (DBG_error, "sunplusfps_init ERROR: not enough memory\n");
      return SANE_STATUS_NO_MEM;
    }

  DBG (DBG_info, "attach_fingerprint: opening USB device %s\n", devicename);

  if (sanei_usb_open (devicename, &fd) != 0)
    {
      DBG (DBG_error, "ERROR: attach_fingerprint: open failed\n");
      sunplusfps_free (dev);
      return SANE_STATUS_INVAL;
    }
  /* Fill some scanner specific values. */
  dev->devicename = strdup (devicename);
  dev->fd = fd;

  /* Now, check that it is a fingerprint we support. */

  if (sunplusfps_identify_fingerprint (dev) == SANE_FALSE)
    {
      DBG (DBG_error,
	   "ERROR: attach_fingerprint: fingerprint-identification failed\n");
      sunplusfps_free (dev);
      return SANE_STATUS_INVAL;
    }

  /* Allocate a buffer memory. */
  status = sunplusfps_init_2 (dev);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "sunplusfps_init_2, ERROR: not enough memory\n");
      return SANE_STATUS_NO_MEM;
    }

  sunplusfps_close (dev);

  DBG (DBG_info, "attach_fingerprint: opening USB device %s\n", devicename);

  /* Build list of fingerprint supported resolutions. */
  DBG (DBG_proc, "attach_fingerprint: build resolution list\n");

  if (dev->hw->color_adjust[0].resolution_x != 0)
    {
      int num_entries;
      int i;
      num_entries = 0;

      while (dev->hw->color_adjust[num_entries].resolution_x != 0)
	num_entries++;

      dev->resolutions_list = malloc (sizeof (SANE_Word) * (num_entries + 1));

      if (dev->resolutions_list == NULL)
	{
	  DBG (DBG_error,
	       "ERROR: attach_fingerprint: fingerprint resolution list failed\n");
	  sunplusfps_free (dev);
	  return SANE_STATUS_NO_MEM;
	}
      dev->resolutions_list[0] = num_entries;
      DBG (DBG_proc, "attach_fingerprint: make color resolution table \n");
      for (i = 0; i < num_entries; i++)
	{
	  dev->resolutions_list[i + 1] =
	    dev->hw->color_adjust[i].resolution_x;
	}
    }
  else
    {
      dev->resolutions_list = NULL;
    }

  /* Set the default options for that fingerprint. */
  dev->sane.name = dev->devicename;
  dev->sane.vendor = dev->hw->vendor_name;
  dev->sane.model = dev->hw->product_name;
  dev->sane.type = SANE_I18N ("fingerprint sensor");

  /* Link the fingerprint with the others. */
  dev->next = first_dev;
  first_dev = dev;

  if (devp)
    {
      *devp = dev;
    }

  num_devices++;

  DBG (DBG_proc, "attach_fingerprint: exit\n");

  return SANE_STATUS_GOOD;
}

static SANE_Status
attach_one (const char *dev)
{
  DBG (DBG_proc, "attach_one: open \n");
  attach_fingerprint (dev, NULL);
  DBG (DBG_proc, "attach_one: exit \n");
  return SANE_STATUS_GOOD;
}

/* Reset the options for that fingerprint. */
static void
sunplusfps_init_options (Sunplusfps_Fingerprint * dev)
{
  int i;

  DBG (DBG_proc, "sunplusfps_init_options: open\n");

  /* Pre-initialize the options. */
  memset (dev->opt, 0, sizeof (dev->opt));
  memset (dev->val, 0, sizeof (dev->val));

  for (i = 0; i < OPT_NUM_OPTIONS; ++i)
    {
      dev->opt[i].size = sizeof (SANE_Word);
      dev->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
    }
  DBG (DBG_proc,
       "sunplusfps_init_options: done loop opt_num_options=%d, i=%d \n",
       OPT_NUM_OPTIONS, i);
  /* Number of options. */
  dev->opt[OPT_NUM_OPTS].name = "";
  dev->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
  dev->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
  dev->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT;
  dev->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
  dev->val[OPT_NUM_OPTS].w = OPT_NUM_OPTIONS;

  /* Mode group */
  dev->opt[OPT_MODE_GROUP].title = SANE_I18N ("Scan Mode");
  dev->opt[OPT_MODE_GROUP].desc = "";	/* not valid for a group */
  dev->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
  dev->opt[OPT_MODE_GROUP].cap = 0;
  dev->opt[OPT_MODE_GROUP].size = 0;
  dev->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* Fingerprint supported modes */
  dev->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE;
  dev->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
  dev->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
  dev->opt[OPT_MODE].type = SANE_TYPE_STRING;
  dev->opt[OPT_MODE].size = max_string_size (scan_mode_list);
  dev->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  dev->opt[OPT_MODE].constraint.string_list = scan_mode_list;
  dev->val[OPT_MODE].s = (SANE_Char *) strdup ("");	/* will be set later */

  /* X and Y resolution */
  dev->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].type = SANE_TYPE_INT;
  dev->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
  dev->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->val[OPT_RESOLUTION].w = dev->resolutions_list[2];

  /* brightness   */
  dev->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT;
  dev->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE;
  dev->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_BRIGHTNESS].constraint.range = &brightness_range;
  dev->val[OPT_BRIGHTNESS].w = 0;	/* to get middle value */

  /* Enhancement group */
  dev->opt[OPT_ENHANCEMENT_GROUP].title = SANE_I18N ("Enhancement");
  dev->opt[OPT_ENHANCEMENT_GROUP].desc = "";	/* not valid for a group */
  dev->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
  dev->opt[OPT_ENHANCEMENT_GROUP].cap = SANE_CAP_ADVANCED;
  dev->opt[OPT_ENHANCEMENT_GROUP].size = 0;
  dev->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* red level calibration manual correction */
  dev->opt[OPT_WHITE_LEVEL_R].name = SANE_NAME_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].title = SANE_TITLE_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].desc = SANE_DESC_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].type = SANE_TYPE_INT;
  dev->opt[OPT_WHITE_LEVEL_R].unit = SANE_UNIT_NONE;
  dev->opt[OPT_WHITE_LEVEL_R].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_WHITE_LEVEL_R].constraint.range = &red_level_range;
  dev->val[OPT_WHITE_LEVEL_R].w = 00;	/* to get middle value */

  /* white level calibration manual correction */
  dev->opt[OPT_WHITE_LEVEL].name = SANE_NAME_WHITE_LEVEL;
  dev->opt[OPT_WHITE_LEVEL].title = SANE_TITLE_WHITE_LEVEL;
  dev->opt[OPT_WHITE_LEVEL].desc = SANE_DESC_WHITE_LEVEL;
  dev->opt[OPT_WHITE_LEVEL].type = SANE_TYPE_INT;
  dev->opt[OPT_WHITE_LEVEL].unit = SANE_UNIT_NONE;
  dev->opt[OPT_WHITE_LEVEL].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_WHITE_LEVEL].constraint.range = &white_level_range;
  dev->val[OPT_WHITE_LEVEL].w = 00;	/* to get middle value */

  DBG (DBG_proc, "sunplusfps_init_options: after white level\n");

  /* Lastly, set the default scan mode. This might change some
   * values previously set here. */

  sane_control_option (dev, OPT_MODE, SANE_ACTION_SET_VALUE,
		       (SANE_String_Const *) scan_mode_list[0], NULL);
  DBG (DBG_proc, "sunplusfps_init_options: exit\n");
}

/* Read the image from the fingerprint and fill the temporary buffer with it. */
static SANE_Status
sunplusfps_fill_image (Sunplusfps_Fingerprint * dev)
{
  SANE_Status status;
  size_t size;
  size_t bulk_size_read;
  size_t sizew;
  int i = 0;
  size_t f;
  size_t size_short;
  int sum;

  assert (dev->image_begin == dev->image_end);
  assert (dev->real_bytes_left > 0);

  DBG (DBG_proc, "sunplusfps_fill_image: enter\n");

  DBG (DBG_proc, "sunplusfps_fill_image: real dev bytes left=0x%zu \n",
       dev->real_bytes_left);
  bulk_size_read = dev->real_bytes_left;

  switch (dev->hw->sunplusfpsref)
    {

    case Sunplusfps_Finger835K:

      status = SANE_STATUS_GOOD;
      /* return status; */
      return status;

      break;
    default:
      usleep (100);
      status = SANE_STATUS_GOOD;
    }

  usleep (1000);

  while (dev->real_bytes_left)
    {
      /* Try to read the maximum number of bytes. */
      DBG (DBG_proc,
	   "sunplusfps_fill_image: real dev bytes left, while loop=0x%zu \n",
	   dev->real_bytes_left);
      switch (dev->hw->sunplusfpsref)
	{
	case Sunplusfps_Finger835K:
	  if (size > 0x226c0)
	    {
	      status = SANE_STATUS_GOOD;
	    }
	  break;
	default:
	  status = SANE_STATUS_GOOD;
	}
      size = dev->real_bytes_left;
      if (size < bulk_size_read)
	{
	  size = bulk_size_read;	/* it seems size can not be smaller then read by bulk */
	}
      if (size == 0)
	{
	  /* Probably reached the end of the buffer. Check, just in case. */
	  assert (dev->image_end != 0);
	  return (SANE_STATUS_GOOD);
	}

      /* Do the transfer */

      DBG (DBG_proc,
	   "sunplusfps_fill_image: dev->real_bytes_left: 0x%zu size: 0x%zu\n",
	   dev->real_bytes_left, size);
      usleep (1000);

      /* urb xx first read bulk */
      status = sanei_usb_read_bulk (dev->fd, dev->buffer, &size);

      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_info,
	       "sunplusfps_fill_image: error size (read) = 0x%lx bytes (bpl=0x%lx)\n",
	       (long) size, (long) dev->params.bytes_per_line);
	  return status;
	}
      size_short = 0x40;
      hexdump (DBG_info2, "urb read bulk data", dev->buffer, size_short);
      DBG (DBG_info,
	   "sunplusfps_fill_image: size (read) = 0x%lx bytes (bpl=0x%lx)\n",
	   (long) size, (long) dev->params.bytes_per_line);

      memcpy (dev->image + dev->image_end, dev->buffer, size);

      dev->image_end += size;
      bulk_size_read = size;
      if (dev->real_bytes_left > size)
	dev->real_bytes_left -= size;
      else if (dev->real_bytes_left <= size)	/* last loop */
	dev->real_bytes_left = 0;
      DBG (DBG_info, "sunplusfps_fill_image: real bytes left = 0x%zu\n",
	   dev->real_bytes_left);
    }

  DBG (DBG_proc, "sunplusfps_fill_image: exit\n");
  return (SANE_STATUS_GOOD);	/* unreachable */
}

/**********************************************************************
*
* The add_text routine and font_6x11.h file are taken from the (GPLed) 
* webcam.c file, part of xawtv,   (c) 1998-2002 Gerd Knorr.
* add_text was slightly modified for the pencam2 program.
* sunplusfps_add_text was taken from the pencam2 program and changed on
* some points
*
*********************************************************************/

#define MSG_MAXLEN   45
#define CHAR_HEIGHT  11
#define CHAR_WIDTH   6
#define CHAR_START   4

static SANE_Status
sunplusfps_add_text (SANE_Byte * image, int width, int height, char *txt)
{
  SANE_Status status;
  time_t t;
  struct tm *tm;
  char line[MSG_MAXLEN + 1];
  SANE_Byte *ptr;
  int i, x, y, f, len;
  char fmtstring[25] = " %Y-%m-%d %H:%M:%S";
  char fmttxt[46];

  DBG (DBG_proc, "sunplusfps_add_text: enter\n");
  time (&t);
  tm = localtime (&t);
  if (strlen (txt) > (MSG_MAXLEN - 23))
    strncpy (fmttxt, txt, (MSG_MAXLEN - 23));
  else
    strcpy (fmttxt, txt);
  strcat (fmttxt, fmtstring);

  len = strftime (line, MSG_MAXLEN, fmttxt, tm);

  for (y = 0; y < CHAR_HEIGHT; y++)
    {
      ptr = image + width * (height - CHAR_HEIGHT - 2 + y) + 12;

      for (x = 0; x < len; x++)
	{
	  f = fontdata[line[x] * CHAR_HEIGHT + y];
	  for (i = CHAR_WIDTH - 1; i >= 0; i--)
	    {
	      if (f & (CHAR_START << i))
		{
		  ptr[0] = 255;
		  /* ptr[1] = 255;
		   * ptr[2] = 255; */
		}
	      ptr += 1;
	    }			/* for i */
	}			/* for x */
    }				/* for y */

  DBG (DBG_proc, "sunplusfps_add_text: exit vw=%d, vh=%d\n", width, height);
  status = (SANE_STATUS_GOOD);
  return status;

}				/*  end of add_text  */

/* **************************  Video Decoding  *********************  */

static SANE_Status
sunplusfps_unshuffle (Sunplusfps_Fingerprint * dev, SANE_Byte * buf,
		      size_t * size)
{
  SANE_Status status;
  int x;
  int i = 0;
  int vw = dev->x_resolution;
  int vh = dev->y_resolution;
  int bright_red;
  int count;

  DBG (DBG_proc, "sunplusfps_unshuffle: enter\n");
/* subroutine for conversion 2x 4bit to 2x 8bit data */
  count = (vw * vh) / 2;

  for (i = 0; i < count; i++)
    {
      x = i * 2;
      dev->output[x] = (dev->image[i] & 0x0f) << 2;
      x++;
      dev->output[x] = (dev->image[i] & 0xf0) >> 2;
    }

  /* memcpy (dev->output, dev->image, size); */

  /* brightness adjustment */

  count = vw * vh;

  bright_red = (dev->val[OPT_BRIGHTNESS].w) + (dev->val[OPT_WHITE_LEVEL_R].w);

  for (x = 0; x < count; x++)
    {
      if ((*(dev->output + x) + bright_red) >= 255)
	*(buf + x) = 255;

      else if ((*(dev->output + x) + bright_red) <= 0)
	*(buf + x) = 0;
      else
	*(buf + x) = (*(dev->output + x) + bright_red);
    }

  if (dev->scan_mode == SUNPLUSFPS_GRAYSCALE_TEXT)
    {
/* insert add text routine                 */
/**********************************************************************
*
* The add_text routine and font_6x11.h file are taken from the (GPLed) 
* webcam.c file, part of xawtv,   (c) 1998-2002 Gerd Knorr.
* add_text was slightly modified for this program.
*
*********************************************************************/
      strcpy (dev->picmsg_ps, "Authentec");

      status = sunplusfps_add_text (buf, vw, vh, dev->picmsg_ps);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_info, "sunplusfps_unshuffle status NOK\n");
	  return (status);
	}
    }

  /* end of add text routine                 */
  DBG (DBG_proc, "sunplusfps_unshuffle: exit vw=%d, vh=%d\n", vw, vh);
  status = (SANE_STATUS_GOOD);
  return status;
}

/* Sane entry points */

SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  FILE *fp;
  char line[PATH_MAX];
  size_t len;

  num_devices = 0;
  devlist = NULL;
  first_dev = NULL;

  DBG_INIT ();

  DBG (DBG_sane_init, "sane_init\n");

  authorize = authorize;	/* silence gcc */

  DBG (DBG_error, "This is sane-sunplusfps version %d.%d-%d\n", V_MAJOR,
       V_MINOR, BUILD);
  DBG (DBG_error, "(C) 2006-2006 by Gerard Klaver\n");

  if (version_code)
    {
      *version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, BUILD);
    }

  DBG (DBG_proc, "sane_init: authorize %s null\n", authorize ? "!=" : "==");

  sanei_usb_init ();

  fp = sanei_config_open (SUNPLUSFPS_CONFIG_FILE);
  if (!fp)
    {
      /* No default fingerprint? */
      DBG (DBG_warning, "configuration file not found (%s)\n",
	   SUNPLUSFPS_CONFIG_FILE);

      return SANE_STATUS_GOOD;
    }

  while (sanei_config_read (line, sizeof (line), fp))
    {
      SANE_Word vendor;
      SANE_Word product;

      if (line[0] == '#')	/* ignore line comments */
	continue;
      len = strlen (line);

      if (!len)
	continue;		/* ignore empty lines */
      if (sscanf (line, "usb %i %i", &vendor, &product) == 2)
	{

	  sanei_usb_attach_matching_devices (line, attach_one);
	}
      else
	{
	  /* Garbage. Ignore. */
	  DBG (DBG_warning, "bad configuration line: \"%s\" - ignoring.\n",
	       line);
	}
    }

  fclose (fp);

  DBG (DBG_proc, "sane_init: leave\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  Sunplusfps_Fingerprint *dev;
  int i;

  DBG (DBG_proc, "sane_get_devices: enter\n");

  local_only = local_only;	/* silence gcc */

  if (devlist)
    free (devlist);

  devlist = malloc ((num_devices + 1) * sizeof (devlist[0]));
  if (!devlist)
    return SANE_STATUS_NO_MEM;

  i = 0;
  for (dev = first_dev; i < num_devices; dev = dev->next)
    devlist[i++] = &dev->sane;
  devlist[i++] = 0;

  *device_list = devlist;

  DBG (DBG_proc, "sane_get_devices: exit\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const devicename, SANE_Handle * handle)
{
  Sunplusfps_Fingerprint *dev;
  SANE_Status status;

  DBG (DBG_proc, "sane_open: enter\n");

  /* search for devicename */
  if (devicename[0])
    {
      DBG (DBG_info, "sane_open: devicename=%s\n", devicename);

      for (dev = first_dev; dev; dev = dev->next)
	{
	  if (strcmp (dev->sane.name, devicename) == 0)
	    {
	      break;
	    }
	}

      if (!dev)
	{
	  status = attach_fingerprint (devicename, &dev);
	  if (status != SANE_STATUS_GOOD)
	    {
	      return status;
	    }
	}
    }
  else
    {
      DBG (DBG_sane_info, "sane_open: no devicename, opening first device\n");
      dev = first_dev;		/* empty devicename -> use first device */
    }

  if (!dev)
    {
      DBG (DBG_error, "No fingerprint found\n");

      return SANE_STATUS_INVAL;
    }

  sunplusfps_init_options (dev);

  *handle = dev;

  DBG (DBG_proc, "sane_open: exit\n");

  return SANE_STATUS_GOOD;
}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  Sunplusfps_Fingerprint *dev = handle;

  DBG (DBG_proc, "sane_get_option_descriptor: enter, option %d\n", option);

  if ((unsigned) option >= OPT_NUM_OPTIONS)
    {
      return NULL;
    }

  DBG (DBG_proc, "sane_get_option_descriptor: exit\n");

  return dev->opt + option;
}

SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
		     SANE_Action action, void *val, SANE_Int * info)
{
  Sunplusfps_Fingerprint *dev = handle;
  SANE_Status status;
  SANE_Word cap;

  DBG (DBG_proc, "sane_control_option: enter, option %d, action %d\n",
       option, action);

  if (info)
    {
      *info = 0;
    }

  if (dev->scanning)
    {
      return SANE_STATUS_DEVICE_BUSY;
    }

  if (option < 0 || option >= OPT_NUM_OPTIONS)
    {
      return SANE_STATUS_INVAL;
    }

  cap = dev->opt[option].cap;
  if (!SANE_OPTION_IS_ACTIVE (cap))
    {
      return SANE_STATUS_INVAL;
    }

  if (action == SANE_ACTION_GET_VALUE)
    {

      switch (option)
	{
	  /* word options */
	case OPT_NUM_OPTS:
	case OPT_RESOLUTION:
	case OPT_BRIGHTNESS:
	case OPT_WHITE_LEVEL_R:
	case OPT_WHITE_LEVEL:
	  *(SANE_Word *) val = dev->val[option].w;
	  return SANE_STATUS_GOOD;
	case OPT_MODE:
	  strcpy (val, dev->val[option].s);
	  return SANE_STATUS_GOOD;
	default:
	  return SANE_STATUS_INVAL;
	}
    }
  else if (action == SANE_ACTION_SET_VALUE)
    {

      if (!SANE_OPTION_IS_SETTABLE (cap))
	{
	  DBG (DBG_error, "could not set option, not settable\n");
	  return SANE_STATUS_INVAL;
	}

      status = sanei_constrain_value (dev->opt + option, val, info);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "could not set option, invalid value\n");
	  return status;
	}

      switch (option)
	{

	  /* Numeric side-effect options */
	case OPT_RESOLUTION:
	case OPT_BRIGHTNESS:
	case OPT_WHITE_LEVEL_R:
	case OPT_WHITE_LEVEL:
	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_PARAMS;
	    }
	  dev->val[option].w = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

	  /* String side-effect options */
	case OPT_MODE:
	  if (strcmp (dev->val[option].s, val) == 0)
	    return SANE_STATUS_GOOD;

	  free (dev->val[OPT_MODE].s);
	  dev->val[OPT_MODE].s = (SANE_Char *) strdup (val);

	  dev->opt[OPT_WHITE_LEVEL_R].cap &= ~SANE_CAP_INACTIVE;
	  dev->opt[OPT_WHITE_LEVEL].cap &= ~SANE_CAP_INACTIVE;

	  if (strcmp (dev->val[OPT_MODE].s, GRAYSCALE_RAW_STR) == 0)
	    {
	      dev->scan_mode = SUNPLUSFPS_GRAYSCALE_RAW;
	    }
	  else if (strcmp (dev->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_GRAY)
		   == 0)
	    {
	      dev->scan_mode = SUNPLUSFPS_GRAYSCALE;

	    }
	  else if (strcmp (dev->val[OPT_MODE].s, GRAYSCALE_TEXT_STR) == 0)
	    {
	      dev->scan_mode = SUNPLUSFPS_GRAYSCALE_TEXT;

	    }

	  /* The SUNPLUSFPS supports only a handful of resolution. */
	  /* This the default resolution range for the SUNPLUSFPS */

	  dev->depth = 8;
	  if (dev->resolutions_list != NULL)
	    {
	      int i;

	      dev->opt[OPT_RESOLUTION].constraint_type =
		SANE_CONSTRAINT_WORD_LIST;
	      dev->opt[OPT_RESOLUTION].constraint.word_list =
		dev->resolutions_list;

	      /* If the resolution isn't in the list, set a default. */
	      for (i = 1; i <= dev->resolutions_list[0]; i++)
		{
		  if (dev->resolutions_list[i] >= dev->val[OPT_RESOLUTION].w)
		    break;
		}
	      if (i > dev->resolutions_list[0])
		{
		  /* Too big. Take lowest. */
		  dev->val[OPT_RESOLUTION].w = dev->resolutions_list[1];
		}
	      else
		{
		  /* Take immediate superioir value. */
		  dev->val[OPT_RESOLUTION].w = dev->resolutions_list[i];
		}
	    }

	  /* String side-effect options */

	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
	    }
	  return SANE_STATUS_GOOD;
	default:
	  return SANE_STATUS_INVAL;
	}
    }

  DBG (DBG_proc, "sane_control_option: exit, bad\n");

  return SANE_STATUS_UNSUPPORTED;
}

SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  Sunplusfps_Fingerprint *dev = handle;
  int i;

  DBG (DBG_proc, "sane_get_parameters: enter\n");

  if (!(dev->scanning))
    {
      dev->x_resolution = dev->val[OPT_RESOLUTION].w;
      /* Prepare the parameters for the caller. */
      memset (&dev->params, 0, sizeof (SANE_Parameters));

      dev->params.last_frame = SANE_TRUE;

      switch (dev->scan_mode)
	{
	case SUNPLUSFPS_GRAYSCALE_RAW:
	case SUNPLUSFPS_GRAYSCALE:
	case SUNPLUSFPS_GRAYSCALE_TEXT:
	  dev->bytes_pixel = 1;
	  break;
	}
      if (dev->resolutions_list != NULL)
	{
	  /* This fingerprint has a fixed number of supported
	   * resolutions. Find the color sequence for that
	   * resolution. */

	  for (i = 0;
	       dev->hw->color_adjust[i].resolution_x != dev->x_resolution;
	       i++);

	  dev->red_s = dev->hw->color_adjust[i].z1_color_0;
	  dev->green_s = dev->hw->color_adjust[i].z1_color_1;
	  dev->blue_s = dev->hw->color_adjust[i].z1_color_2;
	  dev->y_resolution = dev->hw->color_adjust[i].resolution_y;
	}
      dev->params.format = SANE_FRAME_GRAY;
      dev->params.pixels_per_line = dev->x_resolution;
      dev->params.lines = dev->y_resolution;
      dev->params.bytes_per_line =
	dev->params.pixels_per_line * dev->bytes_pixel;
      dev->params.depth = 8;
      DBG (DBG_info, "sane_get_parameters: x=%d, y=%d\n", dev->x_resolution,
	   dev->y_resolution);
    }

  /* Return the current values. */
  if (params)
    {
      *params = (dev->params);
    }

  DBG (DBG_proc, "sane_get_parameters: exit\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_start (SANE_Handle handle)
{
  Sunplusfps_Fingerprint *dev = handle;
  SANE_Status status;

  DBG (DBG_proc, "sane_start: enter\n");

  if (!(dev->scanning))
    {
      sane_get_parameters (dev, NULL);

      /* Open again the fingerprint  */
      if (sanei_usb_open (dev->devicename, &(dev->fd)) != 0)
	{
	  DBG (DBG_error, "ERROR: sane_start: open failed\n");
	  return SANE_STATUS_INVAL;
	}

      /* Initialize the fingerprint. */
      status = sunplusfps_fingerprint_init (dev);
      if (status)
	{
	  DBG (DBG_error, "ERROR: failed to init the fingerprint\n");
	  sunplusfps_close (dev);
	  return status;
	}

    }

  dev->image_end = 0;
  dev->image_begin = 0;
  /* real_byte_left is bulk read bytes, bytes_left is frontend buffer bytes */
  dev->real_bytes_left = (dev->params.bytes_per_line * dev->params.lines) / 2;
  dev->bytes_left = dev->params.bytes_per_line * dev->params.lines;

  dev->scanning = SANE_TRUE;

  DBG (DBG_proc, "sane_start: exit\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
	   SANE_Int * len)
{
  SANE_Status status;
  Sunplusfps_Fingerprint *dev = handle;
  size_t size;

  DBG (DBG_proc, "sane_read: enter\n");

  *len = 0;
  if (dev->deliver_eof)
    {
      dev->deliver_eof = 0;
      return SANE_STATUS_EOF;
    }

  if (!(dev->scanning))
    {
      /* OOPS, not scanning, stop a scan. */
      sunplusfps_reset_fingerprint (dev);
      sunplusfps_close (dev);
      dev->scanning = SANE_FALSE;
      return SANE_STATUS_CANCELLED;
    }

  if (dev->bytes_left <= 0)
    {
      return (SANE_STATUS_EOF);
    }

  if (dev->image_begin == dev->image_end)
    {
      /* Fill image */
      status = sunplusfps_fill_image (dev);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_info, "sane_read: sunplusfps_fill_image status NOK\n");
	  return (status);
	}
    }

  /* Something must have been read */
  if (dev->image_begin == dev->image_end)
    {
      DBG (DBG_info, "sane_read: nothing read\n");
      return SANE_STATUS_IO_ERROR;
    }

  size = dev->bytes_left;
  if (((unsigned int) max_len) < size)
    {
      DBG (DBG_error, "sane_read: max_len < size\n");
      return (SANE_FALSE);
    }
  if ((dev->image_end - dev->image_begin) > size)
    {
      size = dev->image_end - dev->image_begin;
      DBG (DBG_proc, "sane_read: size < dev->image_end - dev->image_begin\n");
    }
  /* diff between size an dev->bytes_left because of 356/352 and 292/288 */
  DBG (DBG_info, "sane_read: size =0x%zu bytes, max_len=0x%x bytes\n",
       size, max_len);

  *len = dev->bytes_left;	/* needed */
  size = dev->bytes_left;
  dev->bytes_left = 0;		/* needed for frontend or ? */

  if (dev->scan_mode != SUNPLUSFPS_GRAYSCALE_RAW)
    {
      /* do unshuffle  after complete frame is read */
      status = sunplusfps_unshuffle (dev, buf, &size);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_info, "sane_read: sunplusfps_unshuffle status NOK\n");
	  return (status);
	}
    }
  else
    {
      /* Copy the raw data to the frontend buffer. */
      memcpy (buf, dev->image, size);
      DBG (DBG_info, "sane_read: raw mode\n");
    }
  DBG (DBG_info, "sane_read: exit\n");
  status = SANE_STATUS_GOOD;
  return status;
}

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{

  DBG (DBG_proc, "sane_set_io_mode: enter\n");

  handle = handle;		/* silence gcc */
  non_blocking = non_blocking;	/* silence gcc */


  DBG (DBG_proc, "sane_set_io_mode: exit\n");

  return SANE_STATUS_UNSUPPORTED;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
  DBG (DBG_proc, "sane_get_select_fd: enter\n");

  handle = handle;		/* silence gcc */
  fd = fd;			/* silence gcc */

  DBG (DBG_proc, "sane_get_select_fd: exit\n");

  return SANE_STATUS_UNSUPPORTED;
}

void
sane_cancel (SANE_Handle handle)
{
  Sunplusfps_Fingerprint *dev = handle;

  DBG (DBG_proc, "sane_cancel: enter\n");

  /* Stop a scan. */
  if (dev->scanning == SANE_TRUE)
    {
      /* Reset the fingerprint */
      sunplusfps_reset_fingerprint (dev);
      sunplusfps_close (dev);
    }
  dev->scanning = SANE_FALSE;
  dev->deliver_eof = 0;

  /* return SANE_STATUS_CANCELLED; */
  DBG (DBG_proc, "sane_cancel: exit\n");
}

void
sane_close (SANE_Handle handle)
{
  Sunplusfps_Fingerprint *dev = handle;
  Sunplusfps_Fingerprint *dev_tmp;

  DBG (DBG_proc, "sane_close: enter\n");

/* Stop a scan. */

  if (dev->scanning == SANE_TRUE)
    {
      sunplusfps_reset_fingerprint (dev);
      sunplusfps_close (dev);
    }
  dev->scanning = SANE_FALSE;

  /* Unlink dev. */
  if (first_dev == dev)
    {
      first_dev = dev->next;
    }
  else
    {
      dev_tmp = first_dev;
      while (dev_tmp->next && dev_tmp->next != dev)
	{
	  dev_tmp = dev_tmp->next;
	}
      if (dev_tmp->next != NULL)
	{
	  dev_tmp->next = dev_tmp->next->next;
	}
    }

  sunplusfps_free (dev);
  num_devices--;

  DBG (DBG_proc, "sane_close: exit\n");
}

void
sane_exit (void)
{
  DBG (DBG_proc, "sane_exit: enter\n");

  while (first_dev)
    {
      sane_close (first_dev);
    }

  if (devlist)
    {
      free (devlist);
      devlist = NULL;
    }

  DBG (DBG_proc, "sane_exit: exit\n");
}
