/* sane - Scanner Access Now Easy.

   Copyright (C) 2007 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 init of the device, usb commands
   Yan Ke (spyder program)
   Gerard Klaver (colorvision skeleton files)

   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.

Other sane info


*/

/*  $Id: colorvision.c,v 1.2 2007/03/19 19:59:53 gerard Exp $

   colorvision colorimeter  driver Gerard Klaver
*/

/*SANE FLOW DIAGRAM

   - sane_init() : initialize backend, attach colormeters
   . - sane_get_devices() : query list of colormeter devices
   . - sane_open() : open a particular colormeter device
   . . - sane_set_io_mode : set blocking mode
   . . - sane_get_select_fd : get colormeter 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 colormeter device
   - sane_exit() : terminate use of backend
*/
/*--------------------------------------------------------------------------*/

#define BUILD 1			/* 2007/03/16  update 16-03-2007 */
#define BACKEND_NAME colorvision
#define COLORVISION_CONFIG_FILE "colorvision.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"

#include "colorvision.h"

#define TIMEOUT 1000

/*--------------------------------------------------------------------------*/
/* Lists of possible scan modes. */
static SANE_String_Const scan_mode_list[] = {
  COLOR_API_STR,
  COLOR_XCAM_API_STR,
  SANE_VALUE_SCAN_MODE_COLOR,
  NULL
};

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

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

static const struct dpi_color_adjust colorvision_dpi_color_adjust[] = {

  /*dpi, y, x, color sequence R G or B */
  {640, 480, 0, 1, 2},		/* for xcam api, for colorfield size */
  {480, 480, 0, 1, 2},          /* for xcam api, for colorfield size */
  {320, 240, 0, 1, 2},		/* for xcam api, for colorfield size */
  /* must be the last entry */
  {0, 0, 0, 0, 0}
};

static const struct colormeter_hardware colormeters[] = {

  {0x085c, 0x0200, USB_CLASS_VENDOR_SPEC, Colorvision_Spyder2express,
   "Colorvision", "Spyder2express",
   colorvision_dpi_color_adjust},

   {0x085c, 0x0200, USB_CLASS_VENDOR_SPEC, Colorvision_SPXX,
   "Colorvision", "Spyder2express",
   colorvision_dpi_color_adjust}


};

/* List of colormeters attached. */
static Colorvision_Colormeter *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 colormeter entry. Return an allocated colormeter with some
 *  */
static Colorvision_Colormeter *
colorvision_init (void)
{
  Colorvision_Colormeter *dev;

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

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

/* 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, "colorvision_init: exit\n");

  return (dev);
}

static SANE_Status
colorvision_init_2 (Colorvision_Colormeter * dev)
{
  SANE_Status status;

  DBG (DBG_proc, "colorvision_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 = 640 * 480;

  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 = 3 * 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 = 3 * 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, "colorvision_init_2: exit\n");
  status = SANE_STATUS_GOOD;
  return status;
}

/* Closes an open colormeters. */
static void
colorvision_close (Colorvision_Colormeter * dev)
{
  DBG (DBG_proc, "colorvision_close: enter \n");

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

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

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

/* Frees the memory used by a colormeter. */
static void
colorvision_free (Colorvision_Colormeter * dev)
{
  int i;

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

  if (dev == NULL)
    return;

  colorvision_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, "colorvision_free: exit\n");
}

/* Reset colormeter */
static SANE_Status
colorvision_reset_colormeter (Colorvision_Colormeter * dev)
{
  SANE_Status status;
  size_t sizew;			/* significant size of window */
  size_t sizer;

  DBG (DBG_proc, "colorvision_reset_colormeter: 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, "colorvision_reset_colormeter: exit\n");

  return status;
}

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

  DBG (DBG_info, "colorvision_identify_colormeter: 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 (colormeters); i++)
    {
      if (colormeters[i].vendor == vendor
	  && colormeters[i].product == product)
	{

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

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

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

static SANE_Status
colorvision_colormeter_init (Colorvision_Colormeter * dev)
{
  SANE_Status status;
  size_t sizer;
  size_t sizew;
  size_t size;
  int i;
  int num_entries;
  DBG (DBG_proc, "colorvision_colormeter_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->colorvisionref)
    {
    case Colorvision_Spyder2express:
    case Colorvision_SPXX:
/*lot of dummy code just as example  */

      /* urb 5 x2   read register values */
      sizer = 126;
      status = sanei_usb_read_int (dev->fd, dev->windowr, &sizer);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_proc, "colorvision_colormeter_init: urb 5 error\n");
	  return SANE_FALSE;
	}
      hexdump (DBG_info2, "urb 5 after 7 read 126 bytes?", dev->windowr,
	       sizer);

      num_entries = 11;
      for (i = 0; i < num_entries; i++)
	{
	  /* urb 10   ff 00   */
	  sizew = 0x02;
	  dev->windoww[0] = 0xff;
	  dev->windoww[1] = 0x00;
	  status = sanei_usb_read_int (dev->fd, dev->windowr, &sizer);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG (DBG_proc, "colorvision_colormeter_init: urb 10 error\n");
	      return SANE_FALSE;
	    }

	  DBG (DBG_proc, "colorvision_colormeter_init: loop=%durb 10\n", i);
	  usleep (200);
	}

  break;

default:
  /*urb 36 control message?  00 03 01 00 00 00 00 00 */
  sizew = 0x00;			/* was 0 ? */
  status =
    sanei_usb_control_msg (dev->fd, 0x00, 0x03, 0x0100, 0, sizew,
			   dev->windoww);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_proc, "colorvision_colormeter_init: urb 36 error\n");
      /*return status; */
    }
  DBG (DBG_proc, "colorvision_colormeter_init: urb36 end\n");

  status = SANE_STATUS_GOOD;

  if (status)
    {
      DBG (DBG_error, "colorvision_colormeter_init failed : %s\n",
	   sane_strstatus (status));
      return status;
    }
  DBG (DBG_proc, "colorvision_colormeter_init: exit\n");

  }
  return status;
}

/* Attach a colormeter to this backend. */
static SANE_Status
attach_colormeter (SANE_String_Const devicename,
		    Colorvision_Colormeter ** devp)
{
  Colorvision_Colormeter *dev;
  int fd;
  SANE_Status status;

  DBG (DBG_proc, "attach_colormeter: %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 colormeter entry. */
  dev = colorvision_init ();
  if (dev == NULL)
    {
      DBG (DBG_error, "colorvision_init ERROR: not enough memory\n");
      return SANE_STATUS_NO_MEM;
    }

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

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

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

  if (colorvision_identify_colormeter (dev) == SANE_FALSE)
    {
      DBG (DBG_error,
	   "ERROR: attach_colormeter: colormeter-identification failed\n");
      colorvision_free (dev);
      return SANE_STATUS_INVAL;
    }

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

  colorvision_close (dev);

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

  /* Build list of colormeter supported resolutions. */
  DBG (DBG_proc, "attach_colormeter: 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_colormeter: colormeter resolution list failed\n");
	  colorvision_free (dev);
	  return SANE_STATUS_NO_MEM;
	}
      dev->resolutions_list[0] = num_entries;
      DBG (DBG_proc, "attach_colormeter: 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 colormeter. */
  dev->sane.name = dev->devicename;
  dev->sane.vendor = dev->hw->vendor_name;
  dev->sane.model = dev->hw->product_name;
  dev->sane.type = SANE_I18N ("colorimeter");

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

  if (devp)
    {
      *devp = dev;
    }

  num_devices++;

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

  return SANE_STATUS_GOOD;
}

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

/* Reset the options for that colormeter. */
static void
colorvision_init_options (Colorvision_Colormeter * dev)
{
  int i;

  DBG (DBG_proc, "colorvision_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,
       "colorvision_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;

  /* Colormeter 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;

  DBG (DBG_proc, "colorvision_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, "colorvision_init_options: exit\n");
}

/* Read the image from the colormeter and fill the temporary buffer with it. */
static SANE_Status
colorvision_fill_image (Colorvision_Colormeter * dev)
{
  SANE_Status status;
  size_t size;
  size_t int_size_read;
  size_t sizer;
  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, "colorvision_fill_image: enter\n");

  DBG (DBG_proc, "colorvision_fill_image: real dev bytes left=0x%lx \n",
       (unsigned long) (size_t) dev->real_bytes_left);
  int_size_read = dev->real_bytes_left;

  switch (dev->hw->colorvisionref)
    {
    case Colorvision_SPXX:
    case Colorvision_Spyder2express:
	       /*-------------start scan part --------------------*/

      DBG (DBG_proc, "colorvision_colormeter_init: start scan\n");
      status = sanei_usb_read_int (dev->fd, dev->windowr, &sizer);

      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_proc, "colorvision_colormeter_init: urb 1x error\n");
	  return SANE_FALSE;
	}
      DBG (DBG_proc, "colorvision_colormeter_init: urb 1x\n");
      usleep (200);

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

  usleep (10000);

  while (dev->real_bytes_left)
    {
      /* Try to read the maximum number of bytes. */
      DBG (DBG_proc,
	   "colorvision_fill_image: real dev bytes left, while loop=0x%lx \n",
	   (unsigned long) (size_t)dev->real_bytes_left);

      size = dev->real_bytes_left;
      if (size < int_size_read)
	{
	  size = int_size_read;	/* it seems size can not be smaller then read by int */
	}
      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,
	   "colorvision_fill_image: dev->real_bytes_left: 0x%lx size: 0x%lx\n",
	   (unsigned long) (size_t) dev->real_bytes_left, (unsigned long) (size_t) size);
      usleep (100000);

      /* urb xx first read interrupt  urb 1812 */
      status = sanei_usb_read_int (dev->fd, dev->buffer, &size);

      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_info,
	       "colorvision_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 int data", dev->buffer, size_short);
      DBG (DBG_info,
	   "colorvision_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;
      int_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, "colorvision_fill_image: real bytes left = 0x%lx\n",
	   (unsigned long) (size_t) dev->real_bytes_left);
    }

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

/* 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-colorvision version %d.%d-%d\n", V_MAJOR,
       V_MINOR, BUILD);
  DBG (DBG_error, "(C) 2007-2007 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 (COLORVISION_CONFIG_FILE);
  if (!fp)
    {
      /* No default colormeter? */
      DBG (DBG_warning, "configuration file not found (%s)\n",
	   COLORVISION_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)
{
  Colorvision_Colormeter *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)
{
  Colorvision_Colormeter *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_colormeter (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 colormeter found\n");

      return SANE_STATUS_INVAL;
    }

  colorvision_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)
{
  Colorvision_Colormeter *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)
{
  Colorvision_Colormeter *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:
	  *(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:
	  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);

	  if (strcmp (dev->val[OPT_MODE].s, COLOR_API_STR) == 0)
	    {
	      dev->scan_mode = COLORVISION_API;
	    }
	  else if (strcmp (dev->val[OPT_MODE].s, COLOR_XCAM_API_STR) == 0)
	    {
	      dev->scan_mode = COLORVISION_XCAM_API;

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

	    }

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

	  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)
{
  Colorvision_Colormeter *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 COLORVISION_API:
	case COLORVISION_XCAM_API:
	case COLORVISION_COLOR:
	  dev->bytes_pixel = 3;
	  break;
	}
      if (dev->resolutions_list != NULL)
	{
	  /* This colormeter 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_RGB;
      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)
{
  Colorvision_Colormeter *dev = handle;
  SANE_Status status;

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

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

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

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

    }

  dev->image_end = 0;
  dev->image_begin = 0;
  /* real_byte_left is int 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;
  Colorvision_Colormeter *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. */
      colorvision_reset_colormeter (dev);
      colorvision_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 = colorvision_fill_image (dev);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_info, "sane_read: colorvision_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");
    }
  DBG (DBG_info, "sane_read: size =0x%lx bytes, max_len=0x%lx bytes\n",
       (unsigned long) (size_t) size, (unsigned long) (size_t) max_len);

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

  if (dev->scan_mode != COLORVISION_XCAM_API)
    {
      /* Copy the image color data to buf from frontend*/
      memcpy (buf, dev->image, size);
      DBG (DBG_info, "sane_read: XCAM_API\n");
    }
  else
    {
      DBG (DBG_info, "sane_read: raw mode, no data to image field\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)
{
  Colorvision_Colormeter *dev = handle;

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

  /* Stop a scan. */
  if (dev->scanning == SANE_TRUE)
    {
      /* Reset the colormeter */
      colorvision_reset_colormeter (dev);
      colorvision_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)
{
  Colorvision_Colormeter *dev = handle;
  Colorvision_Colormeter *dev_tmp;

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

/* Stop a scan. */

  if (dev->scanning == SANE_TRUE)
    {
      colorvision_reset_colormeter (dev);
      colorvision_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;
	}
    }

  colorvision_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");
}
