/* sane - Scanner Access Now Easy.
   Copyright (C) 2003 Martijn van Oosterhout <kleptog@svana.org>
   Copyright (C) 2003 Thomas Soumarmon <thomas.soumarmon@cogitae.net>
   Copyright (c) 2003 Henning Meier-Geinitz, <henning@meier-geinitz.de>

   Originally copied from HP3300 testtools. Original notice follows:

   Copyright (C) 2001 Bertrik Sikken (bertrik@zonnet.nl)

   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.

   HP5400/5470 Test util.
   Currently is only able to read back the scanner version string,
   but this basically demonstrates ability to communicate with the scanner.

   Massively expanded. Can do calibration scan, upload gamma and calibration
   tables and stores the results of a scan. - 19/02/2003 Martijn
*/

#include <stdio.h>		/* for printf */
#include <stdlib.h>		/* for exit */
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <netinet/in.h>		/* for htons */
#include <string.h>


#include "hp5400.h"
#include "hp5400_xfer.h"
#include "hp5400_internal.h"
#include "hp5400_debug.h" /* debug functions */


#ifndef min
#define min(A,B) (((A)<(B)) ? (A) : (B))
#endif
#ifndef max
#define max(A,B) (((A)>(B)) ? (A) : (B))
#endif


#ifndef STRING_VERSION_MATCH
#define NO_STRING_VERSION_MATCH 1
#endif

#ifdef __GNUC__
#define PACKED __attribute__ ((packed))
#else
#define PACKED
#endif

/* If this is enabled, a copy of the raw data from the scanner will be saved to
   imagedebug.dat and the attempted conversion to imagedebug.ppm */
/* #define IMAGE_DEBUG */

/* If this is defined you get extra info on the calibration */
/* #define CALIB_DEBUG */


typedef struct versionString {
	char strVersion[128];
} versionString;

const int numVersions = 3;
versionString *MatchVersions;


static TScannerModel Model_HP54xx =
  { "Hewlett-Packard", "HP54xx Flatbed Scanner" };


HP5400_SANE_STATIC
int
InitHp5400_internal() {

	MatchVersions = malloc( sizeof(versionString) * numVersions );
	strcpy( MatchVersions[0].strVersion, "SilitekIBlizd C3 ScannerV0.84");
	strcpy( MatchVersions[1].strVersion, "SilitekIBlizd C3 ScannerV0.86");
	strcpy( MatchVersions[2].strVersion, "SilitekIBlizd C3 ScannerV0.87");

	return 1;
}

HP5400_SANE_STATIC
int
FreeHp5400_internal() {

	free(MatchVersions);
	MatchVersions = NULL;

	return 1;
}


HP5400_SANE_STATIC
int
WriteByte (int iHandle, int cmd, char data)
{
  if (hp5400_command_write (iHandle, cmd, 1, &data) < 0)
    {
      HP5400_DBG (DBG_MSG, "failed to send byte (cmd=%04X)\n", cmd);
      return -1;
    }
  return 0;
}

HP5400_SANE_STATIC
int
SetLamp (THWParams * pHWParams, int fLampOn)
{
  if (fLampOn)
    {
      if (WriteByte (pHWParams->iXferHandle, 0x0000, 0x01) == 0)
	return 0;
    }
  return -1;
}

HP5400_SANE_STATIC
int
WarmupLamp (int iHandle)
{
  int i = 30;			/* Max 30 seconds, 15 is typical for cold start? */
  int couldRead;
  unsigned char dataVerify[0x02];

  /* Keep writing 01 to 0000 until no error... */
  unsigned char data0000[] = {
    0x01
  };
  unsigned char data0300[3];


  hp5400_command_write_noverify (iHandle, 0x0000, data0000, 1);

  do
    {
      hp5400_command_read_noverify (iHandle, 0x0300, 3, data0300);

      hp5400_command_write_noverify (iHandle, 0x0000, data0000, 1);

      couldRead =
	hp5400_command_read_noverify (iHandle, 0xc500, 0x02, dataVerify);
      if ((dataVerify[0] != 0) || (dataVerify[1] != 0))
	sleep (1);
    }
  while ((i-- > 0) && (couldRead >= 0)
	 && ((dataVerify[0] != 0) || (dataVerify[1] != 0)));

  if (i > 0)
    return 0;

  /*

     while( i > 0 )
     {
     if( WriteByte( iHandle, 0x0000, 0x01 ) == 0 )
     return 0;
     sleep(1);
     i--;
     }
   */

  HP5400_DBG (DBG_MSG, "***WARNING*** Warmup lamp failed...\n");
  return -1;
}

#define CALPIXBYBLOCK  42

HP5400_SANE_STATIC
int
SetCalibration (int iHandle, int numPixels, unsigned int *low_vals[3],
		unsigned int *high_vals[3], int dpi)
{
  char cmd[8];
  /* unsigned char cmd[8]; */ /* should fix the compilation warning
				 but I don't have a scanner right now 
				 to check that the fix does not break
				 calibration */
  
  int i, j, k;
  struct CalPixel
  {
    char highr[2], highg[2], highb[2];
    char lowr[2], lowg[2], lowb[2];
  };
  struct CalBlock
  {
    struct CalPixel pixels[CALPIXBYBLOCK];
    char pad[8];
  }
  PACKED;

  struct CalBlock *calinfo;

  /**
   we did scan test at 300 dpi, so we don't have all the needed pixels.
   To fill the gap, we loop
   */
  int numLoop = max (1, dpi / 300);
  int calBlockSize = CALPIXBYBLOCK * (6 * sizeof (short)) + 8 * sizeof (char);	/* = sizeof(calBlock) */
  int numCalBlock =
    ((numPixels / CALPIXBYBLOCK) +
     ((numPixels % CALPIXBYBLOCK != 0) ? 1 : 0));
  int calSize = numLoop * calBlockSize * numCalBlock;

  calinfo = malloc (calSize);
  memset (calinfo, 0, calSize);

  for (j = 0; j < numLoop * numCalBlock * CALPIXBYBLOCK; j++)
    {
      struct CalPixel *pixel =
	&calinfo[j / CALPIXBYBLOCK].pixels[j % CALPIXBYBLOCK];

      /*
         i = ( j % (int)( 0.80 * numPixels ) ) + (int)(0.10 * numPixels );
       */
      /* better solution : stretch the actual scan size to the calibration size */
      i = j / numLoop;

      /* This is obviously not quite right. The values on
       * the right are approximatly what windows sends */
      k = (high_vals[0][i] > 0x4000) ? 1000000000 / high_vals[0][i] : 0;	/* 0x6700 */
      pixel->highr[0] = k;
      pixel->highr[1] = k >> 8;
      k = (high_vals[1][i] > 0x4000) ? 1000000000 / high_vals[1][i] : 0;	/* 0x5e00 */
      pixel->highg[0] = k;
      pixel->highg[1] = k >> 8;
      k = (high_vals[2][i] > 0x4000) ? 1000000000 / high_vals[2][i] : 0;	/* 0x6000 */
      pixel->highb[0] = k;
      pixel->highb[1] = k >> 8;

      pixel->lowr[0] = low_vals[0][i];		/* 0x0530 */
      pixel->lowr[1] = low_vals[0][i] >> 8;
      pixel->lowg[0] = low_vals[1][i];		/* 0x0530 */
      pixel->lowg[1] = low_vals[1][i] >> 8;
      pixel->lowb[0] = low_vals[2][i];		/* 0x0530 */
      pixel->lowb[1] = low_vals[2][i] >> 8;
    }

  cmd[0] = 0xff & (calSize >> 16);
  cmd[1] = 0xff & (calSize >> 8);
  cmd[2] = 0xff & (calSize >> 0);
  cmd[3] = 0x00;
  cmd[4] = 0x54;
  cmd[5] = 0x02;
  cmd[6] = -128;                // 0x80; fixes compiler warning (for
                                // signed char implementations), see
                                // also comment above
  cmd[7] = 0x00;

  hp5400_bulk_command_write (iHandle, 0xE603, cmd, 8, calSize, calSize,
			     (void *) calinfo);

  free (calinfo);
  return 0;
}

/* Write a gamma table */
HP5400_SANE_STATIC
void
WriteGammaCalibTable (int iHandle, const int *pabGammaR, const int *pabGammaG,
		      const int *pabGammaB)
{
  char cmd[3];
  char *buffer;
  int i, j;

  /* Setup dummy gamma correction table */
  buffer = malloc (2 * 65536);

  cmd[0] = 2;
  cmd[1] = 0;
  cmd[2] = 0;

  for (i = 0; i < 3; i++)
    {
      const int *ptr = (i == 0) ? pabGammaR :
	(i == 1) ? pabGammaG : pabGammaB;

      for (j = 0; j < 65536; j++) {
	buffer[2 * j] = ptr[j];
	buffer[2 * j + 1] = ptr[j] >> 8;
      }

      hp5400_bulk_command_write (iHandle, 0x2A01 + i, cmd, 3, 2 * 65536,
				 65536, (void *) buffer);
    }
  free (buffer);

  return;
}

#ifdef STANDALONE
HP5400_SANE_STATIC
void
SetDefaultGamma (int iHandle)
{
  int *buffer = malloc (sizeof (int) * 65536);
  int i;

  for (i = 0; i < 65336; i++)
    buffer[i] = i;

  WriteGammaCalibTable (iHandle, buffer, buffer, buffer);
}
#endif

#define BUFFER_SIZE (6*65536)

#ifdef IMAGE_DEBUG
FILE *temp;
#endif

/* Bytes per line is the number of pixels. The actual bytes is one more */
HP5400_SANE_STATIC
void
CircBufferInit (int iHandle, TDataPipe * p, int iBytesPerLine,
		int bpp, int iMisAlignment, int blksize, int iTransferSize)
{
  iHandle = iHandle; /* to avoid compilation warning */
  p->buffersize = max (BUFFER_SIZE, 3 * blksize);

  if (p->buffer)
    {
      free (p->buffer);
    }

  /* Allocate a large enough buffer for transfer */
  p->buffer = malloc (p->buffersize);

  p->pixels = (iBytesPerLine / 3) / bpp;

  /* These three must always be positive */
  p->roff = 0;
  p->goff = p->pixels * bpp + 1;
  p->boff = 2 * p->pixels * bpp + 2;;

  p->linelength = iBytesPerLine + 3;	/* NUL at end of each row */
  p->bpp = bpp;
  p->bufstart = p->bufend = 0;

  if (iMisAlignment > 0)
    {
      p->roff += 0;
      p->goff += p->linelength * iMisAlignment;
      p->boff += p->linelength * iMisAlignment * 2;
    }

  if (iMisAlignment < 0)
    {
      p->roff -= p->linelength * iMisAlignment * 2;
      p->goff -= p->linelength * iMisAlignment;
      p->boff -= 0;
    }

  p->blksize = blksize;
  p->transfersize = iTransferSize;

#ifdef IMAGE_DEBUG
  temp = fopen ("imagedebug.dat", "w+b");
#endif

  HP5400_DBG (DBG_MSG,
       "Begin: line=%d (%X), pixels=%d (%X), r=%d (%X), g=%d (%X), b=%d (%X), bpp=%d, step=%d\n",
       p->linelength, p->linelength, p->pixels, p->pixels, p->roff, p->roff,
       p->goff, p->goff, p->boff, p->boff, bpp, iMisAlignment);
}


HP5400_SANE_STATIC
int
CircBufferGetLine (int iHandle, TDataPipe * p, void *pabLine)
{
  int i;
  int maxoff = 0;
  char* buftmp = (char*) (p->buffer);
  
/*  HP5400_DBG(DBG_MSG, "CircBufferGetLine:\n");   */

  if (p->roff > maxoff)
    maxoff = p->roff;
  if (p->goff > maxoff)
    maxoff = p->goff;
  if (p->boff > maxoff)
    maxoff = p->boff;

  maxoff += p->pixels * p->bpp;

  if (maxoff < p->linelength)
    maxoff = p->linelength;


  /* resize buffer if needed */
  if (p->bufstart + maxoff >= p->buffersize + p->blksize)
    {
      /* store actual buffer pointer */
      void *tmpBuf = p->buffer;
      /* calculate new size for buffer (oversize a bit) */
      int newsize = p->bufstart + maxoff + 2 * p->blksize;

      p->buffer = malloc (newsize);
      memcpy (p->buffer, tmpBuf, p->buffersize);
      p->buffersize = newsize;

      /* free old buffer */
      free (tmpBuf);
      buftmp = (char*)(p->buffer);
    }

  while (p->bufstart + maxoff >= p->bufend)	/* Not enough data in buffer */
    {
      int res;
      unsigned char cmd[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
      cmd[4] = p->blksize;
      cmd[5] = p->blksize >> 8;

      assert ((p->bufend + p->blksize) <= p->buffersize);

      HP5400_DBG (DBG_MSG, "Reading block, %d bytes remain\n", p->transfersize);
      p->transfersize -= p->blksize;

      res =
	hp5400_bulk_read_block (iHandle, CMD_INITBULK3, cmd, sizeof (cmd),
    				buftmp + p->bufend, p->blksize);
      if (res != p->blksize)
	{
	  HP5400_DBG (DBG_ERR, "*** ERROR: Read returned %d. FATAL.\n", res);
	  return -1;
	}
#ifdef IMAGE_DEBUG
      fwrite (
    	buftmp + p->bufend
	,p->blksize
	,1
	,temp
	);
#endif
      p->bufend += p->blksize;
    }

  assert (p->bufstart + maxoff < p->bufend);

  /* Copy a line into the result buffer */
  if (p->bpp == 1)
    {
      char *itPix = (char *) pabLine;
      char *itR = (char *) (buftmp + p->bufstart + p->roff);
      char *itG = (char *) (buftmp + p->bufstart + p->goff);
      char *itB = (char *) (buftmp + p->bufstart + p->boff);
      for (i = 0; i < p->pixels; i++)
	{
	  /* pointer move goes a little bit faster than vector access */
	  /* Although I wouldn't be surprised if the compiler worked that out anyway.
	   * No matter, this is easier to read :) */
	  *(itPix++) = *(itR++);
	  *(itPix++) = *(itG++);
	  *(itPix++) = *(itB++);
	}
    }
  else
    {
      short *itPix = (short *) pabLine;
      short *itR = (short *) (buftmp + p->bufstart + p->roff);
      short *itG = (short *) (buftmp + p->bufstart + p->goff);
      short *itB = (short *) (buftmp + p->bufstart + p->boff);
      for (i = 0; i < p->pixels; i++)
	{
#if 0
	  /* This code, while correct for PBM is not correct for the host and
	   * since we need it correct for calibration, it has to go */
	  ((short *) pabLine)[3 * i + 0] =
	    *((short *) (p->buffer + p->bufstart + p->roff + 2 * i));
	  ((short *) pabLine)[3 * i + 1] =
	    *((short *) (p->buffer + p->bufstart + p->goff + 2 * i));
	  ((short *) pabLine)[3 * i + 2] =
	    *((short *) (p->buffer + p->bufstart + p->boff + 2 * i));
#else
	  /* pointer move goes a little bit faster than vector access */
	  *(itPix++) = htons (*(itR++));
	  *(itPix++) = htons (*(itG++));
	  *(itPix++) = htons (*(itB++));
#endif
	}
    }

  p->bufstart += p->linelength;

  assert (p->bufstart <= p->bufend);

  /* If we've used a whole block at the beginning, move it */
  if (p->bufstart > p->blksize)
    {
      memmove (
	buftmp
	,buftmp + p->bufstart
	,p->bufend - p->bufstart
	);

      p->bufend -= p->bufstart;
      p->bufstart = 0;
    }

  return 0;
}

HP5400_SANE_STATIC
void
CircBufferExit (TDataPipe * p)
{
  free (p->buffer);
  p->buffer = NULL;
#ifdef IMAGE_DEBUG
  fclose (temp);
  temp = NULL;
#endif
  return;
}


#ifdef STANDALONE
/* bpp is BYTES per pixel */
HP5400_SANE_STATIC
void
DecodeImage (FILE * file, int planes, int bpp, int xsize, int ysize,
	     const char *filename)
{
  char *in, *buf;
  char *p[3];
  FILE *output;
  int i, j, k;

  /* xsize is byte width, not pixel width */
  xsize /= planes * bpp;

  HP5400_DBG (DBG_MSG,
       "DecodeImage(planes=%d,bpp=%d,xsize=%d,ysize=%d) => %d (file=%s)\n",
       planes, bpp, xsize, ysize, planes * bpp * xsize * ysize, filename);

  in = malloc (planes * (xsize * bpp + 1));

  for (i = 0; i < planes; i++)
    p[i] = in + i * (xsize * bpp + 1);

  buf = malloc (3 * xsize * bpp);

  output = fopen (filename, "wb");

  fprintf (output, "P%d\n%d %d\n", (planes == 3) ? 6 : 5, xsize, ysize);
  fprintf (output, "%d\n", (bpp == 1) ? 255 : 0xb000);

  for (i = 0; i < ysize; i++)
    {
      fread (in, planes * (xsize * bpp + 1), 1, file);

      for (j = 0; j < xsize; j++)
	{
	  for (k = 0; k < planes; k++)
	    {
	      if (bpp == 1)
		{
		  buf[j * planes + k] = p[k][j];
		}
	      else if (bpp == 2)
		{
		  buf[j * planes * 2 + k * 2 + 0] = p[k][2 * j + 0];
		  buf[j * planes * 2 + k * 2 + 1] = p[k][2 * j + 1];
		}
	    }
	}
      fwrite (buf, planes * xsize * bpp, 1, output);
    }

  fclose (output);

  free (in);
  free (buf);
}

HP5400_SANE_STATIC
int
hp5400_test_scan_response (struct ScanResponse *resp, struct ScanRequest *req)
{
  req = req; /* to avoid compilation warning */
  HP5400_DBG (DBG_MSG, "Scan response:\n");
  HP5400_DBG (DBG_MSG, "  transfersize=%d   htonl-> %d\n", resp->transfersize,
       htonl (resp->transfersize));
  HP5400_DBG (DBG_MSG, "  xsize=%d    htonl-> %d\n", resp->xsize,
       htonl (resp->xsize));
  HP5400_DBG (DBG_MSG, "  ysize=%d    htons-> %d\n", resp->ysize,
       htons (resp->ysize));
  return 1;
}
#endif

/* This is a very specialised scanning function. It does the scan request as
 * usual but instead of producing an image it returns three arrays of ints.
 * These are averages of the R,G,B values for each column.
 *
 * Note the array argument should point to an array of three NULL. These
 * will be overwritten with allocated pointers. */

HP5400_SANE_STATIC
int
DoAverageScan (int iHandle, struct ScanRequest *req, int code,
	       unsigned int **array)
{
  THWParams HWParams;
  struct ScanResponse res;
  unsigned short *buffer;
  int i, j, k, length;

  memset (&HWParams, 0, sizeof (HWParams));

  HWParams.iXferHandle = iHandle;

  if (InitScan2 (SCAN_TYPE_CALIBRATION, req, &HWParams, &res, 0, code) != 0)
    return -1;			/* No colour offseting, we want raw */

  length = htonl (res.xsize) / 6;

  HP5400_DBG (DBG_MSG, "Calibration scan: %d pixels wide\n", length);

  for (j = 0; j < 3; j++)
    {
      array[j] = malloc (sizeof (int) * length);
      memset (array[j], 0, sizeof (int) * length);	/* Clear array */
    }

  buffer = malloc (htonl (res.xsize) + 1);

  /* First we just sum them all */
  for (i = 0; i < htons (res.ysize); i++)
    {
      CircBufferGetLine (iHandle, &HWParams.pipe, buffer);

      for (j = 0; j < length; j++)
	for (k = 0; k < 3; k++)
	  array[k][j] += buffer[3 * j + k];
    }

  free (buffer);
  FinishScan (&HWParams);

  /* Now divide by the height to get the average */
  for (j = 0; j < length; j++)
    for (k = 0; k < 3; k++)
      array[k][j] /= htons (res.ysize);

  return 0;
}

#ifdef STANDALONE
HP5400_SANE_STATIC
int
DoScan (int iHandle, struct ScanRequest *req, const char *filename, int code,
	struct ScanResponse *res)
{
  FILE *file;
  THWParams HWParams;
  struct ScanResponse res_temp;
  void *buffer;
/*  int bpp, planes; */
  int i;

  code = code; /*to avoid compilation warning*/

  if (res == NULL)
    res = &res_temp;

  memset (&HWParams, 0, sizeof (HWParams));

  file = fopen (filename, "w+b");
  if (!file)
    {
      HP5400_DBG (DBG_MSG, "Couldn't open outputfile (%s)\n", strerror (errno));
      return -1;
    }

  HWParams.iXferHandle = iHandle;

  if (InitScan2 (SCAN_TYPE_NORMAL, req, &HWParams, res, 1, 0x40) != 0)
    return -1;

  fprintf (file, "P%d\n%d %d\n", 6, htonl (res->xsize) / 3,
	   htons (res->ysize));
  fprintf (file, "%d\n", 255);

  buffer = malloc (htonl (res->xsize) + 1);
  for (i = 0; i < htons (res->ysize); i++)
    {
      CircBufferGetLine (iHandle, &HWParams.pipe, buffer);

      fwrite (buffer, htonl (res->xsize), 1, file);
    }
  free (buffer);

  FinishScan (&HWParams);

  fclose (file);
  return 0;
}
#endif

HP5400_SANE_STATIC
int
Calibrate (int iHandle, int dpi)
{
  struct ScanRequest req;

  unsigned int *low_array[3];
  unsigned int *high_array[3];
#ifdef CALIB_DEBUG
  char buffer[512];
  int i, j;
#endif

/* The first calibration scan. Finds maximum of each CCD */
  memset(&req, 0, sizeof(req));

  req.x1 = 0x08;
  req.dpix = htons (300);	/* = 300 dpi */
  req.dpiy = htons (300);	/* = 300 dpi */
  req.offx = htons (0);		/* = 0cm */
  req.offy = htons (0);		/* = 0cm */
  req.lenx = htons (2690);	/* = 22.78cm */
  req.leny = htons (50);	/* =  0.42cm */

  req.flags1 = htons (0x0000);
  req.flags2 = htons (0x0010);
  req.flags3 = htons (0x3020);	/* First calibration scan, 48bpp */

  req.gamma[0] = htons (100);
  req.gamma[1] = htons (100);
  req.gamma[2] = htons (100);

  if (DoAverageScan (iHandle, &req, 0x40, high_array) != 0)
    return -1;

#ifdef CALIB_DEBUG
  for (i = 0; i < 3; i++)
    {
      int len;
      buffer[0] = 0;
      sprintf (buffer, "Average %d: \n", i);
      len = strlen (buffer);

      for (j = 0; j < 24; j++)
	{
	  sprintf (buffer + len, "%04X ", high_array[i][j]);
	  len += 5;
	}
      strcat (buffer, " ... \n");
      len += 6;

      for (j = 1000; j < 1024; j++)
	{
	  sprintf (buffer + len, "%04X ", high_array[i][j]);
	  len += 5;
	}
      strcat (buffer, " ... \n");
      len += 6;

      for (j = 2000; j < 2024; j++)
	{
	  sprintf (buffer + len, "%04X ", high_array[i][j]);
	  len += 5;
	}
      strcat (buffer, " ... \n");
      len += 6;

      HP5400_DBG (DBG_MSG, buffer);
    }
#endif

/* The second calibration scan. Finds minimum of each CCD */
  memset(&req, 0, sizeof(req));

  req.x1 = 0x08;
  req.dpix = htons (300);	/* = 300 dpi */
  req.dpiy = htons (300);	/* = 300 dpi */
  req.offx = htons (0);		/* = 0cm */
  req.offy = htons (0);		/* = 0cm */
  req.lenx = htons (2690);	/* = 22.78cm */
  req.leny = htons (16);	/* =  0.14cm */

  req.flags1 = htons (0x0000);
  req.flags2 = htons (0x0010);
  req.flags3 = htons (0x3024);	/* Second calibration scan, 48bpp */

  req.gamma[0] = htons (100);
  req.gamma[1] = htons (100);
  req.gamma[2] = htons (100);

  if (DoAverageScan (iHandle, &req, 0x00, low_array) != 0)
    return -1;

#ifdef CALIB_DEBUG
  for (i = 0; i < 3; i++)
    {
      int len;
      buffer[0] = 0;
      sprintf (buffer, "Average %d: \n", i);
      len = strlen (buffer);

      for (j = 0; j < 24; j++)
	{
	  sprintf (buffer + len, "%04X ", low_array[i][j]);
	  len += 5;
	}
      strcat (buffer, " ... \n");
      len += 6;

      for (j = 1000; j < 1024; j++)
	{
	  sprintf (buffer + len, "%04X ", low_array[i][j]);
	  len += 5;
	}
      strcat (buffer, " ... \n");
      len += 6;

      for (j = 2000; j < 2024; j++)
	{
	  sprintf (buffer + len, "%04X ", low_array[i][j]);
	  len += 5;
	}
      strcat (buffer, " ... \n");
      len += 6;

      HP5400_DBG (DBG_MSG, buffer);
    }
#endif

  SetCalibration (iHandle, 2690, low_array, high_array, dpi);

  return 0;
}

#ifdef STANDALONE
HP5400_SANE_STATIC
int
hp5400_scan (int iHandle, TScanParams * params, THWParams * pHWParams,
	     const char *filename)
{
  struct ScanRequest req;
  struct ScanResponse res;
  int result;

  pHWParams = pHWParams; /*to avoid compilation warning*/

  HP5400_DBG (DBG_MSG, "\n");
  HP5400_DBG (DBG_MSG, "Scanning :\n");
  HP5400_DBG (DBG_MSG, "   dpi(x) : %d\n", params->iDpi);
  HP5400_DBG (DBG_MSG, "   dpi(y) : %d\n", params->iLpi);
  HP5400_DBG (DBG_MSG, "   x0 : %d\n", params->iLeft);
  HP5400_DBG (DBG_MSG, "   y0 : %d\n", params->iTop);
  HP5400_DBG (DBG_MSG, "   width : %d\n", params->iWidth);
  HP5400_DBG (DBG_MSG, "   height : %d\n", params->iHeight);
  HP5400_DBG (DBG_MSG, "\n");

  memset(&req, 0, sizeof(req));

  req.x1 = 0x08;
  req.dpix = htons (params->iDpi);
  req.dpiy = htons (params->iLpi);

  /* These offsets and lengths should all be in the reference DPI which
   * is set to HW_LPI */
  req.offx = htons (params->iLeft);
  req.offy = htons (params->iTop);
  req.lenx = htons (params->iWidth);
  req.leny = htons (params->iHeight);

  req.flags1 = htons (0x0080);
  req.flags2 = htons (0x0000);
  req.flags3 = htons ((params->iDpi < 2400) ? 0x18E8 : 0x18C0);	/* Try preview scan */

  req.gamma[0] = htons (100);
  req.gamma[1] = htons (100);
  req.gamma[2] = htons (100);

  if (Calibrate (iHandle, params->iDpi) != 0)
    return -1;
  SetDefaultGamma (iHandle);

  result = DoScan (iHandle, &req, filename, 0x40, &res);

  /* Pass the results back to the parent */
  params->iBytesPerLine = htonl (res.xsize);
  params->iLines = htons (res.ysize);

#if 0
  imageFile = fopen ("output.dat", "r+b");
  if (imageFile)
    {
      int planes = 3;
      int bpp = 1;
      fseek (imageFile, 0, SEEK_SET);
      DecodeImage (imageFile, planes, bpp, planes * bpp * params->iWidth,
		   params->iHeight, filename);
      fclose (imageFile);
    }
#endif
  return result;
}

HP5400_SANE_STATIC
int
PreviewScan (int iHandle)
{
  TScanParams params;
  THWParams pHWParams;

  /* Reference LPI is 300dpi, remember */
  params.iDpi = 75;
  params.iLpi = 75;
  params.iLeft = 0;
  params.iTop = 0;
  params.iWidth = 2552;		/* = 21.61cm * 300dpi */
  params.iHeight = 3510;	/* = 29.72cm * 300dpi */
  params.iColourOffset = 1;

  return hp5400_scan (iHandle, &params, &pHWParams, "output.ppm");
}


static char UISetup1[] = {
/* Offset 40 */
  0x50, 0x72, 0x6F, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6E, 0x67, 0x14, 0x00,
    0x52, 0x65, 0x61, 0x64,
  0x79, 0x00, 0x53, 0x63, 0x61, 0x6E, 0x6E, 0x65, 0x72, 0x20, 0x4C, 0x6F,
    0x63, 0x6B, 0x65, 0x64,
  0x00, 0x45, 0x72, 0x72, 0x6F, 0x72, 0x00, 0x43, 0x61, 0x6E, 0x63, 0x65,
    0x6C, 0x69, 0x6E, 0x67,
  0x14, 0x00, 0x50, 0x6F, 0x77, 0x65, 0x72, 0x53, 0x61, 0x76, 0x65, 0x20,
    0x4F, 0x6E, 0x00, 0x53,
};

static char UISetup2[] = {

  0x63, 0x61, 0x6E, 0x6E, 0x69, 0x6E, 0x67, 0x14, 0x00, 0x41, 0x44, 0x46,
    0x20, 0x70, 0x61, 0x70,
  0x65, 0x72, 0x20, 0x6A, 0x61, 0x6D, 0x00, 0x43, 0x6F, 0x70, 0x69, 0x65,
    0x73, 0x00, 0x00,
};

HP5400_SANE_STATIC
int
InitScanner (int iHandle)
{
  WarmupLamp (iHandle);

  if (WriteByte (iHandle, 0xF200, 0x40) < 0)
    return -1;

  if (hp5400_command_write (iHandle, 0xF10B, sizeof (UISetup1), UISetup1) < 0)
    {
      HP5400_DBG (DBG_MSG, "failed to send UISetup1 (%lu)\n", (u_long) sizeof (UISetup1));
      return -1;
    }

  if (WriteByte (iHandle, 0xF200, 0x00) < 0)
    return -1;

  if (hp5400_command_write (iHandle, 0xF10C, sizeof (UISetup2), UISetup2) < 0)
    {
      HP5400_DBG (DBG_MSG, "failed to send UISetup2\n");
      return -1;
    }
  return 0;
}
#endif

/* Warning! The caller must have configured the gamma tables at this stage */
HP5400_SANE_STATIC
int
InitScan (enum ScanType scantype, TScanParams * pParams,
	  THWParams * pHWParams)
{
  struct ScanRequest req;
  struct ScanResponse res;
  int ret;
  
  memset(&req, 0, sizeof(req));

  req.x1 = 0x08;
  req.dpix = htons (pParams->iDpi);	/* = 300 dpi */
  req.dpiy = htons (pParams->iLpi);	/* = 300 dpi */
  req.offx = htons (pParams->iLeft);	/* = 0cm */
  req.offy = htons (pParams->iTop);	/* = 0cm */
  req.lenx = htons (pParams->iWidth);	/* = 22.78cm */

  /* Scan a few extra rows so we can colour offset properly. At 300dpi the
   * difference is 4 lines */

  req.leny = htons (pParams->iHeight);	/* =  0.42cm */

  req.flags1 = htons ((scantype == SCAN_TYPE_CALIBRATION) ? 0x0000 : 0x0080);
  req.flags2 = htons ((scantype == SCAN_TYPE_CALIBRATION) ? 0x0010 :
		      (scantype == SCAN_TYPE_PREVIEW) ? 0x0000 :
		      /* SCAN_TYPE_NORMAL  */ 0x0040);
  req.flags3 = htons (0x18E8);

  req.gamma[0] = htons (100);
  req.gamma[1] = htons (100);
  req.gamma[2] = htons (100);

  if (Calibrate (pHWParams->iXferHandle, pParams->iDpi) != 0)
    return -1;
/*  SetDefaultGamma( pHWParams->iXferHandle ); ** Must be done by caller */

  HP5400_DBG (DBG_MSG, "Calibration complete\n");
  ret =
    InitScan2 (scantype, &req, pHWParams, &res, pParams->iColourOffset, 0x40);

  HP5400_DBG (DBG_MSG, "InitScan2 returned %d\n", ret);

  /* Pass the results back to the parent */
  pParams->iBytesPerLine = htonl (res.xsize);

  /* Hide the extra lines we're scanning */
  pParams->iLines = htons (res.ysize);

  return ret;			/* 0 is good, -1 is bad */
}

/* For want of a better name ... */
HP5400_SANE_STATIC
int
InitScan2 (enum ScanType scantype, struct ScanRequest *req,
	   THWParams * pHWParams, struct ScanResponse *result,
	   int iColourOffset, int code)
{
  struct ScanResponse res;
  int iHandle = pHWParams->iXferHandle;

  memset(&res, 0, sizeof(res));

  /* Protect scanner from damage. This stops stpuid errors.  It basically
   * limits you to the scanner glass. Stuff like calibrations which need
   * more access do it safely by fiddling other paramters. Note you can
   * still break things by fiddling the ScanOffset, but that is not yet
   * under user control */

  if (scantype != SCAN_TYPE_CALIBRATION)
    {
      HP5400_DBG (DBG_MSG, "Off(%d,%d) : Len(%d,%d)\n", htons (req->offx),
	   htons (req->offy), htons (req->lenx), htons (req->leny));
      /* Yes, all the htons() is silly but we want this check as late as possible */
      if (htons (req->offx) > 0x09F8)
	req->offx = htons (0x09F8);
      if (htons (req->offy) > 0x0DB6)
	req->offy = htons (0x0DB6);
      /* These tests are meaningless as htons() returns unsigned 
         if( htons( req->offx ) < 0      ) req->offx = 0;
         if( htons( req->offy ) < 0      ) req->offy = 0;
       */
      if (htons (req->offx) + htons (req->lenx) > 0x09F8)
	req->lenx = htons (0x09F8 - htons (req->offx));
      if (htons (req->offy) + htons (req->leny) > 0x0DB6)
	req->leny = htons (0x0DB6 - htons (req->offy));
      if (htons (req->lenx) <= 1)
	return -1;
      if (htons (req->leny) <= 1)
	return -1;
    }

  WarmupLamp (iHandle);

  {				/* Try to set it to make scan succeed, URB 53 *//*  0x1B01 => 0x40  */
    /* I think this tries to cancel any existing scan */
    char flag = 0x40;
    if (hp5400_command_write (iHandle, CMD_STOPSCAN, sizeof (flag), &flag) <
	0)
      {
	HP5400_DBG (DBG_MSG, "failed to cancel scan flag\n");
	return -1;
      }
  }

  {				/* Try to set it to make scan succeed, URB 55  */
    char data[4] = { 0x02, 0x03, 0x03, 0x3C };
    if (hp5400_command_write (iHandle, CMD_UNKNOWN3, sizeof (data), data) < 0)
      {
	HP5400_DBG (DBG_MSG, "failed to set unknown1\n");
	return -1;
      }
  }

  {				/* Try to set it to make scan succeed, URB 59 */
    char flag = 0x04;
    if (hp5400_command_write (iHandle, CMD_UNKNOWN2, sizeof (flag), &flag) <
	0)
      {
	HP5400_DBG (DBG_MSG, "failed to set unknown2\n");
	return -1;
      }
  }

  {				/* URB 67 *//* Reference DPI = 300 currently */
    short dpi = htons (HW_LPI);
    if (hp5400_command_write (iHandle, CMD_SETDPI, sizeof (dpi), &dpi) < 0)
      {
	HP5400_DBG (DBG_MSG, "failed to set dpi\n");
	return -1;
      }
  }

  if (scantype != SCAN_TYPE_CALIBRATION)
    {				/* Setup scan offsets - Should only apply to non-calibration scans */
      short offsets[2];

      offsets [0] = htons (0x0054);
      offsets [1] = htons (0x0282);

      if (hp5400_command_write
	  (iHandle, CMD_SETOFFSET, sizeof (offsets), offsets) < 0)
	{
	  HP5400_DBG (DBG_MSG, "failed to set offsets\n");
	  return -1;
	}
    }

  HP5400_DBG (DBG_MSG, "Scan request: \n  ");
  {
    size_t i;
    for (i = 0; i < sizeof (*req); i++)
      {
	HP5400_DBG (DBG_MSG, "%02X ", ((unsigned char *) req)[i]);
      }
    HP5400_DBG (DBG_MSG, "\n");
  }

  if (hp5400_command_write
      (iHandle,
       (scantype !=
	SCAN_TYPE_CALIBRATION) ? CMD_SCANREQUEST2 : CMD_SCANREQUEST,
       sizeof (*req), req) < 0)
    {
      HP5400_DBG (DBG_MSG, "failed to send scan request\n");
      return -1;
    }

  {				/* Try to set it to make scan succeed, URB 71 */
    char flag = code;		/* Start scan with light on or off as requested */
    if (hp5400_command_write (iHandle, CMD_STARTSCAN, sizeof (flag), &flag) <
	0)
      {
	HP5400_DBG (DBG_MSG, "failed to set gamma flag\n");
	return -1;
      }
  }

  if (hp5400_command_read (iHandle, CMD_SCANRESPONSE, sizeof (res), &res) < 0)
    {
      HP5400_DBG (DBG_MSG, "failed to read scan response\n");
      return -1;
    }

  HP5400_DBG (DBG_MSG, "Scan response: \n  ");
  {
    size_t i;
    for (i = 0; i < sizeof (res); i++)
      {
	HP5400_DBG (DBG_MSG, "%02X ", ((unsigned char *) &res)[i]);
      }
    HP5400_DBG (DBG_MSG, "\n");
  }

  HP5400_DBG (DBG_MSG, "Bytes to transfer: %d\nBitmap resolution: %d x %d\n",
       htonl (res.transfersize), htonl (res.xsize), htons (res.ysize));

  HP5400_DBG (DBG_MSG, "Proceeding to scan\n");

  if (htonl (res.transfersize) == 0)
    {
      HP5400_DBG (DBG_MSG, "Hmm, size is zero. Obviously a problem. Aborting...\n");
      return -1;
    }

  {
    char x1 = 0x14, x2 = 0x24;

    float pixels = ((float) htons (req->lenx) * (float) htons (req->leny)) *
      ((float) htons (req->dpix) * (float) htons (req->dpiy)) /
      ((float) HW_LPI * (float) HW_LPI);
    int bpp = (int) ((float) htonl (res.transfersize) / pixels + 0.5);
    int planes = (bpp == 1) ? 1 : 3;
    bpp /= planes;

    HP5400_DBG (DBG_MSG, "bpp = %d / ( (%d * %d) * (%d * %d) / (%d * %d) ) = %d\n",
	 htonl (res.transfersize),
	 htons (req->lenx), htons (req->leny),
	 htons (req->dpix), htons (req->dpiy), HW_LPI, HW_LPI, bpp);

    hp5400_command_write_noverify (iHandle, CMD_INITBULK1, &x1, 1);
    hp5400_command_write_noverify (iHandle, CMD_INITBULK2, &x2, 1);

    if (bpp > 2)		/* Bug!! */
      bpp = 2;

    CircBufferInit (pHWParams->iXferHandle, &pHWParams->pipe,
		    htonl (res.xsize), bpp, iColourOffset, 0xF000,
		    htonl (res.transfersize) + 3 * htons (res.ysize));
  }

  if (result)			/* copy ScanResult to parent if they asked for it */
    memcpy (result, &res, sizeof (*result));

  return 0;
}

HP5400_SANE_STATIC
void
FinishScan (THWParams * pHWParams)
{
  int iHandle = pHWParams->iXferHandle;

  CircBufferExit (&pHWParams->pipe);

  {				/* Finish scan request */
    char flag = 0x40;
    if (hp5400_command_write (iHandle, CMD_STOPSCAN, sizeof (flag), &flag) <
	0)
      {
	HP5400_DBG (DBG_MSG, "failed to set gamma flag\n");
	return;
      }
  }
}

HP5400_SANE_STATIC
int
HP5400Open (THWParams * params, const char *filename)
{
  int iHandle = hp5400_open (filename);
  char szVersion[32];
  int i;
#ifndef NO_STRING_VERSION_MATCH
  int versionMatched;
#endif

  if (iHandle < 0)
    {
      HP5400_DBG (DBG_MSG, "hp5400_open failed\n");
      return -1;
    }

  params->iXferHandle = 0;

  /* read version info */
  if (hp5400_command_read
      (iHandle, CMD_GETVERSION, sizeof (szVersion), szVersion) < 0)
    {
      HP5400_DBG (DBG_MSG, "failed to read version string\n");
      goto hp5400_close_exit;
    }

	  HP5400_DBG (DBG_MSG, "version String :\n");
    for (i=0; i < 32; i++) {
      HP5400_DBG (DBG_MSG, "%c\n", szVersion[i]);
    }
    HP5400_DBG (DBG_MSG, "\n");

#ifndef NO_STRING_VERSION_MATCH
  i = 0;
  versionMatched = 0;
  while ( !versionMatched && (i < numVersions) ) {
	if (!strncmp (szVersion + 1, MatchVersions[i] .strVersion, strlen(MatchVersions[i] .strVersion) - 4)) {
		versionMatched = 1;
	}
  	i++;
  }
  if ( !versionMatched ) {
	HP5400_DBG (DBG_MSG,
	       "Sorry, unknown scanner version. Attempted match on :\n");
	i = 0;
	while ( i < numVersions ) {
		HP5400_DBG (DBG_MSG, "* '%s'\n", MatchVersions[i] .strVersion);
		i++;
	}
	HP5400_DBG (DBG_MSG, "Version is '%s'\n", szVersion);
	goto hp5400_close_exit;
    }
#else
  HP5400_DBG (DBG_MSG, "Warning, Version match is disabled. Version is '%s'\n",
       szVersion);
#endif /* NO_STRING_VERSION_MATCH */

  params->iXferHandle = iHandle;

  /* Start the lamp warming up */
  /* No point checking the return value, we don't care anyway */
  WriteByte (iHandle, 0x0000, 0x01);

  /* Success */
  return 0;

hp5400_close_exit:
  hp5400_close (iHandle);
  return -1;
}

HP5400_SANE_STATIC
void
HP5400Close (THWParams * params)
{
  hp5400_close (params->iXferHandle);
}

HP5400_SANE_STATIC
int
HP5400Detect (const char *filename,
	      int (*_ReportDevice) (TScannerModel * pModel,
				    const char *pszDeviceName))
{
  int iHandle = hp5400_open (filename);

  char szVersion[32];
  int ret = 0;
#ifndef NO_STRING_VERSION_MATCH
  int versionMatched = 0;
  int i = 0;
#endif

  if (iHandle < 0)
    {
      HP5400_DBG (DBG_MSG, "hp5400_open failed\n");
      return -1;
    }

  /* read version info */
  if (hp5400_command_read
      (iHandle, CMD_GETVERSION, sizeof (szVersion), szVersion) < 0)
    {
      HP5400_DBG (DBG_MSG, "failed to read version string\n");
      ret = -1;
      goto hp5400_close_exit;
    }

#ifndef NO_STRING_VERSION_MATCH
  i = 0;
  versionMatched = 0;
  while ( !versionMatched && (i < numVersions) ) {
	if (!memcmp (szVersion + 1, MatchVersions[i] .strVersion, strlen (MatchVersions[i] .strVersion) - 4)) {
		versionMatched = 1;
	}
  	i++;
  }
  if ( !versionMatched ) {
	HP5400_DBG (DBG_MSG,
	       "Sorry, unknown scanner version. Attempted match on :\n");
	i = 0;
	while ( i < numVersions ) {
		HP5400_DBG (DBG_MSG, "* '%s'\n", MatchVersions[i] .strVersion);
		i++;
	}
	HP5400_DBG (DBG_MSG, "Version is '%s'\n", szVersion);
	goto hp5400_close_exit;
    }
#else
  HP5400_DBG (DBG_MSG, "Warning, Version match is disabled. Version is '%s'\n",
       szVersion);
#endif /* NO_STRING_VERSION_MATCH */

  if (_ReportDevice)
    _ReportDevice (&Model_HP54xx, filename);

hp5400_close_exit:
  hp5400_close (iHandle);
  return ret;
}

#ifdef STANDALONE
int
main (int argc, char *argv[])
{
  THWParams scanner;

  assert (sizeof (struct ScanRequest) == 32);
  assert (sizeof (struct ScanResponse) == 16);

  hp5400_dbg_start();

  HP5400_DBG (DBG_MSG,
       "HP5400/5470C sample scan utility, by Martijn van Oosterhout <kleptog@svana.org>\n");
  HP5400_DBG (DBG_MSG,
       "Based on the testutils by Bertrik Sikken (bertrik@zonnet.nl)\n");

  if ((argc == 6) && (!strcmp (argv[1], "-decode")))
    {
      int width = atoi (argv[3]);
      int height = atoi (argv[4]);
      FILE *temp = fopen (argv[2], "r+b");
      if (temp)
	{
	  int planes = 3;
	  int bpp = 1;
	  fseek (temp, 0, SEEK_SET);
	  DecodeImage (temp, planes, bpp, planes * bpp * width, height,
		       argv[5]);
	  fclose (temp);
	}
      return 1;
    }

  if (HP5400Open (&scanner, NULL) < 0)
    {
      return 1;
    }

  PreviewScan (scanner.iXferHandle);

  HP5400Close (&scanner);
  fprintf (stderr, "Note: output is in output.ppm\n");
  return 0;
}

#endif
