//*********************************************************************
//
//                 ScanmakerE3 Scanner device
//
//                    version 98.07.18
//
//*********************************************************************

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <math.h>

#include <exec/types.h>
#include <proto/exec.h>
#include <proto/dos.h>

#include "scsi.h"

#include "Scanner.h"

char DevName[] = "ScanmakerE3.device";
char DevIdString[] = "ScanmakerE3";

typedef unsigned char ScanCmd[6];

// Scanner specifik defines
#define MAX_BASE_RES      300
#define MAX_EXT_RES       300

// scanning modes
#define SCANMODE_HALFTONE   0
#define SCANMODE_BW         1 
#define SCANMODE_GREY       2
#define SCANMODE_COLOR      3


#define SCANBUFSIZE     32000

#define MAX_BUSY_RETRY      5


static UBYTE* scanBuffer = NULL;

// Stored scanning parameters
//
static double x0;             /* Scanning frame in mm               */
static double y0;
static double x1;
static double y1;
static UBYTE  resolutionCode; /* Resolution in % of max resolution  */
static UBYTE  resolutionFlag; /* Further resolution information     */
static WORD   brightness[3];  /* Brightness for red, green and blue */
static WORD   contrast;       /* Contrast                           */
static WORD   shadow;         /* shadow adjust 0..1023 (default 0)  */
static WORD   highlight;      /* highlight adjust 0..1023 (def. 0)  */
static WORD   midtone;        /* midtone adjust 0..1023 (def. 512)  */
static WORD   halftonePattern;/* halftonePattern 0..255 (def. 0)    */
static WORD   exposureTime;   /* Exposure time                      */
static double gamma;          /* gamma value                        */
static WORD   scanMode;       /* see above                          */
static BOOL   extendedRes;    /* extended resolution                */
static BOOL   parametersSet;  /* parameters are set                 */

// Actual scanning values
//
static UWORD imageWidth;
static UWORD imageHeight;
static UWORD bytesPerLine;
static UWORD xResolution;
static UWORD yResolution;

// Parameters used during scanning
//
static BOOL scanning;    /* Scanner is scanning                                */
static int  lineWidth;   /* Length of one scan line including color informat.  */
static int  scanLines;   /* Total number of lines to read from scanner         */
static int  numPlanes;   /* 3 for rgb24, otherwise 1                           */
static int  bufferLen;   /* Length of scanBuffer in number of lines            */
static int  linesRead;   /* Lines already read from scanner                    */
static int  linesInBuf;  /* Number of lines read from scanner in scanBuffer    */
static int  currentLine; /* Next line from scanBuf to return to user           */
static int  nextPlane;   /* next plane number to return to readScanLine        */

//
// Make color correction table brightness and contrast
//

UBYTE corrTab[256];

void makeCorrTab(int bright,int contrast)
{
  int    i,b,c;
  UBYTE* tab;

  tab = corrTab;
  bright = (bright*255)/100;
  c = 100+contrast;
  for( i = 0 ; i < 256 ; i++ )
  {
    b = ((i+bright)*c-50*contrast)/100;
    *(tab++) = b>255?255:(b<0?0:(UBYTE)b);
  }
}

/*------------------------------------------------------------------------*/
static void scannerReady(BYTE *error)
{
  if( *error == 0 )
  {
    ScanCmd TEST_UNIT_READY = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    int retry = 0;

    while( sendCommand(TEST_UNIT_READY, 6, NULL, 0, NULL, 0) )
    {
      retry++;
      if (retry == 5)
      {
        *error = SCAN_ERR_READY;
        return;
      }
      Delay(3*50);
    }
  }
}


/*------------------------------------------------------------------------*/
static void setScanningFrame(BYTE *error)
{
  if( *error == 0 )
  {
    ScanCmd SCANNING_FRAME = { 0x04, 0x00, 0x00, 0x00, 0x09, 0x00 };
    UBYTE   frame_data[9];

    int xf0,yf0,xf1,yf1;
    int dpi;
    
    frame_data[0] = 0x00;
    if( scanMode == SCANMODE_HALFTONE )
      frame_data[0] |= 0x01;

    // Unit is pixel
    //
    frame_data[0] |= 0x08;

    dpi = MAX_BASE_RES;
    xf0 = (int)((double)(x0*dpi)/25.4);
    yf0 = (int)((double)(y0*dpi)/25.4);
    xf1 = (int)((double)(x1*dpi)/25.4);
    yf1 = (int)((double)(y1*dpi)/25.4);
    
    frame_data[1] = (UBYTE)(xf0 & 0xff);
    frame_data[2] = (UBYTE)((xf0 & 0xff00) >> 8);
    frame_data[3] = (UBYTE)(yf0 & 0xff);
    frame_data[4] = (UBYTE)((yf0 & 0xff00) >> 8);
    frame_data[5] = (UBYTE)(xf1 & 0xff);
    frame_data[6] = (UBYTE)((xf1 & 0xff00) >> 8);
    frame_data[7] = (UBYTE)(yf1 & 0xff);
    frame_data[8] = (UBYTE)((yf1 & 0xff00) >> 8);

    *error = sendCommand(SCANNING_FRAME, 6, frame_data, 9, NULL, 0);
  }
}


/*------------------------------------------------------------------------*/
static void selectMode(BYTE *error)
{
  if( *error == 0 )
  {
    ScanCmd MODE_SELECT = { 0x15, 0x00, 0x00, 0x00, 0x0B, 0x00 };
    UBYTE mode_data[11];

    mode_data[0] = 0x81;
    
    // Set resolution
    mode_data[0] |= 0x02;
    mode_data[1] = resolutionCode;
    mode_data[2] = (exposureTime/3)+7;
    mode_data[3] = (contrast+49)/7;
    mode_data[4] = halftonePattern;
    mode_data[5] = 0x01;
    mode_data[6] = shadow;
    mode_data[7] = highlight;
    mode_data[8] = 0x6C;
    mode_data[9] = 0x00;
    mode_data[10] = midtone;
        
    *error = sendCommand(MODE_SELECT, 6, mode_data, 11, NULL, 0);
  }
}

/*------------------------------------------------------------------------*/
void mode_sense_1(BYTE *error)
{

  if( *error == 0 )
  {
    ScanCmd MODE_SELECT_1 = { 0x16, 0x00, 0x00, 0x00, 0x0a, 0x00 };
    unsigned char mode_data[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

    mode_data[1] = (UBYTE)(brightness[0]);

    *error = sendCommand(MODE_SELECT_1, 6, mode_data, 10, NULL, 0);
  }
  if( *error == 0 )
  {
    ScanCmd MODE_SENSE_1 = { 0x19, 0x00, 0x00, 0x00, 0x1e, 0x00 };
    unsigned char mode_data[30] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

    mode_data[1] = (UBYTE)(brightness[0]);
    mode_data[2] = (UBYTE)(brightness[1]);
    mode_data[3] = (UBYTE)(brightness[2]);
    *error = sendCommand(MODE_SENSE_1, 6, mode_data, 30, NULL, 0);
  }
}

/*------------------------------------------------------------------------*/
/* int lut_download (char filter, char *gtable, int gt_entries)           */
/* Issue the LOOK-UP-TABLE DOWNLOAD command to the scanner to download    */
/* the gamma table pointed to by <gtable>. <filter> determines the color  */
/* to which the table applies (RED, GREEN, BLUE, or CLEAR; values other   */
/* than CLEAR may only be specified for one-pass color scanners). The     */
/* number of entries is determined by <entries> (256, 1024, 4096 or       */
/* 65536). The entry width is automatically assumed to be one byte if the */
/* number of entries is 256 and two bytes otherwise.                      */
/*------------------------------------------------------------------------*/

void gammaTable(double g, BYTE *error)
{
  if( *error == 0 )
  {
    UBYTE LUT_DOWNLOAD[10] = {0x55,0x00,0x27,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };
    UBYTE gTable[256];
    int i;

    LUT_DOWNLOAD[7] = (UBYTE)((256 & 0xff00) >> 8);
    LUT_DOWNLOAD[8] = (UBYTE)(256 & 0xff);

    if( (g <= 0.0) || (g == 1.0) )
    {
      for( i = 0 ; i < 256 ; i++ )
        gTable[i] = (UBYTE)i;
    }
    else
    {
      for( i = 0 ; i < 256 ; i++ )
        gTable[i] = (UBYTE)(256.0*pow(((double)i)/256.0,1/g));
    }

    *error = sendCommand(LUT_DOWNLOAD,10,gTable,256,NULL,0);
  }
}

/*------------------------------------------------------------------------*/
static void accessory(BYTE *error)
{
  if( *error == 0 )
  {
    ScanCmd ACCESSORY = { 0x10, 0x00, 0x00, 0x00, 0x9A, 0x00 };

    *error = sendCommand(ACCESSORY, 6, NULL, 0, NULL, 0);
  }
}

/*------------------------------------------------------------------------*/
static void startScan(BYTE *error)
{
  if( *error == 0 )
  {
    ScanCmd START_SCAN = { 0x1b, 0x00, 0x00, 0x00, 0x01, 0x00 };

    switch( scanMode )
    {
      case SCANMODE_HALFTONE:
      case SCANMODE_BW:
        break;
      case SCANMODE_COLOR:
        START_SCAN[4] |= 0x20;  // 1-pass color
      case SCANMODE_GREY:
        START_SCAN[4] |= 0x40;  // Multibit
        break;
    }
    if( extendedRes )
        START_SCAN[4] |= 0x80;  // extended resolution

    *error = sendCommand(START_SCAN, 6, NULL, 0, NULL, 0);
  }
}


/*------------------------------------------------------------------------*/
static void stopScan(BYTE *error)
{
  if( *error == 0 )
  {
    ScanCmd STOP_SCAN = { 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00 };

    *error = sendCommand(STOP_SCAN, 6, NULL, 0, NULL, 0);
  }
}

/*------------------------------------------------------------------------*/
static void readScanStatus(int *busy, int *lineWidth, int *linesRemain,BYTE *error)
{
  if( *error == 0 )
  {
    ScanCmd GET_SCAN_STATUS = { 0x0f, 0x00, 0x00, 0x00, 0x06, 0x00 };
    UBYTE status_reply[6];
    int retry = 0;

    do
    {
      if( *error = sendCommand(GET_SCAN_STATUS,6,NULL,0,status_reply,6) )
        return;

      *busy        = (int)(status_reply[0] != 0);
      *lineWidth   = (int)(status_reply[1] + 256 * status_reply[2]);
      *linesRemain = (int)(status_reply[3]+256*status_reply[4]+256*256*status_reply[5]);
      if( *busy )
      {
        retry++;
        Delay(3*50);
      }
    }
    while( (*busy) && (retry < MAX_BUSY_RETRY) );
    if( *busy )
      *error = SCAN_ERR_READY;
  }
}


/*----------------------------------------------------------------------*/
/*                                                                      */
/* Read lines from scanner                                              */
/*                                                                      */
/* The number of lines to read must be specified. For color scanning    */
/* the length of one line is three times line width although the lines  */
/* returned is not in correct sequence (r-g-b).                         */
/*                                                                      */
/*----------------------------------------------------------------------*/

static void readScanData(int nlines,UBYTE *buffer,int bsize,BYTE *error)
{
  if( *error == 0 )
  {
    ScanCmd READ_SCANNED_DATA = { 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 };

    READ_SCANNED_DATA[2] = (UBYTE)((nlines & 0xff0000) >> 16);
    READ_SCANNED_DATA[3] = (UBYTE)((nlines & 0xff00) >> 8);
    READ_SCANNED_DATA[4] = (UBYTE)(nlines & 0xff);
    *error = sendCommand(READ_SCANNED_DATA,6,NULL,0,buffer,bsize);
  }
}


// 
//
void closeScanner(void)
{
  if( scanBuffer )
  {
    free(scanBuffer);
    scanBuffer = NULL;
  }
  closeScannerDevice();
}


void openScanner(char* name,int unit,struct ScannerOptions* option,BYTE* status)
{
  scanning = FALSE;
  scanLines = 0;
  linesRead = 0;
  
  if( openScannerDevice(name,unit) )
  {
    if( scanBuffer = (UBYTE*)malloc(SCANBUFSIZE) )
    {
      strcpy(option->so_vendor,"Microtek");
      strcpy(option->so_model,"Scanmaker E3");
      option->so_version = 4;
      option->so_revision = 4;
        
      option->so_colorMode  = COLORNUM_HALFTONE|COLORNUM_BW|COLORNUM_GREY8|COLORNUM_RGB24;
      option->so_opticResolution = MAX_BASE_RES;
      option->so_opticResStep = MAX_BASE_RES/100;
      option->so_interResolution = MAX_EXT_RES;
      option->so_interResStep = MAX_EXT_RES/100;
  
      option->so_brightnessMin = 0;   //-100;
      option->so_brightnessMax = 100;
      option->so_brightnessStep = 1;
      option->so_brightnessDefault = 0;
  
      option->so_contrastMin = -50; //-42;
      option->so_contrastMax = 50;  //49;
      option->so_contrastStep = 1;  //7;
      option->so_contrastDefault = 0;
  
      //option->so_shadowMin = 0;
      //option->so_shadowMax = 255;
      //option->so_shadowStep = 1;
      //option->so_shadowDefault = 0;
  
      //option->so_highlightMin = 0;
      //option->so_highlightMax = 255;
      //option->so_highlightStep = 1;
      //option->so_highlightDefault = 255;
  
      //option->so_midtoneMin = 0;
      //option->so_midtoneMax = 255;
      //option->so_midtoneStep = 1;
      //option->so_midtoneDefault = 128;
  
      option->so_exposureTimeMin = -18;
      option->so_exposureTimeMax = 21;
      option->so_exposureTimeStep = 3;
      option->so_exposureTimeDefault = 0;
  
      option->so_velocityMin = 1;
      option->so_velocityMax = 7;
      option->so_velocityStep = 1;
      option->so_velocityDefault = 1;
 
      option->so_halftonePatternMin = 0;
      option->so_halftonePatternMax = 11;
      option->so_halftonePatternStep = 1;
      option->so_halftonePatternDefault = 0;
  
      option->so_maxLookupTableSize = 256;
      option->so_docWidth = 215.9;
      option->so_docLength = 296.9;
      
      *status = 0;
    }
    else
    {
      closeScanner();
      *status = SCAN_ERR_MEMORY;
    }
  }
  else
    *status = SCAN_ERR_DEVICEOPEN;
}

void setParameter(struct ScanParameters* param,BYTE* status)
{
  switch( param->sp_ColorNum )
  {
    case COLORNUM_HALFTONE:
      scanMode = SCANMODE_HALFTONE;
      break;
    case COLORNUM_BW:
      scanMode = SCANMODE_BW;
      break;
    case COLORNUM_GREY8:
      scanMode = SCANMODE_GREY;
      break;
    case COLORNUM_RGB24:
      scanMode = SCANMODE_COLOR;
      break;
    default:
      *status = SCAN_ERR_PARAMETER;
      parametersSet = FALSE;
      return;
  }
  
  // Frame in mm
  x0 = param->sp_x0;
  y0 = param->sp_y0;
  x1 = param->sp_x1;
  y1 = param->sp_y1;
  
  // Resolution code and actual resolution
  if( param->sp_xResolution > MAX_BASE_RES )
  {
    resolutionCode = (param->sp_xResolution*100)/MAX_EXT_RES;
    resolutionFlag = 0;
    extendedRes = TRUE;
    xResolution = (resolutionCode*MAX_EXT_RES)/100;
    yResolution = (resolutionCode*MAX_EXT_RES)/100;
  }
  else
  {
    resolutionCode = (param->sp_xResolution*100)/MAX_BASE_RES;
    resolutionFlag = 0;
    extendedRes = FALSE;
    xResolution = (resolutionCode*MAX_BASE_RES)/100;
    yResolution = (resolutionCode*MAX_BASE_RES)/100;
  }
  
  // Brightness, contrast and exposure time
  brightness[0] = 0; //param->sp_brightness[0];
  brightness[1] = 0; //param->sp_brightness[1];
  brightness[2] = 0; //param->sp_brightness[2];
  shadow = 0; //param->sp_shadow;
  highlight = 255; //param->sp_highlight;
  midtone = 128; //param->sp_midtone;
  halftonePattern = param->sp_halftonePattern;
  contrast = 0; //param->sp_contrast;
  exposureTime = param->sp_exposureTime;
  gamma = param->sp_gamma;
  // extendedRes = 0;

  *status = 0;
  scannerReady(status);
  selectMode(status);
  mode_sense_1(status);
  setScanningFrame(status);
  gammaTable(gamma,status);
  accessory(status);

  makeCorrTab(param->sp_brightness[0],param->sp_contrast);

  if( *status )
    parametersSet = FALSE;
  else
    parametersSet = TRUE;
}

void startScanning(struct ScanInformation* inform,BYTE* status)
{
  if( parametersSet )
  {
    int busy;
      
    *status = 0;
    startScan(status);
    readScanStatus(&busy,&lineWidth,&scanLines,status);

    if( lineWidth <= 0 )
      *status = SCAN_ERR_MISC;

    if( *status == 0 )
    {
      switch( scanMode )
      {
        case SCANMODE_HALFTONE:
        case SCANMODE_BW:
          bytesPerLine = lineWidth;
          imageWidth = lineWidth*8;
          imageHeight = scanLines;
          numPlanes = 1;
          break;
        case SCANMODE_GREY:
          if( extendedRes )
            bytesPerLine = lineWidth*2-1;
          else
            bytesPerLine = lineWidth;
          imageWidth = bytesPerLine;
          imageHeight = scanLines;
          numPlanes = 1;
          break;
        case SCANMODE_COLOR:
          if( extendedRes )
            bytesPerLine = (lineWidth-2)*2-1;
          else
            bytesPerLine = lineWidth-2;
          imageWidth = bytesPerLine;
          imageHeight = scanLines;
          numPlanes = 3;
          break;

      }
      bufferLen = SCANBUFSIZE/lineWidth/numPlanes;
      linesRead = 0;
      linesInBuf = 0;
      currentLine = 0;
      nextPlane = 0;
      
      inform->sv_imageWidth = imageWidth;
      inform->sv_imageHeight = imageHeight;
      inform->sv_bytesPerLine = bytesPerLine;
      inform->sv_Flags = 0;
      inform->sv_xResolution = xResolution;
      inform->sv_yResolution = yResolution;
      
      scanning = TRUE;
    }
  }
  else
    *status = SCAN_ERR_PARAMETER;
}

void stopScanning(void)
{
  BYTE status = 0;
  
  if( scanLines != linesRead )
    stopScan(&status);
  scanning = FALSE;
}

void readScanLine(UBYTE* data,ULONG* length,BYTE* status)
{
  if( !scanning )
    *status = SCAN_ERR_NOTSCANNING;
  else if( *length < bytesPerLine+1 )
    *status = SCAN_ERR_MISC;
  else if( (scanLines == linesRead) && (currentLine == linesInBuf) )
    *status = SCAN_STATUS_EOF;
  else
  {
    UBYTE* bptr;
    
    if( currentLine == linesInBuf )
    {
      if( bufferLen < scanLines-linesRead )
        linesInBuf = bufferLen;
      else
        linesInBuf = scanLines-linesRead;
        
      readScanData(linesInBuf,scanBuffer,linesInBuf*lineWidth*numPlanes,status);
      
      if( *status )
      {
        *length = 0;
        return;
      }
        
      linesRead += linesInBuf;
      currentLine = 0;
    }
    
    bptr = scanBuffer + (numPlanes*currentLine+nextPlane)*lineWidth;
    
    if( scanMode == SCANMODE_COLOR )
    {
      *(data++) = (bptr[1] == 'R') ? 0 : ((bptr[1] == 'G') ? 1 : 2);
      bptr += 2;
    }
    else
      *(data++) = 0;

    if( !extendedRes )
    {
      int i;
      
      for( i = 0 ; i < bytesPerLine ; i++ )
      {
        *(data++) = corrTab[*(bptr++)];
      }

      *length = bytesPerLine+1;

      // memcpy(data,bptr,bytesPerLine);
    }
    else
    {
      int i;
      
      *(data++) = corrTab[*bptr];
      for( i = 1 ; i < (bytesPerLine+1)/2 ; i++ )
      {
        *(data++) = (corrTab[*(bptr++)]+corrTab[(*bptr)])/2;
        *(data++) = corrTab[*bptr];
      }

      *length = bytesPerLine+1;
    }
    
    if( ++nextPlane == numPlanes )
    {
      nextPlane = 0;
      currentLine++;
    }
  }
  if( *status )
    *length = 0;
}
