/*
 * epsonds-ops.c - Epson ESC/I-2 driver, support routines.
 *
 * Copyright (C) 2015 Tower Technologies
 * Author: Alessandro Zummo <a.zummo@towertech.it>
 *
 * 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, version 2.
 */

#define DEBUG_DECLARE_ONLY

#include "sane/config.h"

#include <unistd.h>		/* sleep */
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#include "epsonds.h"
#include "epsonds-io.h"
#include "epsonds-ops.h"
#include "epsonds-cmd.h"

extern struct mode_param mode_params[];

/* Define the different scan sources */

#define FBF_STR	SANE_I18N("Flatbed")
#define TPU_STR	SANE_I18N("Transparency Unit")
#define ADF_STR	SANE_I18N("Automatic Document Feeder")

extern SANE_String_Const source_list[];

void
eds_dev_init(epsonds_device *dev)
{
	dev->res_list = malloc(sizeof(SANE_Word));
	dev->res_list[0] = 0;

	dev->depth_list = malloc(sizeof(SANE_Word));
	dev->depth_list[0] = 0;
}

SANE_Status
eds_dev_post_init(struct epsonds_device *dev)
{
	SANE_String_Const *source_list_add = source_list;

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

	if (dev->has_fb)
		*source_list_add++ = FBF_STR;

	if (dev->has_adf)
		*source_list_add++ = ADF_STR;

	if (source_list[0] == 0
		|| (dev->res_list[0] == 0 && dev->dpi_range.min == 0)
		|| dev->depth_list[0] == 0) {

		DBG(1, "something is wrong in the discovery process, aborting.\n");
		DBG(1, "sources: %ld, res: %d, depths: %d.\n",
			source_list_add - source_list, dev->res_list[0], dev->depth_list[0]);

		return SANE_STATUS_INVAL;
	}

	return SANE_STATUS_GOOD;
}

SANE_Bool
eds_is_model(epsonds_device *dev, const char *model)
{
        if (dev->model == NULL)
                return SANE_FALSE;

        if (strncmp(dev->model, model, strlen(model)) == 0)
                return SANE_TRUE;

        return SANE_FALSE;
}

SANE_Status
eds_add_resolution(epsonds_device *dev, int r)
{
	DBG(10, "%s: add (dpi): %d\n", __func__, r);

	/* first element is the list size */
	dev->res_list[0]++;
	dev->res_list = realloc(dev->res_list,
						(dev->res_list[0] + 1) *
						sizeof(SANE_Word));
	if (dev->res_list == NULL)
		return SANE_STATUS_NO_MEM;

	dev->res_list[dev->res_list[0]] = r;

	return SANE_STATUS_GOOD;
}

SANE_Status
eds_set_resolution_range(epsonds_device *dev, int min, int max)
{
	DBG(10, "%s: set min/max (dpi): %d/%d\n", __func__, min, max);

	dev->dpi_range.min = min;
	dev->dpi_range.max = max;
	dev->dpi_range.quant = 1;

	return SANE_STATUS_GOOD;
}

void
eds_set_fbf_area(epsonds_device *dev, int x, int y, int unit)
{
	if (x == 0 || y == 0)
		return;

	dev->fbf_x_range.min = 0;
	dev->fbf_x_range.max = SANE_FIX(x * MM_PER_INCH / unit);
	dev->fbf_x_range.quant = 0;

	dev->fbf_y_range.min = 0;
	dev->fbf_y_range.max = SANE_FIX(y * MM_PER_INCH / unit);
	dev->fbf_y_range.quant = 0;

	DBG(5, "%s: %f,%f %f,%f %d [mm]\n",
	    __func__,
	    SANE_UNFIX(dev->fbf_x_range.min),
	    SANE_UNFIX(dev->fbf_y_range.min),
	    SANE_UNFIX(dev->fbf_x_range.max),
	    SANE_UNFIX(dev->fbf_y_range.max), unit);
}

void
eds_set_adf_area(struct epsonds_device *dev, int x, int y, int unit)
{
	dev->adf_x_range.min = 0;
	dev->adf_x_range.max = SANE_FIX(x * MM_PER_INCH / unit);
	dev->adf_x_range.quant = 0;

	dev->adf_y_range.min = 0;
	dev->adf_y_range.max = SANE_FIX(y * MM_PER_INCH / unit);
	dev->adf_y_range.quant = 0;

	DBG(5, "%s: %f,%f %f,%f %d [mm]\n",
	    __func__,
	    SANE_UNFIX(dev->adf_x_range.min),
	    SANE_UNFIX(dev->adf_y_range.min),
	    SANE_UNFIX(dev->adf_x_range.max),
	    SANE_UNFIX(dev->adf_y_range.max), unit);
}

void
eds_set_tpu_area(struct epsonds_device *dev, int x, int y, int unit)
{
	dev->tpu_x_range.min = 0;
	dev->tpu_x_range.max = SANE_FIX(x * MM_PER_INCH / unit);
	dev->tpu_x_range.quant = 0;

	dev->tpu_y_range.min = 0;
	dev->tpu_y_range.max = SANE_FIX(y * MM_PER_INCH / unit);
	dev->tpu_y_range.quant = 0;

	DBG(5, "%s: %f,%f %f,%f %d [mm]\n",
	    __func__,
	    SANE_UNFIX(dev->tpu_x_range.min),
	    SANE_UNFIX(dev->tpu_y_range.min),
	    SANE_UNFIX(dev->tpu_x_range.max),
	    SANE_UNFIX(dev->tpu_y_range.max), unit);
}

SANE_Status
eds_add_depth(epsonds_device *dev, SANE_Word depth)
{
	DBG(5, "%s: add (bpp): %d\n", __func__, depth);

	/* > 8bpp not implemented yet */
	if (depth > 8) {
		DBG(1, " not supported");
		return SANE_STATUS_GOOD;
	}

	if (depth > dev->max_depth)
		dev->max_depth = depth;

	/* first element is the list size */
	dev->depth_list[0]++;
	dev->depth_list = realloc(dev->depth_list,
						(dev->depth_list[0] + 1) *
						sizeof(SANE_Word));

	if (dev->depth_list == NULL)
		return SANE_STATUS_NO_MEM;

	dev->depth_list[dev->depth_list[0]] = depth;

	return SANE_STATUS_GOOD;
}

SANE_Status
eds_init_parameters(epsonds_scanner *s)
{
	int dpi, bytes_per_pixel;

	memset(&s->params, 0, sizeof(SANE_Parameters));

	s->dummy = 0;

	/* setup depth according to our mode table */
	if (mode_params[s->val[OPT_MODE].w].depth == 1)
		s->params.depth = 1;
	else
		s->params.depth = s->val[OPT_DEPTH].w;

	dpi = s->val[OPT_RESOLUTION].w;

	if (SANE_UNFIX(s->val[OPT_BR_Y].w) == 0 ||
		SANE_UNFIX(s->val[OPT_BR_X].w) == 0)
		return SANE_STATUS_INVAL;

	s->left = ((SANE_UNFIX(s->val[OPT_TL_X].w) / MM_PER_INCH) *
		s->val[OPT_RESOLUTION].w) + 0.5;

	s->top = ((SANE_UNFIX(s->val[OPT_TL_Y].w) / MM_PER_INCH) *
		s->val[OPT_RESOLUTION].w) + 0.5;

	s->params.pixels_per_line =
		((SANE_UNFIX(s->val[OPT_BR_X].w -
			   s->val[OPT_TL_X].w) / MM_PER_INCH) * dpi) + 0.5;
	s->params.lines =
		((SANE_UNFIX(s->val[OPT_BR_Y].w -
			   s->val[OPT_TL_Y].w) / MM_PER_INCH) * dpi) + 0.5;

	DBG(5, "%s: tlx %f tly %f brx %f bry %f [mm]\n",
	    __func__,
	    SANE_UNFIX(s->val[OPT_TL_X].w), SANE_UNFIX(s->val[OPT_TL_Y].w),
	    SANE_UNFIX(s->val[OPT_BR_X].w), SANE_UNFIX(s->val[OPT_BR_Y].w));

	DBG(5, "%s: tlx %d tly %d brx %d bry %d [dots @ %d dpi]\n",
		__func__, s->left, s->top,
		s->params.pixels_per_line, s->params.lines, dpi);

	/* center aligned? */
	if (s->hw->alignment == 1) {

		SANE_Int offset = ((SANE_UNFIX(s->hw->x_range->max) / MM_PER_INCH) * dpi) + 0.5;

		s->left += ((offset - s->params.pixels_per_line) / 2);

		DBG(5, "%s: centered to tlx %d tly %d brx %d bry %d [dots @ %d dpi]\n",
			__func__, s->left, s->top,
			s->params.pixels_per_line, s->params.lines, dpi);
	}

	/*
	 * Calculate bytes_per_pixel and bytes_per_line for
	 * any color depths.
	 *
	 * The default color depth is stored in mode_params.depth:
	 */

	/* this works because it can only be set to 1, 8 or 16 */
	bytes_per_pixel = s->params.depth / 8;
	if (s->params.depth % 8) {	/* just in case ... */
		bytes_per_pixel++;
	}

	/* pixels_per_line is rounded to the next 8bit boundary */
	s->params.pixels_per_line = s->params.pixels_per_line & ~7;

	s->params.last_frame = SANE_TRUE;

	switch (s->val[OPT_MODE].w) {
	case MODE_BINARY:
	case MODE_GRAY:
		s->params.format = SANE_FRAME_GRAY;
		s->params.bytes_per_line =
			s->params.pixels_per_line * s->params.depth / 8;
		break;
	case MODE_COLOR:
		s->params.format = SANE_FRAME_RGB;
		s->params.bytes_per_line =
			3 * s->params.pixels_per_line * bytes_per_pixel;
		break;
	}

	if (s->params.bytes_per_line == 0) {
		DBG(1, "bytes_per_line is ZERO\n");
		return SANE_STATUS_INVAL;
	}

	/*
	 * If (s->top + s->params.lines) is larger than the max scan area, reset
	 * the number of scan lines:
	 * XXX: precalculate the maximum scanning area elsewhere (use dev max_y)
	 */

	if (SANE_UNFIX(s->val[OPT_BR_Y].w) / MM_PER_INCH * dpi <
	    (s->params.lines + s->top)) {
		s->params.lines =
			((int) SANE_UNFIX(s->val[OPT_BR_Y].w) / MM_PER_INCH *
			 dpi + 0.5) - s->top;
	}

	if (s->params.lines <= 0) {
		DBG(1, "wrong number of lines: %d\n", s->params.lines);
		return SANE_STATUS_INVAL;
	}

	return SANE_STATUS_GOOD;
}

void
eds_copy_image_from_ring(epsonds_scanner *s, SANE_Byte *data, SANE_Int max_length,
                   SANE_Int *length)
{
	int lines, available;
	int hw_line_size = (s->params.bytes_per_line + s->dummy);

	/* trim max_length to a multiple of hw_line_size */
	max_length -= (max_length % hw_line_size);

	/* check available data */
	available = eds_ring_avail(s->current);
	if (max_length > available)
		max_length = available;

	lines = max_length / hw_line_size;

	DBG(18, "copying %d lines (%d, %d)\n", lines, s->params.bytes_per_line, s->dummy);

	/* need more data? */
	if (lines == 0) {
		*length = 0;
		return;
	}

	*length = (lines * s->params.bytes_per_line);

	/* we need to copy one line at time, skipping
	 * dummy bytes at the end of each line
	 */

	/* lineart */
	if (s->params.depth == 1) {

		while (lines--) {

			int i;
			SANE_Byte *p;

			eds_ring_read(s->current, s->line_buffer, s->params.bytes_per_line);
			eds_ring_skip(s->current, s->dummy);

			p = s->line_buffer;

			for (i = 0; i < s->params.bytes_per_line; i++) {
				*data++ = ~*p++;
			}
		}

	} else { /* gray and color */

		while (lines--) {

			eds_ring_read(s->current, data, s->params.bytes_per_line);
			eds_ring_skip(s->current, s->dummy);

			data += s->params.bytes_per_line;

		}
	}
}

SANE_Status eds_ring_init(ring_buffer *ring, SANE_Int size)
{
	ring->ring = realloc(ring->ring, size);
	if (!ring->ring) {
		return SANE_STATUS_NO_MEM;
	}

	ring->size = size;
	ring->fill = 0;
	ring->end = ring->ring + size;
	ring->wp = ring->rp = ring->ring;

	return SANE_STATUS_GOOD;
}

SANE_Status eds_ring_write(ring_buffer *ring, SANE_Byte *buf, SANE_Int size)
{
	SANE_Int tail;

	if (size > (ring->size - ring->fill)) {
		DBG(1, "ring buffer full, requested: %d, available: %d\n", size, ring->size - ring->fill);
		return SANE_STATUS_NO_MEM;
	}

	tail = ring->end - ring->wp;
	if (size < tail) {

		memcpy(ring->wp, buf, size);

		ring->wp += size;
		ring->fill += size;

	} else {

		memcpy(ring->wp, buf, tail);
		size -= tail;

		ring->wp = ring->ring;
		memcpy(ring->wp, buf + tail, size);

		ring->wp += size;
		ring->fill += (tail + size);
	}

	return SANE_STATUS_GOOD;
}

SANE_Int eds_ring_read(ring_buffer *ring, SANE_Byte *buf, SANE_Int size)
{
	SANE_Int tail;

	DBG(18, "reading from ring, %d bytes available\n", (int)ring->fill);

	/* limit read to available */
	if (size > ring->fill) {
		DBG(1, "not enough data in the ring, shouldn't happen\n");
		size = ring->fill;
	}

	tail = ring->end - ring->rp;
	if (size < tail) {

		memcpy(buf, ring->rp, size);

		ring->rp += size;
		ring->fill -= size;

		return size;

	} else {

		memcpy(buf, ring->rp, tail);
		size -= tail;

		ring->rp = ring->ring;
		memcpy(buf + tail, ring->rp, size);

		ring->rp += size;
		ring->fill -= (size + tail);

		return size + tail;
	}
}

SANE_Int eds_ring_skip(ring_buffer *ring, SANE_Int size)
{
	SANE_Int tail;
	/* limit skip to available */
	if (size > ring->fill)
		size = ring->fill;

	tail = ring->end - ring->rp;
	if (size < tail) {
		ring->rp += size;
	} else {

		ring->rp = ring->ring + (size - tail);
	}

	ring->fill -= size;

	return size;
}

SANE_Int eds_ring_avail(ring_buffer *ring)
{
	return ring->fill;
}

void eds_ring_flush(ring_buffer *ring)
{
	eds_ring_skip(ring, ring->fill);
}