/*
 * Copyright 2009 Jeremy White <jwhite@codeweavers.com> for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"

#include <stdlib.h>
#include "twain.h"
#include "sane_i.h"
#include "wine/debug.h"

#ifdef SONAME_LIBSANE
static SANE_Status sane_find_option(SANE_Handle h, const char *option_name,
        const SANE_Option_Descriptor **opt_p, int *optno, SANE_Value_Type type)
{
    SANE_Status rc;
    SANE_Int optcount;
    const SANE_Option_Descriptor *opt;
    int i;

    /* Debian, in 32_net_backend_standard_fix.dpatch,
     *  forces a frontend (that's us) to reload options
     *  manually by invoking get_option_descriptor. */
    opt = psane_get_option_descriptor(h, 0);
    if (! opt)
        return SANE_STATUS_EOF;

    rc = psane_control_option(h, 0, SANE_ACTION_GET_VALUE, &optcount, NULL);
    if (rc != SANE_STATUS_GOOD)
        return rc;

    for (i = 1; i < optcount; i++)
    {
        opt = psane_get_option_descriptor(h, i);
        if (opt && (opt->name && strcmp(opt->name, option_name) == 0) &&
               opt->type == type)
        {
            *opt_p = opt;
            *optno = i;
            return SANE_STATUS_GOOD;
        }
    }
    return SANE_STATUS_EOF;
}

SANE_Status sane_option_get_int(SANE_Handle h, const char *option_name, SANE_Int *val)
{
    SANE_Status rc;
    int optno;
    const SANE_Option_Descriptor *opt;

    rc = sane_find_option(h, option_name, &opt, &optno, SANE_TYPE_INT);
    if (rc != SANE_STATUS_GOOD)
        return rc;

    return psane_control_option(h, optno, SANE_ACTION_GET_VALUE, val, NULL);
}

SANE_Status sane_option_set_int(SANE_Handle h, const char *option_name, SANE_Int val, SANE_Int *status)
{
    SANE_Status rc;
    int optno;
    const SANE_Option_Descriptor *opt;

    rc = sane_find_option(h, option_name, &opt, &optno, SANE_TYPE_INT);
    if (rc != SANE_STATUS_GOOD)
        return rc;

    return psane_control_option(h, optno, SANE_ACTION_SET_VALUE, (void *) &val, status);
}

SANE_Status sane_option_get_bool(SANE_Handle h, const char *option_name, SANE_Bool *val, SANE_Int *status)
{
    SANE_Status rc;
    int optno;
    const SANE_Option_Descriptor *opt;

    rc = sane_find_option(h, option_name, &opt, &optno, SANE_TYPE_BOOL);
    if (rc != SANE_STATUS_GOOD)
        return rc;

    return psane_control_option(h, optno, SANE_ACTION_GET_VALUE, (void *) val, status);
}

SANE_Status sane_option_set_bool(SANE_Handle h, const char *option_name, SANE_Bool val, SANE_Int *status)
{
    SANE_Status rc;
    int optno;
    const SANE_Option_Descriptor *opt;

    rc = sane_find_option(h, option_name, &opt, &optno, SANE_TYPE_BOOL);
    if (rc != SANE_STATUS_GOOD)
        return rc;

    return psane_control_option(h, optno, SANE_ACTION_SET_VALUE, (void *) &val, status);
}

SANE_Status sane_option_set_fixed(SANE_Handle h, const char *option_name, SANE_Fixed val, SANE_Int *status)
{
    SANE_Status rc;
    int optno;
    const SANE_Option_Descriptor *opt;

    rc = sane_find_option(h, option_name, &opt, &optno, SANE_TYPE_FIXED);
    if (rc != SANE_STATUS_GOOD)
        return rc;

    return psane_control_option(h, optno, SANE_ACTION_SET_VALUE, (void *) &val, status);
}

SANE_Status sane_option_get_str(SANE_Handle h, const char *option_name, SANE_String val, size_t len, SANE_Int *status)
{
    SANE_Status rc;
    int optno;
    const SANE_Option_Descriptor *opt;

    rc = sane_find_option(h, option_name, &opt, &optno, SANE_TYPE_STRING);
    if (rc != SANE_STATUS_GOOD)
        return rc;

    if (opt->size < len)
        return psane_control_option(h, optno, SANE_ACTION_GET_VALUE, (void *) val, status);
    else
        return SANE_STATUS_NO_MEM;
}

/* Important:  SANE has the side effect of overwriting val with the returned value */
SANE_Status sane_option_set_str(SANE_Handle h, const char *option_name, SANE_String val, SANE_Int *status)
{
    SANE_Status rc;
    int optno;
    const SANE_Option_Descriptor *opt;

    rc = sane_find_option(h, option_name, &opt, &optno, SANE_TYPE_STRING);
    if (rc != SANE_STATUS_GOOD)
        return rc;

    return psane_control_option(h, optno, SANE_ACTION_SET_VALUE, (void *) val, status);
}

SANE_Status sane_option_probe_resolution(SANE_Handle h, const char *option_name, SANE_Int *minval, SANE_Int *maxval, SANE_Int *quant)
{
    SANE_Status rc;
    int optno;
    const SANE_Option_Descriptor *opt;

    rc = sane_find_option(h, option_name, &opt, &optno, SANE_TYPE_INT);
    if (rc != SANE_STATUS_GOOD)
        return rc;

    if (opt->constraint_type != SANE_CONSTRAINT_RANGE)
        return SANE_STATUS_UNSUPPORTED;

    *minval = opt->constraint.range->min;
    *maxval = opt->constraint.range->max;
    *quant = opt->constraint.range->quant;

    return rc;
}

SANE_Status sane_option_probe_mode(SANE_Handle h, SANE_String_Const **choices, char *current, int current_size)
{
    SANE_Status rc;
    int optno;
    const SANE_Option_Descriptor *opt;
    rc = sane_find_option(h, "mode", &opt, &optno, SANE_TYPE_STRING);
    if (rc != SANE_STATUS_GOOD)
        return rc;

    if (choices && opt->constraint_type == SANE_CONSTRAINT_STRING_LIST)
        *choices = (SANE_String_Const *) opt->constraint.string_list;

    if (opt->size < current_size)
        return psane_control_option(h, optno, SANE_ACTION_GET_VALUE, current, NULL);
    else
        return SANE_STATUS_NO_MEM;

}

SANE_Status sane_option_probe_scan_area(SANE_Handle h, const char *option_name, SANE_Fixed *val,
                                        SANE_Unit *unit, SANE_Fixed *min, SANE_Fixed *max, SANE_Fixed *quant)
{
    SANE_Status rc;
    int optno;
    const SANE_Option_Descriptor *opt;

    rc = sane_find_option(h, option_name, &opt, &optno, SANE_TYPE_FIXED);
    if (rc != SANE_STATUS_GOOD)
        return rc;

    if (unit)
        *unit = opt->unit;
    if (min)
        *min = opt->constraint.range->min;
    if (max)
        *max = opt->constraint.range->max;
    if (quant)
        *quant = opt->constraint.range->quant;

    if (val)
        rc = psane_control_option(h, optno, SANE_ACTION_GET_VALUE, val, NULL);

    return rc;
}

TW_UINT16 sane_status_to_twcc(SANE_Status rc)
{
    switch (rc)
    {
        case SANE_STATUS_GOOD:
            return TWCC_SUCCESS;
        case SANE_STATUS_UNSUPPORTED:
            return TWCC_CAPUNSUPPORTED;
        case SANE_STATUS_JAMMED:
            return TWCC_PAPERJAM;
        case SANE_STATUS_NO_MEM:
            return TWCC_LOWMEMORY;
        case SANE_STATUS_ACCESS_DENIED:
            return TWCC_DENIED;

        case SANE_STATUS_IO_ERROR:
        case SANE_STATUS_NO_DOCS:
        case SANE_STATUS_COVER_OPEN:
        case SANE_STATUS_EOF:
        case SANE_STATUS_INVAL:
        case SANE_STATUS_CANCELLED:
        case SANE_STATUS_DEVICE_BUSY:
        default:
            return TWCC_BUMMER;
    }
}
static void convert_double_fix32(double d, TW_FIX32 *fix32)
{
    TW_INT32 value = (TW_INT32) (d * 65536.0 + 0.5);
    fix32->Whole = value >> 16;
    fix32->Frac = value & 0x0000ffffL;
}

BOOL convert_sane_res_to_twain(double sane_res, SANE_Unit unit, TW_FIX32 *twain_res, TW_UINT16 twtype)
{
    double d;

    if (unit != SANE_UNIT_MM)
        return FALSE;

    if (twtype != TWUN_INCHES)
        return FALSE;

    d = (sane_res / 10.0) / 2.54;
    convert_double_fix32((sane_res / 10.0) / 2.54, twain_res);

    return TRUE;
}
#endif
