/* sane - Scanner Access Now Easy.
   Copyright (C) 2004, 2005 Anderson Lizardo <lizardo@users.sourceforge.net>

   This file is part of the SANE package.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
   MA 02111-1307, USA.

   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.

   SANE backend for the Genius ColorPage-Vivid Pro II scanner.
*/

#define BUILD 0

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

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

#include "geniusvp2.h"
#include "geniusvp2-asic.h"
#include "geniusvp2-misc.h"
#include "geniusvp2-parport.h"
#include "geniusvp2-switcher.h"

#include "../include/sane/saneopts.h"
#include "../include/sane/sanei_thread.h"

#define BACKEND_VERSION "0.2"

#define VP2_CONFIG_FILE "geniusvp2.conf"

#define VP2_DEFAULT_DEVICE "/dev/parport0"

static SANE_Bool inited = SANE_FALSE;
static int num_devices = 0;
static SANE_Device **sane_device_list = NULL;
static VP2_Device *first_vp2_device = NULL;

static SANE_Range x_geometry_range = {
    SANE_FIX (0.0),
    SANE_FIX (216.0),
    SANE_FIX (1.0)
};

static SANE_Range y_geometry_range = {
    SANE_FIX (0.0),
    SANE_FIX (297.0),
    SANE_FIX (1.0)
};

static SANE_Range resolution_range = {
    75,
    600,
    1
};

static SANE_String_Const mode_list[] = {
    SANE_VALUE_SCAN_MODE_COLOR,
    0
};

static SANE_Int depth_list[] = {
    1, 8
};

static void
swap_double (double *a, double *b)
{
    double c;

    c = *a;
    *a = *b;
    *b = c;

    return;
}

static size_t
max_string_size (const SANE_String_Const strings[])
{
    size_t size, max_size = 0;
    SANE_Int i;

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

static SANE_Bool
check_handle (SANE_Handle handle)
{
    VP2_Device *vp2_device = first_vp2_device;

    while (vp2_device)
    {
        if (vp2_device == (VP2_Device *) handle)
            return SANE_TRUE;
        vp2_device = vp2_device->next;
    }
    return SANE_FALSE;
}

static SANE_Status
init_options (VP2_Device * vp2_device)
{
    SANE_Option_Descriptor *od;

    DBG (2, "init_options: vp2_device=%p\n", (void *) vp2_device);

    /* opt_num_opts */
    od = &vp2_device->opt[opt_num_opts];
    od->name = "";
    od->title = SANE_TITLE_NUM_OPTIONS;
    od->desc = SANE_DESC_NUM_OPTIONS;
    od->type = SANE_TYPE_INT;
    od->unit = SANE_UNIT_NONE;
    od->size = sizeof (SANE_Word);
    od->cap = SANE_CAP_SOFT_DETECT;
    od->constraint_type = SANE_CONSTRAINT_NONE;
    od->constraint.range = 0;
    vp2_device->val[opt_num_opts].w = num_options;

    /* opt_mode_group */
    od = &vp2_device->opt[opt_mode_group];
    od->name = "";
    od->title = SANE_I18N ("Scan Mode");
    od->desc = "";
    od->type = SANE_TYPE_GROUP;
    od->unit = SANE_UNIT_NONE;
    od->size = 0;
    od->cap = 0;
    od->constraint_type = SANE_CONSTRAINT_NONE;
    od->constraint.range = 0;
    vp2_device->val[opt_mode_group].w = 0;

    /* opt_mode */
    od = &vp2_device->opt[opt_mode];
    od->name = SANE_NAME_SCAN_MODE;
    od->title = SANE_TITLE_SCAN_MODE;
    od->desc = SANE_DESC_SCAN_MODE;
    od->type = SANE_TYPE_STRING;
    od->unit = SANE_UNIT_NONE;
    od->size = max_string_size (mode_list);
    od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
    od->constraint_type = SANE_CONSTRAINT_STRING_LIST;
    od->constraint.string_list = mode_list;
    vp2_device->val[opt_mode].s = malloc (od->size);
    if (!vp2_device->val[opt_mode].s)
        return SANE_STATUS_NO_MEM;
    strcpy (vp2_device->val[opt_mode].s, "Color");

    /* opt_depth */
    od = &vp2_device->opt[opt_depth];
    od->name = SANE_NAME_BIT_DEPTH;
    od->title = SANE_TITLE_BIT_DEPTH;
    od->desc = SANE_DESC_BIT_DEPTH;
    od->type = SANE_TYPE_INT;
    od->unit = SANE_UNIT_NONE;
    od->size = sizeof (SANE_Word);
    od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
    od->constraint_type = SANE_CONSTRAINT_WORD_LIST;
    od->constraint.word_list = depth_list;
    vp2_device->val[opt_depth].w = 8;

    /* opt_resolution */
    od = &vp2_device->opt[opt_resolution];
    od->name = SANE_NAME_SCAN_RESOLUTION;
    od->title = SANE_TITLE_SCAN_RESOLUTION;
    od->desc = SANE_DESC_SCAN_RESOLUTION;
    od->type = SANE_TYPE_INT;
    od->unit = SANE_UNIT_DPI;
    od->size = sizeof (SANE_Word);
    od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
    od->constraint_type = SANE_CONSTRAINT_RANGE;
    od->constraint.range = &resolution_range;
    vp2_device->val[opt_resolution].w = 200;

    /* opt_special_group */
    od = &vp2_device->opt[opt_special_group];
    od->name = "";
    od->title = SANE_I18N ("Special Options");
    od->desc = "";
    od->type = SANE_TYPE_GROUP;
    od->unit = SANE_UNIT_NONE;
    od->size = 0;
    od->cap = 0;
    od->constraint_type = SANE_CONSTRAINT_NONE;
    od->constraint.range = 0;
    vp2_device->val[opt_special_group].w = 0;

    /* opt_print_options */
    od = &vp2_device->opt[opt_print_options];
    od->name = "print-options";
    od->title = SANE_I18N ("Print options");
    od->desc = SANE_I18N ("Print a list of all options.");
    od->type = SANE_TYPE_BUTTON;
    od->unit = SANE_UNIT_NONE;
    od->size = 0;
    od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
    od->constraint_type = SANE_CONSTRAINT_NONE;
    od->constraint.string_list = 0;
    vp2_device->val[opt_print_options].w = 0;

    /* opt_geometry_group */
    od = &vp2_device->opt[opt_geometry_group];
    od->name = "";
    od->title = SANE_I18N ("Geometry");
    od->desc = "";
    od->type = SANE_TYPE_GROUP;
    od->unit = SANE_UNIT_NONE;
    od->size = 0;
    od->cap = 0;
    od->constraint_type = SANE_CONSTRAINT_NONE;
    od->constraint.range = 0;
    vp2_device->val[opt_geometry_group].w = 0;

    /* opt_tl_x */
    od = &vp2_device->opt[opt_tl_x];
    od->name = SANE_NAME_SCAN_TL_X;
    od->title = SANE_TITLE_SCAN_TL_X;
    od->desc = SANE_DESC_SCAN_TL_X;
    od->type = SANE_TYPE_FIXED;
    od->unit = SANE_UNIT_MM;
    od->size = sizeof (SANE_Word);
    od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
    od->constraint_type = SANE_CONSTRAINT_RANGE;
    od->constraint.range = &x_geometry_range;
    vp2_device->val[opt_tl_x].w = SANE_FIX (0.0);

    /* opt_tl_y */
    od = &vp2_device->opt[opt_tl_y];
    od->name = SANE_NAME_SCAN_TL_Y;
    od->title = SANE_TITLE_SCAN_TL_Y;
    od->desc = SANE_DESC_SCAN_TL_Y;
    od->type = SANE_TYPE_FIXED;
    od->unit = SANE_UNIT_MM;
    od->size = sizeof (SANE_Word);
    od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
    od->constraint_type = SANE_CONSTRAINT_RANGE;
    od->constraint.range = &y_geometry_range;
    vp2_device->val[opt_tl_y].w = SANE_FIX (0.0);

    /* opt_br_x */
    od = &vp2_device->opt[opt_br_x];
    od->name = SANE_NAME_SCAN_BR_X;
    od->title = SANE_TITLE_SCAN_BR_X;
    od->desc = SANE_DESC_SCAN_BR_X;
    od->type = SANE_TYPE_FIXED;
    od->unit = SANE_UNIT_MM;
    od->size = sizeof (SANE_Word);
    od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
    od->constraint_type = SANE_CONSTRAINT_RANGE;
    od->constraint.range = &x_geometry_range;
    vp2_device->val[opt_br_x].w = SANE_FIX (80.0);

    /* opt_br_y */
    od = &vp2_device->opt[opt_br_y];
    od->name = SANE_NAME_SCAN_BR_Y;
    od->title = SANE_TITLE_SCAN_BR_Y;
    od->desc = SANE_DESC_SCAN_BR_Y;
    od->type = SANE_TYPE_FIXED;
    od->unit = SANE_UNIT_MM;
    od->size = sizeof (SANE_Word);
    od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
    od->constraint_type = SANE_CONSTRAINT_RANGE;
    od->constraint.range = &y_geometry_range;
    vp2_device->val[opt_br_y].w = SANE_FIX (100.0);

    return SANE_STATUS_GOOD;
}

static SANE_Status
read_option (SANE_String line, SANE_String option_string,
             parameter_type p_type, void *value)
{
    SANE_String_Const cp;
    SANE_Char *word, *end;

    word = NULL;

    cp = sanei_config_get_string (line, &word);

    if (!word)
        return SANE_STATUS_INVAL;

    if (strcmp (word, option_string) != 0)
        return SANE_STATUS_INVAL;

    free (word);
    word = NULL;

    switch (p_type)
    {
    case param_none:
        return SANE_STATUS_GOOD;
    case param_bool:
        {
            cp = sanei_config_get_string (cp, &word);
            if (!word)
                return SANE_STATUS_INVAL;
            if (strlen (word) == 0)
            {
                DBG (3, "read_option: option `%s' requires parameter\n",
                     option_string);
                return SANE_STATUS_INVAL;
            }
            if (strcmp (word, "true") != 0 && strcmp (word, "false") != 0)
            {
                DBG (3, "read_option: option `%s' requires parameter "
                     "`true' or `false'\n", option_string);
                return SANE_STATUS_INVAL;
            }
            else if (strcmp (word, "true") == 0)
                *(SANE_Bool *) value = SANE_TRUE;
            else
                *(SANE_Bool *) value = SANE_FALSE;
            DBG (3, "read_option: set option `%s' to %s\n", option_string,
                 *(SANE_Bool *) value == SANE_TRUE ? "true" : "false");
            break;
        }
    case param_int:
        {
            SANE_Int int_value;

            cp = sanei_config_get_string (cp, &word);
            if (!word)
                return SANE_STATUS_INVAL;
            errno = 0;
            int_value = (SANE_Int) strtol (word, &end, 0);
            if (end == word)
            {
                DBG (3, "read_option: option `%s' requires parameter\n",
                     option_string);
                return SANE_STATUS_INVAL;
            }
            else if (errno)
            {
                DBG (3,
                     "read_option: option `%s': can't parse parameter `%s' "
                     "(%s)\n", option_string, word, strerror (errno));
                return SANE_STATUS_INVAL;
            }
            else
            {
                DBG (3, "read_option: set option `%s' to %d\n",
                     option_string, int_value);
                *(SANE_Int *) value = int_value;
            }
            break;
        }
    case param_fixed:
        {
            double double_value;
            SANE_Fixed fixed_value;

            cp = sanei_config_get_string (cp, &word);
            if (!word)
                return SANE_STATUS_INVAL;
            errno = 0;
            double_value = strtod (word, &end);
            if (end == word)
            {
                DBG (3, "read_option: option `%s' requires parameter\n",
                     option_string);
                return SANE_STATUS_INVAL;
            }
            else if (errno)
            {
                DBG (3,
                     "read_option: option `%s': can't parse parameter `%s' "
                     "(%s)\n", option_string, word, strerror (errno));
                return SANE_STATUS_INVAL;
            }
            else
            {
                DBG (3, "read_option: set option `%s' to %.0f\n",
                     option_string, double_value);
                fixed_value = SANE_FIX (double_value);
                *(SANE_Fixed *) value = fixed_value;
            }
            break;
        }
    case param_string:
        {
            cp = sanei_config_get_string (cp, &word);
            if (!word)
                return SANE_STATUS_INVAL;
            if (strlen (word) == 0)
            {
                DBG (3, "read_option: option `%s' requires parameter\n",
                     option_string);
                return SANE_STATUS_INVAL;
            }
            else
            {
                DBG (3, "read_option: set option `%s' to `%s'\n",
                     option_string, word);
                *(SANE_String *) value = strdup (word);
            }
            break;
        }
    default:
        DBG (1, "read_option: unknown param_type %d\n", p_type);
        return SANE_STATUS_INVAL;
    }                           /* switch */

    if (word)
        free (word);
    word = NULL;
    return SANE_STATUS_GOOD;
}

static SANE_Status
reader_process (VP2_Device * vp2_device, SANE_Int fd)
{
    SANE_Word byte_count = 0, bytes_total;
    SANE_Byte *buffer = NULL, *buffer_ptr;
    ssize_t bytes_written = 0, buffer_size = 0;
    size_t write_count = 0;

    DBG (2, "(child) reader_process: vp2_device=%p, fd=%d\n",
         (void *) vp2_device, fd);

    bytes_total = vp2_device->lines * vp2_device->bytes_per_line;

    buffer = malloc (vp2_device->bytes_per_line);
    if (!buffer)
    {
        DBG (1, "(child) reader_process: could not malloc buffer\n");
        return SANE_STATUS_NO_MEM;
    }
    DBG (2, "(child) reader_process: buffer=%p\n", buffer);

    while (byte_count < bytes_total)
    {
        buffer_size = sane_geniusvp2_wait_fifo (vp2_device->bytes_per_line);
        if (buffer_size > 0)
        {
            if (buffer_size > vp2_device->bytes_per_line)
                buffer_size = vp2_device->bytes_per_line;
            sane_geniusvp2_read_scan_data (buffer, buffer_size);
        }
        else if (buffer_size == 0)
        {
            DBG (1, "(child) reader_process: scanner returned EOF\n");
            /*write (fd, NULL, 0);*/
            close (fd);
            break;
        }
        else
        {
            DBG (1, "(child) reader_process: scanner timed out\n");
            free (buffer);
            close (fd);
            return SANE_STATUS_IO_ERROR;
        }

        write_count = buffer_size;
        if (byte_count + (SANE_Word) write_count > bytes_total)
            write_count = bytes_total - byte_count;

        buffer_ptr = buffer;
        while (write_count > 0)
        {
            bytes_written = write (fd, buffer_ptr, write_count);
            if (bytes_written < 0)
            {
                DBG (1, "(child) reader_process: write returned %s\n",
                     strerror (errno));
                return SANE_STATUS_IO_ERROR;
            }
            byte_count += bytes_written;
            DBG (4,
                 "(child) reader_process: wrote %ld bytes of %lu (%d total)\n",
                 (long) bytes_written, (u_long) write_count, byte_count);
            buffer_ptr += bytes_written;
            write_count -= bytes_written;
        }
    }

    free (buffer);

    if (sanei_thread_is_forked ())
    {
        DBG (4,
             "(child) reader_process: finished,  wrote %d bytes, expected %d "
             "bytes, now waiting\n", byte_count, bytes_total);
        while (SANE_TRUE)
            sleep (10);
        DBG (4, "(child) reader_process: this should have never happened...");
        close (fd);
    }
    else
    {
        DBG (4,
             "(child) reader_process: finished,  wrote %d bytes, expected %d "
             "bytes\n", byte_count, bytes_total);
    }
    return SANE_STATUS_GOOD;
}

/*
 * this code either runs in child or thread context...
 */
static int
reader_task (void *data)
{
    SANE_Status status;
    struct SIGACTION act;
    struct VP2_Device *vp2_device = (struct VP2_Device *) data;

    DBG (2, "reader_task started\n");
    if (sanei_thread_is_forked ())
    {
        DBG (3, "reader_task started (forked)\n");
        close (vp2_device->pipe);
        vp2_device->pipe = -1;

    }
    else
    {
        DBG (3, "reader_task started (as thread)\n");
    }

    memset (&act, 0, sizeof (act));
    sigaction (SIGTERM, &act, NULL);

    status = reader_process (vp2_device, vp2_device->reader_fds);
    DBG (2, "(child) reader_task: reader_process finished (%s)\n",
         sane_strstatus (status));
    return (int) status;
}

static SANE_Status
finish_pass (VP2_Device * vp2_device)
{
    SANE_Status return_status = SANE_STATUS_GOOD;

    DBG (2, "finish_pass: vp2_device=%p\n", (void *) vp2_device);
    vp2_device->scanning = SANE_FALSE;
    if (vp2_device->pipe >= 0)
    {
        DBG (2, "finish_pass: closing pipe\n");
        close (vp2_device->pipe);
        DBG (2, "finish_pass: pipe closed\n");
        vp2_device->pipe = -1;
    }
    if (vp2_device->reader_pid > 0)
    {
        int status;
        int pid;

        DBG (2, "finish_pass: terminating reader process %d\n",
             vp2_device->reader_pid);
        sanei_thread_kill (vp2_device->reader_pid);
        pid = sanei_thread_waitpid (vp2_device->reader_pid, &status);
        if (pid < 0)
        {
            DBG (1,
                 "finish_pass: sanei_thread_waitpid failed, already terminated? (%s)\n",
                 strerror (errno));
        }
        else
        {
            DBG (2,
                 "finish_pass: reader process terminated with status: %s\n",
                 sane_strstatus (status));
        }
        vp2_device->reader_pid = 0;
    }
    /* this happens when running in thread context... */
    if (vp2_device->reader_fds >= 0)
    {
        DBG (2, "finish_pass: closing reader pipe\n");
        close (vp2_device->reader_fds);
        DBG (2, "finish_pass: reader pipe closed\n");
        vp2_device->reader_fds = -1;
    }

    sane_geniusvp2_go_home ();

    return return_status;
}

static SANE_Status
lamp_warmup (int warmup_time)
{
    SANE_Status status = SANE_STATUS_GOOD;
    static struct timeval last_warmup;
    unsigned int remain_time;

    DBG (2, "lamp_warmup: warmup_time=%d\n", warmup_time);

    if (sane_geniusvp2_is_lamp_on ())
    {
        struct timeval current_time;

        if (gettimeofday (&current_time, NULL) == -1)
        {
            DBG (1, "lamp_warmup: gettimeofday returned %s\n",
                 strerror (errno));
            return SANE_STATUS_IO_ERROR;
        }
        if (current_time.tv_sec - last_warmup.tv_sec < warmup_time)
        {
            remain_time = warmup_time - (current_time.tv_sec -
                                         last_warmup.tv_sec);
        }
        else
            remain_time = 0;
    }
    else
    {
        status = sane_geniusvp2_lamp_on ();
        if (gettimeofday (&last_warmup, NULL) == -1)
        {
            DBG (1, "lamp_warmup: gettimeofday returned %s\n",
                 strerror (errno));
            return SANE_STATUS_IO_ERROR;
        }
        remain_time = warmup_time;
    }

    if (!remain_time)
        DBG (2, "lamp_warmup: lamp warm up is not necessary\n");
    else
    {
        DBG (2, "lamp_warmup: lamp not completely warmed up, "
             "remaining time = %d sec\n", remain_time);
        sleep (remain_time);
    }

    return status;
}

static void
print_options (VP2_Device * vp2_device)
{
    SANE_Option_Descriptor *od;
    SANE_Word option_number;
    SANE_Char caps[1024];

    for (option_number = 0; option_number < num_options; option_number++)
    {
        od = &vp2_device->opt[option_number];
        DBG (0, "-----> number: %d\n", option_number);
        DBG (0, "         name: `%s'\n", od->name);
        DBG (0, "        title: `%s'\n", od->title);
        DBG (0, "  description: `%s'\n", od->desc);
        DBG (0, "         type: %s\n",
             od->type == SANE_TYPE_BOOL ? "SANE_TYPE_BOOL" :
             od->type == SANE_TYPE_INT ? "SANE_TYPE_INT" :
             od->type == SANE_TYPE_FIXED ? "SANE_TYPE_FIXED" :
             od->type == SANE_TYPE_STRING ? "SANE_TYPE_STRING" :
             od->type == SANE_TYPE_BUTTON ? "SANE_TYPE_BUTTON" :
             od->type == SANE_TYPE_GROUP ? "SANE_TYPE_GROUP" : "unknown");
        DBG (0, "         unit: %s\n",
             od->unit == SANE_UNIT_NONE ? "SANE_UNIT_NONE" :
             od->unit == SANE_UNIT_PIXEL ? "SANE_UNIT_PIXEL" :
             od->unit == SANE_UNIT_BIT ? "SANE_UNIT_BIT" :
             od->unit == SANE_UNIT_MM ? "SANE_UNIT_MM" :
             od->unit == SANE_UNIT_DPI ? "SANE_UNIT_DPI" :
             od->unit == SANE_UNIT_PERCENT ? "SANE_UNIT_PERCENT" :
             od->unit == SANE_UNIT_MICROSECOND ? "SANE_UNIT_MICROSECOND" :
             "unknown");
        DBG (0, "         size: %d\n", od->size);
        caps[0] = '\0';
        if (od->cap & SANE_CAP_SOFT_SELECT)
            strcat (caps, "SANE_CAP_SOFT_SELECT ");
        if (od->cap & SANE_CAP_HARD_SELECT)
            strcat (caps, "SANE_CAP_HARD_SELECT ");
        if (od->cap & SANE_CAP_SOFT_DETECT)
            strcat (caps, "SANE_CAP_SOFT_DETECT ");
        if (od->cap & SANE_CAP_EMULATED)
            strcat (caps, "SANE_CAP_EMULATED ");
        if (od->cap & SANE_CAP_AUTOMATIC)
            strcat (caps, "SANE_CAP_AUTOMATIC ");
        if (od->cap & SANE_CAP_INACTIVE)
            strcat (caps, "SANE_CAP_INACTIVE ");
        if (od->cap & SANE_CAP_ADVANCED)
            strcat (caps, "SANE_CAP_ADVANCED ");
        DBG (0, " capabilities: %s\n", caps);
        DBG (0, "constraint type: %s\n",
             od->constraint_type == SANE_CONSTRAINT_NONE ?
             "SANE_CONSTRAINT_NONE" :
             od->constraint_type == SANE_CONSTRAINT_RANGE ?
             "SANE_CONSTRAINT_RANGE" :
             od->constraint_type == SANE_CONSTRAINT_WORD_LIST ?
             "SANE_CONSTRAINT_WORD_LIST" :
             od->constraint_type == SANE_CONSTRAINT_STRING_LIST ?
             "SANE_CONSTRAINT_STRING_LIST" : "unknown");
    }
}

/***************************** SANE API ****************************/

SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
    FILE *fp;
    SANE_Int linenumber;
    SANE_Char line[PATH_MAX], *word;
    SANE_String_Const cp;
    SANE_String device_name = VP2_DEFAULT_DEVICE;
    VP2_Device *vp2_device = NULL;

    DBG_INIT ();
    sanei_thread_init ();

    DBG (2, "sane_init: version_code=%p, authorize=%p\n",
         (void *) version_code, (void *) authorize);
    DBG (1, "sane_init: SANE geniusvp2 backend version " BACKEND_VERSION
         " from %s\n", PACKAGE_STRING);

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

    if (inited)
        DBG (3, "sane_init: warning: already inited\n");

    fp = sanei_config_open (VP2_CONFIG_FILE);
    if (fp)
    {
        linenumber = 0;
        DBG (4, "sane_init: reading config file `%s'\n", VP2_CONFIG_FILE);
        while (sanei_config_read (line, sizeof (line), fp))
        {
            word = NULL;
            linenumber++;

            cp = sanei_config_get_string (line, &word);
            if (!word || cp == line)
            {
                DBG (5,
                     "sane_init: config file line %3d: ignoring empty line\n",
                     linenumber);
                if (word)
                    free (word);
                continue;
            }
            if (word[0] == '#')
            {
                DBG (5,
                     "sane_init: config file line %3d: ignoring comment line\n",
                     linenumber);
                free (word);
                continue;
            }

            DBG (5, "sane_init: config file line %3d: `%s'\n",
                 linenumber, line);
            if (read_option (line, "device", param_string,
                             &device_name) == SANE_STATUS_GOOD)
                continue;

            DBG (3, "sane-init: I don't know how to handle option `%s'\n",
                 word);
        }                       /* while */
        fclose (fp);
    }                           /* if */
    else
    {
        DBG (3, "sane_init: couldn't find config file (%s), using default "
             "settings\n", VP2_CONFIG_FILE);
    }

    /* the scanner does not need to be detected on this stage, so sane_init
     * always return success (except for "out of memory" errors) */
    if (!sane_geniusvp2_parport_open (device_name))
    {
        sane_geniusvp2_scanner_on ();
        sane_geniusvp2_reg_read (0, &reg0.w);
        sane_geniusvp2_reg_read (24, &reg24.w);
        DBG (4, "sane_init: ASIC ID = 0x%02x, Product ID = 0x%02x\n",
             reg0.r.AsicType, reg24.r.ProductID);
        sane_geniusvp2_scanner_off ();
        sane_geniusvp2_parport_close ();
        if (reg0.r.AsicType == VP2_ASIC_ID)
        {
            /* create device */
            vp2_device = malloc (sizeof (VP2_Device));
            if (!vp2_device)
                return SANE_STATUS_NO_MEM;
            switch (reg24.r.ProductID)
            {
            case VP2_PRODUCT_ID:
                vp2_device->sane.vendor = "Genius";
                vp2_device->sane.model = "ColorPage-Vivid Pro II";
                break;

            default:
                vp2_device->sane.vendor = "Unknown Vendor";
                vp2_device->sane.model = "Unknown Model";
            }
            vp2_device->sane.type = "flatbed scanner";
            vp2_device->name = device_name;
            vp2_device->sane.name = vp2_device->name;
            vp2_device->open = SANE_FALSE;
            vp2_device->scanning = SANE_FALSE;
            vp2_device->cancelled = SANE_FALSE;
            vp2_device->eof = SANE_FALSE;
            vp2_device->reader_pid = -1;
            vp2_device->pipe = -1;

            /* this is currently redundant as we don't support multiple devices yet */
            vp2_device->next = first_vp2_device;
            first_vp2_device = vp2_device;
            num_devices++;

            DBG (4, "sane_init: new device: `%s' is a %s %s %s\n",
                 vp2_device->sane.name, vp2_device->sane.vendor,
                 vp2_device->sane.model, vp2_device->sane.type);
        }
        else
        {
            DBG (1, "sane_init: scanner not found on %s or "
                 "ASIC is not supported\n", device_name);
        }
    }

    inited = SANE_TRUE;
    return SANE_STATUS_GOOD;
}

void
sane_exit (void)
{
    VP2_Device *vp2_device, *previous_device;

    DBG (2, "sane_exit\n");
    if (!inited)
    {
        DBG (1, "sane_exit: not inited, call sane_init() first\n");
        return;
    }

    vp2_device = first_vp2_device;
    while (vp2_device)
    {
        DBG (4, "sane_exit: freeing device %s\n", vp2_device->name);
        previous_device = vp2_device;
        vp2_device = vp2_device->next;
        if (previous_device->name)
            free (previous_device->name);
        free (previous_device);
    }
    DBG (4, "sane_exit: freeing device list\n");
    if (sane_device_list)
        free (sane_device_list);
    sane_device_list = NULL;
    first_vp2_device = NULL;
    inited = SANE_FALSE;
    return;
}

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

    DBG (2, "sane_get_devices: device_list=%p, local_only=%d\n",
         (void *) device_list, local_only);
    if (!inited)
    {
        DBG (1, "sane_get_devices: not inited, call sane_init() first\n");
        return SANE_STATUS_INVAL;
    }

    /* already called, so cleanup */
    if (sane_device_list)
        free (sane_device_list);

    sane_device_list = malloc ((num_devices + 1) * sizeof (SANE_Device *));
    if (!sane_device_list)
        return SANE_STATUS_NO_MEM;
    for (i = 0, dev = first_vp2_device; (i < num_devices) && dev;
         i++, dev = dev->next)
        sane_device_list[i] = &dev->sane;
    sane_device_list[i] = NULL;

    *device_list = (const SANE_Device **) sane_device_list;
    return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const devicename, SANE_Handle * handle)
{
    VP2_Device *vp2_device = first_vp2_device;
    SANE_Status status;

    DBG (2, "sane_open: devicename = \"%s\", handle=%p\n",
         devicename, (void *) handle);
    if (!inited)
    {
        DBG (1, "sane_open: not inited, call sane_init() first\n");
        return SANE_STATUS_INVAL;
    }

    if (!handle)
    {
        DBG (1, "sane_open: handle == NULL\n");
        return SANE_STATUS_INVAL;
    }

    if (!devicename || strlen (devicename) == 0)
    {
        DBG (2, "sane_open: device name NULL or empty\n");
    }
    else
    {
        for (vp2_device = first_vp2_device; vp2_device;
             vp2_device = vp2_device->next)
        {
            if (strcmp (devicename, vp2_device->name) == 0)
                break;
        }
    }
    if (!vp2_device)
    {
        DBG (1, "sane_open: device `%s' not found\n", devicename);
        return SANE_STATUS_INVAL;
    }
    if (vp2_device->open)
    {
        DBG (1, "sane_open: device `%s' already open\n", devicename);
        return SANE_STATUS_DEVICE_BUSY;
    }
    DBG (2, "sane_open: opening device `%s', handle = %p\n", vp2_device->name,
         (void *) vp2_device);

    status = init_options (vp2_device);
    if (status != SANE_STATUS_GOOD)
        return status;

    if (sane_geniusvp2_parport_open (devicename))
        return SANE_STATUS_IO_ERROR;
    sane_geniusvp2_scanner_on ();

    sane_geniusvp2_lamp_off (); /* just in case lamp was left on */
    sane_geniusvp2_go_home ();
    lamp_warmup (0);

    vp2_device->open = SANE_TRUE;
    vp2_device->scanning = SANE_FALSE;
    vp2_device->cancelled = SANE_FALSE;
    vp2_device->eof = SANE_FALSE;
    vp2_device->bytes_total = 0;

    *handle = vp2_device;

    return SANE_STATUS_GOOD;
}

void
sane_close (SANE_Handle handle)
{
    VP2_Device *vp2_device = handle;

    DBG (2, "sane_close: handle=%p\n", (void *) handle);

    if (!inited)
    {
        DBG (1, "sane_close: not inited, call sane_init() first\n");
        return;
    }
    if (!check_handle (handle))
    {
        DBG (1, "sane_close: handle %p unknown\n", (void *) handle);
        return;
    }
    if (!vp2_device->open)
    {
        DBG (1, "sane_close: handle %p not open\n", (void *) handle);
        return;
    }

    sane_geniusvp2_lamp_off ();
    sane_geniusvp2_scanner_off ();
    sane_geniusvp2_parport_close ();
    vp2_device->open = SANE_FALSE;

    return;
}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
    VP2_Device *vp2_device = handle;

    DBG (4, "sane_get_option_descriptor: handle=%p, option = %d\n",
         (void *) handle, option);
    if (!inited)
    {
        DBG (1, "sane_get_option_descriptor: not inited, call sane_init() "
             "first\n");
        return 0;
    }

    if (!check_handle (handle))
    {
        DBG (1, "sane_get_option_descriptor: handle %p unknown\n",
             (void *) handle);
        return 0;
    }
    if (!vp2_device->open)
    {
        DBG (1, "sane_get_option_descriptor: not open\n");
        return 0;
    }
    if (option < 0 || option >= num_options)
    {
        DBG (3, "sane_get_option_descriptor: option < 0 || "
             "option > num_options\n");
        return 0;
    }

    return &vp2_device->opt[option];
}

SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option, SANE_Action action,
                     void *value, SANE_Int * info)
{
    VP2_Device *vp2_device = handle;
    SANE_Int myinfo = 0;
    SANE_Status status;

    DBG (4,
         "sane_control_option: handle=%p, opt=%d, act=%d, val=%p, info=%p\n",
         (void *) handle, option, action, (void *) value, (void *) info);
    if (!inited)
    {
        DBG (1, "sane_control_option: not inited, call sane_init() first\n");
        return SANE_STATUS_INVAL;
    }

    if (!check_handle (handle))
    {
        DBG (1, "sane_control_option: handle %p unknown\n", (void *) handle);
        return SANE_STATUS_INVAL;
    }
    if (!vp2_device->open)
    {
        DBG (1, "sane_control_option: not open\n");
        return SANE_STATUS_INVAL;
    }
    if (vp2_device->scanning)
    {
        DBG (1, "sane_control_option: is scanning\n");
        return SANE_STATUS_INVAL;
    }
    if (option < 0 || option >= num_options)
    {
        DBG (1, "sane_control_option: option < 0 || option > num_options\n");
        return SANE_STATUS_INVAL;
    }

    if (!SANE_OPTION_IS_ACTIVE (vp2_device->opt[option].cap))
    {
        DBG (1, "sane_control_option: option is inactive\n");
        return SANE_STATUS_INVAL;
    }

    if (vp2_device->opt[option].type == SANE_TYPE_GROUP)
    {
        DBG (1, "sane_control_option: option is a group\n");
        return SANE_STATUS_INVAL;
    }

    switch (action)
    {
    case SANE_ACTION_SET_AUTO:
        if (!SANE_OPTION_IS_SETTABLE (vp2_device->opt[option].cap))
        {
            DBG (1, "sane_control_option: option is not setable\n");
            return SANE_STATUS_INVAL;
        }
        if (!(vp2_device->opt[option].cap & SANE_CAP_AUTOMATIC))
        {
            DBG (1, "sane_control_option: option is not automatically "
                 "setable\n");
            return SANE_STATUS_INVAL;
        }
        switch (option)
        {
        default:
            DBG (1, "sane_control_option: trying to automatically set "
                 "unexpected option\n");
            return SANE_STATUS_INVAL;
        }
        break;

    case SANE_ACTION_SET_VALUE:
        if (!SANE_OPTION_IS_SETTABLE (vp2_device->opt[option].cap))
        {
            DBG (1, "sane_control_option: option is not setable\n");
            return SANE_STATUS_INVAL;
        }
        status = sanei_constrain_value (&vp2_device->opt[option],
                                        value, &myinfo);
        if (status != SANE_STATUS_GOOD)
        {
            DBG (3,
                 "sane_control_option: sanei_constrain_value returned %s\n",
                 sane_strstatus (status));
            return status;
        }
        switch (option)
        {
        case opt_tl_x:         /* Fixed with parameter reloading */
        case opt_tl_y:
        case opt_br_x:
        case opt_br_y:
            if (vp2_device->val[option].w == *(SANE_Fixed *) value)
            {
                DBG (4,
                     "sane_control_option: option %d (%s) not changed\n",
                     option, vp2_device->opt[option].name);
                break;
            }
            vp2_device->val[option].w = *(SANE_Fixed *) value;
            myinfo |= SANE_INFO_RELOAD_PARAMS;
            DBG (4,
                 "sane_control_option: set option %d (%s) to %.0f %s\n",
                 option, vp2_device->opt[option].name,
                 SANE_UNFIX (*(SANE_Fixed *) value),
                 vp2_device->opt[option].unit == SANE_UNIT_MM ? "mm" : "");
            break;
        case opt_resolution: /* Int */
            if (vp2_device->val[option].w == *(SANE_Int *) value)
            {
                DBG (4,
                     "sane_control_option: option %d (%s) not changed\n",
                     option, vp2_device->opt[option].name);
                break;
            }
            vp2_device->val[option].w = *(SANE_Int *) value;
            DBG (4, "sane_control_option: set option %d (%s) to %d\n",
                 option, vp2_device->opt[option].name, *(SANE_Int *) value);
            break;
        case opt_depth:        /* Word list with parameter and options reloading */
            if (vp2_device->val[option].w == *(SANE_Int *) value)
            {
                DBG (4,
                     "sane_control_option: option %d (%s) not changed\n",
                     option, vp2_device->opt[option].name);
                break;
            }
            vp2_device->val[option].w = *(SANE_Int *) value;
            myinfo |= SANE_INFO_RELOAD_PARAMS;
            myinfo |= SANE_INFO_RELOAD_OPTIONS;
            DBG (4, "sane_control_option: set option %d (%s) to %d\n",
                 option, vp2_device->opt[option].name, *(SANE_Int *) value);
            break;
            /* options with side-effects */
        case opt_print_options:
            DBG (4, "sane_control_option: set option %d (%s)\n",
                 option, vp2_device->opt[option].name);
            print_options (vp2_device);
            break;
        case opt_mode:
            if (strcmp (vp2_device->val[option].s, value) == 0)
            {
                DBG (4,
                     "sane_control_option: option %d (%s) not changed\n",
                     option, vp2_device->opt[option].name);
                break;
            }
            strcpy (vp2_device->val[option].s, (SANE_String) value);
            myinfo |= SANE_INFO_RELOAD_PARAMS;
            myinfo |= SANE_INFO_RELOAD_OPTIONS;
            DBG (4, "sane_control_option: set option %d (%s) to %s\n",
                 option, vp2_device->opt[option].name, (SANE_String) value);
            break;
        default:
            DBG (1, "sane_control_option: trying to set unexpected option\n");
            return SANE_STATUS_INVAL;
        }
        break;

    case SANE_ACTION_GET_VALUE:
        switch (option)
        {
        case opt_num_opts:
            *(SANE_Word *) value = num_options;
            DBG (4, "sane_control_option: get option 0, value = %d\n",
                 num_options);
            break;
        case opt_tl_x:         /* Fixed options */
        case opt_tl_y:
        case opt_br_x:
        case opt_br_y:
            {
                *(SANE_Fixed *) value = vp2_device->val[option].w;
                DBG (4,
                     "sane_control_option: get option %d (%s), value=%.1f %s\n",
                     option, vp2_device->opt[option].name,
                     SANE_UNFIX (*(SANE_Fixed *) value),
                     vp2_device->opt[option].unit ==
                     SANE_UNIT_MM ? "mm" : "");
                break;
            }
        case opt_mode:         /* String (list) options */
            strcpy (value, vp2_device->val[option].s);
            DBG (4,
                 "sane_control_option: get option %d (%s), value=`%s'\n",
                 option, vp2_device->opt[option].name, (SANE_String) value);
            break;
        case opt_depth:        /* Int + word list options */
        case opt_resolution:
            *(SANE_Int *) value = vp2_device->val[option].w;
            DBG (4, "sane_control_option: get option %d (%s), value=%d\n",
                 option, vp2_device->opt[option].name, *(SANE_Int *) value);
            break;
        default:
            DBG (1, "sane_control_option: trying to get unexpected option\n");
            return SANE_STATUS_INVAL;
        }
        break;
    default:
        DBG (1, "sane_control_option: trying unexpected action %d\n", action);
        return SANE_STATUS_INVAL;
    }
    if (info)
        *info = myinfo;
    DBG (4, "sane_control_option: finished, info=%s %s %s \n",
         myinfo & SANE_INFO_INEXACT ? "inexact" : "",
         myinfo & SANE_INFO_RELOAD_PARAMS ? "reload_parameters" : "",
         myinfo & SANE_INFO_RELOAD_OPTIONS ? "reload_options" : "");
    return SANE_STATUS_GOOD;
}


SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
    VP2_Device *vp2_device = handle;
    SANE_Parameters *p;
    double tl_x = 0, tl_y = 0, br_x = 0, br_y = 0;
    SANE_String text_format, mode;
    SANE_Int channels = 1, res;

    DBG (2, "sane_get_parameters: handle=%p, params=%p\n",
         (void *) handle, (void *) params);
    if (!inited)
    {
        DBG (1, "sane_get_parameters: not inited, call sane_init() first\n");
        return SANE_STATUS_INVAL;
    }
    if (!check_handle (handle))
    {
        DBG (1, "sane_get_parameters: handle %p unknown\n", (void *) handle);
        return SANE_STATUS_INVAL;
    }
    if (!vp2_device->open)
    {
        DBG (1, "sane_get_parameters: handle %p not open\n", (void *) handle);
        return SANE_STATUS_INVAL;
    }

    res = vp2_device->val[opt_resolution].w;
    mode = vp2_device->val[opt_mode].s;
    p = &vp2_device->params;
    p->depth = vp2_device->val[opt_depth].w;

    tl_x = SANE_UNFIX (vp2_device->val[opt_tl_x].w);
    tl_y = SANE_UNFIX (vp2_device->val[opt_tl_y].w);
    br_x = SANE_UNFIX (vp2_device->val[opt_br_x].w);
    br_y = SANE_UNFIX (vp2_device->val[opt_br_y].w);
    if (tl_x > br_x)
        swap_double (&tl_x, &br_x);
    if (tl_y > br_y)
        swap_double (&tl_y, &br_y);
    vp2_device->lines = (SANE_Word) (res * (br_y - tl_y) / MM_PER_INCH);
    if (vp2_device->lines < 1)
        vp2_device->lines = 1;
    p->lines = vp2_device->lines;

    if (strcmp (mode, "Gray") == 0)
    {
        p->format = SANE_FRAME_GRAY;
        p->last_frame = SANE_TRUE;
    }
    else                        /* Color */
    {
        p->format = SANE_FRAME_RGB;
        p->last_frame = SANE_TRUE;
    }

    p->pixels_per_line = (SANE_Int) (res * (br_x - tl_x) / MM_PER_INCH);
    if (p->pixels_per_line < 1)
        p->pixels_per_line = 1;

    if (p->format == SANE_FRAME_RGB)
        channels = 3;

    if (p->depth == 1)
        p->bytes_per_line = channels * (int) ((p->pixels_per_line + 7) / 8);
    else                        /* depth == 8 || depth == 16 */
        p->bytes_per_line =
            channels * p->pixels_per_line * ((p->depth + 7) / 8);

    vp2_device->bytes_per_line = p->bytes_per_line;

    if (p->pixels_per_line < 1)
        p->pixels_per_line = 1;
    vp2_device->pixels_per_line = p->pixels_per_line;

    switch (p->format)
    {
    case SANE_FRAME_GRAY:
        text_format = "gray";
        break;
    case SANE_FRAME_RGB:
        text_format = "rgb";
        break;
    case SANE_FRAME_RED:
        text_format = "red";
        break;
    case SANE_FRAME_GREEN:
        text_format = "green";
        break;
    case SANE_FRAME_BLUE:
        text_format = "blue";
        break;
    default:
        text_format = "unknown";
        break;
    }

    DBG (3, "sane_get_parameters: format=%s\n", text_format);
    DBG (3, "sane_get_parameters: last_frame=%s\n",
         p->last_frame ? "true" : "false");
    DBG (3, "sane_get_parameters: lines=%d\n", p->lines);
    DBG (3, "sane_get_parameters: depth=%d\n", p->depth);
    DBG (3, "sane_get_parameters: pixels_per_line=%d\n", p->pixels_per_line);
    DBG (3, "sane_get_parameters: bytes_per_line=%d\n", p->bytes_per_line);

    if (params)
        *params = *p;

    return SANE_STATUS_GOOD;
}

SANE_Status
sane_start (SANE_Handle handle)
{
    VP2_Device *vp2_device = handle;
    SANE_Status status;
    int pipe_descriptor[2];
    ScanArea area;

    DBG (2, "sane_start: handle=%p\n", handle);
    if (!inited)
    {
        DBG (1, "sane_start: not inited, call sane_init() first\n");
        return SANE_STATUS_INVAL;
    }
    if (!check_handle (handle))
    {
        DBG (1, "sane_start: handle %p unknown\n", handle);
        return SANE_STATUS_INVAL;
    }
    if (!vp2_device->open)
    {
        DBG (1, "sane_start: not open\n");
        return SANE_STATUS_INVAL;
    }
    if (vp2_device->scanning
        && strcmp (vp2_device->val[opt_mode].s, "Color") == 0)
    {
        DBG (1, "sane_start: already scanning\n");
        return SANE_STATUS_INVAL;
    }

    vp2_device->scanning = SANE_TRUE;
    vp2_device->cancelled = SANE_FALSE;
    vp2_device->eof = SANE_FALSE;
    vp2_device->bytes_total = 0;

    sane_get_parameters (handle, 0);

    if (vp2_device->params.lines == 0)
    {
        DBG (1, "sane_start: lines == 0\n");
        vp2_device->scanning = SANE_FALSE;
        return SANE_STATUS_INVAL;
    }
    if (vp2_device->params.pixels_per_line == 0)
    {
        DBG (1, "sane_start: pixels_per_line == 0\n");
        vp2_device->scanning = SANE_FALSE;
        return SANE_STATUS_INVAL;
    }
    if (vp2_device->params.bytes_per_line == 0)
    {
        DBG (1, "sane_start: bytes_per_line == 0\n");
        vp2_device->scanning = SANE_FALSE;
        return SANE_STATUS_INVAL;
    }

    if (pipe (pipe_descriptor) < 0)
    {
        DBG (1, "sane_start: pipe failed (%s)\n", strerror (errno));
        return SANE_STATUS_IO_ERROR;
    }

    status = lamp_warmup (35);
    if (status != SANE_STATUS_GOOD)
    {
        DBG (1, "sane_start: lamp warm up failed (%s)\n",
             sane_strstatus (status));
    }

    area.top = (SANE_UNFIX (vp2_device->val[opt_tl_y].w) / MM_PER_INCH +
                0.47) * OPT_RESOLUTION * 2;
    /* Add some offset to skip over hidden left margin */
    area.left = (SANE_UNFIX (vp2_device->val[opt_tl_x].w) / MM_PER_INCH +
                 left_leading) * OPT_RESOLUTION;
    area.height = vp2_device->lines *
        (OPT_RESOLUTION / SANE_UNFIX (vp2_device->val[opt_resolution].w));
    area.width = vp2_device->pixels_per_line;
    sane_geniusvp2_prepare_scan (area, vp2_device->val[opt_resolution].w);

    /* create reader routine as new process or thread */
    vp2_device->pipe = pipe_descriptor[0];
    vp2_device->reader_fds = pipe_descriptor[1];
    vp2_device->reader_pid =
        sanei_thread_begin (reader_task, (void *) vp2_device);

    if (vp2_device->reader_pid < 0)
    {
        DBG (1, "sane_start: sanei_thread_begin failed (%s)\n",
             strerror (errno));
        return SANE_STATUS_NO_MEM;
    }

    if (sanei_thread_is_forked ())
    {
        close (vp2_device->reader_fds);
        vp2_device->reader_fds = -1;
    }

    return SANE_STATUS_GOOD;
}


SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * data,
           SANE_Int max_length, SANE_Int * length)
{
    VP2_Device *vp2_device = handle;
    ssize_t bytes_read;
    SANE_Int bytes_total = vp2_device->lines * vp2_device->bytes_per_line;


    DBG (4, "sane_read: handle=%p, data=%p, max_length=%d, length=%p\n",
         handle, data, max_length, (void *) length);
    if (!inited)
    {
        DBG (1, "sane_read: not inited, call sane_init() first\n");
        return SANE_STATUS_INVAL;
    }
    if (!check_handle (handle))
    {
        DBG (1, "sane_read: handle %p unknown\n", handle);
        return SANE_STATUS_INVAL;
    }
    if (!length)
    {
        DBG (1, "sane_read: length == NULL\n");
        return SANE_STATUS_INVAL;
    }

    *length = 0;

    if (!data)
    {
        DBG (1, "sane_read: data == NULL\n");
        return SANE_STATUS_INVAL;
    }
    if (!vp2_device->open)
    {
        DBG (1, "sane_read: not open\n");
        return SANE_STATUS_INVAL;
    }
    if (vp2_device->cancelled)
    {
        DBG (1, "sane_read: scan was cancelled\n");
        return SANE_STATUS_CANCELLED;
    }
    if (vp2_device->eof)
    {
        DBG (2, "sane_read: No more data available, sending EOF\n");
        return SANE_STATUS_EOF;
    }
    if (!vp2_device->scanning)
    {
        DBG (1, "sane_read: not scanning (call sane_start first)\n");
        return SANE_STATUS_INVAL;
    }

    bytes_read = read (vp2_device->pipe, data, max_length);
    if (bytes_read == 0
        || (bytes_read + vp2_device->bytes_total >= bytes_total))
    {
        SANE_Status status;
        DBG (2, "sane_read: EOF reached\n");
        status = finish_pass (vp2_device);
        if (status != SANE_STATUS_GOOD)
        {
            DBG (1, "sane_read: finish_pass returned `%s'\n",
                 sane_strstatus (status));
            return status;
        }
        vp2_device->eof = SANE_TRUE;
        if (bytes_read == 0)
            return SANE_STATUS_EOF;
    }
    else if (bytes_read < 0)
    {
        if (errno == EAGAIN)
        {
            DBG (2, "sane_read: no data available, try again\n");
            return SANE_STATUS_GOOD;
        }
        else
        {
            DBG (1, "sane_read: read returned error: %s\n", strerror (errno));
            return SANE_STATUS_IO_ERROR;
        }
    }
    *length = bytes_read;
    vp2_device->bytes_total += bytes_read;

    DBG (2, "sane_read: read %ld bytes of %d, total %d\n", (long) bytes_read,
         max_length, vp2_device->bytes_total);
    return SANE_STATUS_GOOD;
}

void
sane_cancel (SANE_Handle handle)
{
    VP2_Device *vp2_device = handle;

    DBG (2, "sane_cancel: handle = %p\n", handle);
    if (!inited)
    {
        DBG (1, "sane_cancel: not inited, call sane_init() first\n");
        return;
    }
    if (!check_handle (handle))
    {
        DBG (1, "sane_cancel: handle %p unknown\n", handle);
        return;
    }
    if (!vp2_device->open)
    {
        DBG (1, "sane_cancel: not open\n");
        return;
    }
    if (vp2_device->cancelled)
    {
        DBG (1, "sane_cancel: scan already cancelled\n");
        return;
    }
    if (!vp2_device->scanning)
    {
        DBG (2, "sane_cancel: scan is already finished\n");
        return;
    }
    finish_pass (vp2_device);
    vp2_device->cancelled = SANE_TRUE;
    vp2_device->scanning = SANE_FALSE;
    vp2_device->eof = SANE_FALSE;
    return;
}

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{
    VP2_Device *vp2_device = handle;

    DBG (2, "sane_set_io_mode: handle = %p, non_blocking = %d\n", handle,
         non_blocking);
    if (!inited)
    {
        DBG (1, "sane_set_io_mode: not inited, call sane_init() first\n");
        return SANE_STATUS_INVAL;
    }
    if (!check_handle (handle))
    {
        DBG (1, "sane_set_io_mode: handle %p unknown\n", handle);
        return SANE_STATUS_INVAL;
    }
    if (!vp2_device->open)
    {
        DBG (1, "sane_set_io_mode: not open\n");
        return SANE_STATUS_INVAL;
    }
    if (!vp2_device->scanning)
    {
        DBG (1, "sane_set_io_mode: not scanning\n");
        return SANE_STATUS_INVAL;
    }
    if (fcntl (vp2_device->pipe,
               F_SETFL, non_blocking ? O_NONBLOCK : 0) < 0)
    {
        DBG (1, "sane_set_io_mode: can't set io mode");
        return SANE_STATUS_INVAL;
    }
    return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
    VP2_Device *vp2_device = handle;

    DBG (2, "sane_get_select_fd: handle = %p, fd %s 0\n", handle,
         fd ? "!=" : "==");
    if (!inited)
    {
        DBG (1, "sane_get_select_fd: not inited, call sane_init() first\n");
        return SANE_STATUS_INVAL;
    }
    if (!check_handle (handle))
    {
        DBG (1, "sane_get_select_fd: handle %p unknown\n", handle);
        return SANE_STATUS_INVAL;
    }
    if (!vp2_device->open)
    {
        DBG (1, "sane_get_select_fd: not open\n");
        return SANE_STATUS_INVAL;
    }
    if (!vp2_device->scanning)
    {
        DBG (1, "sane_get_select_fd: not scanning\n");
        return SANE_STATUS_INVAL;
    }
    *fd = vp2_device->pipe;
    return SANE_STATUS_GOOD;
}
