// -*-c++-*-
/**********************************************************************
 *     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>
// A class for controlling an Epson scanner.
#ifndef _SCANNER_H_
#define _SCANNER_H_

#include <sys/types.h>
#include <stdio.h>
#include <fstream.h>
#include <tiffio.h>
#include <string>

#include "IODevice.h"

// see ESC C, Set Color Mode, documentation.  Not all of these modes
// are supported by all scanners
enum ScanColorMode { 
   SCN_CLR_MONO = 0x0,		// Monochrome, no dropout color (B1-4)
   SCN_CLR_MONO_RED = 0x10,	// monochrome, dropout color red (B2-4)
   SCN_CLR_MONO_GREEN = 0x20,	// mono, dropout color green (B2-4)
   SCN_CLR_MONO_BLUE = 0x30,	// mono, dropout color blue (B2-4)
   SCN_CLR_COLOR_PAGE = 0x1,	// color scanning, page mode (B1-4)
   SCN_CLR_COLOR_LINE = 0x2	// color, line mode (B3,4)
};

// see ESC Z, Set Gamma Correction documentation.
enum ScanGamma {
   SCN_GAMMA_CRTA = 0x1,	// CRT display A (B2-4)
   SCN_GAMMA_CRTB = 0x2,	// CRT display B (B2-4)
   SCN_GAMMA_PRINTA = 0x0,	// Printer output A (B2-4)
   SCN_GAMMA_PRINTB = 0x10,	// Printer output B (B2-4)
   SCN_GAMMA_PRINTC = 0x20,	// Printer output C (B2-4)
   SCN_GAMMA_USER = 0x3		// User defined Gamma table (B4)
};

// see ESC Q, Set Sharpness documentation. Only supported in format B4
enum ScanSharpness {
   SCN_SHARP_XDEFOCUSED = 0xfe,	// very defocused
   SCN_SHARP_DEFOCUSED = 0xff,	// defocused
   SCN_SHARP_NORMAL = 0x0,	// normal
   SCN_SHARP_SHARP = 0x1,	// sharp
   SCN_SHARP_XSHARP = 0x2	// very sharp
};

// see ESC B, Set Halftoning Mode documentation
enum ScanHalftoning {
   SCN_HTONE_HARD = 0x0,	// halftoning mode A (hard tone) (B1-4)
   SCN_HTONE_SOFT = 0x10,	// mode B (soft tone). (B1-4)
   SCN_HTONE_NET = 0x20,	// mode C (net-screen?). (B1-4)
   SCN_HTONE_DITHERA = 0x80,	// dither mode A (4x4 Bayer). (B4)
   SCN_HTONE_DITHERB = 0x90,	// mode B (4x4 Spiral). (B4)
   SCN_HTONE_DITHERC = 0xa0,	// mode C (4x4 net-screen). (B4)
   SCN_HTONE_DITHERD = 0xb0,	// mode D (8x4 net-screen). (B4)
   SCN_HTONE_UDITHERA = 0xc0,	// user defined dither pattern A. (B4)
   SCN_HTONE_UDITHERB = 0xd0,	// user defined dither pattern B. (B4)
   SCN_HTONE_DISABLE = 0x1	// disable halftoning process (B1-4)
};

// see ESC M, Set Color Correction.  For B3,4
enum ScanColorCorrection {
   SCN_CLR_CORRECT_DOTMAT = 0x0, // color dot matrix printers
   SCN_CLR_CORRECT_USER = 0x01,	 // user defined color correction (B4)
   SCN_CLR_CORRECT_IMPACT = 0x10, // impact dot printers (B4)
   SCN_CLR_CORRECT_LASER = 0x20, // color laser
   SCN_CLR_CORRECT_INKJET = 0x40, // color inkjet
   SCN_CLR_CORRECT_CRT = 0x80	  // color monitors
};

// see ESC g, Set Scanning Mode.  For EXT format
enum ScanScanmode {
   SCN_MODE_NORMAL = 0x0,	// normal speed mode
   SCN_MODE_FAST = 0x1		// high speed mode
};

// to hold the current settings, as returned by ESC S
struct ScanSettings {
   ScanColorMode color_mode;	// ref (ESC C)
   int main_scan_res;		// main scan resolution (ref ESC R)
   int sub_scan_res;		// sub scan resolution
   int main_scan_offset;	// ref. ESC A
   int sub_scan_offset;
   int main_scan_area;
   int sub_scan_area;
   int bits_per_pixel;		// ref. ESC D
   ScanHalftoning halftone;	// ref. ESC B
   int brightness;		// ref. ESC L
   ScanGamma gamma;		// gamma correction ref. ESC Z
   int main_scan_zoom;		// horizontal zoom. ref. ESC H
   int sub_scan_zoom;		// vertical zoom
   ScanColorCorrection color_correction; // ref. ESC M
   ScanSharpness sharpness;	// ref. ESC Q
   ScanScanmode scanmode;	// ref ESC g
   int linecounter;		// number of line per block ref. ESC d
};

// to hold the current settings, as returned by ESC S.  This is a new
// version that takes the main and subscan areas in inches instead of
// pixels, and let's the driver figure out how many pixels that should
// be.  
struct ScanNewSettings {
   ScanColorMode color_mode;	// ref (ESC C)
   int main_scan_res;		// main scan resolution (ref ESC R)
   int sub_scan_res;		// sub scan resolution
   float main_scan_offset;	// ref. ESC A
   float sub_scan_offset;
   float main_scan_area;
   float sub_scan_area;
   int bits_per_pixel;		// ref. ESC D
   ScanHalftoning halftone;	// ref. ESC B
   int brightness;		// ref. ESC L
   ScanGamma gamma;		// gamma correction ref. ESC Z
   int main_scan_zoom;		// horizontal zoom. ref. ESC H
   int sub_scan_zoom;		// vertical zoom
   ScanColorCorrection color_correction; // ref. ESC M
   ScanSharpness sharpness;	// ref. ESC Q
   ScanScanmode scanmode;	// ref ESC g
   int linecounter;		// number of line per block ref. ESC d
};


// to hold info returned by ESC I
struct ScanIdentity {
   int software_level;	// B1-4
   bool option_installed;
   int resolutions[30];	// 0 terminated list of available res.
   int max_resolution;	// need this in some places
   int max_horiz_area;	// max horizontal reading area
   int max_vert_area;	// max vertical reading area
};

// to hold info returned by ESC I, new version with max_horiz_area and
// max_vert_area in inches rather than in pixels
struct ScanNewIdentity {
   int software_level;	// B1-4
   bool option_installed;
   int resolutions[30];	// 0 terminated list of available res.
   int max_resolution;	// need this in some places
   float max_horiz_area;	// max horizontal reading area
   float max_vert_area;	// max vertical reading area
};

ostream& operator<<(ostream& s, const ScanSettings& x);
ostream& operator<<(ostream& s, const ScanIdentity& x);

class Scanner {
public:
   Scanner(void);		// use initialize() to init

   ~Scanner(void);
  
   int open(const char *device); // open the device
   int open();			// try to find the scanner device
   int close(void);		 // close device


   static int getVersionMajor() { return versionMajor; }
   static int getVersionMinor() { return versionMinor; }
  
   const char *strError() const { return errStr; }
   void getSettings(ScanSettings& s) const { s = Settings; }
   void getIdentity(ScanIdentity& i) const { i = Identity; }
   int applySettings(const ScanSettings& s);

   // the new version of the above commands.
   void getSettings(ScanNewSettings& s) const;
   void getIdentity(ScanNewIdentity& i) const;
   int applySettings(const ScanNewSettings& s);


   // initScan and scanBlock are the interface for scanning into a tiff
   // file. 
   int initScan(const char *fname);
   // scanBlock returns the number of bytes it read, 0 when no more
   // input is availabe, and returns -1 on a failure.
   int scanBlock(bool cancel = false);
  
   // return the size the scanned image will take up.
   int scanSize(void) const;
   int maxBlockSize() const { return 32 * 1024; }
  
   // just for debugging
   const u_char *getdata() { return (const u_char *)&Head; }
   ////// scanner commands /////////////
   
   // Execution commands
   int identify(void); // Identify the properties of scanner (ESC I, B1-4)
   int requestStatus(void); // Check status of scanner (ESC F, B1-4)
   int requestXStatus(void); // Extended status info (ESC f, EXT)
   int requestCondition(void); // Current parameter values (ESC S, B1-4)
   int initialize(void); // initialize device (ESC @, B2-4)
   
protected:
   int cancelScan(void);
   //    // Data form definition commands
   int setColorMode(ScanColorMode mode); // (ESC C, B1-4)
   int setDataFormat(int bpp);      // Set bits per pixel (ESC D, B1-4)
   int setResolution(int main, int sub);   // (ESC R, B1-4)
   int setZoom(int main, int sub);	   // (ESC H, B2-4)
   int setScanArea(int mainoff, int suboff, // (ESC A, B1-4)
		   int mainlen, int sublen);
  
   //    // Image definition commands
   // Brightness range is -3...3, with 3 being brightest
   int setBrightness(int bright); // (ESC L, B2-4)
   int setGamma(ScanGamma gamma); // (ESC Z, B2-4)
   //    int setGammaTable(char color, u_char *table); // (ESC z, B4)
   int setSharpness(ScanSharpness sharp);	 // (ESC Q, B4)

   //    // Image processing commands
   int setHalftoning(ScanHalftoning htone); // (ESC B, B1-4)
   //    int SetDitherPattern(int pat, int size, u_char *data); // (ESC b, B4)
   int setColorCorrection(ScanColorCorrection correct); // (ESC M, B3-4)

   //    // Auxiliary commands
   int setLineCounter(int lines); // lines per block (ESC d, B4)
   int setScanMode(ScanScanmode mode);	// (ESC g, EXT)
   //    int SetControlOption(bool enable);	// (ESC e, EXT)
   //    int EjectDocument(void);		// (FF, EXT)

private:
   static const int versionMajor;
   static const int versionMinor;
  
   // EPSON data structures
   enum {
      MAX_DATA_SIZE = 128 // max size for data blocks other than
      // image data.
   };
   
   // line data header
   struct header_line_t {
      u_short count;
   };
   
   // block data header
   struct header_block_t {
      u_short b_count;		// byte counter
      u_short l_count;		// line counter
   };

   struct header_image_t {
      u_char code;
      u_char status;
      union {
	 header_line_t line;
	 header_block_t block;
      };
   };

   struct header_data_t {	// to hold data for commands other
      u_char code;		// than ESC G, i.e. not IMAGE data
      u_char status;
      u_short count;
      u_char data[MAX_DATA_SIZE];
   } Head;		


   // to keep track of the state of a scan in progress
   struct scan_state_t {
      int spp,			// samples per pixel
	 photo,			// photometric
	 planar,			// separate of contiguous
	 rps;			// rows per strip
      string fname;
      TIFF *out;

      bool block;			// true if we're using block mode
      size_t blk_no,		// block number
	 bytes_in;			// how many bytes read so far
      int line_no;
      int times_through;
   };    

   ScanIdentity Identity;
   ScanSettings Settings;
   scan_state_t State;
   IODevice *Dev;		// the scanner device
   string idString;

   // send a command to the scanner.  The actual workhorse.
   int command(u_char cmd, int args_len, u_char *args, int reply_len);
   
   // send a command and don't read any reply.  up to the caller to read it
   int command(u_char cmd);

   // send a command which takes parameters
   int command(u_char cmd, int args_len, u_char *args);

   // send a command and read a reply.  *NOTE* reply len should only
   // be either 0, 1, or 4.  The routine will determine if there is a
   // data block and how many bytes it is from the header.  Use 1 if
   // command just returns an ACK, 4 to read just the header, and 0 to
   // let the caller read the response.
   int command(u_char cmd, int reply_len);
   
   char errStr[200];
   void error(char *format, ...);
};

inline int
Scanner::scanSize(void) const
{
   int ppb = 8 / Settings.bits_per_pixel; // pixels per byte
   int headerlen = 25;		// leave some room for the header info
   int colors;
   if (Settings.color_mode == SCN_CLR_COLOR_PAGE ||
       Settings.color_mode == SCN_CLR_COLOR_LINE)
      colors = 3;
   else
      colors = 1;
   
   return (Settings.main_scan_area * Settings.sub_scan_area * colors / ppb +
	   headerlen);
}

#endif // _SCANNER_H_
