/**********************************************************************
 *     EPSCAN, an EPSON ES-1200 scanner driver     
 *     Adam P. Jenkins <ajenkins@cs.umass.edu>
 *     
 *     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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **********************************************************************/

// Adam P. Jenkins <ajenkins@cs.umass.edu>
// Implentation of EPSON scanner driver
#include <fstream.h>
#include <tiffio.h>
#include <stdio.h>
#include <string.h>

#include "Scanner.h"
#include "Scsi.h"
#include "debug.h"

// scanner control codes
#define STX 0x2
#define CAN 0x18
#define ACK 0x6
#define NAK 0x15

// status byte flags
// bits 6, 1, and 0 in the status byte are reserved.
#define FATAL_ERROR(s)        ((s) & (1 << 7))
#define AREA_END(s)           ((s) & (1 << 5))
#define OPTION_FLAG(s)        ((s) & (1 << 4))
#define COLOR_ATTRIB(s)       (((s) & 0x0c) >> 2)
#define COLOR_ATTRIB_MONO  0
#define COLOR_ATTRIB_GREEN 1
#define COLOR_ATTRIB_RED   2
#define COLOR_ATTRIB_BLUE  3

// The tiff sample values, since tiff.h doesn't define them
#define SAMPLE_RED 0
#define SAMPLE_GREEN 1
#define SAMPLE_BLUE 2
#define TIFF_SAMPLE(a) (((a) == COLOR_ATTRIB_GREEN) ? SAMPLE_GREEN :	\
			((a) == COLOR_ATTRIB_RED) ? SAMPLE_RED :	\
			SAMPLE_BLUE)
  
const int Scanner::versionMajor = 0;
const int Scanner::versionMinor = 4;

inline int
getword(u_char lo, u_char hi)
{
  return ((int)hi << 8) | (int)lo;
}


// Function: operator<<
// Parameters: 
//   ostream& s            - stream to print on
//   const ScanSettings& x - settings structure to print
//   
// Description: Pretty-prints a ScanSettings structure on stream s.
// Return value: reference to s.
ostream& operator<<(ostream& s, const ScanSettings& x)
{
  s << "Settings:\n";
  s << "color_mode: " << hex << x.color_mode << dec << endl;
  s << "main_scan_res: " << x.main_scan_res << endl;
  s << "sub_scan_res: " << x.sub_scan_res << endl;
  s << "main_scan_offset: " << x.main_scan_offset << endl;
  s << "sub_scan_offset: " << x.sub_scan_offset << endl;
  s << "main_scan_area: " << x.main_scan_area << endl;
  s << "sub_scan_area: " << x.sub_scan_area << endl;
  s << "bits_per_pixel: " << x.bits_per_pixel << endl;
  s << "halftone: " << hex << x.halftone << dec << endl;
  s << "brightness: " << x.brightness << endl;
  s << "gamma: " << hex << x.gamma << dec << endl;
  s << "main_scan_zoom: " << x.main_scan_zoom << endl;
  s << "sub_scan_zoom: " << x.sub_scan_zoom << endl;
  s << "color_correction: " << hex << x.color_correction << dec << endl;
  s << "sharpness: " << hex << x.sharpness << dec << endl;
  s << "scanmode: " << hex << x.scanmode << dec << endl;
  s << "linecounter: " << x.linecounter << endl;
   
  return s;
} 

// Function: operator<<
// Parameters: 
//   ostream& s            - stream to print on
//   const ScanIdentity& x - identity structure to print.
//   
// Description: Pretty prints a ScanIdentity structure on stream s.
// Return value: a reference to s.
ostream& operator<<(ostream& s, const ScanIdentity& x)
{
  s << "Identity: " << endl;
  s << "software_level: " << x.software_level << endl;
  s << "option_installed: " << x.option_installed << endl;
  s << "resolutions: ";
  for (int i = 0; x.resolutions[i]; i++)
    s << x.resolutions[i] << ' ';
  s << endl;
  s << "max_resolution: " << x.max_resolution << endl;
  s << "max_horiz_area: " << x.max_horiz_area << endl;
  s << "max_vert_area: " << x.max_vert_area << endl;

  return s;
}

// Function: Scanner::error
// Parameters: 
//   char *format - a printf style format string
//   ...          - arguments to format
//   
// Description: This function is called whenever an error message
// should be printed. 
// Return value: void
void
Scanner::error(char *format, ...)
{
  va_list args;
  va_start(args, format);
  vsnprintf(errStr, sizeof(errStr), format, args);
}

// Function: Scanner::Scanner
// Parameters: 
//   void - 
// Description: Initializes a scanner object.
// Return value: 

Scanner::Scanner(void)
{
  Dev = NULL;
}

// Function: Scanner::~Scanner
// Parameters: 
//   void - 
// Description: Closes the scanner device.
// Return value: 

Scanner::~Scanner(void)
{
  delete Dev;
}

// Function: Scanner::open
// Parameters: 
//   const char *device - the name of a device.
//   
// Description: "device" is assumed to be a scanner device.  This
// function opens the scanner device and identifies the scanner's
// capabilities. 
// Return value: Returns 0 on success, -1 on failure.
int
Scanner::open(const char *device)
{
  ScsiDevice *dev = new ScsiDevice;
  if (!dev) {
    error("Failed to allocate ScsiDevice");
    return -1;
  }
  if (dev->open(device)) {
    error("Failed to open scsi device %s: %s", device, dev->strError());
    delete dev;
    return -1;
  }
   
  if (dev->initialize()) {
    error("Failed to initialize SCSI device: %s", dev->strError());
    delete dev;
    return -1;
  }
  Dev = dev;			// have to do this before other commands

   
  if (identify()) {
    delete dev;
    Dev = NULL;
    return -1;
  }
  //    if (Identity.software_level >= 2)
  //      if (initialize())
  //      {
  // 	delete dev;
  // 	Dev = NULL;
  // 	return -1;
  //      }
  if (requestCondition()) {
    delete dev;
    Dev = NULL;
    return -1;
  }

  return 0;
}

// Function: Scanner::open
// Parameters:
//   void -
// Description: Opens a scanner device, but searches for the device.
// Currently just searches /dev/sg[a-h], since I've only implemented
// SCSI, so if I were to implement the parallel interface I'd need to
// modify this function.
// 
// Return value: 0 on success, -1 on failure.
int
Scanner::open(void)
{
  ScsiDevice *dev = new ScsiDevice;
  if (!dev) {
    error("Failed to allocate ScsiDevice");
    return -1;
  }
  char device[10];
  const char chars[] = "abcdefgh123456789";
  for (char *pc = chars; *pc; ++pc) {
    sprintf(device, "/dev/sg%c", *pc);
    if (dev->open(device) == 0)	{ // found a scsi device
      const u_char *inq = dev->inquiry();
      if (inq &&
	  !strncmp(inq + 8, "EPSON SCANNER", strlen("EPSON SCANNER"))) {
	dev->close();
	delete dev;
	return open(device);
      }
    }
  }

  // if we got to here, we didn't find it
  error("Failed to find Scanner device");
  delete dev;
  return -1;
}

// Function: Scanner::close
// Parameters: 
//   void - 
// Description: closes the scanner device.  This function doesn't
// normally need to be called since the destructor will do this.
// 
// Return value: 0 on success, -1 on failure.
int
Scanner::close(void)
{
  if (Dev) {
    delete Dev;
    Dev = NULL;
  }
  return 0;
}

// Function: Scanner::applySettings
// Parameters: 
//   const ScanSettings& s - scanner settings.
//   
// Description: This function is used to change scanner settings.  To
// use this, first initialize a ScanSettings struct with
// getSettings(), and then change only the fields that you want to
// change.  I use this rather than making each individual setting
// publicly available because the settings must be made in a certain
// order, and this way a user doesn't need to be aware of that.
// 
// Return value: 0 on success, or -1 if any of the settings are incorrect.
int
Scanner::applySettings(const ScanSettings& s)
{
  if (setColorMode(s.color_mode) ||
      setDataFormat(s.bits_per_pixel) ||
      setHalftoning(s.halftone) ||
      setBrightness(s.brightness) ||
      setGamma(s.gamma) ||
      setColorCorrection(s.color_correction) ||
      setResolution(s.main_scan_res, s.sub_scan_res) ||
      setZoom(s.main_scan_zoom, s.sub_scan_zoom) ||
      setScanArea(s.main_scan_offset, s.sub_scan_offset,
		  s.main_scan_area, s.sub_scan_area) ||
      setSharpness(s.sharpness) ||
      setScanMode(s.scanmode))
    return -1;
  
  if (s.linecounter > 1) {
    int l = s.linecounter;
    if (s.color_mode == SCN_CLR_COLOR_LINE)
      while ((l % 3) != 0)
	l++;
    if (setLineCounter(l))
      return -1;
  }

  //   cerr << Settings << endl;

  return 0;
}

// Function: Scanner::getSettings
// Parameters: 
//   ScanNewSettings& s - will be filled in with the current settings
//   
// Description: Gets the current scanner settings, returning them in
// "s".  This is a new version of the function that converts the scan
// dimensions to inches rather than leaving them as raw pixel values.
// 
// Return value: 
void
Scanner::getSettings(ScanNewSettings& s) const
{
  const ScanSettings& S = Settings;	// make a shorter name
  float main_res, sub_res;

  // get effective main and sub resolutions
  main_res = (float)S.main_scan_res * ((float)S.main_scan_zoom / 100.0);
  sub_res = (float)S.sub_scan_res * ((float)S.sub_scan_zoom / 100.0);
  
  s = (ScanNewSettings) {
    S.color_mode,
      S.main_scan_res,
      S.sub_scan_res,
      (float)S.main_scan_offset / main_res,
      (float)S.sub_scan_offset / sub_res,
      (float)S.main_scan_area / main_res,
      (float)S.sub_scan_area / sub_res,
      S.bits_per_pixel,
      S.halftone,
      S.brightness,
      S.gamma,
      S.main_scan_zoom,
      S.sub_scan_zoom,
      S.color_correction,
      S.sharpness,
      S.scanmode,
      S.linecounter
      };
}


// Function: Scanner::getIdentity
// Parameters: 
//   ScanNewIdentity& i - A structure to receive scanner identity.
//   
// Description: Retreives the scanner identity and stores it in the
// argument "i".  The identity is information about the scanner's
// capabilities, such as what resolutions it supports, what
// instructions it supports, etc.  See the header file for individual fields.
// 
// Return value: void
void
Scanner::getIdentity(ScanNewIdentity& i) const
{
  const ScanIdentity& I = Identity;	// give it a short name
  
  i.software_level = I.software_level;
  i.option_installed = I.option_installed;
  bcopy(I.resolutions, i.resolutions, sizeof(I.resolutions));
  i.max_resolution = I.max_resolution;
  i.max_horiz_area = (float)I.max_horiz_area / (float)I.max_resolution;
  i.max_vert_area = (float)I.max_vert_area / (float)I.max_resolution;
}

// Function: Scanner::applySettings
// Parameters: 
//   const ScanNewSettings& s - scanner settings.
//   
// Description: This function is used to change scanner settings.  To
// use this, first initialize a ScanSettings struct with
// getSettings(), and then change only the fields that you want to
// change.  I use this rather than making each individual setting
// publicly available because the settings must be made in a certain
// order, and this way a user doesn't need to be aware of that.
// 
// Return value: 0 on success, or -1 if any of the settings are incorrect.
int
Scanner::applySettings(const ScanNewSettings& s)
{
  ScanSettings ns;		// new settings
  float main_res, sub_res;

  // get effective main and sub resolutions
  main_res = (float)s.main_scan_res * ((float)s.main_scan_zoom / 100.0);
  sub_res = (float)s.sub_scan_res * ((float)s.sub_scan_zoom / 100.0);
  
  ns = (ScanSettings) {
    s.color_mode,
      s.main_scan_res,
      s.sub_scan_res,
      (int)(s.main_scan_offset * main_res),
      (int)(s.sub_scan_offset * sub_res),
      (int)(s.main_scan_area * main_res),
      (int)(s.sub_scan_area * sub_res),
      s.bits_per_pixel,
      s.halftone,
      s.brightness,
      s.gamma,
      s.main_scan_zoom,
      s.sub_scan_zoom,
      s.color_correction,
      s.sharpness,
      s.scanmode,
      s.linecounter
      };

  if (ns.color_mode == SCN_CLR_COLOR_LINE)
    ns.linecounter = maxBlockSize() /
      ((ns.main_scan_area / (8 / ns.bits_per_pixel)) * 3);
  else
    ns.linecounter = maxBlockSize() /
      (ns.main_scan_area / (8 / ns.bits_per_pixel));

  ns.linecounter = ns.linecounter <? 255;
  
  return applySettings(ns);
}

// Function: Scanner::initScan
// Parameters: 
//   const char *fname - a filename to store the scanned image in.
//   
// Description: This function is called once each time a picture is to
// be scanned.  The argument is a file name, which will be used to
// store the scanned image in.  To scan an image, first call this
// function, and then use scanBlock() to scan the actual image.  The
// file will be stored in TIFF format.
// 
// Return value: 0 on success, -1 on failure.
int
Scanner::initScan(const char *fname)
{
  State.fname = fname;
  // samples per pixel
  State.spp = (Settings.color_mode == SCN_CLR_COLOR_PAGE ||
	       Settings.color_mode == SCN_CLR_COLOR_LINE) ? 3 : 1;
  State.photo = (State.spp == 3) ? PHOTOMETRIC_RGB : PHOTOMETRIC_MINISBLACK;
  State.planar = (State.spp == 3) ? PLANARCONFIG_SEPARATE :
    PLANARCONFIG_CONTIG;
  // rows per strip
  State.rps = (State.spp == 3) ? 1 : Settings.linecounter;

  // TIFF requires that for RGB images, bpp must be 8, and for
  // grayscale images, bpp must be 1, 4, or 8.
  
  State.out = TIFFOpen(fname, "w");
  if (!State.out) {
    error("Failed to open `%s' for output", fname);
    return -1;
  }
   
  TIFFSetField(State.out, TIFFTAG_BITSPERSAMPLE, Settings.bits_per_pixel);
  TIFFSetField(State.out, TIFFTAG_IMAGEWIDTH, Settings.main_scan_area);
  TIFFSetField(State.out, TIFFTAG_IMAGELENGTH, Settings.sub_scan_area);
  TIFFSetField(State.out, TIFFTAG_SAMPLESPERPIXEL, State.spp);
  TIFFSetField(State.out, TIFFTAG_PHOTOMETRIC, State.photo);
  TIFFSetField(State.out, TIFFTAG_PLANARCONFIG, State.planar);
  TIFFSetField(State.out, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
  TIFFSetField(State.out, TIFFTAG_ROWSPERSTRIP, State.rps);
  if (Settings.bits_per_pixel == 1)
    TIFFSetField(State.out, TIFFTAG_COMPRESSION, COMPRESSION_PACKBITS);
  else
    TIFFSetField(State.out, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
  TIFFSetField(State.out, TIFFTAG_XRESOLUTION, (float)Settings.main_scan_res);
  TIFFSetField(State.out, TIFFTAG_YRESOLUTION, (float)Settings.sub_scan_res);
  TIFFSetField(State.out, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);
   
  if (command('G')) {
    TIFFClose(State.out);
    return -1;
  }

  State.blk_no = 0;		// how many block we've read
  State.bytes_in = 0;		// how many bytes we've read
      
  if (Settings.linecounter > 1)
    State.block = true;
  else
    State.block = false;
   
  Settings.linecounter = 1;	// gets reset by ESC G, so update here

  if (Settings.color_mode == SCN_CLR_COLOR_PAGE)
    State.times_through = 3;
  else
    State.times_through = 1;

  State.line_no = 0;

  return 0;
}

// Function: Scanner::scanBlock
// Parameters:
//   bool cancel - If true, then stops the scan.
// 
// Description: Reads a block from the scanner and saves it to a
// file.  Call this function repeatedly until the whole image has been
// scanned.
// 
// Return value: returns the number of bytes read for this block, or 0
// when no more input is available, or -1 on error.
int
Scanner::scanBlock(bool cancel)
{
  u_char const *obuf;
  u_char headbuf[6];
  header_image_t hd;
  size_t blk_size;
  int sample,
    lines,
    linelen;

  if (State.times_through <= 0) { // done scanning
    TIFFClose(State.out);
    return 0;
  }
  
  if (State.block) {
    if (Dev->receive(6, headbuf)) {
      error("Failed to read data header for block %d: %s",
	    State.blk_no, Dev->strError());
      TIFFClose(State.out);	       
      return -1;
    }
    hd.code = headbuf[0];
    hd.status = headbuf[1];
    hd.block.b_count = (u_short)getword(headbuf[2], headbuf[3]);
    hd.block.l_count = (u_short)getword(headbuf[4], headbuf[5]);
  } else {
    if (Dev->receive(4, headbuf)) {
      error("Failed to read data header for block %d: %s",
	    State.blk_no, Dev->strError());
      TIFFClose(State.out);
      return -1;
    }
    hd.code = headbuf[0];
    hd.status = headbuf[1];
    hd.line.count = (u_short)getword(headbuf[2], headbuf[3]);
  }
	 
  switch (hd.code) {
  case STX:		// data header
    if (FATAL_ERROR(hd.status)) {
      error("Received fatal error. status = %#x, blk_no = %d\n",
	    (u_int)hd.status, State.blk_no);
      TIFFClose(State.out);
      return -1;
    }
    if (State.block)
      blk_size = hd.block.b_count * hd.block.l_count;
    else
      blk_size = hd.line.count;
    
    if (Dev->receive(blk_size)) {
      cancelScan();
      error("Failed to read data block %d: %s", State.blk_no,
	    Dev->strError());
      TIFFClose(State.out);	       
      return -1;
    }

    // Write the block to disk
    lines = State.block ? hd.block.l_count : 1;
    linelen = State.block ? hd.block.b_count : hd.line.count;
    obuf = Dev->getRbuf();
    switch (Settings.color_mode) {
    case SCN_CLR_COLOR_PAGE:
      sample = TIFF_SAMPLE(COLOR_ATTRIB(hd.status));
      for (int j = 0; j < lines; j++) {
	if (TIFFWriteScanline(State.out, const_cast<u_char*>(obuf),
			      State.line_no, sample) < 0) {
	  cancelScan();
	  error("failed to write line %d\n", State.line_no);
	  TIFFClose(State.out);
	  return -1;
	}
	State.line_no++;
	obuf += linelen;
      }
      break;
      
    case SCN_CLR_COLOR_LINE:
      // blocks of lines start with green, then red and blue.
      // if in line mode then just check status
      if (State.block) {
	for (int j = 0; j < lines; j++) {
	  if (TIFFWriteScanline(State.out,
				const_cast<u_char*>(obuf),
				State.line_no,
				TIFF_SAMPLE(j%3 + 1)) < 0) {
	    cancelScan();
	    error("failed to write line %d\n", State.line_no);
	    TIFFClose(State.out);
	    return -1;
	  }
	  obuf += linelen;
	  if ((j+1) % 3 == 0)
	    State.line_no++;
	}
      } else {
	sample = TIFF_SAMPLE(COLOR_ATTRIB(hd.status));
	
	if (TIFFWriteScanline(State.out,
			      const_cast<u_char*>(obuf),
			      State.line_no,
			      sample) < 0) {
	  cancelScan();
	  error("failed to write line %d\n", State.line_no);
	  TIFFClose(State.out);
	  return -1;
	}
	if (sample == SAMPLE_BLUE)
	  State.line_no++;
      }
      break;
      
    default:		// mono modes
      if (TIFFWriteEncodedStrip(State.out, State.blk_no,
				const_cast<u_char*>(obuf), blk_size) < 0) {
	cancelScan();
	error("failed to strip %d\n", State.blk_no);
	TIFFClose(State.out);
	return -1;
      }
      break;
    }			// switch
    
    State.blk_no++;
    State.bytes_in += blk_size;
    break;
    
  default:
    error("Expected STX, got %#x instead at block %#x",
	  (u_int)hd.code, State.blk_no);
    TIFFClose(State.out);
    return -1;
  }
	 
  if (!AREA_END(hd.status)) {
    if (cancel) {
      cancelScan();
      State.times_through = 0;	// make scanning stop
    } else {
      u_char cmd = ACK;
      if (Dev->send(&cmd, 1)) {
	error("failed to send ACK at data block %d: %s",
	      State.blk_no, Dev->strError());
	TIFFClose(State.out);
	return -1;
      }
    }
  } else {
    State.times_through--;
    State.line_no = 0;

  }

  return blk_size;
}

  
// Function: Scanner::command
// Parameters: 
//   u_char cmd    - Single character command for scanner
//   
//   int args_len  - length of args, 0 if none
//   
//   u_char *args  - arguments to the command, NULL if none
//   
//   int reply_len - length of header to read in response to command.
//   This is *not* the total length of the data expected back, it's
//   just the length of the header expected, i.e. 0, 1, or 4.  If 0,
//   don't read any header back, let the caller handle it.  If 1, read
//   one byte, i.e. an ACK signal. If 4, read a normal data header.
//   command will then know from the byte count in the header how many
//   bytes it should read if there's a data block.
//   
// Description: This is a generic function to send any command to the
// scanner.  If args is not NULL, args_len bytes will be sent
// following the command as a parameter.  Otherwise, if reply_len is
// greater than 0, it reads reply_len bytes back as a response.
// reply_len should be 0, 1, or 4.  If reply_len = 4, the function
// will read a 4 byte data header, and determine from the byte count
// if there's a data block and how many bytes it is.  Any data read is
// stored in the member structure Head.
//
// *NOTE* This won't handle the startScan (ESC G) command.  This is
// because it expects to read any data back in one block, which is
// true for all commands except ESC G. This is why I made the command
// so that if args = NULL, and reply_len = 0, it will just send the
// command and not read any response.  So the caller will handle the
// read.
// 
// Return value: 0 on success, -1 on failure
int
Scanner::command(u_char cmd, int args_len, u_char *args, int reply_len)
{
  dprintf("cmd = %c, args_len = %d, args = %p, reply_len = %d\n",
	  cmd, args_len, args, reply_len);
  if (Dev->sendf("\e%c", cmd)) {
    error("Cannot send command '%c' to scanner: %s",
	  cmd, Dev->strError());
    return -1;
  }

  if (reply_len) {		// expect a reply
    u_char buf[4];
    if (reply_len > 4)
      reply_len = 4;

    if (Dev->receive(reply_len, buf)) {
      error("can't read response from scanner: %s", Dev->strError());
      return -1;
    }
    Head.code = buf[0];
    switch (Head.code) {
    case STX:
      Head.status = buf[1];
      Head.count = (u_short)getword(buf[2], buf[3]);
#ifdef DEBUG
      cerr.form("Head.code = %#x\n", (u_int)Head.code);
      cerr.form("Head.status = %#x\n", (u_int)Head.status);
      cerr.form("Head.count = %#x\n", (u_int)Head.count);
#endif
      // This needs to be changed to use ESC F to find what the error is.
      if (FATAL_ERROR(Head.status)) {
	error("fatal error on command '%c'. status = %#x",
	      cmd, (u_int)Head.status);
	return -1;
      }
      if ((int)Head.count > MAX_DATA_SIZE) {
	error("count %d too large for command '%c'",
	      (int)Head.count, cmd);
	return -1;
      }
      else if (Head.count == 0) // no data block, done
	return 0;
	 
      if (Dev->receive((int)Head.count, Head.data)) { // read data block
	error("can't read data block for command '%c': %s",
	      cmd, Dev->strError());
	return -1;
      }
      return 0;

    case NAK:
      error("scanner sent NAK for command '%c'", cmd);
      return -1;

    case ACK:
      dprintf("got ACK for command '%c'\n", cmd);
      return 0;

    default:
      error("unexpected response, %#x, to command '%c'\n"
	    "status = %#x, count = %d",
	    (u_int)Head.code, cmd, (u_int)Head.status, Head.count);	 
      return -1;
    }
  }				// if (reply)
  else if (args)		// send an argument
    {
      u_char code;
      if (Dev->receive(sizeof(code), &code)) {
	error("can't read response from scanner: %s", Dev->strError());
	return -1;
      }
      switch (code) {
      case ACK:
	if (Dev->send(args, args_len))	{ // send arguments
	  error("failed to send arguments to command '%c': %s",
		cmd, Dev->strError());
	  return -1;
	}
	if (Dev->receive(sizeof(code), &code)) {
	  error("failed to read ACK for arguments to command '%c': %s",
		cmd, Dev->strError());
	  return -1;
	}
	if (code != ACK) {
	  error("Invalid arguments to command '%c'", cmd);
#ifdef DEBUG
	  for (int i = 0; i < args_len; i++)
	    cerr.form("%#x ", (u_int)args[i]);
	  cerr << endl;
#endif
	  return -1;
	}
	else
	  return 0;

      case NAK:
	error("received NAK for command '%c'", cmd);
	return -1;

      default:
	error("unexpected response, %#x, for command '%c'",
	      (int)code, cmd);
	return -1;
      } // switch
    } // else if
  else	// no args and no reply
    return 0;
}

int
Scanner::command(u_char cmd)
{
  return command(cmd, 0, NULL, 0);
}

int
Scanner::command(u_char cmd, int args_len, u_char *args)
{
  return command(cmd, args_len, args, 0);
}

int
Scanner::command(u_char cmd, int reply_len)
{
  return command(cmd, 0, NULL, reply_len);
}


// Function: Scanner::identify
// Parameters: 
//   void - 
// Description: Implements ESC I command.  Gets information from the
// scanner about what software level is supported, what resolutions
// are supported, and what the maximum horizontal and vertical
// scanning area are.  It stores all this information in the Identity
// member structure.
// 
// Return value: 0 on success, -1 on failure
int
Scanner::identify(void)
{
  if (command('I', 4))
    return -1;
   
  // software level is in ASCII
  Identity.software_level = Head.data[1] - '0';
  Identity.option_installed = OPTION_FLAG(Head.status) != 0;

  int *p = Identity.resolutions;
  for (int i = 2; i < Head.count; i += 3) {
    switch (Head.data[i]) {
    case 'R':
      *(p++) = getword(Head.data[i+1], Head.data[i+2]);
      break;
    case 'A':
      *p = 0;		// Make it 0 terminated
      Identity.max_resolution = *(p-1);
      Identity.max_horiz_area = getword(Head.data[i+1], Head.data[i+2]);
      Identity.max_vert_area = getword(Head.data[i+3], Head.data[i+4]);
      i += 2;			// this block is 5 instead of 3 bytes
      break;
    default:			// don't know why this would happen
      error("Unrecognized byte '%#x' in identify data", (u_int)Head.data[i]);
      return -1;
    }
  }

#ifdef DEBUG
  cerr << Identity << endl;
#endif
   
  return 0;
}

// Function: Scanner::requestStatus
// Parameters: 
//   void - 
// Description: Implements ESC F command.  This command just reads a
// data header from the scanner.  It is stored in the Head member
// structure.  Use this to check the status of the scanner by
// examining Head.status.
// 
// Return value: 0 on success, -1 on failure
int
Scanner::requestStatus(void)
{
  if (command('F', 4))
    return -1;
  else
    return 0;
}

// Function: Scanner::requestXStatus
// Parameters: 
//   void - 
// Description: Implements ESC f command.  Requests status of the
// scanner plus status of optional equipment.  Not really useful
// unless you have an automatic document feeder or transperancy unit
// attached to the scanner.
// 
// Return value: 0 on success, -1 on failure
int
Scanner::requestXStatus(void)
{
  if (Identity.option_installed && command('f', 4))
    return -1;
  else
    return 0;
}

// Function: Scanner::requestCondtion
// Parameters: 
//   void - 
// Description: Implements ESC S command.  This command gets
// information from the scanner about its current settings, such as
// data format, brightness, scan area, etc.  It stores the information
// in the member struct Settings.  This must be called before any
// information in Settings is valid.
//
// *NOTE* This may need to be changed to work with a different
// scanner model than the GT-9000/ES-1200, because not all of these
// fields are present on some models.  Hopefully the way I wrote it
// would prevent it from actually being a problem.
// 
// Return value: 0 on success, -1 on failure
int
Scanner::requestCondition(void)
{
  if (command('S', 4))
    return -1;

  // Not all scanner models have all of these fields.
  const u_char *data = Head.data;

  if (Identity.software_level >= 1) {
    if (data[0] == 'C')
      Settings.color_mode = (ScanColorMode)data[1];
    if (data[2] == 'R') {
      Settings.main_scan_res = getword(data[3], data[4]);
      Settings.sub_scan_res = getword(data[5], data[6]);
    }
    if (data[7] == 'A') {
      Settings.main_scan_offset = getword(data[8], data[9]);
      Settings.sub_scan_offset = getword(data[10], data[11]);
      Settings.main_scan_area = getword(data[12], data[13]);
      Settings.sub_scan_area = getword(data[14], data[15]);
    }
    if (data[16] == 'D')
      Settings.bits_per_pixel = data[17];
    if (data[18] == 'B')
      Settings.halftone = (ScanHalftoning)data[19];
    if (data[20] == 'L')
      Settings.brightness = data[21];
    if (data[22] == 'Z')
      Settings.gamma = (ScanGamma)data[23];
    if (data[24] == 'H') {
      Settings.main_scan_zoom = data[25];
      Settings.sub_scan_zoom = data[26];
    }
  } // software_level >= 1

  if (Identity.software_level >= 3) {
    if (data[27] == 'M')
      Settings.color_correction = (ScanColorCorrection)data[28];
  }

  if (Identity.software_level >= 4) {
    if (data[29] == 'Q')
      Settings.sharpness = (ScanSharpness)data[30];
    if (data[31] == 'g')
      Settings.scanmode = (ScanScanmode)data[32];
  }
   
  Settings.linecounter = 1;	// default setting
   
#ifdef DEBUG
  cerr << Settings << endl;
#endif


  return 0;
}

int
Scanner::cancelScan(void)
{
  u_char cmd = CAN;
  if (Dev->send(&cmd, 1)) {
    error("Failed to cancel scan: %s", Dev->strError());
    return -1;
  }
  if (Dev->receive(1, &cmd) || cmd != ACK) {
    error("Failed to cancel scan: %s", Dev->strError());
    return -1;
  }
  return 0;
}

   
// Function: Scanner::setColorMode
// Parameters: 
//   ScanColorMode mode - a color mode of type enum ScanColorMode
//   
// Description: Implements ESC C.  The argument is one of
// SCN_CLR_MONO  -      monochrome scanning
// SCN_CLR_MONO_RED -   monochrome with droupout color red
// SCN_CLR_MONO_GREEN - monochrome with dropout color green
// SCN_CLR_MONO_BLUE -  monochrome with dropout color blue
// SCN_CLR_COLOR_PAGE - color scanning, page mode
// SCN_CLR_COLOR_LINE - color scanning, line mode
// For monochrome scanning you can select red, green, or blue as the
// dropout color.  Then the scanner ignores just that color.  This can
// be used to ignore certain color markings on a page.  For color
// scanning, you can choose page scanning or line scanning.  Page
// scanning scans the entire page three times, once for each color.
// Line scanning scans all three colors line by line.
// 
// Return value: 0 on success, -1 on failure
int
Scanner::setColorMode(ScanColorMode mode)
{
  u_char m = (u_char)mode;
  if (command('C', 1, &m))
    return -1;
  else {
    Settings.color_mode = mode;
    return 0;
  }
}

// Function: Scanner::setDataFormat
// Parameters: 
//   int bpp - bits per pixel to scan. Must be 1, 4, or 8
//   
// Description: Implements ESC D.  This sets the bits per pixel the
// scanner uses, allowing for more color variation at the expense of
// more space for the data.   The scanner allows the range
// 1 <= bpp <= 8. But tiff only allows 1, 4, or 8 bits per pixel, so
// that's all I allow.  Also, for color images, only 8 bits per pixel
// is allowed by tiff, so this setting will be ignored completely.
// 
// Return value: 0 on success, -1 on failure
int
Scanner::setDataFormat(int bpp)
{
  if (bpp > 8 || bpp < 1) {
    error("Scanner::setDataFormat: bpp '%d' out of range",
	  bpp);
    return -1;
  }

  if (Settings.color_mode == SCN_CLR_COLOR_LINE ||
      Settings.color_mode == SCN_CLR_COLOR_PAGE)
    bpp = 8;
  else if (bpp > 1 && bpp < 4)
    bpp = 4;
  else if (bpp > 4 && bpp < 8)
    bpp = 8;
   
  u_char b = (u_char)bpp;
  if (command('D', 1, &b))
    return -1;
  else {
    Settings.bits_per_pixel = bpp;
    return 0;
  }
}

// Function: Scanner::setResolution
// Parameters: 
//   int main - main scan resolution in dpi
//   int sub  - sub scan resolution in dpi
//   
// Description: Implements ESC R.  main is the horizontal resolution
// in dots per inch, sub is the vertical resolution.  Both must be one
// of the resolutions supported by this scanner, as obtained by the
// Identity (ESC I) command.  If either main or sub are not valid the
// command returns an error.
// 
// Return value: 0 on success, -1 on error or invalid input.
int
Scanner::setResolution(int main, int sub)
{
  int i = 0;
  do {
    if (Identity.resolutions[i] == 0) {
      error("Invalid main scan resolution %d\n", main);
      return -1;
    }
  } while (main != Identity.resolutions[i++]);
   
  i = 0;
  do {
    if (Identity.resolutions[i] == 0) {
      error("Invalid sub scan resolution %d\n", sub);
      return -1;
    }
  } while (sub != Identity.resolutions[i++]);

  u_char buf[4];
  buf[0] = main;		// LSB
  buf[1] = main >> 8;		// MSB
  buf[2] = sub;		// LSB
  buf[3] = sub >> 8;		// MSB

  if (command('R', 4, buf))
    return -1;
  else {
    Settings.main_scan_res = main;
    Settings.sub_scan_res = sub;
    return 0;
  }
}

// Function: Scanner::setZoom
// Parameters: 
//   int main - main scan zoom by percentage. range 50-200
//   int sub  - sub scan zoom by percentage. range 50-200
//   
// Description: Implemets ESC H. Sets the main scan (horizontal) and
// sub scan (vertical) zoom factors.  The range for both parameters
// must be within 50 to 200.  100 percent is actual size.  Scanner
// must have software level B2 or higher.
// 
// Return value: 0 on success, -1 on error or out of range parameters
int
Scanner::setZoom(int main, int sub)
{
  if (Identity.software_level < 2) {
    dprintf("setZoom (ESC H) not supported by this scanner");
    return 0;
  }
  if (main > 200 || main < 50) {
    error("main scan zoom %d out of range\n", main);
    return -1;
  }
  if (sub > 200 || sub < 50) {
    error("sub scan zoom %d out of range\n", sub);
    return -1;
  }

  u_char buf[2];
  buf[0] = main;
  buf[1] = sub;

  if (command('H', 2, buf))
    return -1;
  else {
    Settings.main_scan_zoom = main;
    Settings.sub_scan_zoom = sub;
    return 0;
  }
}

// Function: Scanner::setScanArea
// Parameters: 
//   int mainoff - offset from origin for horizontal scan in pixels
//   int suboff  - offset from origin for vertical scan in pixels
//   int mainlen - length of horizontal scan in pixels
//   int sublen  - length of vertical scan in pixels
//   
// Description: ESC A command. Sets which portion of the scanning area
// is actually scanned.  mainoff and suboff set the offset from the
// origin in the horizontal and vertical directions to start scanning
// at.  mainlen and sublen set the length that is scanned, from the
// origin or from mainoff and suboff.  If mainoff and suboff are 0,
// scanning starts from the origin.
//
// (mainoff + mainlen) must not exceed the maximum main scan length,
// and (suboff + sublen) must not exceed the maximum sub scan length.
// The max main scan and sub scan lengths are dependant on the current
// resolution settings (see setResolution ESC R), maximum resolution
// avialable, and current zoom.  Here's the formula from the EPSON
// manual.
// 
// max main scan length = 8 * INT((X/Rmax) * (R*H/100)/8)
// max sub scan length  = INT((Y/Rmax) * (R*H/100))
//
// where X is the max main scan length obtained with ESC I command, Y
// is max sub scan length obtained with ESC I, Rmax is max resolution
// as obtained by ESC I, R is the current resolution setting, H is the
// current zoom setting.
//
// *NOTE* mainlen must be a multiple of 8!!.  The other values can be
// any value.
//
// *NOTE* The reading area is reset to its maximum values by the
// scanner when it receives setResolution (ESC R) or setZoom (ESC H)
// commands.  So make those settings before making this one.
// 
// Return value: 0 on success, -1 on failure or out of range parameters
int
Scanner::setScanArea(int mainoff, int suboff,
                     int mainlen, int sublen)
{
  dprintf("%d, %d, %d, %d\n", mainoff, suboff, mainlen, sublen);
  // See if paramters are in range
  mainlen &= ~(0x7L); // make sure it's a multiple of 8

  float resmax = (float)Identity.max_resolution;
   
  // Calculate the maximum main and sub scan lengths taking current
  // zoom and resolution into account
  // These formulas taken from EPSON manuals
  int mainmax = (int)(8 * (((float)Identity.max_horiz_area / resmax) *
			   ((float)Settings.main_scan_res *
			    (float)Settings.main_scan_zoom / 100.0) / 8.0));
  int submax = (int)(((float)Identity.max_vert_area / resmax) *
		     ((float)Settings.sub_scan_res *
		      (float)Settings.sub_scan_zoom / 100.0));
   
  if ((mainoff < 0) || (mainoff > (mainmax - 8))       ||
      (suboff < 0)  || (suboff > (submax - 1))         ||
      (mainlen < 8) || (mainlen > (mainmax - mainoff)) ||
      (sublen < 1)  || (sublen > (submax - suboff))) {
    error("Parameters out of range for setScanArea");
#if 1
    cerr.form("%d, %d, %d, %d\n", mainoff, suboff, mainlen, sublen);
    cerr << "Range with the current zoom and resolution is:\n";
    cerr.form("Main scan max size = %d\n", mainmax);
    cerr.form("Sub scan max size = %d\n", submax);
#endif
    return -1;
  }

  u_char buf[8];
  buf[0] = mainoff;		// LSB
  buf[1] = mainoff >> 8;	// MSB

  buf[2] = suboff;		// LSB
  buf[3] = suboff >> 8;	// MSB

  buf[4] = mainlen;		// LSB
  buf[5] = mainlen >> 8;	// LSB

  buf[6] = sublen;		// LSB
  buf[7] = sublen >> 8;	// MSB
   
  if (command('A', 8, buf))
    return -1;
  else {
    Settings.main_scan_offset = mainoff;
    Settings.sub_scan_offset = suboff;
    Settings.main_scan_area = mainlen;
    Settings.sub_scan_area = sublen;
    return 0;
  }
}


// Function: Scanner::setBrightness
// Parameters: 
//   int bright - image brightness, range -3 - 3, 3 is brightest
//   
// Description: ESC L command. Sets image brightness to one of seven
// levels, -3 being darkest, 3 being lightest
// 
// Return value: 0 on success, -1 on failure
int
Scanner::setBrightness(int bright)
{
  if (Identity.software_level < 2) {
    dprintf("setBrightness (ESC L) not supported by this scanner");
    return 0;
  }
  if (bright > 3 || bright < -3) {
    error("setBrightness bright = %d, out of range", bright);
    return -1;
  }

  u_char b = (u_char)bright;
  if (command('L', 1, &b)) {
    error("Scanner::setBrightness command error");
    return -1;
  }
  else {
    Settings.brightness = bright;
    return 0;
  }
}

// Function: Scanner::setGamma
// Parameters: 
//   ScanGamma gamma - one of ScanGamma enum values
//   
// Description: ESC Z command.  Must be software level 2 to support.
// Sets the gamma correction for different types of devices.  Only the
// following values are accepted.  Note that SCN_GAMMA_USER is only
// supported by B4 level scanners.
// SCN_GAMMA_CRTA   - CRT display A (for bi-level data)
// SCN_GAMMA_CRTB   - CRT display B (for multi-level 8-bit data)
// SCN_GAMMA_PRINTA - Printer A (high density printing/24 dot mode)
// SCN_GAMMA_PRINTB - Printer B (low density printing/8 dot mode)
// SCN_GAMMA_PRINTC - Printer C (high contrast printing/Image and
//                    chars mixed)
// SCN_GAMMA_USER   - User defined gamma table (Set with
//                    setGammaTable() (ESC z))
//
// Return value: 0 on success, -1 on failure.
int
Scanner::setGamma(ScanGamma gamma)
{
  if (Identity.software_level < 2) {
    dprintf("setGamma (ESC Z) not supported by this scanner");
    return 0;
  }
  u_char g = (u_char)gamma;
  if (command('Z', 1, &g))
    return -1;
  else {
    Settings.gamma = gamma;
    return 0;
  }
}

// int
// Scanner::setGammaTable(char color, u_char *table)
// {
//     cerr << __PRETTY_FUNCTION__ << "not implemented yet\n";
// }

// Function: Scanner::setSharpness
// Parameters: 
//   ScanSharpness sharp - one of ScanSharpness enum values
//   
// Description: ESC Q command.  Scanner must be level B4.  Sets the
// sharpness of the image. sharp must be one of the following enum
// values.
// SCN_SHARP_XDEFOCUSED - very defocused
// SCN_SHARP_DEFOCUSED  - defocused
// SCN_SHARP_NORMAL     - normal
// SCN_SHARP_SHARP      - sharp focus
// SCN_SHARP_XSHARP     - very sharp
// 
// Return value: 0 on success, -1 on failure
int
Scanner::setSharpness(ScanSharpness sharp)
{
  if (Identity.software_level < 4) {
    dprintf("setSharpness (ESC Q) not supported by this scanner");
    return 0;
  }
  u_char s = (u_char)sharp;
  if (command('Q', 1, &s))
    return -1;
  else {
    Settings.sharpness = sharp;
    return 0;
  }
}

// Function: Scanner::setHalftoning
// Parameters: 
//   ScanHalftoning htone - one of the ScanHalftoning enum values
//   
// Description: ESC B command. Set halftoning mode.  Only accepts the
// following values
// SCN_HTONE_HARD     - halftoning mode A (hard)
// SCN_HTONE_SOFT     - halftoning mode B (soft)
// SCN_HTONE_NET      - halftoning mode C (net-screen)
// SCN_HTONE_DITHERA  - dither mode A (4x4 bayer)
// SCN_HTONE_DITHERB  - dither mode B (4x4 spiral)
// SCN_HTONE_DITHERC  - dither mode C (4x4 net screen)
// SCN_HTONE_DITHERD  - dither mode D (8x4 net screen)
// SCN_HTONE_UDITHERA - user defined dither pattern A
// SCN_HTONE_UDITHERB - user defined dither pattern B
// SCN_HTONE_DISABLE  - disable halftoning process
// The dithering options are only availabe on B4 level scanners.  Use
// the SetDitherPattern() (ESC b) command to download a dither pattern
// to the scanner.
//
// *NOTE* This setting is only available when bits_per_pixel is 1 or 2
// (see setDataFormat() ESC D).  If using quad-level data (bpp = 2)
// the SOFT and NET halftoning are reset to HARD. Also if using color
// line sequence mode, SOFT and NET are reset to HARD.  The dither
// modes are not effective when bpp = 2.
// 
// Return value: 0 on success, -1 on failure
int
Scanner::setHalftoning(ScanHalftoning htone)
{
  u_char h = (u_char)htone;
  if (command('B', 1, &h))
    return -1;
  else {
    Settings.halftone = htone;
    return 0;
  }
}

// int
// Scanner::SetDitherPattern(int pat, int size, u_char *data)
// {
//     cerr << __PRETTY_FUNCTION__ << "not implemented yet\n";
// }

// Function: Scanner::setColorCorrection
// Parameters: 
//   ScanColorCorrection correct - one of ScanColorCorrection enums
//   
// Description: ESC M command. Scanner must be level B3 or higher.
// Sets color correction for different output devices.  Only accepts
// the following values:
// SCN_CLR_CORRECT_DOTMAT - color dot matrix printers
// SCN_CLR_CORRECT_USER   - user defined color correction (see ESC m)
// SCN_CLR_CORRECT_IMPACT - impact dot printers
// SCN_CLR_CORRECT_LASER  - color laser printers
// SCN_CLR_CORRECT_INKJET - color inkjet printers
// SCN_CLR_CORRECT_CRT    - color monitors
// The USER and IMPACT options are only available with B4 scanners.
// 
// Return value: 0 on success, -1 on failure
int
Scanner::setColorCorrection(ScanColorCorrection correct)
{
  if (Identity.software_level < 3) {
    dprintf("setColorCorrection (ESC M) not supported by this scanner");
    return 0;
  }
  u_char c = (u_char)correct;
  if (command('M', 1, &c))
    return -1;
  else {
    Settings.color_correction = correct;
    return 0;
  }
}

// Function: Scanner::initialize
// Parameters: 
//   void - 
// Description: ESC @ command.  Resets scanner settings to their
// defaults.  Must have software level 2 or higher.
// 
// Return value: 0 on success, -1 on failure or wrong software level
int
Scanner::initialize(void)
{
  if (Identity.software_level < 2) {
    dprintf("Initialize (ESC @) not supported by this scanner");
    return 0;
  }
  if (command('@', 1))
    return -1;
  else
    return 0;
}

// Function: Scanner::setLineCounter
// Parameters: 
//   int lines - number of lines to read per block. range 1 to 255
//   
// Description: ESC d command.  Scanner must be level B4.  Sets the
// scanner to block mode, so 'lines' lines will be sent to the
// computer at once instead of 1.  Must be in range 0 < lines <= 255.
//
// *NOTE* When startScan (ESC G) is executed, this is set back to 1,
// so it needs to be set before startScan each time.
// 
// Return value: 0 on success, -1 on failure
int
Scanner::setLineCounter(int lines)
{
  if (Identity.software_level < 4) {
    dprintf("setLineCounter (ESC d) not available on this scanner");
    return 0;
  }
  if (lines < 1 || lines > 255) {
    error("setLineCounter called with out of range parameter");
    return -1;
  }
  u_char l = (u_char)lines;
  if (command('d', 1, &l))
    return -1;
  else {
    Settings.linecounter = lines;
    return 0;
  }
}

// Function: Scanner::setScanMode
// Parameters: 
//   ScanScanmode mode - one of ScanScanmode enums
//   
// Description: ESC g command.  Sets scanning speed mode. Accepts one
// of these values:
// SCN_MODE_NORMAL - normal speed mode
// SCN_MODE_FAST   - high speed mode
// High speed mode causes the carriage to move at twice the speed of
// normal mode.  EPSON recommends using fast mode for bi-level images, such
// as text and line-art, and normal mode for for continuous tone
// images, such as 8-bit monochrome or color.  Most effective when
// used along with block data mode (see setLineCounter() ESC d).  I
// believe only software level 4 models support this command; the
// manual isn't clear, but only level 4 scanners have the 'g' field in 
// the condition block, so I guess only they support it.
// 
// Return value: 0 on success, -1 on failure
int
Scanner::setScanMode(ScanScanmode mode)
{
  if (Identity.software_level < 4) {
    dprintf("setScanMode (ESC g) not supported by this scanner.");
    return 0;
  }
  
  u_char m = (u_char)mode;
  if (command('g', 1, &m))
    return -1;
  else {
    Settings.scanmode = mode;
    return 0;
  }
}

// int
// Scanner::SetControlOption(bool enable)
// {
//     cerr << __PRETTY_FUNCTION__ << "not implemented yet\n";
// }

// int
// Scanner::EjectDocument(void)
// {
//     cerr << __PRETTY_FUNCTION__ << "not implemented yet\n";
// }

