#include <exec/types.h>

#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>

#include <sane/sane.h>
#include <sane/saneopts.h>

#include <DriverIo.h>
#include <scanner.h>
#include <scanner_protos.h>
#include "SaneInterface.h"

struct OptionInfo*       optionTable[ID_NUMBER_OF_OPTIONS];
struct OptionDescriptor* scannerOptions;

struct memList
{
  struct memList* nextMem;
};

// Internal variables
static BOOL        saneInitOk;
static SANE_Handle handle;

static struct memList* firstMem;

static struct OptionDescriptor* thresholdDescr = NULL;

/* SANE to BetaScan status conversion table */
BYTE betaStatus[] = 
  {
    SCAN_STATUS_OK,             /* SANE_STATUS_GOOD          */
    SCAN_ERR_PARAMETER,         /* SANE_STATUS_UNSUPPORTED   */
    SCAN_STATUS_ABORTED,        /* SANE_STATUS_CANCELLED     */
    SCAN_ERR_READY,             /* SANE_STATUS_DEVICE_BUSY   */
    SCAN_ERR_PARAMETER,         /* SANE_STATUS_INVAL         */
    SCAN_STATUS_EOF,            /* SANE_STATUS_EOF           */
    SCAN_ERR_MISC,              /* SANE_STATUS_JAMMED        */
    SCAN_ERR_MISC,              /* SANE_STATUS_NO_DOCS       */
    SCAN_ERR_MISC,              /* SANE_STATUS_COVER_OPEN    */
    SCAN_ERR_COMMUNICATION,     /* SANE_STATUS_IO_ERROR      */
    SCAN_ERR_MEMORY,            /* SANE_STATUS_NO_MEM        */
    SCAN_ERR_MISC               /* SANE_STATUS_ACCESS_DENIED */
  };

void getOptionValue(OptionId id,int* val,ULONG* flags);
void setOptionValue(OptionId id,int  val,ULONG* flags);
int  saneNum(char* str); 
BYTE readBlock(UBYTE* buf,int len);
int  calcThresholdCorrection(int thVal);

void openScanner(struct ScannerOptions* option,BYTE* status)
{
  short           numOptions;
  OptionId        id;   
  SANE_Int        ver;
  SANE_Device**   dev_list;
    
  *status = SCAN_STATUS_OK;
  
  if( sane_init(&ver,NULL) != SANE_STATUS_GOOD )
  {
    *status = SCAN_OPNERR_UNKNOWN;
      
    return;
  }

  if( saneGetDevices(&dev_list,TRUE) == SANE_STATUS_NO_MEM )
  {
    sane_exit();
    
    *status = SCAN_OPNERR_MEMORY;
    return;
  }
  
      
  if( saneOpen(dev_list[0]?(*dev_list)[0].name:"/dev/scanner",&handle) != SANE_STATUS_GOOD )
  {
    *status = SCAN_OPNERR_UNKNOWN;
      
    sane_exit();

    return;
  }

  saneInitOk = TRUE;

  // Fill up optionTable
  numOptions = 0;
  for( id = ID_SCANMODE ; id < ID_NUMBER_OF_OPTIONS ; id++ )
  {
    struct OptionInfo* info;
    
    if( info = getOption(handle,id) )
    {
      if( info->oi_saneNum = saneNum(info->oi_name) )
      {
        optionTable[id] = info;  
        numOptions++;
      }
      else if( id < ID_HALFTONEPATTERN )
      {
        closeScanner();
        *status = SCAN_OPNERR_FATAL;
        return;
      }
    }
  }

  // Create scanner option table to return to BetaScan
  if( !(scannerOptions = (struct OptionDescriptor*)calloc(numOptions+1,sizeof(struct OptionDescriptor))) )
  {
    closeScanner();
    *status = SCAN_OPNERR_MEMORY;
    return;
  }
  
  strncpy(option->so_scannerVendor,dev_list[0]->vendor,40);
  strncpy(option->so_scannerModel,dev_list[0]->model,40);
  
  // Get and set current version
  {
    int   verStrLen,i;
    
    verStrLen = strlen(versionString);
    i = 0;    
    while( (i < verStrLen-8) && strncmp(versionString+i,".device ",8) )
      i++;
      
    if( (strncmp(versionString+i,".device ",8) == 0) && isdigit(versionString[i+8]) )
    {
      char* np = NULL;
      
      option->so_driverVersion = strtol(versionString+i+8,&np,10);
      if( np && (*(np++) == '.') )
        option->so_driverRevision = strtol(np,NULL,10);
      else
        option->so_driverRevision = 0;
    }
    else
    {
      option->so_driverVersion = 0;
      option->so_driverRevision = 0;
    }
  }

  option->so_flags = 0;
  option->so_optionNum = numOptions;
  option->so_descriptor = scannerOptions;

  // Set document size and fill table of legal scanner options
  readOption(option,status);  
}

void closeScanner(void)
{
  if( scannerOptions )
  {
    free(scannerOptions);
    scannerOptions = NULL;
  }
  
  if( handle )
    saneClose(handle);

  if( saneInitOk )
    sane_exit();

  while( firstMem )
  {
    struct memList* next;
    
    next = firstMem->nextMem;
    free(firstMem);
    firstMem = next;
  }

  saneInitOk = FALSE;
  handle = NULL;
}

void readOption(struct ScannerOptions* option,BYTE* status)
{
  OptionId id;   
  int      i;
  fixed    s;
   
  // Set document size 
  //
  getOptionValue(ID_BR_X,&s,NULL);
  option->so_docWidth = FIX_DOUBLE(s);

  getOptionValue(ID_BR_Y,&s,NULL);
  option->so_docHeight = FIX_DOUBLE(s);
  
  // Fill table of legal scanner options
  //
  i = 0;
  for( id = ID_SCANMODE ; id < ID_NUMBER_OF_OPTIONS ; id++ )
  {
    if( optionTable[id] )
    {
      const SANE_Option_Descriptor *sod;

      if( optionTable[id]->oi_getDescr )
        sod = (*optionTable[id]->oi_getDescr)(handle,optionTable[id]->oi_saneNum);
      else
        sod = sane_get_option_descriptor(handle,optionTable[id]->oi_saneNum);
      
      scannerOptions[i].od_optionID = id;
      if( sod->unit == SANE_UNIT_PIXEL )
      {
        scannerOptions[i].od_valueType = TYPE_FIXED;
        scannerOptions[i].od_valueUnit = UNIT_MM;
        if( sod->constraint_type == CONSTRAINT_RANGE )
        {
          struct memList* newMem;
          
          newMem = malloc(sizeof(struct memList)+sizeof(Range));
          newMem->nextMem = firstMem;
          firstMem = newMem;
                
          scannerOptions[i].od_constraint.numberRange = (Range*)(newMem+1);
          if( sod->type == SANE_TYPE_FIXED )
          {
            scannerOptions[i].od_constraint.numberRange->min = DOUBLE_FIX(FIX_DOUBLE(sod->constraint.range->min)/pixelPerMM);
            scannerOptions[i].od_constraint.numberRange->max = DOUBLE_FIX(FIX_DOUBLE(sod->constraint.range->max)/pixelPerMM);
            scannerOptions[i].od_constraint.numberRange->quant = DOUBLE_FIX(FIX_DOUBLE(sod->constraint.range->quant)/pixelPerMM);
          }
          else
          {
            scannerOptions[i].od_constraint.numberRange->min = DOUBLE_FIX((double)(sod->constraint.range->min)/pixelPerMM);
            scannerOptions[i].od_constraint.numberRange->max = DOUBLE_FIX((double)(sod->constraint.range->max)/pixelPerMM);
            scannerOptions[i].od_constraint.numberRange->quant = DOUBLE_FIX((double)(sod->constraint.range->quant)/pixelPerMM);
          }
        }
        else
        {
          struct memList* newMem;
          int listSize;
          
          listSize = sod->constraint.word_list[0];
          
          newMem = malloc(sizeof(struct memList)+(listSize+1)*sizeof(int));
          newMem->nextMem = firstMem;
          firstMem = newMem;
                
          scannerOptions[i].od_constraint.numberList = (int*)(newMem+1);
          scannerOptions[i].od_constraint.numberList[0] = listSize;
          if( sod->type == SANE_TYPE_FIXED )
          {
            int j;
            
            for( j = 0 ;j < listSize ; j++ )
              scannerOptions[i].od_constraint.numberList[j+1] = DOUBLE_FIX(FIX_DOUBLE(sod->constraint.word_list[j+1])/pixelPerMM);
          }
          else
          {
            int j;
            
            for( j = 0 ;j < listSize ; j++ )
              scannerOptions[i].od_constraint.numberList[j+1] = DOUBLE_FIX((double)(sod->constraint.word_list[j+1])/pixelPerMM);
          }
        }
      }
      else
      {
        scannerOptions[i].od_valueType = sod->type;
        scannerOptions[i].od_valueUnit = sod->unit;
        scannerOptions[i].od_constraint.stringList = sod->constraint.string_list;
      }
      scannerOptions[i].od_constraintType = sod->constraint_type;

      scannerOptions[i].od_specialInfo = optionTable[id]->oi_specialInfo;
      
      if( simulateThreshold && (id == ID_THRESHOLD) && scannerOptions[i].od_specialInfo )
        thresholdDescr = &scannerOptions[i];
      
      i++;       
    }
  }
  
  *status = SCAN_STATUS_OK;
}

void controlOption(struct OptionValue* value,BYTE* status)
{
  const SANE_Option_Descriptor *sod;

  *status = SCAN_STATUS_OK;

  if( value->sp_optionID != ID_NONE )
  {
    struct OptionInfo* info = optionTable[value->sp_optionID];

    value->sp_flags &= CONTROL_CALLMASK;

    if( info )
    {
      if( info->oi_getDescr )
        sod = (*info->oi_getDescr)(handle,info->oi_saneNum);
      else
        sod = sane_get_option_descriptor(handle,info->oi_saneNum);

      if( sod )
      {
        if( value->sp_flags & CONTROL_SET )
          setOptionValue(value->sp_optionID,value->sp_value,&value->sp_flags);
        
        if( value->sp_flags & CONTROL_GET )
          getOptionValue(value->sp_optionID,&value->sp_value,&value->sp_flags);
        
        if( sod->cap & SANE_CAP_INACTIVE )
          value->sp_flags |= CONTROL_DISABLED;
      }
      else
        value->sp_flags |= CONTROL_INVALID;
    }
    else
      value->sp_flags |= CONTROL_INVALID;
  }
}

static LineFormat colorCode;
static ULONG      bytesPerLine;
static BOOL       lastFrame;
static BOOL       lineArtMapping = FALSE;
static int        scanModeValue;
static int        thresholdValue;
static UBYTE*     buffer = NULL;

void startScanning(struct ScanInformation* inform,BYTE* status)
{
  SANE_Status stat;
  int res;

  lineArtMapping = FALSE;
  
  getOptionValue(ID_RESOLUTION,&res,NULL);
  
  if( (stat = saneStart(handle)) == SANE_STATUS_GOOD )
  {
    SANE_Parameters pars;

    saneGetParameters(handle,&pars);

    switch( pars.format )
    {
      case SANE_FRAME_GRAY:
        if( pars.depth == 1 )
        {
          inform->sv_lineFormat   = FORMAT_BW;
          inform->sv_imageDepth   = 1;
          inform->sv_bytesPerLine = pars.bytes_per_line;
          colorCode = FORMAT_BW;
        }
        else if( thresholdDescr && (scanModeValue != ((int*)thresholdDescr->od_specialInfo)[scanModeValue]) )
        {
          inform->sv_lineFormat   = FORMAT_BW;
          inform->sv_imageDepth   = 1;
          inform->sv_bytesPerLine = (pars.bytes_per_line+7)/8;
          colorCode = FORMAT_BW;
          if( !(buffer = malloc(pars.bytes_per_line)) )
          {
            stopScanning();
            *status = SCAN_ERR_MEMORY;
            return;
          }
          lineArtMapping = TRUE;
        }
        else
        {
          inform->sv_lineFormat   = FORMAT_GRAY;
          inform->sv_imageDepth   = 8;
          inform->sv_bytesPerLine = pars.bytes_per_line;
          colorCode = FORMAT_GRAY;
        }
        break;
      case SANE_FRAME_RGB:
        inform->sv_lineFormat   = FORMAT_RGB;
        inform->sv_imageDepth   = 24;
        inform->sv_bytesPerLine = pars.bytes_per_line;
        colorCode = FORMAT_RGB;
        break;
      case SANE_FRAME_RED:
        if( pars.last_frame )
        {
          inform->sv_lineFormat   = FORMAT_RED;
          inform->sv_imageDepth   = 8;
        }
        else
        {
          inform->sv_lineFormat   = FORMAT_RGB_RANDOM;
          inform->sv_imageDepth   = 24;
        }
        inform->sv_bytesPerLine = pars.bytes_per_line;
        colorCode = FORMAT_RED;
        break;
      case SANE_FRAME_GREEN:
        if( pars.last_frame )
        {
          inform->sv_lineFormat   = FORMAT_GREEN;
          inform->sv_imageDepth   = 8;
        }
        else
        {
          inform->sv_lineFormat   = FORMAT_RGB_RANDOM;
          inform->sv_imageDepth   = 24;
        }
        inform->sv_bytesPerLine = pars.bytes_per_line;
        colorCode = FORMAT_GREEN;
        break;
      case SANE_FRAME_BLUE:
        if( pars.last_frame )
        {
          inform->sv_lineFormat   = FORMAT_BLUE;
          inform->sv_imageDepth   = 8;
        }
        else
        {
          inform->sv_lineFormat   = FORMAT_RGB_RANDOM;
          inform->sv_imageDepth   = 24;
        }
        inform->sv_bytesPerLine = pars.bytes_per_line;
        colorCode = FORMAT_BLUE;
        break;
      default:
        stopScanning();
        *status = SCAN_ERR_MISC;
        return;
    }

    inform->sv_imageWidth   = pars.pixels_per_line;
    inform->sv_imageHeight  = pars.lines;
    
    if( scannerOptions[ID_RESOLUTION].od_valueType == SANE_TYPE_INT )
      inform->sv_xResolution = (double)res;
    else
      inform->sv_xResolution = FIX_DOUBLE(res);

    inform->sv_yResolution  = inform->sv_xResolution;
    inform->sv_Flags = 0;

    // Set internal parameters
    lastFrame = pars.last_frame;
    bytesPerLine = pars.bytes_per_line;
  }
  else
    *status = betaStatus[stat];
}

void stopScanning(void)
{
  saneStop(handle);
  colorCode = 0;
  lastFrame = TRUE;
  lineArtMapping = FALSE;
  
  if( buffer )
  {
    free(buffer);
    buffer = NULL;
  }
}

void readScanLine(struct ScanLine* line,BYTE* status)
{
  *status = readBlock(lineArtMapping?buffer:line->sl_data,bytesPerLine);
    
  if( *status == SCAN_STATUS_EOF )
  {
    if( !lastFrame && betaStatus[saneStart(handle)] == SCAN_STATUS_OK )
    {
      SANE_Parameters pars;

      saneGetParameters(handle,&pars);

      switch( pars.format )
      {
        case SANE_FRAME_RED:
          colorCode = FORMAT_RED;
          break;
        case SANE_FRAME_GREEN:
          colorCode = FORMAT_GREEN;
          break;
        case SANE_FRAME_BLUE:
          colorCode = FORMAT_BLUE;
          break;
        default:
          *status = SCAN_ERR_MISC;
          return;
      }
      lastFrame = pars.last_frame;

      *status = readBlock(line->sl_data,bytesPerLine);
    }
  }
  
  if( lineArtMapping )
  {
    int    midtone,i;
    UBYTE* dst;
    UBYTE* src;
    UBYTE  mask;
      
    src = buffer;
    dst = line->sl_data;
    mask = 0x80;
    *dst = 0;
        
    midtone = 255-thresholdValue;
    for( i = 0 ; i < bytesPerLine ; i++ )
    {
      if( mask == 0 )
      {
        mask = 0x80;
        dst++;
        *dst = 0;
      }
        
      if( *(src++) < midtone )
        *dst |= mask;
      mask >>= 1;
    }
  }

  line->sl_color = colorCode;
}

/**********************************************************/
/*                                                        */
/* Utilities                                              */
/*                                                        */
/**********************************************************/

// Get option value 
//
//   input:  id     = option id
//
//   return: val    = actual option value (index for string type values)
//           flags set
//
void getOptionValue(OptionId id,int* val,ULONG* flags)
{
  struct OptionInfo* info = optionTable[id];

  if( info )
  {
    const SANE_Option_Descriptor *sod;
  
    if( info->oi_getDescr )
      sod = (*info->oi_getDescr)(handle,info->oi_saneNum);
    else
      sod = sane_get_option_descriptor(handle,info->oi_saneNum);

    if( sod )
    {
      SANE_Status s = SANE_STATUS_GOOD;

      switch( sod->type )
      {
        case SANE_TYPE_BOOL:
        case SANE_TYPE_INT:
        case SANE_TYPE_FIXED:
          if( info->oi_getValue )
            s = (*info->oi_getValue)(handle,info->oi_saneNum,val,NULL);
          else 
            s=sane_control_option(handle,info->oi_saneNum,SANE_ACTION_GET_VALUE,val,NULL);

          if( s == SANE_STATUS_GOOD )
          {
            if( sod->unit == SANE_UNIT_PIXEL )
            {
              double w;
              
              if( sod->type == SANE_TYPE_FIXED )
                w = FIX_DOUBLE(*val);
              else
                w = (double)(*val);
                
              *val = DOUBLE_FIX(w/pixelPerMM);
            }
          }
          break;
        case SANE_TYPE_STRING:
          {
            char str[128];

            if( info->oi_getValue )
              s = (*info->oi_getValue)(handle,info->oi_saneNum,str,NULL);
            else 
              s=sane_control_option(handle,info->oi_saneNum,SANE_ACTION_GET_VALUE,str,NULL);

            *val = 0;
        
            if( s == SANE_STATUS_GOOD )
            {
              while( strcmp(str,sod->constraint.string_list[*val]) )
                (*val)++;
            }
          }
          break;
      }
      
      if( flags && (s != SANE_STATUS_GOOD) )
        *flags |= CONTROL_INVALID;
    }
  }
}

// Set option value 
//
//   input:  id     = option id
//
//   return: val    = option value to set (index for string type values)
//           flags set
//
void setOptionValue(OptionId id,int val,ULONG* flags)
{
  struct OptionInfo* info = optionTable[id];

  if( info )
  {
    const SANE_Option_Descriptor *sod;

    if( (id == ID_SCANMODE) )
    {
      scanModeValue = val;
      
      if( thresholdDescr )
        val = ((int*)thresholdDescr->od_specialInfo)[val];
    }
      
    if( id == ID_THRESHOLD )
      thresholdValue = calcThresholdCorrection(val);

    if( info->oi_getDescr )
      sod = (*info->oi_getDescr)(handle,info->oi_saneNum);
    else
      sod = sane_get_option_descriptor(handle,info->oi_saneNum);

    if( sod )
    {
      SANE_Status s;
      SANE_Int    f = 0;
      
      switch( sod->type )
      {
        case SANE_TYPE_INT:
        case SANE_TYPE_FIXED:
          if( sod->unit == SANE_UNIT_PIXEL )
          {
            if( sod->type == SANE_TYPE_FIXED )
              val = DOUBLE_FIX(FIX_DOUBLE(val)*pixelPerMM);
            else
              val = (int)(FIX_DOUBLE(val)*pixelPerMM);
          }
        case SANE_TYPE_BOOL:
          if( info->oi_setValue )
            s = (*info->oi_setValue)(handle,info->oi_saneNum,&val,&f);
          else 
            s=sane_control_option(handle,info->oi_saneNum,SANE_ACTION_SET_VALUE,&val,&f);
          break;
        case SANE_TYPE_STRING:
          if( info->oi_setValue )
            s = (*info->oi_setValue)(handle,info->oi_saneNum,sod->constraint.string_list[val],&f);
          else
            s=sane_control_option(handle,info->oi_saneNum,SANE_ACTION_SET_VALUE,sod->constraint.string_list[val],&f);
          break;
        default:
          return;
      }
      if( flags )
      {
        if( s != SANE_STATUS_GOOD )
        {
          switch( s )
          {
            case SANE_STATUS_INVAL:
              *flags |= CONTROL_RANGE;
              break;
            default:
              *flags |= CONTROL_INVALID;
              break;
          }
        }
        else
        {
          if( f & SANE_INFO_INEXACT )
            *flags |= CONTROL_ROUNDED;
          if( f & SANE_INFO_RELOAD_OPTIONS )
            *flags |= CONTROL_RELOAD;
        }
      }
    }
  }
}

// Get SANE option number
//
//   input:  str = SANE option identifier
//
//   return: the SANE option number (0 if not supported)
//
int saneNum(char* str)
{
  if( str == NULL )
    return -1;
  else
  {
    const SANE_Option_Descriptor *sod;
    int   i;
  
    i = 1;
    while( (sod = sane_get_option_descriptor(handle,i)) && ((sod->name==0) || strcmp(str,sod->name)) )
      i++;

    if( sod )
      return i;
    else
      return 0;
  }
}

// Read block
//
//   input:  buf = buffer for the data
//           len = number of bytes to read
//
//   return: status code
//
BYTE readBlock(UBYTE* buf,int len)
{
  SANE_Status status;
  
  do
  {
    SANE_Int actualLen;

    status = saneRead(handle,buf,len,&actualLen);
    len -= actualLen;
    buf += actualLen;
  }
  while( len && !status );

  return betaStatus[status];
}

//
// Return value for threshold
//
int calcThresholdCorrection(int thVal)
{
  int t;
  
  if( thresholdDescr )
  {
    switch( thresholdDescr->od_constraintType )
    {
      case CONSTRAINT_RANGE:
        {
          int min,max,val;

          if( thresholdDescr->od_valueType == TYPE_INT )
          {
            val = thVal;
            min = thresholdDescr->od_constraint.numberRange->min;
            max = thresholdDescr->od_constraint.numberRange->max;
          }
          else
          {
            val = FIX_INT(thVal);
            min = FIX_INT(thresholdDescr->od_constraint.numberRange->min);
            max = FIX_INT(thresholdDescr->od_constraint.numberRange->max);
          }
          t = (val-min)*255/(max-min);
        }
        break;
      case CONSTRAINT_WORD_LIST:
        if( thresholdDescr->od_valueType == TYPE_INT )
          t = thVal;
        else
          t = FIX_INT(thVal);
        break;
      default:
        t = 128;
        break;
    }
  }
  else
    t = 128;
    
  return t;
}
