/* sane - Scanner Access Now Easy.
   Copyright (C) 2007 Ilia Sotnikov <hostcc@gmail.com>
   HP ScanJet 4570c support by Markham Thomas
   ADF page detection and high DPI fixes by Bernard Badeer
   scanbd integration by Damiano Scaramuzza and Bernard Badeer
   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 file is part of a SANE backend for
   HP ScanJet 4500C/4570C/5500C/5550C/5590/7650 Scanners
*/

#include "../include/sane/config.h"

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#include "../include/sane/sane.h"
#define BACKEND_NAME hp5590
#include "../include/sane/sanei_backend.h"
#include "../include/sane/sanei_usb.h"
#include "../include/sane/saneopts.h"
#include "hp5590_cmds.c"
#include "hp5590_low.c"
#include "../include/sane/sanei_net.h"

/* Debug levels */
#define DBG_err         0
#define DBG_proc        10
#define DBG_verbose     20
#define DBG_details     30

#define hp5590_assert(exp) if(!(exp)) { \
        DBG (DBG_err, "Assertion '%s' failed at %s:%u\n", #exp, __FILE__, __LINE__);\
        return SANE_STATUS_INVAL; \
}

#define hp5590_assert_void_return(exp) if(!(exp)) { \
        DBG (DBG_err, "Assertion '%s' failed at %s:%u\n", #exp, __FILE__, __LINE__);\
        return; \
}

#define MY_MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MY_MAX(a, b) (((a) > (b)) ? (a) : (b))

/* #define HAS_WORKING_COLOR_48 */
#define BUILD           8
#define USB_TIMEOUT     30 * 1000

static SANE_Word
res_list[] = { 6, 100, 200, 300, 600, 1200, 2400 };

#define SANE_VALUE_SCAN_SOURCE_FLATBED          SANE_I18N("Flatbed")
#define SANE_VALUE_SCAN_SOURCE_ADF              SANE_I18N("ADF")
#define SANE_VALUE_SCAN_SOURCE_ADF_DUPLEX       SANE_I18N("ADF Duplex")
#define SANE_VALUE_SCAN_SOURCE_TMA_SLIDES       SANE_I18N("TMA Slides")
#define SANE_VALUE_SCAN_SOURCE_TMA_NEGATIVES    SANE_I18N("TMA Negatives")
static SANE_String_Const
sources_list[] = {
  SANE_VALUE_SCAN_SOURCE_FLATBED,
  SANE_VALUE_SCAN_SOURCE_ADF,
  SANE_VALUE_SCAN_SOURCE_ADF_DUPLEX,
  SANE_VALUE_SCAN_SOURCE_TMA_SLIDES,
  SANE_VALUE_SCAN_SOURCE_TMA_NEGATIVES,
  NULL
};

#define SANE_VALUE_SCAN_MODE_COLOR_24           SANE_VALUE_SCAN_MODE_COLOR
#define SANE_VALUE_SCAN_MODE_COLOR_48           SANE_I18N("Color (48 bits)")
#define HAS_WORKING_COLOR_48 1

#define SANE_NAME_LAMP_TIMEOUT                  "extend-lamp-timeout"
#define SANE_TITLE_LAMP_TIMEOUT                 SANE_I18N("Extend lamp timeout")
#define SANE_DESC_LAMP_TIMEOUT                  SANE_I18N("Extends lamp timeout (from 15 minutes to 1 hour)")
#define SANE_NAME_WAIT_FOR_BUTTON               "wait-for-button"
#define SANE_TITLE_WAIT_FOR_BUTTON              SANE_I18N("Wait for button")
#define SANE_DESC_WAIT_FOR_BUTTON               SANE_I18N("Waits for button before scanning")
#define SANE_NAME_BUTTON_PRESSED                "button-pressed"
#define SANE_TITLE_BUTTON_PRESSED               SANE_I18N("Last button pressed")
#define SANE_DESC_BUTTON_PRESSED                SANE_I18N("Get ID of last button pressed (read only)")
#define SANE_NAME_LCD_COUNTER                   "counter-value"
#define SANE_TITLE_LCD_COUNTER                  SANE_I18N("LCD counter")
#define SANE_DESC_LCD_COUNTER                   SANE_I18N("Get value of LCD counter (read only)")
#define SANE_NAME_COLOR_LED                     "color-led"
#define SANE_TITLE_COLOR_LED                    SANE_I18N("Color LED indicator")
#define SANE_DESC_COLOR_LED                     SANE_I18N("Get value of LED indicator (read only)")
#define SANE_NAME_DOC_IN_ADF                    "doc-in-adf"
#define SANE_TITLE_DOC_IN_ADF                   SANE_I18N("Document available in ADF")
#define SANE_DESC_DOC_IN_ADF                    SANE_I18N("Get state of document-available indicator in ADF (read only)")
#define SANE_NAME_OVERWRITE_EOP_PIXEL           "hide-eop-pixel"
#define SANE_TITLE_OVERWRITE_EOP_PIXEL          SANE_I18N("Hide end-of-page pixel")
#define SANE_DESC_OVERWRITE_EOP_PIXEL           SANE_I18N("Hide end-of-page indicator pixels and overwrite with neighbor pixels")
#define SANE_NAME_TRAILING_LINES_MODE           "trailing-lines-mode"
#define SANE_TITLE_TRAILING_LINES_MODE          SANE_I18N("Filling mode of trailing lines after scan data (ADF)")
#define SANE_DESC_TRAILING_LINES_MODE           SANE_I18N("raw = raw scan data, last = repeat last scan line, raster = b/w raster, "\
                                                          "white = white color, black = black color, color = RGB or gray color value")
#define SANE_NAME_TRAILING_LINES_COLOR          "trailing-lines-color"
#define SANE_TITLE_TRAILING_LINES_COLOR         SANE_I18N("RGB or gray color value for filling mode 'color'")
#define SANE_DESC_TRAILING_LINES_COLOR          SANE_I18N("Color value for trailing lines filling mode 'color'. "\
                                                          "RGB color as r*65536+256*g+b or gray value (default=violet or gray)")

#define BUTTON_PRESSED_VALUE_COUNT 11
#define BUTTON_PRESSED_VALUE_NONE_KEY "none"
#define BUTTON_PRESSED_VALUE_POWER_KEY "power"
#define BUTTON_PRESSED_VALUE_SCAN_KEY "scan"
#define BUTTON_PRESSED_VALUE_COLLECT_KEY "collect"
#define BUTTON_PRESSED_VALUE_FILE_KEY "file"
#define BUTTON_PRESSED_VALUE_EMAIL_KEY "email"
#define BUTTON_PRESSED_VALUE_COPY_KEY "copy"
#define BUTTON_PRESSED_VALUE_UP_KEY "up"
#define BUTTON_PRESSED_VALUE_DOWN_KEY "down"
#define BUTTON_PRESSED_VALUE_MODE_KEY "mode"
#define BUTTON_PRESSED_VALUE_CANCEL_KEY "cancel"
#define BUTTON_PRESSED_VALUE_MAX_KEY_LEN 32
static SANE_String_Const
buttonstate_list[] = {
  BUTTON_PRESSED_VALUE_NONE_KEY,
  BUTTON_PRESSED_VALUE_POWER_KEY,
  BUTTON_PRESSED_VALUE_SCAN_KEY,
  BUTTON_PRESSED_VALUE_COLLECT_KEY,
  BUTTON_PRESSED_VALUE_FILE_KEY,
  BUTTON_PRESSED_VALUE_EMAIL_KEY,
  BUTTON_PRESSED_VALUE_COPY_KEY,
  BUTTON_PRESSED_VALUE_UP_KEY,
  BUTTON_PRESSED_VALUE_DOWN_KEY,
  BUTTON_PRESSED_VALUE_MODE_KEY,
  BUTTON_PRESSED_VALUE_CANCEL_KEY,
  NULL
};

#define COLOR_LED_VALUE_COUNT 2
#define COLOR_LED_VALUE_COLOR_KEY "color"
#define COLOR_LED_VALUE_BLACKWHITE_KEY "black_white"
#define COLOR_LED_VALUE_MAX_KEY_LEN 32
static SANE_String_Const
colorledstate_list[] = {
  COLOR_LED_VALUE_COLOR_KEY,
  COLOR_LED_VALUE_BLACKWHITE_KEY,
  NULL
};

#define LCD_COUNTER_VALUE_MIN 1
#define LCD_COUNTER_VALUE_MAX 99
#define LCD_COUNTER_VALUE_QUANT 1
static SANE_Range
lcd_counter_range = {
  LCD_COUNTER_VALUE_MIN,
  LCD_COUNTER_VALUE_MAX,
  LCD_COUNTER_VALUE_QUANT
};

#define TRAILING_LINES_MODE_RAW 0
#define TRAILING_LINES_MODE_LAST 1
#define TRAILING_LINES_MODE_RASTER 2
#define TRAILING_LINES_MODE_WHITE 3
#define TRAILING_LINES_MODE_BLACK 4
#define TRAILING_LINES_MODE_COLOR 5
#define TRAILING_LINES_MODE_VALUE_COUNT 6

#define TRAILING_LINES_MODE_RAW_KEY "raw"
#define TRAILING_LINES_MODE_LAST_KEY "last"
#define TRAILING_LINES_MODE_RASTER_KEY "raster"
#define TRAILING_LINES_MODE_WHITE_KEY "white"
#define TRAILING_LINES_MODE_BLACK_KEY "black"
#define TRAILING_LINES_MODE_COLOR_KEY "color"
#define TRAILING_LINES_MODE_MAX_KEY_LEN 24
static SANE_String_Const
trailingmode_list[] = {
  TRAILING_LINES_MODE_RAW_KEY,
  TRAILING_LINES_MODE_LAST_KEY,
  TRAILING_LINES_MODE_RASTER_KEY,
  TRAILING_LINES_MODE_WHITE_KEY,
  TRAILING_LINES_MODE_BLACK_KEY,
  TRAILING_LINES_MODE_COLOR_KEY,
  NULL
};

#define MAX_SCAN_SOURCE_VALUE_LEN       24
#define MAX_SCAN_MODE_VALUE_LEN         24

static SANE_Range
range_x, range_y, range_qual;

static SANE_String_Const
mode_list[] = {
  SANE_VALUE_SCAN_MODE_COLOR_24,
#ifdef HAS_WORKING_COLOR_48
  SANE_VALUE_SCAN_MODE_COLOR_48,
#endif /* HAS_WORKING_COLOR_48 */
  SANE_VALUE_SCAN_MODE_GRAY,
  SANE_VALUE_SCAN_MODE_LINEART,
  NULL
};

enum hp5590_opt_idx {
  HP5590_OPT_NUM = 0,
  HP5590_OPT_TL_X,
  HP5590_OPT_TL_Y,
  HP5590_OPT_BR_X,
  HP5590_OPT_BR_Y,
  HP5590_OPT_MODE,
  HP5590_OPT_SOURCE,
  HP5590_OPT_RESOLUTION,
  HP5590_OPT_LAMP_TIMEOUT,
  HP5590_OPT_WAIT_FOR_BUTTON,
  HP5590_OPT_BUTTON_PRESSED,
  HP5590_OPT_COLOR_LED,
  HP5590_OPT_LCD_COUNTER,
  HP5590_OPT_DOC_IN_ADF,
  HP5590_OPT_PREVIEW,
  HP5590_OPT_OVERWRITE_EOP_PIXEL,
  HP5590_OPT_TRAILING_LINES_MODE,
  HP5590_OPT_TRAILING_LINES_COLOR,
  HP5590_OPT_LAST
};

struct hp5590_scanner {
  struct scanner_info           *info;
  enum proto_flags              proto_flags;
  SANE_Device                   sane;
  SANE_Int                      dn;
  float                         br_x, br_y, tl_x, tl_y;
  unsigned int                  dpi;
  enum color_depths             depth;
  enum scan_sources             source;
  SANE_Bool                     extend_lamp_timeout;
  SANE_Bool                     wait_for_button;
  SANE_Bool                     preview;
  unsigned int                  quality;
  SANE_Option_Descriptor        *opts;
  struct hp5590_scanner         *next;
  unsigned long long            image_size;
  unsigned long long            transferred_image_size;
  void                          *bulk_read_state;
  SANE_Bool                     scanning;
  SANE_Bool                     overwrite_eop_pixel;
  SANE_Byte                     *eop_last_line_data;
  unsigned int                  eop_last_line_data_rpos;
  SANE_Int                      eop_trailing_lines_mode;
  SANE_Int                      eop_trailing_lines_color;
  SANE_Byte                     *adf_next_page_lines_data;
  unsigned int                  adf_next_page_lines_data_size;
  unsigned int                  adf_next_page_lines_data_rpos;
  unsigned int                  adf_next_page_lines_data_wpos;
  SANE_Byte                     *one_line_read_buffer;
  unsigned int                  one_line_read_buffer_rpos;
  SANE_Byte                     *color_shift_line_buffer1;
  unsigned int                  color_shift_buffered_lines1;
  SANE_Byte                     *color_shift_line_buffer2;
  unsigned int                  color_shift_buffered_lines2;
};

static
struct hp5590_scanner *scanners_list;

/******************************************************************************/
static SANE_Status
calc_image_params (struct hp5590_scanner *scanner,
                   unsigned int *pixel_bits,
                   unsigned int *pixels_per_line,
                   unsigned int *bytes_per_line,
                   unsigned int *lines,
                   unsigned long long *image_size)
{
  unsigned int  _pixel_bits;
  SANE_Status   ret;
  unsigned int  _pixels_per_line;
  unsigned int  _bytes_per_line;
  unsigned int  _lines;
  unsigned int  _image_size;
  float         var;

  DBG (DBG_proc, "%s\n", __func__);

  if (!scanner)
    return SANE_STATUS_INVAL;

  ret = hp5590_calc_pixel_bits (scanner->dpi, scanner->depth, &_pixel_bits);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  var = (float) (1.0 * (scanner->br_x - scanner->tl_x) * scanner->dpi);
  _pixels_per_line = var;
  if (var > _pixels_per_line)
    _pixels_per_line++;

  var  = (float) (1.0 * (scanner->br_y - scanner->tl_y) * scanner->dpi);
  _lines = var;
  if (var > _lines)
    _lines++;

  var  = (float) (1.0 * _pixels_per_line / 8 * _pixel_bits);
  _bytes_per_line  = var;
  if (var > _bytes_per_line)
    _bytes_per_line++;

  _image_size      = (unsigned long long) _lines * _bytes_per_line;

  DBG (DBG_verbose, "%s: pixel_bits: %u, pixels_per_line: %u, "
       "bytes_per_line: %u, lines: %u, image_size: %u\n",
       __func__,
       _pixel_bits, _pixels_per_line, _bytes_per_line, _lines, _image_size);

  if (pixel_bits)
    *pixel_bits = _pixel_bits;

  if (pixels_per_line)
    *pixels_per_line = _pixels_per_line;

  if (bytes_per_line)
    *bytes_per_line = _bytes_per_line;

  if (lines)
    *lines = _lines;

  if (image_size)
    *image_size = _image_size;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
attach_usb_device (SANE_String_Const devname,
                   enum hp_scanner_types hp_scanner_type)
{
  struct scanner_info           *info;
  struct hp5590_scanner         *scanner, *ptr;
  unsigned int                  max_count, count;
  SANE_Int                      dn;
  SANE_Status                   ret;
  const struct hp5590_model     *hp5590_model;

  DBG (DBG_proc, "%s: Opening USB device\n", __func__);
  if (sanei_usb_open (devname, &dn) != SANE_STATUS_GOOD)
    return SANE_STATUS_IO_ERROR;
  DBG (DBG_proc, "%s: USB device opened\n", __func__);

  ret = hp5590_model_def (hp_scanner_type, &hp5590_model);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  if (hp5590_init_scanner (dn, hp5590_model->proto_flags,
                           &info, hp_scanner_type) != 0)
    return SANE_STATUS_IO_ERROR;

  DBG (1, "%s: found HP%s scanner at '%s'\n",
       __func__, info->model, devname);

  DBG (DBG_verbose, "%s: Reading max scan count\n", __func__);
  if (hp5590_read_max_scan_count (dn, hp5590_model->proto_flags,
                                  &max_count) != 0)
    return SANE_STATUS_IO_ERROR;
  DBG (DBG_verbose, "%s: Max Scanning count %u\n", __func__, max_count);

  DBG (DBG_verbose, "%s: Reading scan count\n", __func__);
  if (hp5590_read_scan_count (dn, hp5590_model->proto_flags,
                              &count) != 0)
    return SANE_STATUS_IO_ERROR;
  DBG (DBG_verbose, "%s: Scanning count %u\n", __func__, count);

  ret = hp5590_read_part_number (dn, hp5590_model->proto_flags);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = hp5590_stop_scan (dn, hp5590_model->proto_flags);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  scanner = malloc (sizeof(struct hp5590_scanner));
  if (!scanner)
    return SANE_STATUS_NO_MEM;
  memset (scanner, 0, sizeof(struct hp5590_scanner));

  scanner->sane.model = info->model;
  scanner->sane.vendor = "HP";
  scanner->sane.type = info->kind;
  scanner->sane.name = devname;
  scanner->dn = dn;
  scanner->proto_flags = hp5590_model->proto_flags;
  scanner->info = info;
  scanner->bulk_read_state = NULL;
  scanner->opts = NULL;
  scanner->eop_last_line_data = NULL;
  scanner->eop_last_line_data_rpos = 0;
  scanner->adf_next_page_lines_data = NULL;
  scanner->adf_next_page_lines_data_size = 0;
  scanner->adf_next_page_lines_data_rpos = 0;
  scanner->adf_next_page_lines_data_wpos = 0;
  scanner->one_line_read_buffer = NULL;
  scanner->one_line_read_buffer_rpos = 0;
  scanner->color_shift_line_buffer1 = NULL;
  scanner->color_shift_buffered_lines1 = 0;
  scanner->color_shift_line_buffer2 = NULL;
  scanner->color_shift_buffered_lines2 = 0;

  if (!scanners_list)
    scanners_list = scanner;
  else
    {
      for (ptr = scanners_list; ptr->next; ptr = ptr->next);
      ptr->next = scanner;
    }

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static SANE_Status
attach_hp4570 (SANE_String_Const devname)
{
  return attach_usb_device (devname, SCANNER_HP4570);
}

/******************************************************************************/
static SANE_Status
attach_hp5550 (SANE_String_Const devname)
{
  return attach_usb_device (devname, SCANNER_HP5550);
}

/******************************************************************************/
static SANE_Status
attach_hp5590 (SANE_String_Const devname)
{
  return attach_usb_device (devname, SCANNER_HP5590);
}

/******************************************************************************/
static SANE_Status
attach_hp7650 (SANE_String_Const devname)
{
  return attach_usb_device (devname, SCANNER_HP7650);
}

/******************************************************************************/
SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback __sane_unused__ authorize)
{
  SANE_Status   ret;
  SANE_Word     vendor_id, product_id;

  DBG_INIT();

  DBG (1, "SANE backed for HP ScanJet 4500C/4570C/5500C/5550C/5590/7650 %u.%u.%u\n",
       SANE_CURRENT_MAJOR, SANE_CURRENT_MINOR, BUILD);
  DBG (1, "(c) Ilia Sotnikov <hostcc@gmail.com>\n");

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

  sanei_usb_init();

  sanei_usb_set_timeout (USB_TIMEOUT);

  scanners_list = NULL;

  ret = hp5590_vendor_product_id (SCANNER_HP4570, &vendor_id, &product_id);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = sanei_usb_find_devices (vendor_id, product_id, attach_hp4570);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = hp5590_vendor_product_id (SCANNER_HP5550, &vendor_id, &product_id);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = sanei_usb_find_devices (vendor_id, product_id, attach_hp5550);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = hp5590_vendor_product_id (SCANNER_HP5590, &vendor_id, &product_id);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = sanei_usb_find_devices (vendor_id, product_id, attach_hp5590);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = hp5590_vendor_product_id (SCANNER_HP7650, &vendor_id, &product_id);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = sanei_usb_find_devices (vendor_id, product_id, attach_hp7650);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
void sane_exit (void)
{
  struct hp5590_scanner *ptr, *pnext;

  DBG (DBG_proc, "%s\n", __func__);

  for (ptr = scanners_list; ptr; ptr = pnext)
    {
      if (ptr->opts != NULL)
        free (ptr->opts);
      if (ptr->eop_last_line_data != NULL) {
        free (ptr->eop_last_line_data);
        ptr->eop_last_line_data = NULL;
        ptr->eop_last_line_data_rpos = 0;
      }
      if (ptr->adf_next_page_lines_data != NULL) {
        free (ptr->adf_next_page_lines_data);
        ptr->adf_next_page_lines_data = NULL;
        ptr->adf_next_page_lines_data_size = 0;
        ptr->adf_next_page_lines_data_wpos = 0;
        ptr->adf_next_page_lines_data_rpos = 0;
      }
      if (ptr->one_line_read_buffer != NULL) {
        free (ptr->one_line_read_buffer);
        ptr->one_line_read_buffer = NULL;
        ptr->one_line_read_buffer_rpos = 0;
      }
      if (ptr->color_shift_line_buffer1 != NULL) {
        free (ptr->color_shift_line_buffer1);
        ptr->color_shift_line_buffer1 = NULL;
        ptr->color_shift_buffered_lines1 = 0;
      }
      if (ptr->color_shift_line_buffer2 != NULL) {
        free (ptr->color_shift_line_buffer2);
        ptr->color_shift_line_buffer2 = NULL;
        ptr->color_shift_buffered_lines2 = 0;
      }
      pnext = ptr->next;
      free (ptr);
    }
}

/******************************************************************************/
SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  struct hp5590_scanner *ptr;
  unsigned int found, i;

  DBG (DBG_proc, "%s, local only: %u\n", __func__, local_only);

  if (!device_list)
    return SANE_STATUS_INVAL;

  for (found = 0, ptr = scanners_list; ptr; found++, ptr = ptr->next);
  DBG (1, "Found %u devices\n", found);

  found++;
  *device_list = malloc (found * sizeof (SANE_Device));
  if (!*device_list)
    return SANE_STATUS_NO_MEM;
  memset (*device_list, 0, found * sizeof(SANE_Device));

  for (i = 0, ptr = scanners_list; ptr; i++, ptr = ptr->next)
    {
      (*device_list)[i] = &(ptr->sane);
    }

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
SANE_Status
sane_open (SANE_String_Const devicename, SANE_Handle * handle)
{
  struct hp5590_scanner         *ptr;
  SANE_Option_Descriptor        *opts;

  DBG (DBG_proc, "%s: device name: %s\n", __func__, devicename);

  if (!handle)
    return SANE_STATUS_INVAL;

  /* Allow to open the first available device by specifying zero-length name */
  if (!devicename || !devicename[0]) {
    ptr = scanners_list;
  } else {
    for (ptr = scanners_list;
         ptr && strcmp (ptr->sane.name, devicename) != 0;
         ptr = ptr->next);
  }

  if (!ptr)
    return SANE_STATUS_INVAL;

  /* DS: Without this after the first scan (and sane_close)
   * it was impossible to use again the read_buttons usb routine.
   * Function sane_close puts dn = -1. Now sane_open needs to open
   * the usb communication again.
   */
  if (ptr->dn < 0) {
    DBG (DBG_proc, "%s: Reopening USB device\n", __func__);
    if (sanei_usb_open (ptr->sane.name, &ptr->dn) != SANE_STATUS_GOOD)
      return SANE_STATUS_IO_ERROR;
    DBG (DBG_proc, "%s: USB device reopened\n", __func__);
  }

  ptr->tl_x = 0;
  ptr->tl_y = 0;
  ptr->br_x = ptr->info->max_size_x;
  ptr->br_y = ptr->info->max_size_y;
  ptr->dpi = res_list[1];
  ptr->depth = DEPTH_BW;
  ptr->source = SOURCE_FLATBED;
  ptr->extend_lamp_timeout = SANE_FALSE;
  ptr->wait_for_button = SANE_FALSE;
  ptr->preview = SANE_FALSE;
  ptr->quality = 4;
  ptr->image_size = 0;
  ptr->scanning = SANE_FALSE;
  ptr->overwrite_eop_pixel = SANE_TRUE;
  ptr->eop_trailing_lines_mode = TRAILING_LINES_MODE_LAST;
  ptr->eop_trailing_lines_color = 0x7f007f;

  *handle = ptr;

  opts = malloc (sizeof (SANE_Option_Descriptor) * HP5590_OPT_LAST);
  if (!opts)
    return SANE_STATUS_NO_MEM;

  opts[HP5590_OPT_NUM].name = SANE_NAME_NUM_OPTIONS;
  opts[HP5590_OPT_NUM].title = SANE_TITLE_NUM_OPTIONS;
  opts[HP5590_OPT_NUM].desc = SANE_DESC_NUM_OPTIONS;
  opts[HP5590_OPT_NUM].type = SANE_TYPE_INT;
  opts[HP5590_OPT_NUM].unit = SANE_UNIT_NONE;
  opts[HP5590_OPT_NUM].size = sizeof(SANE_Word);
  opts[HP5590_OPT_NUM].cap =  SANE_CAP_INACTIVE | SANE_CAP_SOFT_DETECT;
  opts[HP5590_OPT_NUM].constraint_type = SANE_CONSTRAINT_NONE;
  opts[HP5590_OPT_NUM].constraint.string_list = NULL;

  range_x.min = SANE_FIX(0);
  range_x.max = SANE_FIX(ptr->info->max_size_x * 25.4);
  range_x.quant = SANE_FIX(0.1);
  range_y.min = SANE_FIX(0);
  range_y.max = SANE_FIX(ptr->info->max_size_y * 25.4);
  range_y.quant = SANE_FIX(0.1);

  range_qual.min = SANE_FIX(4);
  range_qual.max = SANE_FIX(16);
  range_qual.quant = SANE_FIX(1);

  opts[HP5590_OPT_TL_X].name = SANE_NAME_SCAN_TL_X;
  opts[HP5590_OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
  opts[HP5590_OPT_TL_X].desc = SANE_DESC_SCAN_TL_X;
  opts[HP5590_OPT_TL_X].type = SANE_TYPE_FIXED;
  opts[HP5590_OPT_TL_X].unit = SANE_UNIT_MM;
  opts[HP5590_OPT_TL_X].size = sizeof(SANE_Fixed);
  opts[HP5590_OPT_TL_X].cap =  SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opts[HP5590_OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
  opts[HP5590_OPT_TL_X].constraint.range = &range_x;

  opts[HP5590_OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y;
  opts[HP5590_OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y;
  opts[HP5590_OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y;
  opts[HP5590_OPT_TL_Y].type = SANE_TYPE_FIXED;
  opts[HP5590_OPT_TL_Y].unit = SANE_UNIT_MM;
  opts[HP5590_OPT_TL_Y].size = sizeof(SANE_Fixed);
  opts[HP5590_OPT_TL_Y].cap =  SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opts[HP5590_OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  opts[HP5590_OPT_TL_Y].constraint.range = &range_y;

  opts[HP5590_OPT_BR_X].name = SANE_NAME_SCAN_BR_X;
  opts[HP5590_OPT_BR_X].title = SANE_TITLE_SCAN_BR_X;
  opts[HP5590_OPT_BR_X].desc = SANE_DESC_SCAN_BR_X;
  opts[HP5590_OPT_BR_X].type = SANE_TYPE_FIXED;
  opts[HP5590_OPT_BR_X].unit = SANE_UNIT_MM;
  opts[HP5590_OPT_BR_X].size = sizeof(SANE_Fixed);
  opts[HP5590_OPT_BR_X].cap =  SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opts[HP5590_OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
  opts[HP5590_OPT_BR_X].constraint.range = &range_x;

  opts[HP5590_OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y;
  opts[HP5590_OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y;
  opts[HP5590_OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y;
  opts[HP5590_OPT_BR_Y].type = SANE_TYPE_FIXED;
  opts[HP5590_OPT_BR_Y].unit = SANE_UNIT_MM;
  opts[HP5590_OPT_BR_Y].size = sizeof(SANE_Fixed);
  opts[HP5590_OPT_BR_Y].cap =  SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opts[HP5590_OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  opts[HP5590_OPT_BR_Y].constraint.range = &range_y;

  opts[HP5590_OPT_MODE].name = SANE_NAME_SCAN_MODE;
  opts[HP5590_OPT_MODE].title = SANE_TITLE_SCAN_MODE;
  opts[HP5590_OPT_MODE].desc = SANE_DESC_SCAN_MODE;
  opts[HP5590_OPT_MODE].type = SANE_TYPE_STRING;
  opts[HP5590_OPT_MODE].unit = SANE_UNIT_NONE;
  opts[HP5590_OPT_MODE].size = MAX_SCAN_MODE_VALUE_LEN;
  opts[HP5590_OPT_MODE].cap =  SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opts[HP5590_OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  opts[HP5590_OPT_MODE].constraint.string_list = mode_list;

  /* Show all features, check on feature in command line evaluation. */
  opts[HP5590_OPT_SOURCE].name = SANE_NAME_SCAN_SOURCE;
  opts[HP5590_OPT_SOURCE].title = SANE_TITLE_SCAN_SOURCE;
  opts[HP5590_OPT_SOURCE].desc = SANE_DESC_SCAN_SOURCE;
  opts[HP5590_OPT_SOURCE].type = SANE_TYPE_STRING;
  opts[HP5590_OPT_SOURCE].unit = SANE_UNIT_NONE;
  opts[HP5590_OPT_SOURCE].size = MAX_SCAN_SOURCE_VALUE_LEN;
  opts[HP5590_OPT_SOURCE].cap =  SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opts[HP5590_OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  opts[HP5590_OPT_SOURCE].constraint.string_list = sources_list;

  opts[HP5590_OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
  opts[HP5590_OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
  opts[HP5590_OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
  opts[HP5590_OPT_RESOLUTION].type = SANE_TYPE_INT;
  opts[HP5590_OPT_RESOLUTION].unit = SANE_UNIT_DPI;
  opts[HP5590_OPT_RESOLUTION].size = sizeof(SANE_Int);
  opts[HP5590_OPT_RESOLUTION].cap =  SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opts[HP5590_OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
  opts[HP5590_OPT_RESOLUTION].constraint.word_list = res_list;

  opts[HP5590_OPT_LAMP_TIMEOUT].name = SANE_NAME_LAMP_TIMEOUT;
  opts[HP5590_OPT_LAMP_TIMEOUT].title = SANE_TITLE_LAMP_TIMEOUT;
  opts[HP5590_OPT_LAMP_TIMEOUT].desc = SANE_DESC_LAMP_TIMEOUT;
  opts[HP5590_OPT_LAMP_TIMEOUT].type = SANE_TYPE_BOOL;
  opts[HP5590_OPT_LAMP_TIMEOUT].unit = SANE_UNIT_NONE;
  opts[HP5590_OPT_LAMP_TIMEOUT].size = sizeof(SANE_Bool);
  opts[HP5590_OPT_LAMP_TIMEOUT].cap =  SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
  opts[HP5590_OPT_LAMP_TIMEOUT].constraint_type = SANE_CONSTRAINT_NONE;
  opts[HP5590_OPT_LAMP_TIMEOUT].constraint.string_list = NULL;

  opts[HP5590_OPT_WAIT_FOR_BUTTON].name = SANE_NAME_WAIT_FOR_BUTTON;
  opts[HP5590_OPT_WAIT_FOR_BUTTON].title = SANE_TITLE_WAIT_FOR_BUTTON;
  opts[HP5590_OPT_WAIT_FOR_BUTTON].desc = SANE_DESC_WAIT_FOR_BUTTON;
  opts[HP5590_OPT_WAIT_FOR_BUTTON].type = SANE_TYPE_BOOL;
  opts[HP5590_OPT_WAIT_FOR_BUTTON].unit = SANE_UNIT_NONE;
  opts[HP5590_OPT_WAIT_FOR_BUTTON].size = sizeof(SANE_Bool);
  opts[HP5590_OPT_WAIT_FOR_BUTTON].cap =  SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opts[HP5590_OPT_WAIT_FOR_BUTTON].constraint_type = SANE_CONSTRAINT_NONE;
  opts[HP5590_OPT_WAIT_FOR_BUTTON].constraint.string_list = NULL;

  opts[HP5590_OPT_BUTTON_PRESSED].name = SANE_NAME_BUTTON_PRESSED;
  opts[HP5590_OPT_BUTTON_PRESSED].title = SANE_TITLE_BUTTON_PRESSED;
  opts[HP5590_OPT_BUTTON_PRESSED].desc = SANE_DESC_BUTTON_PRESSED;
  opts[HP5590_OPT_BUTTON_PRESSED].type = SANE_TYPE_STRING;
  opts[HP5590_OPT_BUTTON_PRESSED].unit = SANE_UNIT_NONE;
  opts[HP5590_OPT_BUTTON_PRESSED].size = BUTTON_PRESSED_VALUE_MAX_KEY_LEN;
  opts[HP5590_OPT_BUTTON_PRESSED].cap =  SANE_CAP_HARD_SELECT | SANE_CAP_SOFT_DETECT;
  opts[HP5590_OPT_BUTTON_PRESSED].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  opts[HP5590_OPT_BUTTON_PRESSED].constraint.string_list = buttonstate_list;

  opts[HP5590_OPT_COLOR_LED].name = SANE_NAME_COLOR_LED;
  opts[HP5590_OPT_COLOR_LED].title = SANE_TITLE_COLOR_LED;
  opts[HP5590_OPT_COLOR_LED].desc = SANE_DESC_COLOR_LED;
  opts[HP5590_OPT_COLOR_LED].type = SANE_TYPE_STRING;
  opts[HP5590_OPT_COLOR_LED].unit = SANE_UNIT_NONE;
  opts[HP5590_OPT_COLOR_LED].size = COLOR_LED_VALUE_MAX_KEY_LEN;
  opts[HP5590_OPT_COLOR_LED].cap =  SANE_CAP_HARD_SELECT | SANE_CAP_SOFT_DETECT;
  opts[HP5590_OPT_COLOR_LED].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  opts[HP5590_OPT_COLOR_LED].constraint.string_list = colorledstate_list;

  opts[HP5590_OPT_LCD_COUNTER].name = SANE_NAME_LCD_COUNTER;
  opts[HP5590_OPT_LCD_COUNTER].title = SANE_TITLE_LCD_COUNTER;
  opts[HP5590_OPT_LCD_COUNTER].desc = SANE_DESC_LCD_COUNTER;
  opts[HP5590_OPT_LCD_COUNTER].type = SANE_TYPE_INT;
  opts[HP5590_OPT_LCD_COUNTER].unit = SANE_UNIT_NONE;
  opts[HP5590_OPT_LCD_COUNTER].size = sizeof(SANE_Int);
  opts[HP5590_OPT_LCD_COUNTER].cap =  SANE_CAP_HARD_SELECT | SANE_CAP_SOFT_DETECT;
  opts[HP5590_OPT_LCD_COUNTER].constraint_type = SANE_CONSTRAINT_RANGE;
  opts[HP5590_OPT_LCD_COUNTER].constraint.range = &lcd_counter_range;

  opts[HP5590_OPT_DOC_IN_ADF].name = SANE_NAME_DOC_IN_ADF;
  opts[HP5590_OPT_DOC_IN_ADF].title = SANE_TITLE_DOC_IN_ADF;
  opts[HP5590_OPT_DOC_IN_ADF].desc = SANE_DESC_DOC_IN_ADF;
  opts[HP5590_OPT_DOC_IN_ADF].type = SANE_TYPE_BOOL;
  opts[HP5590_OPT_DOC_IN_ADF].unit = SANE_UNIT_NONE;
  opts[HP5590_OPT_DOC_IN_ADF].size = sizeof(SANE_Bool);
  opts[HP5590_OPT_DOC_IN_ADF].cap =  SANE_CAP_HARD_SELECT | SANE_CAP_SOFT_DETECT;
  opts[HP5590_OPT_DOC_IN_ADF].constraint_type = SANE_CONSTRAINT_NONE;
  opts[HP5590_OPT_DOC_IN_ADF].constraint.range = NULL;

  opts[HP5590_OPT_PREVIEW].name = SANE_NAME_PREVIEW;
  opts[HP5590_OPT_PREVIEW].title = SANE_TITLE_PREVIEW;
  opts[HP5590_OPT_PREVIEW].desc = SANE_DESC_PREVIEW;
  opts[HP5590_OPT_PREVIEW].type = SANE_TYPE_BOOL;
  opts[HP5590_OPT_PREVIEW].unit = SANE_UNIT_NONE;
  opts[HP5590_OPT_PREVIEW].size = sizeof(SANE_Bool);
  opts[HP5590_OPT_PREVIEW].cap =  SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opts[HP5590_OPT_PREVIEW].constraint_type = SANE_CONSTRAINT_NONE;
  opts[HP5590_OPT_PREVIEW].constraint.string_list = NULL;

  opts[HP5590_OPT_OVERWRITE_EOP_PIXEL].name = SANE_NAME_OVERWRITE_EOP_PIXEL;
  opts[HP5590_OPT_OVERWRITE_EOP_PIXEL].title = SANE_TITLE_OVERWRITE_EOP_PIXEL;
  opts[HP5590_OPT_OVERWRITE_EOP_PIXEL].desc = SANE_DESC_OVERWRITE_EOP_PIXEL;
  opts[HP5590_OPT_OVERWRITE_EOP_PIXEL].type = SANE_TYPE_BOOL;
  opts[HP5590_OPT_OVERWRITE_EOP_PIXEL].unit = SANE_UNIT_NONE;
  opts[HP5590_OPT_OVERWRITE_EOP_PIXEL].size = sizeof(SANE_Bool);
  opts[HP5590_OPT_OVERWRITE_EOP_PIXEL].cap =  SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
  opts[HP5590_OPT_OVERWRITE_EOP_PIXEL].constraint_type = SANE_CONSTRAINT_NONE;
  opts[HP5590_OPT_OVERWRITE_EOP_PIXEL].constraint.string_list = NULL;

  opts[HP5590_OPT_TRAILING_LINES_MODE].name = SANE_NAME_TRAILING_LINES_MODE;
  opts[HP5590_OPT_TRAILING_LINES_MODE].title = SANE_TITLE_TRAILING_LINES_MODE;
  opts[HP5590_OPT_TRAILING_LINES_MODE].desc = SANE_DESC_TRAILING_LINES_MODE;
  opts[HP5590_OPT_TRAILING_LINES_MODE].type = SANE_TYPE_STRING;
  opts[HP5590_OPT_TRAILING_LINES_MODE].unit = SANE_UNIT_NONE;
  opts[HP5590_OPT_TRAILING_LINES_MODE].size = TRAILING_LINES_MODE_MAX_KEY_LEN;
  opts[HP5590_OPT_TRAILING_LINES_MODE].cap =  SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
  opts[HP5590_OPT_TRAILING_LINES_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  opts[HP5590_OPT_TRAILING_LINES_MODE].constraint.string_list = trailingmode_list;

  opts[HP5590_OPT_TRAILING_LINES_COLOR].name = SANE_NAME_TRAILING_LINES_COLOR;
  opts[HP5590_OPT_TRAILING_LINES_COLOR].title = SANE_TITLE_TRAILING_LINES_COLOR;
  opts[HP5590_OPT_TRAILING_LINES_COLOR].desc = SANE_DESC_TRAILING_LINES_COLOR;
  opts[HP5590_OPT_TRAILING_LINES_COLOR].type = SANE_TYPE_INT;
  opts[HP5590_OPT_TRAILING_LINES_COLOR].unit = SANE_UNIT_NONE;
  opts[HP5590_OPT_TRAILING_LINES_COLOR].size = sizeof(SANE_Int);
  opts[HP5590_OPT_TRAILING_LINES_COLOR].cap =  SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
  opts[HP5590_OPT_TRAILING_LINES_COLOR].constraint_type = SANE_CONSTRAINT_NONE;
  opts[HP5590_OPT_TRAILING_LINES_COLOR].constraint.string_list = NULL;

  ptr->opts = opts;

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
void
sane_close (SANE_Handle handle)
{
  struct hp5590_scanner *scanner = handle;

  DBG (DBG_proc, "%s\n", __func__);

  sanei_usb_close (scanner->dn);
  scanner->dn = -1;
}

/******************************************************************************/
const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  struct hp5590_scanner *scanner = handle;

  DBG (DBG_proc, "%s, option: %u\n", __func__, option);

  if (option >= HP5590_OPT_LAST)
    return NULL;

  return &scanner->opts[option];
}

/*************************************DS:Support function read buttons status */
SANE_Status
read_button_pressed(SANE_Handle handle, enum button_status * button_pressed)
{
  struct hp5590_scanner * scanner = handle;
  *button_pressed = BUTTON_NONE;
  enum button_status status = BUTTON_NONE;
  DBG (DBG_verbose, "%s: Checking button status (device_number = %u) (device_name = %s)\n", __func__, scanner->dn, scanner->sane.name);
  SANE_Status ret = hp5590_read_buttons (scanner->dn, scanner->proto_flags, &status);
  if (ret != SANE_STATUS_GOOD)
    {
      DBG (DBG_proc, "%s: Error reading button status (%u)\n", __func__, ret);
      return ret;
    }
  DBG (DBG_verbose, "%s: Button pressed = %d\n", __func__, status);
  *button_pressed = status;
  return SANE_STATUS_GOOD;
}

/******************************************************************************/
SANE_Status
read_lcd_and_led_values(SANE_Handle handle,
        SANE_Int * lcd_counter,
        enum color_led_status * color_led)
{
  struct hp5590_scanner * scanner = handle;
  *lcd_counter = 1;
  *color_led = LED_COLOR;
  DBG (DBG_verbose, "%s: Reading LCD and LED values (device_number = %u) (device_name = %s)\n",
        __func__, scanner->dn, scanner->sane.name);
  SANE_Status ret = hp5590_read_lcd_and_led (scanner->dn, scanner->proto_flags, lcd_counter, color_led);
  if (ret != SANE_STATUS_GOOD)
    {
      DBG (DBG_proc, "%s: Error reading LCD and LED values (%u)\n", __func__, ret);
      return ret;
    }
  DBG (DBG_verbose, "%s: LCD = %d, LED = %s\n", __func__, *lcd_counter,
        *color_led == LED_BLACKWHITE ? COLOR_LED_VALUE_BLACKWHITE_KEY : COLOR_LED_VALUE_COLOR_KEY);
  return SANE_STATUS_GOOD;
}

/******************************************************************************/
SANE_Status
read_doc_in_adf_value(SANE_Handle handle,
        SANE_Bool * doc_in_adf)
{
  struct hp5590_scanner * scanner = handle;
  DBG (DBG_verbose, "%s: Reading state of document-available in ADF (device_number = %u) (device_name = %s)\n",
        __func__, scanner->dn, scanner->sane.name);
  SANE_Status ret = hp5590_is_data_available (scanner->dn, scanner->proto_flags);
  if (ret == SANE_STATUS_GOOD)
    *doc_in_adf = SANE_TRUE;
  else if (ret == SANE_STATUS_NO_DOCS)
    *doc_in_adf = SANE_FALSE;
  else
    {
      DBG (DBG_proc, "%s: Error reading state of document-available in ADF (%u)\n", __func__, ret);
      return ret;
    }
  DBG (DBG_verbose, "%s: doc_in_adf = %s\n", __func__, *doc_in_adf == SANE_FALSE ? "false" : "true");
  return SANE_STATUS_GOOD;
}

/******************************************************************************/
SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
                     SANE_Action action, void *value,
                     SANE_Int * info)
{
  struct hp5590_scanner *scanner = handle;

  if (!value)
    return SANE_STATUS_INVAL;

  if (!handle)
    return SANE_STATUS_INVAL;

  if (option >= HP5590_OPT_LAST)
    return SANE_STATUS_INVAL;

  if (action == SANE_ACTION_GET_VALUE)
    {
      if (option == HP5590_OPT_NUM)
        {
          DBG(3, "%s: get total number of options - %u\n", __func__, HP5590_OPT_LAST);
          *((SANE_Int *) value) = HP5590_OPT_LAST;
          return SANE_STATUS_GOOD;
        }

      if (!scanner->opts)
        return SANE_STATUS_INVAL;

      DBG (DBG_proc, "%s: get option '%s' value\n", __func__, scanner->opts[option].name);

      if (option == HP5590_OPT_BR_X)
        {
          *(SANE_Fixed *) value = SANE_FIX (scanner->br_x * 25.4);
        }

      if (option == HP5590_OPT_BR_Y)
        {
          *(SANE_Fixed *) value = SANE_FIX (scanner->br_y * 25.4);
        }

      if (option == HP5590_OPT_TL_X)
        {
          *(SANE_Fixed *) value = SANE_FIX ((scanner->tl_x) * 25.4);
        }

      if (option == HP5590_OPT_TL_Y)
        {
          *(SANE_Fixed *) value = SANE_FIX (scanner->tl_y * 25.4);
        }

      if (option == HP5590_OPT_MODE)
        {
          switch (scanner->depth) {
            case DEPTH_BW:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, SANE_VALUE_SCAN_MODE_LINEART, strlen (SANE_VALUE_SCAN_MODE_LINEART));
              break;
            case DEPTH_GRAY:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, SANE_VALUE_SCAN_MODE_GRAY, strlen (SANE_VALUE_SCAN_MODE_GRAY));
              break;
            case DEPTH_COLOR_24:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, SANE_VALUE_SCAN_MODE_COLOR_24, strlen (SANE_VALUE_SCAN_MODE_COLOR_24));
              break;
            case DEPTH_COLOR_48:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, SANE_VALUE_SCAN_MODE_COLOR_48, strlen (SANE_VALUE_SCAN_MODE_COLOR_48));
              break;
            default:
              return SANE_STATUS_INVAL;
          }
        }

      if (option == HP5590_OPT_SOURCE)
        {
          switch (scanner->source) {
            case SOURCE_FLATBED:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, SANE_VALUE_SCAN_SOURCE_FLATBED, strlen (SANE_VALUE_SCAN_SOURCE_FLATBED));
              break;
            case SOURCE_ADF:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, SANE_VALUE_SCAN_SOURCE_ADF, strlen (SANE_VALUE_SCAN_SOURCE_ADF));
              break;
            case SOURCE_ADF_DUPLEX:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, SANE_VALUE_SCAN_SOURCE_ADF_DUPLEX, strlen (SANE_VALUE_SCAN_SOURCE_ADF_DUPLEX));
              break;
            case SOURCE_TMA_SLIDES:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, SANE_VALUE_SCAN_SOURCE_TMA_SLIDES, strlen (SANE_VALUE_SCAN_SOURCE_TMA_SLIDES));
              break;
            case SOURCE_TMA_NEGATIVES:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, SANE_VALUE_SCAN_SOURCE_TMA_NEGATIVES, strlen (SANE_VALUE_SCAN_SOURCE_TMA_NEGATIVES));
              break;
            case SOURCE_NONE:
            default:
              return SANE_STATUS_INVAL;
          }
        }

      if (option == HP5590_OPT_RESOLUTION)
        {
          *(SANE_Int *) value = scanner->dpi;
        }

      if (option == HP5590_OPT_LAMP_TIMEOUT)
        {
          *(SANE_Bool *) value = scanner->extend_lamp_timeout;
        }

      if (option == HP5590_OPT_WAIT_FOR_BUTTON)
        {
          *(SANE_Bool *) value = scanner->wait_for_button;
        }

      if (option == HP5590_OPT_BUTTON_PRESSED)
        {
          enum button_status button_pressed = BUTTON_NONE;
          SANE_Status ret = read_button_pressed(scanner, &button_pressed);
          if (ret != SANE_STATUS_GOOD)
            return ret;
          switch (button_pressed) {
            case BUTTON_POWER:
              strncpy (value, BUTTON_PRESSED_VALUE_POWER_KEY, scanner->opts[option].size);
              break;
            case BUTTON_SCAN:
              strncpy (value, BUTTON_PRESSED_VALUE_SCAN_KEY, scanner->opts[option].size);
              break;
            case BUTTON_COLLECT:
              strncpy (value, BUTTON_PRESSED_VALUE_COLLECT_KEY, scanner->opts[option].size);
              break;
            case BUTTON_FILE:
              strncpy (value, BUTTON_PRESSED_VALUE_FILE_KEY, scanner->opts[option].size);
              break;
            case BUTTON_EMAIL:
              strncpy (value, BUTTON_PRESSED_VALUE_EMAIL_KEY, scanner->opts[option].size);
              break;
            case BUTTON_COPY:
              strncpy (value, BUTTON_PRESSED_VALUE_COPY_KEY, scanner->opts[option].size);
              break;
            case BUTTON_UP:
              strncpy (value, BUTTON_PRESSED_VALUE_UP_KEY, scanner->opts[option].size);
              break;
            case BUTTON_DOWN:
              strncpy (value, BUTTON_PRESSED_VALUE_DOWN_KEY, scanner->opts[option].size);
              break;
            case BUTTON_MODE:
              strncpy (value, BUTTON_PRESSED_VALUE_MODE_KEY, scanner->opts[option].size);
              break;
            case BUTTON_CANCEL:
              strncpy (value, BUTTON_PRESSED_VALUE_CANCEL_KEY, scanner->opts[option].size);
              break;
            case BUTTON_NONE:
            default:
              strncpy (value, BUTTON_PRESSED_VALUE_NONE_KEY, scanner->opts[option].size);
          }
        }

      if (option == HP5590_OPT_COLOR_LED)
        {
          SANE_Int lcd_counter = 0;
          enum color_led_status color_led = LED_COLOR;
          SANE_Status ret = read_lcd_and_led_values(scanner, &lcd_counter, &color_led);
          if (ret != SANE_STATUS_GOOD)
            return ret;
          switch (color_led) {
            case LED_BLACKWHITE:
              strncpy (value, COLOR_LED_VALUE_BLACKWHITE_KEY, scanner->opts[option].size);
              break;
            case LED_COLOR:
            default:
              strncpy (value, COLOR_LED_VALUE_COLOR_KEY, scanner->opts[option].size);
          }
        }

      if (option == HP5590_OPT_LCD_COUNTER)
        {
          SANE_Int lcd_counter = 0;
          enum color_led_status color_led = LED_COLOR;
          SANE_Status ret = read_lcd_and_led_values(scanner, &lcd_counter, &color_led);
          if (ret != SANE_STATUS_GOOD)
            return ret;
          *(SANE_Int *) value = lcd_counter;
        }

      if (option == HP5590_OPT_DOC_IN_ADF)
        {
          SANE_Bool doc_in_adf = SANE_FALSE;
          SANE_Status ret = read_doc_in_adf_value(scanner, &doc_in_adf);
          if (ret != SANE_STATUS_GOOD)
            return ret;
          *(SANE_Bool *) value = doc_in_adf;
        }

      if (option == HP5590_OPT_PREVIEW)
        {
          *(SANE_Bool *) value = scanner->preview;
        }

      if (option == HP5590_OPT_OVERWRITE_EOP_PIXEL)
        {
          *(SANE_Bool *) value = scanner->overwrite_eop_pixel;
        }

      if (option == HP5590_OPT_TRAILING_LINES_MODE)
        {
          switch (scanner->eop_trailing_lines_mode) {
            case TRAILING_LINES_MODE_RAW:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, TRAILING_LINES_MODE_RAW_KEY, strlen (TRAILING_LINES_MODE_RAW_KEY));
              break;
            case TRAILING_LINES_MODE_LAST:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, TRAILING_LINES_MODE_LAST_KEY, strlen (TRAILING_LINES_MODE_LAST_KEY));
              break;
            case TRAILING_LINES_MODE_RASTER:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, TRAILING_LINES_MODE_RASTER_KEY, strlen (TRAILING_LINES_MODE_RASTER_KEY));
              break;
            case TRAILING_LINES_MODE_BLACK:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, TRAILING_LINES_MODE_BLACK_KEY, strlen (TRAILING_LINES_MODE_BLACK_KEY));
              break;
            case TRAILING_LINES_MODE_WHITE:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, TRAILING_LINES_MODE_WHITE_KEY, strlen (TRAILING_LINES_MODE_WHITE_KEY));
              break;
            case TRAILING_LINES_MODE_COLOR:
              memset (value , 0, scanner->opts[option].size);
              memcpy (value, TRAILING_LINES_MODE_COLOR_KEY, strlen (TRAILING_LINES_MODE_COLOR_KEY));
              break;
            default:
              return SANE_STATUS_INVAL;
          }
        }

      if (option == HP5590_OPT_TRAILING_LINES_COLOR)
        {
          *(SANE_Int *) value = scanner->eop_trailing_lines_color;
        }
    }

  if (action == SANE_ACTION_SET_VALUE)
    {
      if (option == HP5590_OPT_NUM)
        return SANE_STATUS_INVAL;

      if (option == HP5590_OPT_BR_X)
        {
          float val = SANE_UNFIX(*(SANE_Fixed *) value) / 25.4;
          if (val <= scanner->tl_x)
            return SANE_STATUS_GOOD;
          scanner->br_x = val;
          if (info)
            *info = SANE_INFO_RELOAD_PARAMS;
        }

      if (option == HP5590_OPT_BR_Y)
        {
          float val = SANE_UNFIX(*(SANE_Fixed *) value) / 25.4;
          if (val <= scanner->tl_y)
            return SANE_STATUS_GOOD;
          scanner->br_y = val;
          if (info)
            *info = SANE_INFO_RELOAD_PARAMS;
        }

      if (option == HP5590_OPT_TL_X)
        {
          float val = SANE_UNFIX(*(SANE_Fixed *) value) / 25.4;
          if (val >= scanner->br_x)
            return SANE_STATUS_GOOD;
          scanner->tl_x = val;
          if (info)
            *info = SANE_INFO_RELOAD_PARAMS;
        }

      if (option == HP5590_OPT_TL_Y)
        {
          float val = SANE_UNFIX(*(SANE_Fixed *) value) / 25.4;
          if (val >= scanner->br_y)
            return SANE_STATUS_GOOD;
          scanner->tl_y = val;
          if (info)
            *info = SANE_INFO_RELOAD_PARAMS;
        }

      if (option == HP5590_OPT_MODE)
        {
          if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_MODE_LINEART) == 0)
            {
              scanner->depth = DEPTH_BW;
            }
          else if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_MODE_GRAY) == 0)
            {
              scanner->depth = DEPTH_GRAY;
            }
          else if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_MODE_COLOR_24) == 0)
            {
              scanner->depth = DEPTH_COLOR_24;
            }
          else if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_MODE_COLOR_48) == 0)
            {
              scanner->depth = DEPTH_COLOR_48;
            }
          else
            {
              return SANE_STATUS_INVAL;
            }

          if (info)
            *info = SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
        }

      if (option == HP5590_OPT_SOURCE)
        {
          range_y.max = SANE_FIX(scanner->info->max_size_y * 25.4);

          if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_SOURCE_FLATBED) == 0)
            {
              scanner->source = SOURCE_FLATBED;
              range_x.max = SANE_FIX(scanner->info->max_size_x * 25.4);
              range_y.max = SANE_FIX(scanner->info->max_size_y * 25.4);
              scanner->br_x = scanner->info->max_size_x;
              scanner->br_y = scanner->info->max_size_y;
            }
          else if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_SOURCE_ADF) == 0)
            {
              /* In ADF modes the device can scan up to ADF_MAX_Y_INCHES, which is usually
               * bigger than what scanner reports back during initialization
               */
              if (! (scanner->info->features & FEATURE_ADF))
                {
                  DBG(DBG_err, "ADF feature not available: %s\n", (char *) value);
                  return SANE_STATUS_UNSUPPORTED;
                }
              scanner->source = SOURCE_ADF;
              range_x.max = SANE_FIX(scanner->info->max_size_x * 25.4);
              range_y.max = SANE_FIX(ADF_MAX_Y_INCHES * 25.4);
              scanner->br_x = scanner->info->max_size_x;
              scanner->br_y = ADF_MAX_Y_INCHES;
            }
          else if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_SOURCE_ADF_DUPLEX) == 0)
            {
              if (! (scanner->info->features & FEATURE_ADF))
                {
                  DBG(DBG_err, "ADF feature not available: %s\n", (char *) value);
                  return SANE_STATUS_UNSUPPORTED;
                }
              scanner->source = SOURCE_ADF_DUPLEX;
              range_x.max = SANE_FIX(scanner->info->max_size_x * 25.4);
              range_y.max = SANE_FIX(ADF_MAX_Y_INCHES * 25.4);
              scanner->br_x = scanner->info->max_size_x;
              scanner->br_y = ADF_MAX_Y_INCHES;
            }
          else if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_SOURCE_TMA_SLIDES) == 0)
            {
              if (! (scanner->info->features & FEATURE_TMA))
                {
                  DBG(DBG_err, "TMA feature not available: %s\n", (char *) value);
                  return SANE_STATUS_UNSUPPORTED;
                }
              scanner->source = SOURCE_TMA_SLIDES;
              range_x.max = SANE_FIX(TMA_MAX_X_INCHES * 25.4);
              range_y.max = SANE_FIX(TMA_MAX_Y_INCHES * 25.4);
              scanner->br_x = TMA_MAX_X_INCHES;
              scanner->br_y = TMA_MAX_Y_INCHES;
            }
          else if (strcmp ((char *) value, (char *) SANE_VALUE_SCAN_SOURCE_TMA_NEGATIVES) == 0)
            {
              if (! (scanner->info->features & FEATURE_TMA))
                {
                  DBG(DBG_err, "TMA feature not available: %s\n", (char *) value);
                  return SANE_STATUS_UNSUPPORTED;
                }
              scanner->source = SOURCE_TMA_NEGATIVES;
              range_x.max = SANE_FIX(TMA_MAX_X_INCHES * 25.4);
              range_y.max = SANE_FIX(TMA_MAX_Y_INCHES * 25.4);
              scanner->br_x = TMA_MAX_X_INCHES;
              scanner->br_y = TMA_MAX_Y_INCHES;
            }
          else
            {
              return SANE_STATUS_INVAL;
            }
          if (info)
            *info = SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
        }

      if (option == HP5590_OPT_RESOLUTION)
        {
          scanner->dpi = *(SANE_Int *) value;
          if (info)
            *info = SANE_INFO_RELOAD_PARAMS;
        }

      if (option == HP5590_OPT_LAMP_TIMEOUT)
        {
          scanner->extend_lamp_timeout = *(SANE_Bool *) value;
        }

      if (option == HP5590_OPT_WAIT_FOR_BUTTON)
        {
          scanner->wait_for_button = *(SANE_Bool *) value;
        }

      if (option == HP5590_OPT_BUTTON_PRESSED)
        {
          DBG(DBG_verbose, "State of buttons is read only. Setting of state will be ignored.\n");
        }

      if (option == HP5590_OPT_COLOR_LED)
        {
          DBG(DBG_verbose, "State of color LED indicator is read only. Setting of state will be ignored.\n");
        }

      if (option == HP5590_OPT_LCD_COUNTER)
        {
          DBG(DBG_verbose, "Value of LCD counter is read only. Setting of value will be ignored.\n");
        }

      if (option == HP5590_OPT_DOC_IN_ADF)
        {
          DBG(DBG_verbose, "Value of document-available indicator is read only. Setting of value will be ignored.\n");
        }

      if (option == HP5590_OPT_PREVIEW)
        {
          scanner->preview = *(SANE_Bool *) value;
        }

      if (option == HP5590_OPT_OVERWRITE_EOP_PIXEL)
        {
          scanner->overwrite_eop_pixel = *(SANE_Bool *) value;
        }

      if (option == HP5590_OPT_TRAILING_LINES_MODE)
        {
          if (strcmp ((char *) value, (char *) TRAILING_LINES_MODE_RAW_KEY) == 0)
            scanner->eop_trailing_lines_mode = TRAILING_LINES_MODE_RAW;
          if (strcmp ((char *) value, (char *) TRAILING_LINES_MODE_LAST_KEY) == 0)
            scanner->eop_trailing_lines_mode = TRAILING_LINES_MODE_LAST;
          if (strcmp ((char *) value, (char *) TRAILING_LINES_MODE_RASTER_KEY) == 0)
            scanner->eop_trailing_lines_mode = TRAILING_LINES_MODE_RASTER;
          if (strcmp ((char *) value, (char *) TRAILING_LINES_MODE_BLACK_KEY) == 0)
            scanner->eop_trailing_lines_mode = TRAILING_LINES_MODE_BLACK;
          if (strcmp ((char *) value, (char *) TRAILING_LINES_MODE_WHITE_KEY) == 0)
            scanner->eop_trailing_lines_mode = TRAILING_LINES_MODE_WHITE;
          if (strcmp ((char *) value, (char *) TRAILING_LINES_MODE_COLOR_KEY) == 0)
            scanner->eop_trailing_lines_mode = TRAILING_LINES_MODE_COLOR;
        }

      if (option == HP5590_OPT_TRAILING_LINES_COLOR)
        {
          scanner->eop_trailing_lines_color = *(SANE_Int *) value;
        }
    }

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
SANE_Status sane_get_parameters (SANE_Handle handle,
                                 SANE_Parameters * params)
{
  struct hp5590_scanner *scanner = handle;
  SANE_Status           ret;
  unsigned int          pixel_bits;

  DBG (DBG_proc, "%s\n", __func__);

  if (!params)
    return SANE_STATUS_INVAL;

  if (!handle)
    return SANE_STATUS_INVAL;

  ret = calc_image_params (scanner,
                           (unsigned int *) &pixel_bits,
                           (unsigned int *) &params->pixels_per_line,
                           (unsigned int *) &params->bytes_per_line,
                           (unsigned int *) &params->lines, NULL);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  switch (scanner->depth) {
    case DEPTH_BW:
      params->depth = pixel_bits;
      params->format = SANE_FRAME_GRAY;
      params->last_frame = SANE_TRUE;
      break;
    case DEPTH_GRAY:
      params->depth = pixel_bits;
      params->format = SANE_FRAME_GRAY;
      params->last_frame = SANE_TRUE;
      break;
    case DEPTH_COLOR_24:
      params->depth = pixel_bits / 3;
      params->last_frame = SANE_TRUE;
      params->format = SANE_FRAME_RGB;
      break;
    case DEPTH_COLOR_48:
      params->depth = pixel_bits / 3;
      params->last_frame = SANE_TRUE;
      params->format = SANE_FRAME_RGB;
      break;
    default:
      DBG(DBG_err, "%s: Unknown depth\n", __func__);
      return SANE_STATUS_INVAL;
  }


  DBG (DBG_proc, "format: %u, last_frame: %u, bytes_per_line: %u, "
       "pixels_per_line: %u, lines: %u, depth: %u\n",
       params->format, params->last_frame,
       params->bytes_per_line, params->pixels_per_line,
       params->lines, params->depth);

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
SANE_Status
sane_start (SANE_Handle handle)
{
  struct hp5590_scanner *scanner = handle;
  SANE_Status           ret;
  unsigned int          bytes_per_line;

  DBG (DBG_proc, "%s\n", __func__);

  if (!scanner)
    return SANE_STATUS_INVAL;

  /* Cleanup for all pages. */
  if (scanner->eop_last_line_data)
    {
      /* Release last line data */
      free (scanner->eop_last_line_data);
      scanner->eop_last_line_data = NULL;
      scanner->eop_last_line_data_rpos = 0;
    }
  if (scanner->one_line_read_buffer)
    {
      /* Release temporary line buffer. */
      free (scanner->one_line_read_buffer);
      scanner->one_line_read_buffer = NULL;
      scanner->one_line_read_buffer_rpos = 0;
    }
  if (scanner->color_shift_line_buffer1)
    {
      /* Release line buffer1 for shifting colors. */
      free (scanner->color_shift_line_buffer1);
      scanner->color_shift_line_buffer1 = NULL;
      scanner->color_shift_buffered_lines1 = 0;
    }
  if (scanner->color_shift_line_buffer2)
    {
      /* Release line buffer2 for shifting colors. */
      free (scanner->color_shift_line_buffer2);
      scanner->color_shift_line_buffer2 = NULL;
      scanner->color_shift_buffered_lines2 = 0;
    }

  if (   scanner->scanning == SANE_TRUE
      && (  scanner->source == SOURCE_ADF
         || scanner->source == SOURCE_ADF_DUPLEX))
    {
      DBG (DBG_verbose, "%s: Scanner is scanning, check if more data is available\n",
           __func__);
      ret = hp5590_is_data_available (scanner->dn, scanner->proto_flags);
      if (ret == SANE_STATUS_GOOD)
        {
          DBG (DBG_verbose, "%s: More data is available\n", __func__);
          scanner->transferred_image_size = scanner->image_size;
          return SANE_STATUS_GOOD;
        }

      if (ret != SANE_STATUS_NO_DOCS)
        return ret;
    }

  sane_cancel (handle);

  if (scanner->wait_for_button)
    {
      enum button_status status;
      for (;;)
        {
          ret = hp5590_read_buttons (scanner->dn,
                                     scanner->proto_flags,
                                     &status);
          if (ret != SANE_STATUS_GOOD)
            return ret;

          if (status == BUTTON_CANCEL)
            return SANE_STATUS_CANCELLED;

          if (status != BUTTON_NONE && status != BUTTON_POWER)
            break;
          usleep (100 * 1000);
        }
    }

  DBG (DBG_verbose, "Init scanner\n");
  ret = hp5590_init_scanner (scanner->dn, scanner->proto_flags,
                             NULL, SCANNER_NONE);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = hp5590_power_status (scanner->dn, scanner->proto_flags);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  DBG (DBG_verbose, "Wakeup\n");
  ret = hp5590_select_source_and_wakeup (scanner->dn, scanner->proto_flags,
                                         scanner->source,
                                         scanner->extend_lamp_timeout);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = hp5590_set_scan_params (scanner->dn,
                                scanner->proto_flags,
                                scanner->info,
                                scanner->tl_x * scanner->dpi,
                                scanner->tl_y * scanner->dpi,
                                (scanner->br_x - scanner->tl_x) * scanner->dpi,
                                (scanner->br_y - scanner->tl_y) * scanner->dpi,
                                scanner->dpi,
                                scanner->depth, scanner->preview ? MODE_PREVIEW : MODE_NORMAL,
                                scanner->source);
  if (ret != SANE_STATUS_GOOD)
    {
      hp5590_reset_scan_head (scanner->dn, scanner->proto_flags);
      return ret;
    }

  ret = calc_image_params (scanner, NULL, NULL,
                           &bytes_per_line, NULL,
                           &scanner->image_size);
  if (ret != SANE_STATUS_GOOD)
    {
      hp5590_reset_scan_head (scanner->dn, scanner->proto_flags);
      return ret;
    }

  scanner->transferred_image_size = scanner->image_size;

  if (   scanner->depth == DEPTH_COLOR_24
      || scanner->depth == DEPTH_COLOR_48)
    {
      DBG (1, "Color 24/48 bits: checking if image size is correctly "
           "aligned on number of colors\n");
      if (bytes_per_line % 3)
        {
          DBG (DBG_err, "Color 24/48 bits: image size doesn't lined up on number of colors (3) "
               "(image size: %llu, bytes per line %u)\n",
               scanner->image_size, bytes_per_line);
          hp5590_reset_scan_head (scanner->dn, scanner->proto_flags);
          return SANE_STATUS_INVAL;
        }
      DBG (1, "Color 24/48 bits: image size is correctly aligned on number of colors "
           "(image size: %llu, bytes per line %u)\n",
           scanner->image_size, bytes_per_line);

      DBG (1, "Color 24/48 bits: checking if image size is correctly "
           "aligned on bytes per line\n");
      if (scanner->image_size % bytes_per_line)
        {
          DBG (DBG_err, "Color 24/48 bits: image size doesn't lined up on bytes per line "
               "(image size: %llu, bytes per line %u)\n",
               scanner->image_size, bytes_per_line);
          hp5590_reset_scan_head (scanner->dn, scanner->proto_flags);
          return SANE_STATUS_INVAL;
        }
      DBG (1, "Color 24/48 bits: image size correctly aligned on bytes per line "
           "(images size: %llu, bytes per line: %u)\n",
           scanner->image_size, bytes_per_line);
    }

  DBG (DBG_verbose, "Final image size: %llu\n", scanner->image_size);

  DBG (DBG_verbose, "Reverse calibration maps\n");
  ret = hp5590_send_reverse_calibration_map (scanner->dn, scanner->proto_flags);
  if (ret != SANE_STATUS_GOOD)
    {
      hp5590_reset_scan_head (scanner->dn, scanner->proto_flags);
      return ret;
    }

  DBG (DBG_verbose, "Forward calibration maps\n");
  ret = hp5590_send_forward_calibration_maps (scanner->dn, scanner->proto_flags);
  if (ret != SANE_STATUS_GOOD)
    {
      hp5590_reset_scan_head (scanner->dn, scanner->proto_flags);
      return ret;
    }

  if (scanner->adf_next_page_lines_data)
    {
      free (scanner->adf_next_page_lines_data);
      scanner->adf_next_page_lines_data = NULL;
      scanner->adf_next_page_lines_data_size = 0;
      scanner->adf_next_page_lines_data_rpos = 0;
      scanner->adf_next_page_lines_data_wpos = 0;
    }

  scanner->scanning = SANE_TRUE;

  DBG (DBG_verbose, "Starting scan\n");
  ret = hp5590_start_scan (scanner->dn, scanner->proto_flags);
  /* Check for paper jam */
  if (    ret == SANE_STATUS_DEVICE_BUSY
      && (   scanner->source == SOURCE_ADF
          || scanner->source == SOURCE_ADF_DUPLEX))
    return SANE_STATUS_JAMMED;

  if (ret != SANE_STATUS_GOOD)
    {
      hp5590_reset_scan_head (scanner->dn, scanner->proto_flags);
      return ret;
    }

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static void
invert_negative_colors (unsigned char *buf, unsigned int bytes_per_line, struct hp5590_scanner *scanner)
{
  /* Invert lineart or negatives. */
  int is_linear = (scanner->depth == DEPTH_BW);
  int is_negative = (scanner->source == SOURCE_TMA_NEGATIVES);
  if (is_linear ^ is_negative)
    {
      for (unsigned int k = 0; k < bytes_per_line; k++)
        buf[k] ^= 0xff;
    }
}

/******************************************************************************/
static SANE_Status
convert_gray_and_lineart (struct hp5590_scanner *scanner, SANE_Byte *data, SANE_Int size)
{
  unsigned int pixels_per_line;
  unsigned int pixel_bits;
  unsigned int bytes_per_line;
  unsigned int lines;
  unsigned char *buf;
  SANE_Status ret;

  hp5590_assert (scanner != NULL);
  hp5590_assert (data != NULL);

  if ( ! (scanner->depth == DEPTH_BW || scanner->depth == DEPTH_GRAY))
    return SANE_STATUS_GOOD;

  DBG (DBG_proc, "%s\n", __func__);

  ret = calc_image_params (scanner,
                           &pixel_bits,
                           &pixels_per_line, &bytes_per_line,
                           NULL, NULL);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  lines = size / bytes_per_line;

  buf = data;
  for (unsigned int i = 0; i < lines; buf += bytes_per_line, ++i)
    {
      if (! scanner->eop_last_line_data)
        {
          if (pixels_per_line > 0)
            {
              /* Test for last-line indicator pixel. If found, store last line
               * and optionally overwrite indicator pixel with neighbor value.
               */
              unsigned int j = bytes_per_line - 1;
              int eop_found = 0;
              if (scanner->depth == DEPTH_GRAY)
                {
                  eop_found = (buf[j] != 0);
                  if (scanner->overwrite_eop_pixel && (j > 0))
                    {
                      buf[j] = buf[j-1];
                    }
                }
              else if (scanner->depth == DEPTH_BW)
                {
                  eop_found = (buf[j] != 0);
                  if (scanner->overwrite_eop_pixel && (j > 0))
                    {
                      buf[j] = (buf[j-1] & 0x01) ? 0xff : 0;
                    }
                }

              invert_negative_colors (buf, bytes_per_line, scanner);

              if (eop_found && (! scanner->eop_last_line_data))
                {
                  DBG (DBG_verbose, "Found end-of-page at line %u in reading block.\n", i);
                  scanner->eop_last_line_data = malloc(bytes_per_line);
                  if (! scanner->eop_last_line_data)
                    return SANE_STATUS_NO_MEM;

                  memcpy (scanner->eop_last_line_data, buf, bytes_per_line);
                  scanner->eop_last_line_data_rpos = 0;

                  /* Fill trailing line buffer with requested color. */
                  if (scanner->eop_trailing_lines_mode == TRAILING_LINES_MODE_RASTER)
                    {
                      /* Black-white raster. */
                      if (scanner->depth == DEPTH_BW)
                        {
                          memset (scanner->eop_last_line_data, 0xaa, bytes_per_line);
                        }
                      else
                        {
                          /* Gray. */
                          for (unsigned int k = 0; k < bytes_per_line; ++k)
                            {
                              scanner->eop_last_line_data[k] = (k & 1 ? 0xff : 0);
                            }
                        }
                    }
                  else if (scanner->eop_trailing_lines_mode == TRAILING_LINES_MODE_WHITE)
                    {
                      /* White. */
                      if (scanner->depth == DEPTH_BW)
                        {
                          memset (scanner->eop_last_line_data, 0x00, bytes_per_line);
                        }
                      else
                        {
                          memset (scanner->eop_last_line_data, 0xff, bytes_per_line);
                        }
                    }
                  else if (scanner->eop_trailing_lines_mode == TRAILING_LINES_MODE_BLACK)
                    {
                      /* Black. */
                      if (scanner->depth == DEPTH_BW)
                        {
                          memset (scanner->eop_last_line_data, 0xff, bytes_per_line);
                        }
                      else
                        {
                          memset (scanner->eop_last_line_data, 0x00, bytes_per_line);
                        }
                    }
                  else if (scanner->eop_trailing_lines_mode == TRAILING_LINES_MODE_COLOR)
                    {
                      if (scanner->depth == DEPTH_BW)
                        {
                          /* Black or white. */
                          memset (scanner->eop_last_line_data, scanner->eop_trailing_lines_color & 0x01 ? 0x00 : 0xff, bytes_per_line);
                        }
                      else
                        {
                          /* Gray value */
                          memset (scanner->eop_last_line_data, scanner->eop_trailing_lines_color & 0xff, bytes_per_line);
                        }
                    }
                }
            }
        }
      else
        {
          DBG (DBG_verbose, "Trailing lines mode: line=%u, mode=%d, color=%u\n",
               i, scanner->eop_trailing_lines_mode, scanner->eop_trailing_lines_color);

          if ((scanner->source == SOURCE_ADF) || (scanner->source == SOURCE_ADF_DUPLEX))
            {
              /* We are in in ADF mode after last-line and store next page data
               * to buffer.
               */
              if (! scanner->adf_next_page_lines_data)
                {
                  unsigned int n_rest_lines = lines - i;
                  unsigned int buf_size = n_rest_lines * bytes_per_line;
                  scanner->adf_next_page_lines_data = malloc(buf_size);
                  if (! scanner->adf_next_page_lines_data)
                    return SANE_STATUS_NO_MEM;
                  scanner->adf_next_page_lines_data_size = buf_size;
                  scanner->adf_next_page_lines_data_rpos = 0;
                  scanner->adf_next_page_lines_data_wpos = 0;
                  DBG (DBG_verbose, "ADF between pages: Save n=%u next page lines in buffer.\n", n_rest_lines);
                }
              DBG (DBG_verbose, "ADF between pages: Store line %u of %u.\n", i, lines);
              invert_negative_colors (buf, bytes_per_line, scanner);
              memcpy (scanner->adf_next_page_lines_data + scanner->adf_next_page_lines_data_wpos, buf, bytes_per_line);
              scanner->adf_next_page_lines_data_wpos += bytes_per_line;
            }

          if (scanner->eop_trailing_lines_mode != TRAILING_LINES_MODE_RAW)
            {
              /* Copy last line data or corresponding color over trailing lines
               * data.
               */
              memcpy (buf, scanner->eop_last_line_data, bytes_per_line);
            }
        }
    }

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static unsigned char
get_checked (unsigned char *ptr, unsigned int i, unsigned int length)
{
  if (i < length)
    {
      return ptr[i];
    }
  DBG (DBG_details, "get from array out of range: idx=%u, size=%u\n", i, length);
  return 0;
}

/******************************************************************************/
static SANE_Status
convert_to_rgb (struct hp5590_scanner *scanner, SANE_Byte *data, SANE_Int size)
{
  unsigned int pixels_per_line;
  unsigned int pixel_bits;
  unsigned int bytes_per_color;
  unsigned int bytes_per_line;
  unsigned int bytes_per_line_limit;
  unsigned int lines;
  unsigned int i, j;
  unsigned char *buf;
  unsigned char *bufptr;
  unsigned char *ptr;
  SANE_Status   ret;

  hp5590_assert (scanner != NULL);
  hp5590_assert (data != NULL);

  if ( ! (scanner->depth == DEPTH_COLOR_24 || scanner->depth == DEPTH_COLOR_48))
    return SANE_STATUS_GOOD;

  DBG (DBG_proc, "%s\n", __func__);

#ifndef HAS_WORKING_COLOR_48
  if (scanner->depth == DEPTH_COLOR_48)
    return SANE_STATUS_UNSUPPORTED;
#endif

  ret = calc_image_params (scanner,
                           &pixel_bits,
                           &pixels_per_line, &bytes_per_line,
                           NULL, NULL);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  lines = size / bytes_per_line;
  bytes_per_color = (pixel_bits + 7) / 8;

  bytes_per_line_limit = bytes_per_line;
  if ((scanner->depth == DEPTH_COLOR_48) && (bytes_per_line_limit > 3))
    {
      /* Last-line indicator pixel has only 3 bytes instead of 6. */
      bytes_per_line_limit -= 3;
    }

  DBG (DBG_verbose, "Length : %u\n", size);

  DBG (DBG_verbose, "Converting row RGB to normal RGB\n");

  DBG (DBG_verbose, "Bytes per line %u\n", bytes_per_line);
  DBG (DBG_verbose, "Bytes per line limited %u\n", bytes_per_line_limit);
  DBG (DBG_verbose, "Bytes per color %u\n", bytes_per_color);
  DBG (DBG_verbose, "Pixels per line %u\n", pixels_per_line);
  DBG (DBG_verbose, "Lines %u\n", lines);

  /* Use working buffer for color mapping. */
  bufptr = malloc (size);
  if (! bufptr)
    return SANE_STATUS_NO_MEM;
  memset (bufptr, 0, size);
  buf = bufptr;

  ptr = data;
  for (j = 0; j < lines; ptr += bytes_per_line_limit, buf += bytes_per_line, j++)
    {
      for (i = 0; i < pixels_per_line; i++)
        {
          /* Color mapping from raw scanner data to RGB buffer. */
          if (scanner->depth == DEPTH_COLOR_24)
            {
              /* R */
              buf[i*3]   = get_checked(ptr, i, bytes_per_line_limit);
              /* G */
              buf[i*3+1] = get_checked(ptr, i+pixels_per_line, bytes_per_line_limit);;
              /* B */
              buf[i*3+2] = get_checked(ptr, i+pixels_per_line*2, bytes_per_line_limit);
            }
          else if (scanner->depth == DEPTH_COLOR_48)
            {
              /* Note: The last-line indicator pixel uses only 24 bits, not 48.
               *Blue uses offset of 2 bytes. Green swaps lo and hi.
               */
              /* R lo, hi*/
              buf[i*6]   = get_checked(ptr, 2*i+(pixels_per_line-1)*0+1, bytes_per_line_limit);
              buf[i*6+1] = get_checked(ptr, 2*i+(pixels_per_line-1)*0+0, bytes_per_line_limit);
              /* G lo, hi*/
              buf[i*6+2] = get_checked(ptr, 2*i+(pixels_per_line-1)*2+0, bytes_per_line_limit);
              buf[i*6+3] = get_checked(ptr, 2*i+(pixels_per_line-1)*2+1, bytes_per_line_limit);
              /* B lo, hi*/
              buf[i*6+4] = get_checked(ptr, 2*i+(pixels_per_line-1)*4+1+2, bytes_per_line_limit);
              buf[i*6+5] = get_checked(ptr, 2*i+(pixels_per_line-1)*4+0+2, bytes_per_line_limit);
            }
        }

      if (! scanner->eop_last_line_data)
        {
          if (pixels_per_line > 0)
            {
              /* Test for last-line indicator pixel on blue. If found, store
               * last line and optionally overwrite indicator pixel with
               * neighbor value.
               */
              i = pixels_per_line - 1;
              int eop_found = 0;
              if (scanner->depth == DEPTH_COLOR_24)
                {
                  /* DBG (DBG_details, "BUF24: %u %u %u\n", buf[i*3], buf[i*3+1], buf[i*3+2]); */
                  eop_found = (buf[i*3+2] != 0);
                  if (scanner->overwrite_eop_pixel && (i > 0))
                    {
                      buf[i*3] = buf[(i-1)*3];
                      buf[i*3+1] = buf[(i-1)*3+1];
                      buf[i*3+2] = buf[(i-1)*3+2];
                    }
                }
              else if (scanner->depth == DEPTH_COLOR_48)
                {
                  /* DBG (DBG_details, "BUF48: %u %u %u\n", buf[i*6+1], buf[i*6+3], buf[i*6+5]); */
                  eop_found = (buf[i*6+5] != 0);
                  if (scanner->overwrite_eop_pixel && (i > 0))
                    {
                      buf[i*6] = buf[(i-1)*6];
                      buf[i*6+1] = buf[(i-1)*6+1];
                      buf[i*6+2] = buf[(i-1)*6+2];
                      buf[i*6+3] = buf[(i-1)*6+3];
                      buf[i*6+4] = buf[(i-1)*6+4];
                      buf[i*6+5] = buf[(i-1)*6+5];
                    }
                }

              invert_negative_colors (buf, bytes_per_line, scanner);

              if (eop_found && (! scanner->eop_last_line_data))
                {
                  DBG (DBG_verbose, "Found end-of-page at line %u in reading block.\n", j);
                  scanner->eop_last_line_data = malloc(bytes_per_line);
                  if (! scanner->eop_last_line_data)
                    return SANE_STATUS_NO_MEM;

                  memcpy (scanner->eop_last_line_data, buf, bytes_per_line);
                  scanner->eop_last_line_data_rpos = 0;

                  /* Fill trailing line buffer with requested color. */
                  if (scanner->eop_trailing_lines_mode == TRAILING_LINES_MODE_RASTER)
                    {
                      /* Black-white raster. */
                      if (scanner->depth == DEPTH_COLOR_24)
                        {
                          for (unsigned int k = 0; k < bytes_per_line; ++k)
                            {
                              scanner->eop_last_line_data[k] = (k % 6 < 3 ? 0xff : 0);
                            }
                        }
                      else
                        {
                          /* Color48. */
                          for (unsigned int k = 0; k < bytes_per_line; ++k)
                            {
                              scanner->eop_last_line_data[k] = (k % 12 < 6 ? 0xff : 0);
                            }
                        }
                    }
                  else if (scanner->eop_trailing_lines_mode == TRAILING_LINES_MODE_WHITE)
                    {
                      memset (scanner->eop_last_line_data, 0xff, bytes_per_line);
                    }
                  else if (scanner->eop_trailing_lines_mode == TRAILING_LINES_MODE_BLACK)
                    {
                      memset (scanner->eop_last_line_data, 0x00, bytes_per_line);
                    }
                  else if (scanner->eop_trailing_lines_mode == TRAILING_LINES_MODE_COLOR)
                    {
                      /* RGB color value. */
                      int rgb[3];
                      rgb[0] = (scanner->eop_trailing_lines_color >> 16) & 0xff;
                      rgb[1] = (scanner->eop_trailing_lines_color >> 8) & 0xff;
                      rgb[2] = scanner->eop_trailing_lines_color & 0xff;
                      if (scanner->depth == DEPTH_COLOR_24)
                        {
                          for (unsigned int k = 0; k < bytes_per_line; ++k)
                            {
                              scanner->eop_last_line_data[k] = rgb[k % 3];
                            }
                        }
                      else
                        {
                          /* Color48. */
                          for (unsigned int k = 0; k < bytes_per_line; ++k)
                            {
                              scanner->eop_last_line_data[k] = rgb[(k % 6) >> 1];
                            }
                        }
                    }
                }
            }
        }
      else
        {
          DBG (DBG_verbose, "Trailing lines mode: line=%u, mode=%d, color=%u\n",
               j, scanner->eop_trailing_lines_mode, scanner->eop_trailing_lines_color);

          if ((scanner->source == SOURCE_ADF) || (scanner->source == SOURCE_ADF_DUPLEX))
            {
              /* We are in in ADF mode after last-line and store next page data
               * to buffer.
               */
              if (! scanner->adf_next_page_lines_data)
                {
                  unsigned int n_rest_lines = lines - j;
                  unsigned int buf_size = n_rest_lines * bytes_per_line;
                  scanner->adf_next_page_lines_data = malloc(buf_size);
                  if (! scanner->adf_next_page_lines_data)
                    return SANE_STATUS_NO_MEM;
                  scanner->adf_next_page_lines_data_size = buf_size;
                  scanner->adf_next_page_lines_data_rpos = 0;
                  scanner->adf_next_page_lines_data_wpos = 0;
                  DBG (DBG_verbose, "ADF between pages: Save n=%u next page lines in buffer.\n", n_rest_lines);
                }
              DBG (DBG_verbose, "ADF between pages: Store line %u of %u.\n", j, lines);
              invert_negative_colors (buf, bytes_per_line, scanner);
              memcpy (scanner->adf_next_page_lines_data + scanner->adf_next_page_lines_data_wpos, buf, bytes_per_line);
              scanner->adf_next_page_lines_data_wpos += bytes_per_line;
            }

          if (scanner->eop_trailing_lines_mode != TRAILING_LINES_MODE_RAW)
            {
              /* Copy last line data or corresponding color over trailing lines
               * data.
               */
              memcpy (buf, scanner->eop_last_line_data, bytes_per_line);
            }
        }
    }
  memcpy (data, bufptr, size);
  free (bufptr);

  return SANE_STATUS_GOOD;
}

/******************************************************************************/
static void
read_data_from_temporary_buffer(struct hp5590_scanner *scanner,
    SANE_Byte * data, unsigned int max_length,
    unsigned int bytes_per_line, SANE_Int *length)
{
  *length = 0;
  if (scanner && scanner->one_line_read_buffer)
  {
    /* Copy scan data from temporary read buffer and return size copied data. */
    /* Release buffer, when no data left. */
    unsigned int rest_len;
    rest_len = bytes_per_line - scanner->one_line_read_buffer_rpos;
    rest_len = (rest_len < max_length) ? rest_len : max_length;
    if (rest_len > 0)
      {
        memcpy (data, scanner->one_line_read_buffer + scanner->one_line_read_buffer_rpos, rest_len);
        scanner->one_line_read_buffer_rpos += rest_len;
        scanner->transferred_image_size -= rest_len;
        *length = rest_len;
      }

    DBG (DBG_verbose, "Copy scan data from temporary buffer: length = %u, rest in buffer = %u.\n",
        *length, bytes_per_line - scanner->one_line_read_buffer_rpos);

    if (scanner->one_line_read_buffer_rpos >= bytes_per_line)
      {
        DBG (DBG_verbose, "Release temporary buffer.\n");
        free (scanner->one_line_read_buffer);
        scanner->one_line_read_buffer = NULL;
        scanner->one_line_read_buffer_rpos = 0;
      }
  }
}

/******************************************************************************/
static SANE_Status
sane_read_internal (struct hp5590_scanner * scanner, SANE_Byte * data,
    SANE_Int max_length, SANE_Int * length, unsigned int bytes_per_line)
{
  SANE_Status ret;

  DBG (DBG_proc, "%s, length %u, left %llu\n",
       __func__,
       max_length,
       scanner->transferred_image_size);

  /*
   * We will truncate down the buffer size to *under* what the
   * internal USB reading buffer can supply. This will avoid page read issues
   * at the end of the buffer.
   *
   * See: https://gitlab.com/sane-project/backends/-/issues/781
   *
   */
  if (max_length > BULK_READ_PAGE_SIZE * MAX_READ_PAGES)
    {
      DBG (DBG_proc, "%s, truncating sane_read buffer from %u to %u\n",
           __func__,
           max_length,
	   BULK_READ_PAGE_SIZE * MAX_READ_PAGES);

      max_length = BULK_READ_PAGE_SIZE * MAX_READ_PAGES;
    }

  SANE_Int length_limited = 0;
  *length = max_length;
  if ((unsigned long long) *length > scanner->transferred_image_size)
    *length = (SANE_Int) scanner->transferred_image_size;

  /* Align reading size to bytes per line. */
  *length -= *length % bytes_per_line;

  if (scanner->depth == DEPTH_COLOR_48)
    {
      /* Note: The last-line indicator pixel uses only 24 bits (3 bytes), not
       * 48 bits (6 bytes).
       */
      if (bytes_per_line > 3)
        {
          length_limited = *length - *length % (bytes_per_line - 3);
        }
    }

  DBG (DBG_verbose, "Aligning requested size to bytes per line "
      "(requested: %d, aligned: %u, limit_for_48bit: %u)\n",
      max_length, *length, length_limited);

  if (max_length <= 0)
    {
      DBG (DBG_verbose, "Buffer too small for one scan line. Need at least %u bytes per line.\n",
          bytes_per_line);
      scanner->scanning = SANE_FALSE;
      return SANE_STATUS_UNSUPPORTED;
    }

  if (scanner->one_line_read_buffer)
    {
      /* Copy scan data from temporary read buffer. */
      read_data_from_temporary_buffer (scanner, data, max_length, bytes_per_line, length);
      if (*length > 0)
        {
          DBG (DBG_verbose, "Return %d bytes, left %llu bytes.\n", *length, scanner->transferred_image_size);
          return SANE_STATUS_GOOD;
        }
    }

  /* Buffer to return scanned data. We need at least space for one line to
   * simplify color processing and last-line detection. If call buffer is too
   * small, use temporary read buffer for reading one line instead.
   */
  SANE_Byte * scan_data;
  SANE_Int scan_data_length;
  scan_data = data;
  scan_data_length = *length;

  /* Note, read length is shorter in 48bit mode. */
  SANE_Int length_for_read = length_limited ? length_limited : scan_data_length;
  if (length_for_read == 0)
    {
      /* Call buffer is too small for one line. Use temporary read buffer
       * instead.
       */
      if (! scanner->one_line_read_buffer)
        {
          scanner->one_line_read_buffer = malloc (bytes_per_line);
          if (! scanner->one_line_read_buffer)
            return SANE_STATUS_NO_MEM;
          memset (scanner->one_line_read_buffer, 0, bytes_per_line);
        }

      DBG (DBG_verbose, "Call buffer too small for one scan line. Use temporary read buffer for one line with %u bytes.\n",
          bytes_per_line);

      /* Scan and process next line in temporary buffer. */
      scan_data = scanner->one_line_read_buffer;
      scan_data_length = bytes_per_line;
      length_for_read = bytes_per_line;
      if (scanner->depth == DEPTH_COLOR_48)
        {
          /* The last-line indicator pixel uses only 24 bits (3 bytes), not 48
           * bits (6 bytes).
           */
          if (length_for_read > 3)
            {
              length_for_read -= 3;
            }
        }
    }

  int read_from_scanner = 1;
  if ((scanner->source == SOURCE_ADF) || (scanner->source == SOURCE_ADF_DUPLEX))
    {
      if (scanner->eop_last_line_data)
        {
          /* Scanner is in ADF mode between last-line of previous page and
           * start of next page.
           * Fill remaining lines with last-line data.
           */
          unsigned int wpos = 0;
          while (wpos < (unsigned int) scan_data_length)
            {
              unsigned int n1 = scan_data_length - wpos;
              unsigned int n2 = bytes_per_line - scanner->eop_last_line_data_rpos;
              n1 = (n1 < n2) ? n1 : n2;
              memcpy (scan_data + wpos, scanner->eop_last_line_data + scanner->eop_last_line_data_rpos, n1);
              wpos += n1;
              scanner->eop_last_line_data_rpos += n1;
              if (scanner->eop_last_line_data_rpos >= bytes_per_line)
                scanner->eop_last_line_data_rpos = 0;
            }
          read_from_scanner = (wpos == 0);
          DBG (DBG_verbose, "ADF use last-line data, wlength=%u, length=%u\n", wpos, scan_data_length);
        }
      else if (scanner->adf_next_page_lines_data)
        {
          /* Scanner is in ADF mode at start of next page and already some next
           * page data is available from earlier read operation. Return this
           * data.
           */
          unsigned int wpos = 0;
          while ((wpos < (unsigned int) scan_data_length) &&
                 (scanner->adf_next_page_lines_data_rpos < scanner->adf_next_page_lines_data_size))
            {
              unsigned int n1 = scan_data_length - wpos;
              unsigned int n2 = scanner->adf_next_page_lines_data_size - scanner->adf_next_page_lines_data_rpos;
              n1 = (n1 < n2) ? n1 : n2;
              memcpy (scan_data + wpos, scanner->adf_next_page_lines_data + scanner->adf_next_page_lines_data_rpos, n1);
              wpos += n1;
              scanner->adf_next_page_lines_data_rpos += n1;
              if (scanner->adf_next_page_lines_data_rpos >= scanner->adf_next_page_lines_data_size)
                {
                  free (scanner->adf_next_page_lines_data);
                  scanner->adf_next_page_lines_data = NULL;
                  scanner->adf_next_page_lines_data_size = 0;
                  scanner->adf_next_page_lines_data_rpos = 0;
                  scanner->adf_next_page_lines_data_wpos = 0;
                }
            }
          scan_data_length = wpos;
          read_from_scanner = (wpos == 0);
          DBG (DBG_verbose, "ADF use next-page data, wlength=%u, length=%u\n", wpos, scan_data_length);
        }
    }

  if (read_from_scanner)
    {
      /* Read data from scanner. */
      ret = hp5590_read (scanner->dn, scanner->proto_flags,
                         scan_data, length_for_read,
                         scanner->bulk_read_state);
      if (ret != SANE_STATUS_GOOD)
        {
          scanner->scanning = SANE_FALSE;
          return ret;
        }

      /* Look for last-line indicator pixels in convert functions.
       * If found:
       *   - Overwrite indicator pixel with neighboring color (optional).
       *   - Save last line data for later use.
       */
      ret = convert_to_rgb (scanner, scan_data, scan_data_length);
      if (ret != SANE_STATUS_GOOD)
        {
          scanner->scanning = SANE_FALSE;
          return ret;
        }

      ret = convert_gray_and_lineart (scanner, scan_data, scan_data_length);
      if (ret != SANE_STATUS_GOOD)
        return ret;
    }

  if (data == scan_data)
    {
      /* Scanned to call buffer. */
      scanner->transferred_image_size -= scan_data_length;
      *length = scan_data_length;
    }
  else
    {
      /* Scanned to temporary read buffer. */
      if (scanner->one_line_read_buffer)
        {
          /* Copy scan data from temporary read buffer. */
          read_data_from_temporary_buffer (scanner, data, max_length, scan_data_length, length);
        }
      else
        {
          *length = 0;
        }
    }

  DBG (DBG_verbose, "Return %d bytes, left %llu bytes\n", *length, scanner->transferred_image_size);
  return SANE_STATUS_GOOD;
}

/******************************************************************************
 * Copy at maximum the last n lines from the src buffer to the begin of the dst
 * buffer.
 * Return number of lines copied.
 */
static SANE_Int
copy_n_last_lines(SANE_Byte * src, SANE_Int src_len, SANE_Byte * dst, SANE_Int n, unsigned int bytes_per_line)
{
  DBG (DBG_proc, "%s\n", __func__);
  SANE_Int n_copy = MY_MIN(src_len, n);
  SANE_Byte * src1 = src + (src_len - n_copy) * bytes_per_line;
  memcpy (dst, src1, n_copy * bytes_per_line);
  return n_copy;
}

/******************************************************************************
 * Copy the color values from line - delta_lines to line.
 * buffer2 : Source and target buffer.
 * buffer1 : Only source buffer. Contains lines scanned before lines in buffer1.
 * color_idx : Index of color to be copied (0..2).
 * delta_lines : color shift.
 * color_48 : True = 2 byte , false = 1 byte per color.
 */
static void
shift_color_lines(SANE_Byte * buffer2, SANE_Int n_lines2, SANE_Byte * buffer1, SANE_Int n_lines1, SANE_Int color_idx, SANE_Int delta_lines, SANE_Bool color_48, unsigned int bytes_per_line)
{
  DBG (DBG_proc, "%s\n", __func__);
  for (SANE_Int i = n_lines2 - 1; i >= 0; --i) {
    SANE_Byte * dst = buffer2 + i * bytes_per_line;
    SANE_Int ii = i - delta_lines;
    SANE_Byte * src = NULL;
    SANE_Int source_color_idx = color_idx;
    if (ii >= 0) {
      /* Read from source and target buffer. */
      src = buffer2 + ii * bytes_per_line;
    } else {
      ii += n_lines1;
      if (ii >= 0) {
        /* Read from source only buffer. */
        src = buffer1 + ii * bytes_per_line;
      } else {
        /* Read other color from source position. */
        src = dst;
        source_color_idx = 2;
      }
    }
    /* Copy selected color values. */
    SANE_Int step = color_48 ? 2 : 1;
    SANE_Int stride = 3 * step;
    for (unsigned int pos = 0; pos < bytes_per_line; pos += stride) {
      SANE_Int p1 = pos + step * source_color_idx;
      SANE_Int p2 = pos + step * color_idx;
      dst[p2] = src[p1];
      if (color_48) {
        dst[p2 + 1] = src[p1 + 1];
      }
    }
  }
}

/******************************************************************************
 * Append all lines from buffer2 to the end of buffer1 and keep max_lines last
 * lines.
 * buffer2 : Source line buffer.
 * buffer1 : Target line buffer. Length will be adjusted.
 * max_lines : Max number of lines in buffer1.
 */
static void
append_and_move_lines(SANE_Byte * buffer2, SANE_Int n_lines2, SANE_Byte * buffer1, unsigned int * n_lines1_ptr, SANE_Int max_lines, unsigned int bytes_per_line)
{
  DBG (DBG_proc, "%s\n", __func__);
  SANE_Int rest1 = max_lines - *n_lines1_ptr;
  SANE_Int copy2 = MY_MIN(n_lines2, max_lines);
  if (copy2 > rest1) {
    SANE_Int shift1 = *n_lines1_ptr + copy2 - max_lines;
    SANE_Int blen = MY_MIN(max_lines - shift1, (SANE_Int) *n_lines1_ptr);
    SANE_Byte * pdst = buffer1;
    SANE_Byte * psrc = pdst + shift1 * bytes_per_line;
    for (SANE_Int i = 0; i < blen; ++i) {
      memcpy (pdst, psrc, bytes_per_line);
      pdst += bytes_per_line;
      psrc += bytes_per_line;
    }
    *n_lines1_ptr -= shift1;
  }
  SANE_Int n_copied = copy_n_last_lines(buffer2, n_lines2, buffer1 + *n_lines1_ptr * bytes_per_line, copy2, bytes_per_line);
  *n_lines1_ptr += n_copied;
}


/******************************************************************************/
SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * data,
           SANE_Int max_length, SANE_Int * length)
{
  struct hp5590_scanner *scanner = handle;
  SANE_Status ret;

  DBG (DBG_proc, "%s, length %u, left %llu\n",
       __func__,
       max_length,
       scanner->transferred_image_size);

  if (!length)
    {
      scanner->scanning = SANE_FALSE;
      return SANE_STATUS_INVAL;
    }

  if (scanner->transferred_image_size == 0)
    {
      *length = 0;
      DBG (DBG_verbose, "Setting scan count\n");

      ret = hp5590_inc_scan_count (scanner->dn, scanner->proto_flags);
      if (ret != SANE_STATUS_GOOD)
        return ret;

      /* Don't free bulk read state, some bytes could be left
       * for the next images from ADF
       */
      return SANE_STATUS_EOF;
    }

  if (!scanner->bulk_read_state)
    {
      ret = hp5590_low_init_bulk_read_state (&scanner->bulk_read_state);
      if (ret != SANE_STATUS_GOOD)
        {
          scanner->scanning = SANE_FALSE;
          return ret;
        }
    }

  unsigned int bytes_per_line;
  ret = calc_image_params (scanner,
                           NULL, NULL,
                           &bytes_per_line,
                           NULL, NULL);
  if (ret != SANE_STATUS_GOOD)
    return ret;

  ret = sane_read_internal(scanner, data, max_length, length, bytes_per_line);

  if ((ret == SANE_STATUS_GOOD) && (scanner->dpi == 2400) &&
          ((scanner->depth == DEPTH_COLOR_48) || (scanner->depth == DEPTH_COLOR_24)))
    {
      /* Correct color shift bug for 2400 dpi.
       * Note: 2400 dpi only works in color mode. Grey mode and lineart seem to
       * fail.
       * Align colors by shifting B channel by 48 lines and G channel by 24
       * lines.
       */
      const SANE_Int offset_max = 48;
      const SANE_Int offset_part = 24;
      SANE_Bool color_48 = (scanner->depth == DEPTH_COLOR_48);

      if (! scanner->color_shift_line_buffer1)
        {
          scanner->color_shift_buffered_lines1 = 0;
          scanner->color_shift_line_buffer1 = malloc (bytes_per_line * offset_max);
          if (! scanner->color_shift_line_buffer1)
            return SANE_STATUS_NO_MEM;
          memset (scanner->color_shift_line_buffer1, 0, bytes_per_line * offset_max);
        }
      if (! scanner->color_shift_line_buffer2)
        {
          scanner->color_shift_buffered_lines2 = 0;
          scanner->color_shift_line_buffer2 = malloc (bytes_per_line * offset_max);
          if (! scanner->color_shift_line_buffer2)
            return SANE_STATUS_NO_MEM;
          memset (scanner->color_shift_line_buffer2, 0, bytes_per_line * offset_max);
        }

      SANE_Int n_lines = *length / bytes_per_line;
      scanner->color_shift_buffered_lines2 = MY_MIN(n_lines, offset_max);
      copy_n_last_lines(data, n_lines, scanner->color_shift_line_buffer2, scanner->color_shift_buffered_lines2, bytes_per_line);

      shift_color_lines(data, n_lines, scanner->color_shift_line_buffer1, scanner->color_shift_buffered_lines1, 1, offset_part, color_48, bytes_per_line);
      shift_color_lines(data, n_lines, scanner->color_shift_line_buffer1, scanner->color_shift_buffered_lines1, 0, offset_max, color_48, bytes_per_line);

      append_and_move_lines(scanner->color_shift_line_buffer2, scanner->color_shift_buffered_lines2, scanner->color_shift_line_buffer1, &(scanner->color_shift_buffered_lines1), offset_max, bytes_per_line);
    }

  return ret;
}

/******************************************************************************/
void
sane_cancel (SANE_Handle handle)
{
  struct hp5590_scanner *scanner = handle;
  SANE_Status ret;

  DBG (DBG_proc, "%s\n", __func__);

  scanner->scanning = SANE_FALSE;

  if (scanner->dn < 0)
   return;

  hp5590_low_free_bulk_read_state (&scanner->bulk_read_state);

  ret = hp5590_stop_scan (scanner->dn, scanner->proto_flags);
  if (ret != SANE_STATUS_GOOD)
    return;
}

/******************************************************************************/

SANE_Status
sane_set_io_mode (SANE_Handle __sane_unused__ handle,
                  SANE_Bool __sane_unused__ non_blocking)
{
  DBG (DBG_proc, "%s\n", __func__);

  return SANE_STATUS_UNSUPPORTED;
}

/******************************************************************************/
SANE_Status
sane_get_select_fd (SANE_Handle __sane_unused__ handle,
                    SANE_Int __sane_unused__ * fd)
{
  DBG (DBG_proc, "%s\n", __func__);

  return SANE_STATUS_UNSUPPORTED;
}

/* vim: sw=2 ts=8
 */
