#include <exec/types.h>
#include <exec/errors.h>
#include <exec/io.h>
#include <devices/scsidisk.h>
#include <dos/dosextens.h>

#include <proto/exec.h>

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

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

#include <DriverIo.h>
#include <scanner.h>

static struct scsiHandler* firstHandler;

#ifndef MAX_DATA
# define MAX_DATA       (32*1024)
#endif

static u_char cdb_sizes[8] = { 6, 10, 10, 12, 12, 12, 10, 10 };

#define CDB_SIZE(opcode)     cdb_sizes[(((opcode) >> 5) & 7)]


int sanei_scsi_max_request_size = MAX_DATA;

void sanei_scsi_find_devices(const char *findvendor,
                             const char *findmodel,
                             const char *findtype,
                             int findbus, int findchannel,
                             int findid, int findlun,
                             SANE_Status (*attach)(const char *dev)
                            )
{
  int hd;

  if( sanei_scsi_open("",&hd,NULL,NULL) == SANE_STATUS_GOOD )
  {
    char type[32];
    unsigned char inquiry[256];    /* buffer for INQUIRY data */
    unsigned char INQUIRY[] = { 0x12, 0x00, 0x00, 0x00, 0xFF, 0x00 };
    size_t        inqLen;

    inqLen = INQUIRY[4];
    if( sanei_scsi_cmd2(hd,INQUIRY,6,NULL,0,inquiry,&inqLen) != SANE_STATUS_GOOD ) 
      return;

    if( inquiry[0] & 0x1f )
      strcpy(type,"Scanner");
    else
      *type=0;

    if(
          (!findvendor || strncmp(&inquiry[8],findvendor,strlen(findvendor)) == 0)
             && (!findmodel || strncmp (&inquiry[16],findmodel,strlen(findmodel)) == 0)
             && (!findtype || strncmp(type,findtype,strlen(findtype)) == 0)
      )
      (*attach)("Scanner");
    
    sanei_scsi_close(hd);
  }
}

SANE_Status sanei_scsi_open(const char * device_name, int * fd,
                            SANEI_SCSI_Sense_Handler sense_handler,
                            void *sense_arg)
{
  int ioHd;
  
  if( ioHd = obtainIoHandle() )
  {
    struct scsiHandler* hd;
  
    if( hd = (struct scsiHandler*)malloc(sizeof(struct scsiHandler)) )
    {
      hd->IoHandle    = ioHd;
      hd->handler     = sense_handler;
      hd->handler_arg = sense_arg;
      hd->nextHandler = firstHandler;
      firstHandler = hd;
    
      *fd = (int)hd;

      return SANE_STATUS_GOOD;
    }
    else
    {
      releaseIoHandle(ioHd);
      return SANE_STATUS_NO_MEM;
    }
  }
  else
    return SANE_STATUS_INVAL;
}

void sanei_scsi_close(int fd)
{
  struct scsiHandler *hd,**next;
  
  hd = (struct scsiHandler*)fd;

  next = &firstHandler;
  while( *next && ((*next) != hd) )
    next = &(*next)->nextHandler;
  
  if( *next )
    *next = (*next)->nextHandler;
  
  releaseIoHandle(hd->IoHandle);
  free(hd);
}


/* One or more scsi commands can be enqueued by calling req_enter().
   SRC is the pointer to the SCSI command and associated write data
   and SRC_SIZE is the length of the command and data.  DST is a
   pointer to a buffer in which data is returned (if any).  It may be
   NULL if no data is returned by the command.  On input *DST_SIZE is
   the size of the buffer pointed to by DST, on exit, *DST_SIZE is set
   to the number of bytes returned in the buffer (which is less than
   or equal to the buffer size).  DST_SIZE may be NULL if no data is
   expected.  IDP is a pointer to a void* that uniquely identifies
   the entered request.

   NOTE: Some systems may not support multiple outstanding commands.
   On such systems, enter() may block.  In other words, it is not
   proper to assume that enter() is a non-blocking routine.  */


SANE_Status sanei_scsi_req_enter2(int fd,
                                  const void * cmd, size_t cmd_size,
                                  const void * src, size_t src_size,
                                  void * dst, size_t * dst_size,
                                  void **idp)
{
  struct scsiHandler* hd = (struct scsiHandler*)fd;

  memset(&hd->Cmd,0,sizeof(struct SCSICmd)); /* Clear command structure      */

  hd->io_Len = sizeof(struct SCSICmd);
  *idp = NULL;
        
  if( dst_size )
  {
    hd->Cmd.scsi_Data = (UWORD *)dst;        /* where we put the read data   */
    hd->Cmd.scsi_Length = *dst_size;         /* how much we will accept      */
    hd->Cmd.scsi_Flags = SCSIF_AUTOSENSE|SCSIF_READ;
  }                                        
  else if( src_size )
  {
    hd->Cmd.scsi_Data = (UWORD*)src;                    /* the write data    */
    hd->Cmd.scsi_Length = (src_size+1)&0xFFFFFFFE;      /* length            */
    hd->Cmd.scsi_Flags = SCSIF_AUTOSENSE|SCSIF_WRITE;
  }
  else
  {
    hd->Cmd.scsi_Data = NULL;                /* no data                      */
    hd->Cmd.scsi_Length = 0;                 /* with zero length             */
    hd->Cmd.scsi_Flags = SCSIF_AUTOSENSE;
  }

  hd->Cmd.scsi_SenseData = hd->Sense;        /* where sense data will go     */
  hd->Cmd.scsi_SenseLength = 16;             /* how much we will accept      */
  hd->Cmd.scsi_SenseActual = 0;              /* how much has been received   */
  hd->Cmd.scsi_Status = 0;

  hd->Cmd.scsi_Command = (UBYTE*)cmd;        /* the command bytes            */
  hd->Cmd.scsi_CmdLength = cmd_size;         /* length of the command        */

  doScannerIo(hd->IoHandle,HD_SCSICMD,&hd->Cmd,&hd->io_Len,&hd->io_Status);
  
  if( hd->io_Status )
  {
    if( hd->io_Status == IOERR_ABORTED )
      return SANE_STATUS_CANCELLED;
    else
      return SANE_STATUS_IO_ERROR;
  }
  else if( hd->Cmd.scsi_Status )
  {
    if( hd->handler )
      return (*hd->handler)((int)hd,hd->Sense,hd->handler_arg);
    else
      return SANE_STATUS_IO_ERROR;
  }
  else
  {
    if( dst_size )
      *dst_size = hd->Cmd.scsi_Actual;
    return SANE_STATUS_GOOD;
  }
}

/* Wait for the completion of the SCSI command with id ID.  */
SANE_Status sanei_scsi_req_wait(void *id)
{
  return SANE_STATUS_GOOD;
}

/* This is a convenience function that is equivalent to a pair of
   enter()/wait() calls.  */

SANE_Status sanei_scsi_cmd2(int fd,
                            const void * cmd, size_t cmd_size,
                            const void * src, size_t src_size,
                            void * dst, size_t * dst_size)
{
  SANE_Status status;
  void*       id;
  
  status = sanei_scsi_req_enter2(fd,cmd,cmd_size,src,src_size,dst,dst_size,&id);
  if( status != SANE_STATUS_GOOD )
    return status;
  else
    return sanei_scsi_req_wait(id);
}

/* Flush all pending SCSI commands.  */
void sanei_scsi_req_flush_all(void)
{
}


SANE_Status sanei_scsi_req_enter(int fd,
                                 const void *src, size_t src_size,
                                 void *dst, size_t * dst_size, void **idp)
{
  size_t cmd_size = CDB_SIZE (*(char *) src);

  if (dst_size && *dst_size)
    assert(src_size == cmd_size);
  else
    assert(src_size >= cmd_size);

  return sanei_scsi_req_enter2(fd,  src, cmd_size,
                               (char*) src + cmd_size, src_size - cmd_size,
                               dst, dst_size, idp);
}

SANE_Status sanei_scsi_cmd(int fd,
                           const void *src, size_t src_size,
                           void *dst, size_t * dst_size)
{
  size_t cmd_size = CDB_SIZE (*(char *) src);

  if (dst_size && *dst_size)
    assert(src_size == cmd_size);
  else
    assert(src_size >= cmd_size);

  return sanei_scsi_cmd2(fd,  src, cmd_size,
                             (char*) src + cmd_size, src_size - cmd_size,
                             dst, dst_size);
}

