/* sane - Scanner Access Now Easy.

   Copyright (C) 2007-2008 Philippe Rétornaz

   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, see <https://www.gnu.org/licenses/>.

   As a special exception, the authors of SANE give permission for
   additional uses of the libraries contained in this release of SANE.

   The exception is that, if you link a SANE library with other files
   to produce an executable, this does not by itself cause the
   resulting executable to be covered by the GNU General Public
   License.  Your use of that executable is in no way restricted on
   account of linking the SANE library code into it.

   This exception does not, however, invalidate any other reasons why
   the executable file might be covered by the GNU General Public
   License.

   If you submit changes to SANE to the maintainers to be included in
   a subsequent release, you agree by submitting the changes that
   those changes may be distributed with this exception intact.

   If you write modifications of your own for SANE, it is your choice
   whether to permit this exception to apply to your modifications.
   If you do not wish that, delete this exception notice.

   This backend is for HP LaserJet M1005 MFP

   Highly inspired from the epson backend
*/

#define BUILD 1

#include  "../include/sane/config.h"
#include  <math.h>

#include  <limits.h>
#include  <stdio.h>
#include  <string.h>
#include  <stdlib.h>
#include  <ctype.h>
#include  <fcntl.h>
#include  <unistd.h>
#include  <errno.h>
#include  <stdint.h>
#include <netinet/in.h>
#define  BACKEND_NAME hpljm1005
#include  "../include/sane/sanei_backend.h"
#include  "../include/sane/sanei_usb.h"
#include  "../include/sane/saneopts.h"

#define MAGIC_NUMBER 0x41535001
#define PKT_READ_STATUS 0x0
#define PKT_UNKNOW_1 0x1
#define PKT_START_SCAN 0x2
#define PKT_GO_IDLE 0x3
#define PKT_DATA 0x5
#define PKT_READCONF 0x6
#define PKT_SETCONF 0x7
#define PKT_END_DATA 0xe
#define PKT_RESET 0x15

#define RED_LAYER 0x3
#define GREEN_LAYER 0x4
#define BLUE_LAYER 0x5
#define GRAY_LAYER 0x6

#define MIN_SCAN_ZONE 101

struct usbdev_s
{
  SANE_Int vendor_id;
  SANE_Int product_id;
  SANE_String_Const vendor_s;
  SANE_String_Const model_s;
  SANE_String_Const type_s;
};

/* Zero-terminated USB VID/PID array */
static struct usbdev_s usbid[] = {
  {0x03f0, 0x3b17, "Hewlett-Packard", "LaserJet M1005",
   "multi-function peripheral"},
  {0x03f0, 0x5617, "Hewlett-Packard", "LaserJet M1120",
   "multi-function peripheral"},
  {0x03f0, 0x5717, "Hewlett-Packard", "LaserJet M1120n",
   "multi-function peripheral"},
  {0, 0, NULL, NULL, NULL},
  {0, 0, NULL, NULL, NULL}
};

static int cur_idx;

#define BR_CONT_MIN 0x1
#define BR_CONT_MAX 0xb

#define RGB 1
#define GRAY 0

#define MAX_X_H 0x350
#define MAX_Y_H 0x490
#define MAX_X_S 220
#define MAX_Y_S 330

#define OPTION_MAX 9

static SANE_Word resolution_list[] = {
  7, 75, 100, 150, 200, 300, 600, 1200
};
static SANE_Range range_x = { 0, MAX_X_S, 0 };
static SANE_Range range_y = { 0, MAX_Y_S, 0 };

static SANE_Range range_br_cont = { BR_CONT_MIN, BR_CONT_MAX, 0 };

static const SANE_String_Const mode_list[] = {
  SANE_VALUE_SCAN_MODE_GRAY,
  SANE_VALUE_SCAN_MODE_COLOR,
  NULL
};

#define X1_OFFSET 2
#define X2_OFFSET 4
#define Y1_OFFSET 3
#define Y2_OFFSET 5
#define RES_OFFSET 1
#define COLOR_OFFSET 8
#define BRIGH_OFFSET 6
#define CONTR_OFFSET 7

#define STATUS_IDLE 0
#define STATUS_SCANNING 1
#define STATUS_CANCELING 2

struct device_s
{
  struct device_s *next;
  SANE_String_Const devname;
  int idx;			/* Index in the usbid array */
  int dn;			/* Usb "Handle" */
  SANE_Option_Descriptor optiond[OPTION_MAX];
  char *buffer;
  int bufs;
  int read_offset;
  int write_offset_r;
  int write_offset_g;
  int write_offset_b;
  int status;
  int width;
  int height;
  SANE_Word optionw[OPTION_MAX];
  uint32_t conf_data[512];
  uint32_t packet_data[512];
};


static void
do_cancel(struct device_s *dev);


static struct device_s *devlist_head;
static int devlist_count;	/* Number of element in the list */

/*
 * List of pointers to devices - will be dynamically allocated depending
 * on the number of devices found.
 */
static SANE_Device **devlist = NULL;

/* round() is c99, so we provide our own, though this version won't return -0 */
static double
round2(double x)
{
    return (double)(x >= 0.0) ? (int)(x+0.5) : (int)(x-0.5);
}

static void
update_img_size (struct device_s *dev)
{
  int dx, dy;

  /* Only update the width when not scanning,
   * otherwise the scanner give us the correct width */
  if (dev->status == STATUS_SCANNING)
    {
      dev->height = -1;
      return;
    }

  dx = dev->optionw[X2_OFFSET] - dev->optionw[X1_OFFSET];
  dy = dev->optionw[Y2_OFFSET] - dev->optionw[Y1_OFFSET];

  switch (dev->optionw[RES_OFFSET])
    {
    case 75:
      dev->width = round2 ((dx / ((double) MAX_X_S)) * 640);
      dev->height = round2 ((dy / ((double) MAX_Y_S)) * 880);
      break;
    case 100:
      dev->width = round2 ((dx / ((double) MAX_X_S)) * 848);
      dev->height = round2 ((dy / ((double) MAX_Y_S)) * 1180);
      break;
    case 150:
      dev->width = round2 ((dx / ((double) MAX_X_S)) * 1264);
      dev->height = round2 ((dy / ((double) MAX_Y_S)) * 1775);
      break;
    case 200:
      dev->width = round2 ((dx / ((double) MAX_X_S)) * 1696);
      dev->height = round2 ((dy / ((double) MAX_Y_S)) * 2351);
      break;
    case 300:
      dev->width = round2 ((dx / ((double) MAX_X_S)) * 2528);
      dev->height = round2 ((dy / ((double) MAX_Y_S)) * 3510);
      break;
    case 600:
      dev->width = round2 ((dx / ((double) MAX_X_S)) * 5088);
      dev->height = round2 ((dy / ((double) MAX_Y_S)) * 7020);
      break;
    case 1200:
      dev->width = round2 ((dx / ((double) MAX_X_S)) * 10208);
      dev->height = round2 ((dy / ((double) MAX_Y_S)) * 14025);
      break;
    }

    DBG(2,"New image size: %dx%d\n",dev->width, dev->height);

}

/* This function is copy/pasted from the Epson backend */
static size_t
max_string_size (const 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;
}


static SANE_Status
attach (SANE_String_Const devname)
{
  struct device_s *dev;

  dev = malloc (sizeof (struct device_s));
  if (!dev)
    return SANE_STATUS_NO_MEM;
  memset (dev, 0, sizeof (struct device_s));

  dev->devname = devname;
  DBG(1,"New device found: %s\n",dev->devname);

/* Init the whole structure with default values */
  /* Number of options */
  dev->optiond[0].name = "";
  dev->optiond[0].title = NULL;
  dev->optiond[0].desc = NULL;
  dev->optiond[0].type = SANE_TYPE_INT;
  dev->optiond[0].unit = SANE_UNIT_NONE;
  dev->optiond[0].size = sizeof (SANE_Word);
  dev->optionw[0] = OPTION_MAX;

  /* resolution */
  dev->optiond[RES_OFFSET].name = "resolution";
  dev->optiond[RES_OFFSET].title = "resolution";
  dev->optiond[RES_OFFSET].desc = "resolution";
  dev->optiond[RES_OFFSET].type = SANE_TYPE_INT;
  dev->optiond[RES_OFFSET].unit = SANE_UNIT_DPI;
  dev->optiond[RES_OFFSET].type = SANE_TYPE_INT;
  dev->optiond[RES_OFFSET].size = sizeof (SANE_Word);
  dev->optiond[RES_OFFSET].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  dev->optiond[RES_OFFSET].constraint_type = SANE_CONSTRAINT_WORD_LIST;
  dev->optiond[RES_OFFSET].constraint.word_list = resolution_list;
  dev->optionw[RES_OFFSET] = 75;

  /* scan area */
  dev->optiond[X1_OFFSET].name = "tl-x";
  dev->optiond[X1_OFFSET].title = "tl-x";
  dev->optiond[X1_OFFSET].desc = "tl-x";
  dev->optiond[X1_OFFSET].type = SANE_TYPE_INT;
  dev->optiond[X1_OFFSET].unit = SANE_UNIT_MM;
  dev->optiond[X1_OFFSET].size = sizeof (SANE_Word);
  dev->optiond[X1_OFFSET].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  dev->optiond[X1_OFFSET].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->optiond[X1_OFFSET].constraint.range = &range_x;
  dev->optionw[X1_OFFSET] = 0;

  dev->optiond[Y1_OFFSET].name = "tl-y";
  dev->optiond[Y1_OFFSET].title = "tl-y";
  dev->optiond[Y1_OFFSET].desc = "tl-y";
  dev->optiond[Y1_OFFSET].type = SANE_TYPE_INT;
  dev->optiond[Y1_OFFSET].unit = SANE_UNIT_MM;
  dev->optiond[Y1_OFFSET].size = sizeof (SANE_Word);
  dev->optiond[Y1_OFFSET].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  dev->optiond[Y1_OFFSET].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->optiond[Y1_OFFSET].constraint.range = &range_y;
  dev->optionw[Y1_OFFSET] = 0;

  dev->optiond[X2_OFFSET].name = "br-x";
  dev->optiond[X2_OFFSET].title = "br-x";
  dev->optiond[X2_OFFSET].desc = "br-x";
  dev->optiond[X2_OFFSET].type = SANE_TYPE_INT;
  dev->optiond[X2_OFFSET].unit = SANE_UNIT_MM;
  dev->optiond[X2_OFFSET].size = sizeof (SANE_Word);
  dev->optiond[X2_OFFSET].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  dev->optiond[X2_OFFSET].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->optiond[X2_OFFSET].constraint.range = &range_x;
  dev->optionw[X2_OFFSET] = MAX_X_S;

  dev->optiond[Y2_OFFSET].name = "br-y";
  dev->optiond[Y2_OFFSET].title = "br-y";
  dev->optiond[Y2_OFFSET].desc = "br-y";
  dev->optiond[Y2_OFFSET].type = SANE_TYPE_INT;
  dev->optiond[Y2_OFFSET].unit = SANE_UNIT_MM;
  dev->optiond[Y2_OFFSET].size = sizeof (SANE_Word);
  dev->optiond[Y2_OFFSET].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  dev->optiond[Y2_OFFSET].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->optiond[Y2_OFFSET].constraint.range = &range_y;
  dev->optionw[Y2_OFFSET] = MAX_Y_S;

  /* brightness */
  dev->optiond[BRIGH_OFFSET].name = "brightness";
  dev->optiond[BRIGH_OFFSET].title = "Brightness";
  dev->optiond[BRIGH_OFFSET].desc = "Set the brightness";
  dev->optiond[BRIGH_OFFSET].type = SANE_TYPE_INT;
  dev->optiond[BRIGH_OFFSET].unit = SANE_UNIT_NONE;
  dev->optiond[BRIGH_OFFSET].size = sizeof (SANE_Word);
  dev->optiond[BRIGH_OFFSET].cap =
    SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  dev->optiond[BRIGH_OFFSET].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->optiond[BRIGH_OFFSET].constraint.range = &range_br_cont;
  dev->optionw[BRIGH_OFFSET] = 0x6;

  /* contrast */
  dev->optiond[CONTR_OFFSET].name = "contrast";
  dev->optiond[CONTR_OFFSET].title = "Contrast";
  dev->optiond[CONTR_OFFSET].desc = "Set the contrast";
  dev->optiond[CONTR_OFFSET].type = SANE_TYPE_INT;
  dev->optiond[CONTR_OFFSET].unit = SANE_UNIT_NONE;
  dev->optiond[CONTR_OFFSET].size = sizeof (SANE_Word);
  dev->optiond[CONTR_OFFSET].cap =
    SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  dev->optiond[CONTR_OFFSET].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->optiond[CONTR_OFFSET].constraint.range = &range_br_cont;
  dev->optionw[CONTR_OFFSET] = 0x6;

  /* Color */
  dev->optiond[COLOR_OFFSET].name = SANE_NAME_SCAN_MODE;
  dev->optiond[COLOR_OFFSET].title = SANE_TITLE_SCAN_MODE;
  dev->optiond[COLOR_OFFSET].desc = SANE_DESC_SCAN_MODE;
  dev->optiond[COLOR_OFFSET].type = SANE_TYPE_STRING;
  dev->optiond[COLOR_OFFSET].size = max_string_size (mode_list);
  dev->optiond[COLOR_OFFSET].cap =
    SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  dev->optiond[COLOR_OFFSET].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  dev->optiond[COLOR_OFFSET].constraint.string_list = mode_list;
  dev->optionw[COLOR_OFFSET] = RGB;
  dev->dn = 0;
  dev->idx = cur_idx;
  dev->status = STATUS_IDLE;

  dev->next = devlist_head;
  devlist_head = dev;
  devlist_count++;



  return SANE_STATUS_GOOD;
}

SANE_Status
sane_init (SANE_Int * version_code,
	   SANE_Auth_Callback __sane_unused__ authorize)
{

  if (version_code != NULL)
    *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, SANE_CURRENT_MINOR, BUILD);

  DBG_INIT();

  sanei_usb_init ();

  return SANE_STATUS_GOOD;
}

void
sane_exit (void)
{
  /* free everything */
  struct device_s *iter;

  if (devlist)
    {
      int i;
      for (i = 0; devlist[i]; i++)
	free (devlist[i]);
      free (devlist);
      devlist = NULL;
    }
  if (devlist_head)
    {
      iter = devlist_head->next;
      free (devlist_head);
      devlist_head = NULL;
      while (iter)
	{
	  struct device_s *tmp = iter;
	  iter = iter->next;
	  free (tmp);
	}
    }
  devlist_count = 0;
}

SANE_Status
sane_get_devices (const SANE_Device * **device_list,
		  SANE_Bool __sane_unused__ local_only)
{
  struct device_s *iter;
  int i;

  devlist_count = 0;

  if (devlist_head)
    {
      iter = devlist_head->next;
      free (devlist_head);
      devlist_head = NULL;
      while (iter)
	{
	  struct device_s *tmp = iter;
	  iter = iter->next;
	  free (tmp);
	}
    }

  /* Rebuild our internal scanner list */
  for (cur_idx = 0; usbid[cur_idx].vendor_id; cur_idx++)
    sanei_usb_find_devices (usbid[cur_idx].vendor_id,
			    usbid[cur_idx].product_id, attach);

  if (devlist)
    {
      for (i = 0; devlist[i]; i++)
	free (devlist[i]);
      free (devlist);
    }

  /* rebuild the sane-API scanner list array */
  devlist = malloc (sizeof (devlist[0]) * (devlist_count + 1));
  if (!devlist)
    return SANE_STATUS_NO_MEM;

  memset (devlist, 0, sizeof (devlist[0]) * (devlist_count + 1));

  for (i = 0, iter = devlist_head; i < devlist_count; i++, iter = iter->next)
    {
      devlist[i] = malloc (sizeof (SANE_Device));
      if (!devlist[i])
	{
	  int j;
	  for (j = 0; j < i; j++)
	    free (devlist[j]);
	  free (devlist);
	  devlist = NULL;
	  return SANE_STATUS_NO_MEM;
	}
      devlist[i]->name = iter->devname;
      devlist[i]->vendor = usbid[iter->idx].vendor_s;
      devlist[i]->model = usbid[iter->idx].model_s;
      devlist[i]->type = usbid[iter->idx].type_s;
    }
  if (device_list)
    *device_list = (const SANE_Device **) devlist;
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const name, SANE_Handle * h)
{
  struct device_s *dev;
  int ret;

  if(!devlist_head)
    sane_get_devices(NULL,(SANE_Bool)0);

  dev = devlist_head;

  if (strlen (name))
    for (; dev; dev = dev->next)
      if (!strcmp (name, dev->devname))
	break;

  if (!dev) {
    DBG(1,"Unable to find device %s\n",name);
    return SANE_STATUS_INVAL;
  }

  DBG(1,"Found device %s\n",name);

  /* Now open the usb device */
  ret = sanei_usb_open (name, &(dev->dn));
  if (ret != SANE_STATUS_GOOD) {
    DBG(1,"Unable to open device %s\n",name);
    return ret;
  }

  /* Claim the first interface */
  ret = sanei_usb_claim_interface (dev->dn, 0);
  if (ret != SANE_STATUS_GOOD)
    {
      sanei_usb_close (dev->dn);
      /* if we cannot claim the interface, this is because
         someone else is using it */
      DBG(1,"Unable to claim scanner interface on device %s\n",name);
      return SANE_STATUS_DEVICE_BUSY;
    }
#ifdef HAVE_SANEI_USB_SET_TIMEOUT
  sanei_usb_set_timeout (30000);	/* 30s timeout */
#endif

  *h = dev;

  return SANE_STATUS_GOOD;
}

void
sane_close (SANE_Handle h)
{
  struct device_s *dev = (struct device_s *) h;

  /* Just in case if sane_cancel() is called
   * after starting a scan but not while a sane_read
   */
  if (dev->status == STATUS_CANCELING)
    {
       do_cancel(dev);
    }

  sanei_usb_release_interface (dev->dn, 0);
  sanei_usb_close (dev->dn);

}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle h, SANE_Int option)
{
  struct device_s *dev = (struct device_s *) h;

  if (option >= OPTION_MAX || option < 0)
    return NULL;
  return &(dev->optiond[option]);
}

static SANE_Status
getvalue (SANE_Handle h, SANE_Int option, void *v)
{
  struct device_s *dev = (struct device_s *) h;

  if (option != COLOR_OFFSET)
    *((SANE_Word *) v) = dev->optionw[option];
  else
    {
      strcpy ((char *) v,
	      dev->optiond[option].constraint.string_list[dev->
							  optionw[option]]);
    }
  return SANE_STATUS_GOOD;
}

static SANE_Status
setvalue (SANE_Handle h, SANE_Int option, void *value, SANE_Int * info)
{
  struct device_s *dev = (struct device_s *) h;
  SANE_Status status = SANE_STATUS_GOOD;
  int s_unit;
  int s_unit_2;

  if (option == 0)
    return SANE_STATUS_UNSUPPORTED;


  status = sanei_constrain_value (&(dev->optiond[option]), value, info);

  if (status != SANE_STATUS_GOOD)
    return status;



  if (info)
    *info |= SANE_INFO_RELOAD_PARAMS;
  switch (option)
    {
    case X1_OFFSET:
      dev->optionw[option] = *((SANE_Word *) value);
      s_unit = (int) round2 ((dev->optionw[option] / ((double) MAX_X_S))
			    * MAX_X_H);
      s_unit_2 = (int) round2 ((dev->optionw[X2_OFFSET] / ((double) MAX_X_S))
			      * MAX_X_H);
      if (abs (s_unit_2 - s_unit) < MIN_SCAN_ZONE)
	s_unit = s_unit_2 - MIN_SCAN_ZONE;
      dev->optionw[option] = round2 ((s_unit / ((double) MAX_X_H)) * MAX_X_S);
      if (info)
	*info |= SANE_INFO_INEXACT;
      break;

    case X2_OFFSET:
      /* X units */
      /* convert into "scanner" unit, then back into mm */
      dev->optionw[option] = *((SANE_Word *) value);

      s_unit = (int) round2 ((dev->optionw[option] / ((double) MAX_X_S))
			    * MAX_X_H);
      s_unit_2 = (int) round2 ((dev->optionw[X1_OFFSET] / ((double) MAX_X_S))
			      * MAX_X_H);
      if (abs (s_unit_2 - s_unit) < MIN_SCAN_ZONE)
	s_unit = s_unit_2 + MIN_SCAN_ZONE;
      dev->optionw[option] = round2 ((s_unit / ((double) MAX_X_H)) * MAX_X_S);
      if (info)
	*info |= SANE_INFO_INEXACT;
      break;
    case Y1_OFFSET:
      /* Y units */
      dev->optionw[option] = *((SANE_Word *) value);

      s_unit = (int) round2 ((dev->optionw[option] / ((double) MAX_Y_S))
			    * MAX_Y_H);

      s_unit_2 = (int) round2 ((dev->optionw[Y2_OFFSET] / ((double) MAX_Y_S))
			      * MAX_Y_H);
      if (abs (s_unit_2 - s_unit) < MIN_SCAN_ZONE)
	s_unit = s_unit_2 - MIN_SCAN_ZONE;

      dev->optionw[option] = round2 ((s_unit / ((double) MAX_Y_H)) * MAX_Y_S);
      if (info)
	*info |= SANE_INFO_INEXACT;
      break;
    case Y2_OFFSET:
      /* Y units */
      dev->optionw[option] = *((SANE_Word *) value);

      s_unit = (int) round2 ((dev->optionw[option] / ((double) MAX_Y_S))
			    * MAX_Y_H);

      s_unit_2 = (int) round2 ((dev->optionw[Y1_OFFSET] / ((double) MAX_Y_S))
			      * MAX_Y_H);
      if (abs (s_unit_2 - s_unit) < MIN_SCAN_ZONE)
	s_unit = s_unit_2 + MIN_SCAN_ZONE;

      dev->optionw[option] = round2 ((s_unit / ((double) MAX_Y_H)) * MAX_Y_S);
      if (info)
	*info |= SANE_INFO_INEXACT;
      break;
    case COLOR_OFFSET:
      if (!strcmp ((char *) value, mode_list[0]))
	dev->optionw[option] = GRAY;	/* Gray */
      else if (!strcmp ((char *) value, mode_list[1]))
	dev->optionw[option] = RGB;	/* RGB */
      else
	return SANE_STATUS_INVAL;
      break;
    default:
      dev->optionw[option] = *((SANE_Word *) value);
    }
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_control_option (SANE_Handle h, SANE_Int option,
		     SANE_Action a, void *v, SANE_Int * i)
{

  if (option < 0 || option >= OPTION_MAX)
    return SANE_STATUS_INVAL;

  if (i)
    *i = 0;


  switch (a)
    {
    case SANE_ACTION_GET_VALUE:
      return getvalue (h, option, v);

    case SANE_ACTION_SET_VALUE:
      return setvalue (h, option, v, i);

    default:
      return SANE_STATUS_INVAL;
    }
}

SANE_Status
sane_get_parameters (SANE_Handle h, SANE_Parameters * p)
{
  struct device_s *dev = (struct device_s *) h;

  if (!p)
    return SANE_STATUS_INVAL;

  p->format =
    dev->optionw[COLOR_OFFSET] == RGB ? SANE_FRAME_RGB : SANE_FRAME_GRAY;
  p->last_frame = SANE_TRUE;
  p->depth = 8;

  update_img_size (dev);
  p->pixels_per_line = dev->width;
  p->lines = dev->height;
  p->bytes_per_line = p->pixels_per_line;
  if (p->format == SANE_FRAME_RGB)
    p->bytes_per_line *= 3;

  return SANE_STATUS_GOOD;
}

static void
send_pkt (int command, int data_size, struct device_s *dev)
{
  size_t size = 32;

  DBG(100,"Sending packet %d, next data size %d, device %s\n", command, data_size, dev->devname);

  memset (dev->packet_data, 0, size);
  dev->packet_data[0] = htonl (MAGIC_NUMBER);
  dev->packet_data[1] = htonl (command);
  dev->packet_data[5] = htonl (data_size);
  sanei_usb_write_bulk (dev->dn, (unsigned char *) dev->packet_data, &size);
}


/* s: printer status */
/* Return the next packet size */
static int
wait_ack (struct device_s *dev, int *s)
{
  SANE_Status ret;
  size_t size;
  DBG(100, "Waiting scanner answer on device %s\n",dev->devname);
  do
    {
      size = 32;
      ret =
	sanei_usb_read_bulk (dev->dn, (unsigned char *) dev->packet_data,
			     &size);
    }
  while (SANE_STATUS_EOF == ret || size == 0);
  if (s)
    *s = ntohl (dev->packet_data[4]);
  return ntohl (dev->packet_data[5]);
}

static void
send_conf (struct device_s *dev)
{
  int y1, y2, x1, x2;
  size_t size = 100;
  DBG(100,"Sending configuration packet on device %s\n",dev->devname);
  y1 = (int) round2 ((dev->optionw[Y1_OFFSET] / ((double) MAX_Y_S)) * MAX_Y_H);
  y2 = (int) round2 ((dev->optionw[Y2_OFFSET] / ((double) MAX_Y_S)) * MAX_Y_H);
  x1 = (int) round2 ((dev->optionw[X1_OFFSET] / ((double) MAX_X_S)) * MAX_X_H);
  x2 = (int) round2 ((dev->optionw[X2_OFFSET] / ((double) MAX_X_S)) * MAX_X_H);

  DBG(100,"\t x1: %d, x2: %d, y1: %d, y2: %d\n",x1, x2, y1, y2);
  DBG(100,"\t brightness: %d, contrast: %d\n", dev->optionw[BRIGH_OFFSET], dev->optionw[CONTR_OFFSET]);
  DBG(100,"\t resolution: %d\n",dev->optionw[RES_OFFSET]);

  dev->conf_data[0] = htonl (0x15);
  dev->conf_data[1] = htonl (dev->optionw[BRIGH_OFFSET]);
  dev->conf_data[2] = htonl (dev->optionw[CONTR_OFFSET]);
  dev->conf_data[3] = htonl (dev->optionw[RES_OFFSET]);
  dev->conf_data[4] = htonl (0x1);
  dev->conf_data[5] = htonl (0x1);
  dev->conf_data[6] = htonl (0x1);
  dev->conf_data[7] = htonl (0x1);
  dev->conf_data[8] = 0;
  dev->conf_data[9] = 0;
  dev->conf_data[10] = htonl (0x8);
  dev->conf_data[11] = 0;
  dev->conf_data[12] = 0;
  dev->conf_data[13] = 0;
  dev->conf_data[14] = 0;
  dev->conf_data[16] = htonl (y1);
  dev->conf_data[17] = htonl (x1);
  dev->conf_data[18] = htonl (y2);
  dev->conf_data[19] = htonl (x2);
  dev->conf_data[20] = 0;
  dev->conf_data[21] = 0;
  dev->conf_data[22] = htonl (0x491);
  dev->conf_data[23] = htonl (0x352);

  if (dev->optionw[COLOR_OFFSET] == RGB)
    {
      dev->conf_data[15] = htonl (0x2);
      dev->conf_data[24] = htonl (0x1);
      DBG(100,"\t Scanning in RGB format\n");
    }
  else
    {
      dev->conf_data[15] = htonl (0x6);
      dev->conf_data[24] = htonl (0x0);
      DBG(100,"\t Scanning in Grayscale format\n");
    }
  sanei_usb_write_bulk (dev->dn, (unsigned char *) dev->conf_data, &size);
}

static SANE_Status
get_data (struct device_s *dev)
{
  int color;
  size_t size;
  int packet_size;
  unsigned char *buffer = (unsigned char *) dev->packet_data;
  if (dev->status == STATUS_IDLE)
    return SANE_STATUS_IO_ERROR;
  /* first wait a standard data pkt */
  do
    {
      size = 32;
      sanei_usb_read_bulk (dev->dn, buffer, &size);
      if (size)
	{
	  if (ntohl (dev->packet_data[0]) == MAGIC_NUMBER)
	    {
	      if (ntohl (dev->packet_data[1]) == PKT_DATA)
		break;
	      if (ntohl (dev->packet_data[1]) == PKT_END_DATA)
		{
		  dev->status = STATUS_IDLE;
		  DBG(100,"End of scan encountered on device %s\n",dev->devname);
		  send_pkt (PKT_GO_IDLE, 0, dev);
		  wait_ack (dev, NULL);
		  wait_ack (dev, NULL);
		  send_pkt (PKT_UNKNOW_1, 0, dev);
		  wait_ack (dev, NULL);
		  send_pkt (PKT_RESET, 0, dev);
		  sleep (2);	/* Time for the scanning head to go back home */
		  return SANE_STATUS_EOF;
		}
	    }
	}
    }
  while (1);
  packet_size = ntohl (dev->packet_data[5]);
  if (!dev->buffer)
    {
      dev->bufs = packet_size - 24 /* size of header */ ;
      if (dev->optionw[COLOR_OFFSET] == RGB)
	dev->bufs *= 3;
      dev->buffer = malloc (dev->bufs);
      if (!dev->buffer)
	return SANE_STATUS_NO_MEM;
      dev->write_offset_r = 0;
      dev->write_offset_g = 1;
      dev->write_offset_b = 2;

    }
  /* Get the "data header" */
  do
    {
      size = 24;
      sanei_usb_read_bulk (dev->dn, buffer, &size);
    }
  while (!size);
  color = ntohl (dev->packet_data[0]);
  packet_size -= size;
  dev->width = ntohl (dev->packet_data[5]);
  DBG(100,"Got data size %d on device %s. Scan width: %d\n",packet_size, dev->devname, dev->width);
  /* Now, read the data */
  do
    {
      int j;
      int i;
      int ret;
      do
	{
	  size = packet_size > 512 ? 512 : packet_size;
	  ret = sanei_usb_read_bulk (dev->dn, buffer, &size);
	}
      while (!size || ret != SANE_STATUS_GOOD);
      packet_size -= size;
      switch (color)
	{
	case RED_LAYER:
	  DBG(101,"Got red layer data on device %s\n",dev->devname);
	  i = dev->write_offset_r + 3 * size;
	  if (i > dev->bufs)
	    i = dev->bufs;
	  for (j = 0; dev->write_offset_r < i; dev->write_offset_r += 3)
	    dev->buffer[dev->write_offset_r] = buffer[j++];
	  break;
	case GREEN_LAYER:
	  DBG(101,"Got green layer data on device %s\n",dev->devname);
	  i = dev->write_offset_g + 3 * size;
	  if (i > dev->bufs)
	    i = dev->bufs;
	  for (j = 0; dev->write_offset_g < i; dev->write_offset_g += 3)
	    dev->buffer[dev->write_offset_g] = buffer[j++];
	  break;
	case BLUE_LAYER:
          DBG(101,"Got blue layer data on device %s\n",dev->devname);
	  i = dev->write_offset_b + 3 * size;
	  if (i > dev->bufs)
	    i = dev->bufs;
	  for (j = 0; dev->write_offset_b < i; dev->write_offset_b += 3)
	    dev->buffer[dev->write_offset_b] = buffer[j++];
	  break;
	case GRAY_LAYER:
	  DBG(101,"Got gray layer data on device %s\n",dev->devname);
	  if (dev->write_offset_r + (int)size >= dev->bufs)
	    size = dev->bufs - dev->write_offset_r;
	  memcpy (dev->buffer + dev->write_offset_r, buffer, size);
	  dev->write_offset_r += size;
	  break;
	}
    }
  while (packet_size > 0);
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_start (SANE_Handle h)
{
  struct device_s *dev = (struct device_s *) h;
  int status;
  size_t size;

  dev->read_offset = 0;
  dev->write_offset_r = 0;
  dev->write_offset_g = 1;
  dev->write_offset_b = 2;

  free (dev->buffer);
  dev->buffer = NULL;


  send_pkt (PKT_RESET, 0, dev);
  send_pkt (PKT_READ_STATUS, 0, dev);
  wait_ack (dev, &status);
  if (status)
    return SANE_STATUS_IO_ERROR;

  send_pkt (PKT_READCONF, 0, dev);

  if ((size = wait_ack (dev, NULL)))
    {
      sanei_usb_read_bulk (dev->dn, (unsigned char *) dev->conf_data, &size);
    }
  send_pkt (PKT_SETCONF, 100, dev);
  send_conf (dev);
  wait_ack (dev, NULL);

  send_pkt (PKT_START_SCAN, 0, dev);
  wait_ack (dev, NULL);
  if ((size = wait_ack (dev, NULL)))
    {
      sanei_usb_read_bulk (dev->dn, (unsigned char *) dev->conf_data, &size);
    }
  if ((size = wait_ack (dev, NULL)))
    {
      sanei_usb_read_bulk (dev->dn, (unsigned char *) dev->conf_data, &size);
    }
  if ((size = wait_ack (dev, NULL)))
    {
      sanei_usb_read_bulk (dev->dn, (unsigned char *) dev->conf_data, &size);
    }

  dev->status = STATUS_SCANNING;
  /* Get the first data */
  return get_data (dev);
}


static void
do_cancel(struct device_s *dev)
{
  while (get_data (dev) == SANE_STATUS_GOOD);
  free (dev->buffer);
  dev->buffer = NULL;
}

static int
min3 (int r, int g, int b)
{
  /* Optimize me ! */
  g--;
  b -= 2;
  if (r < g && r < b)
    return r;
  if (b < r && b < g)
    return b;
  return g;
}

SANE_Status
sane_read (SANE_Handle h, SANE_Byte * buf, SANE_Int maxlen, SANE_Int * len)
{
  struct device_s *dev = (struct device_s *) h;
  int available;
  int ret;
  *len = 0;
  if (dev->status == STATUS_IDLE)
    return SANE_STATUS_IO_ERROR;
  if (dev->optionw[COLOR_OFFSET] == RGB)
    {
      while (min3 (dev->write_offset_r, dev->write_offset_g,
		   dev->write_offset_b) <= dev->read_offset)
	{
	  ret = get_data (dev);
	  if (ret != SANE_STATUS_GOOD)
	    {
	      if (min3 (dev->write_offset_r,
			dev->write_offset_g,
			dev->write_offset_b) <= dev->read_offset)
		return ret;
	    }
	}
      available = min3 (dev->write_offset_r, dev->write_offset_g,
			dev->write_offset_b);
    }
  else
    {
      while (dev->write_offset_r <= dev->read_offset)
	{
	  ret = get_data (dev);
	  if (ret != SANE_STATUS_GOOD)
	    if (dev->write_offset_r <= dev->read_offset)
	      return ret;
	}
      available = dev->write_offset_r;
    }
  *len = available - dev->read_offset;
  if (*len > maxlen)
    *len = maxlen;
  memcpy (buf, dev->buffer + dev->read_offset, *len);
  dev->read_offset += *len;
  if (dev->read_offset == dev->bufs)
    {
      free (dev->buffer);
      dev->buffer = NULL;
      dev->read_offset = 0;
      dev->write_offset_r = 0;
      dev->write_offset_g = 1;
      dev->write_offset_b = 2;
    }

  /* Special case where sane_cancel is called while scanning */
  if (dev->status == STATUS_CANCELING)
    {
       do_cancel(dev);
       return SANE_STATUS_CANCELLED;
    }
  return SANE_STATUS_GOOD;
}

void
sane_cancel (SANE_Handle h)
{
  struct device_s *dev = (struct device_s *) h;


  if (dev->status == STATUS_SCANNING)
    {
      dev->status = STATUS_CANCELING;
      return;
    }

  free (dev->buffer);
  dev->buffer = NULL;
}

SANE_Status
sane_set_io_mode (SANE_Handle __sane_unused__ handle,
		  SANE_Bool __sane_unused__ non_blocking)
{
  return SANE_STATUS_UNSUPPORTED;
}

SANE_Status
sane_get_select_fd (SANE_Handle __sane_unused__ handle,
		    SANE_Int __sane_unused__ * fd)
{
  return SANE_STATUS_UNSUPPORTED;
}
