/*  cdrdao - write audio CD-Rs in disc-at-once mode
 *
 *  Copyright (C) 1998-2001  Andreas Mueller <andreas@daneb.de>
 *
 *  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.
 */

#include "config.h"

#include <windows.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <assert.h>

#include "winaspi.h"
#include "ScsiIf.h"
#include "util.h"

#include "decodeSense.cc"


typedef DWORD (*GETSUPPORTINFO) (void);
typedef DWORD (*GETDLLVERSION)  (void);
typedef DWORD (*SENDCOMMAND)    (char *);

#define BUF_SIZE  (32*1024)

class ScsiIfImpl 
{
public:
  char *dev_;
  
  int maxSendLen_;

  SRB_ExecSCSICmd6    cmd6_;
  SRB_ExecSCSICmd10   cmd10_;
  SRB_ExecSCSICmd12   cmd12_;

  unsigned char senseBuffer_[SENSE_LEN];

  char haid_;
  char lun_;
  char scsi_id_;

  HINSTANCE   hinstlib;
  SENDCOMMAND Sendcommand;
};

ScsiIf::ScsiIf(const char *dev)
{
  char *p, *q;
 
  impl_ = new ScsiIfImpl;

  impl_->dev_ = strdupCC(dev);

  maxDataLen_ = BUF_SIZE;

  impl_->haid_ = 0;
  impl_->lun_ = 0;
  impl_->scsi_id_ = 0;

  if (p = strchr (impl_->dev_, ':'))
  {
     *p = 0;
     impl_->haid_ = atoi (impl_->dev_);
     q = ++p;

     if (p = strchr (q, ':'))
     {
        *p = 0;
        impl_->lun_ = atoi (q);
        q = ++p;
        impl_->scsi_id_ = atoi (q);
     }
  }

  impl_->hinstlib = 0;

  vendor_[0] = 0;
  product_[0] = 0;
  revision_[0] = 0;
}

ScsiIf::~ScsiIf()
{
  delete[] impl_->dev_;
  impl_->dev_ = NULL;

  if (impl_->hinstlib)
     FreeLibrary (impl_->hinstlib);

  delete impl_;
}

// opens and flushes scsi device
// return: 0: OK
//         1: device could not be opened
//         2: inquiry failed

int ScsiIf::init()
{
  impl_->hinstlib = LoadLibrary ("WNASPI32.DLL");

  if (!impl_->hinstlib)
  {
     message (-2, "Can't load WNASPI32.DLL");
     return 1;
  }
  else {
    impl_->Sendcommand = (SENDCOMMAND)GetProcAddress (impl_->hinstlib, "SendASPI32Command");
  }

  if (inquiry() != 0) 
    return 2;

  return 0;
}

// Sets given timeout value in seconds and returns old timeout.
// return: old timeout

int ScsiIf::timeout (int t)
{
  return 0;
}

// sends a scsi command and receives data
// return 0: OK
//        1: scsi command failed (os level, no sense data available)
//        2: scsi command failed (sense data available)

int ScsiIf::sendCmd (const unsigned char *cmd,     int cmdLen, 
		     const unsigned char *dataOut, int dataOutLen,
		     unsigned char *dataIn,  int dataInLen,
		     int showMessage)
{
  int status, i = 10;

  switch (cmdLen)
  {
      case 6: memset (&impl_->cmd6_, 0, sizeof (SRB_ExecSCSICmd6));
              memcpy (impl_->cmd6_.CDBByte, cmd, 6);

              impl_->cmd6_.SRB_Cmd        = SC_EXEC_SCSI_CMD;
              impl_->cmd6_.SRB_HaId       = impl_->haid_;
              impl_->cmd6_.SRB_Target     = impl_->scsi_id_;
              impl_->cmd6_.SRB_Lun        = impl_->lun_;
              impl_->cmd6_.SRB_SenseLen   = SENSE_LEN;
              impl_->cmd6_.SRB_CDBLen     = 6;

              if (dataOut && dataOutLen)
              {
                 impl_->cmd6_.SRB_Flags      = SRBF_WRITE;
                 impl_->cmd6_.SRB_BufPointer = dataOut;
                 impl_->cmd6_.SRB_BufLen     = dataOutLen;
              }
              else
              {
                 impl_->cmd6_.SRB_Flags      = SRBF_READ;
                 impl_->cmd6_.SRB_BufPointer = dataIn;
                 impl_->cmd6_.SRB_BufLen     = dataInLen;
              }


              (impl_->Sendcommand) ((char *) &impl_->cmd6_);

              while (i-- && impl_->cmd6_.SRB_Status != SS_COMP)
              {
                   while (!impl_->cmd6_.SRB_Status)
                        Sleep (10);
              }

	      // AM: Try to get sense data
	      if (impl_->cmd6_.SRB_Status == SS_ERR &&
		  impl_->cmd6_.SRB_TargStat == STATUS_CHKCOND) {
		memcpy(impl_->senseBuffer_, impl_->cmd6_.SenseArea, SENSE_LEN);

		if (showMessage)
		  printError();

		return 2;
	      }

              if (impl_->cmd6_.SRB_Status != SS_COMP)
                 return (1);
              break; 

     case 10: memset (&impl_->cmd10_, 0, sizeof (SRB_ExecSCSICmd10));
              memcpy (impl_->cmd10_.CDBByte, cmd, 10);

              impl_->cmd10_.SRB_Cmd        = SC_EXEC_SCSI_CMD;
              impl_->cmd10_.SRB_HaId       = impl_->haid_;
              impl_->cmd10_.SRB_Target     = impl_->scsi_id_;
              impl_->cmd10_.SRB_Lun        = impl_->lun_;
              impl_->cmd10_.SRB_SenseLen   = SENSE_LEN;
              impl_->cmd10_.SRB_CDBLen     = 10;

              if (dataOut && dataOutLen)
              {
                 impl_->cmd10_.SRB_Flags      = SRBF_WRITE;
                 impl_->cmd10_.SRB_BufPointer = dataOut;
                 impl_->cmd10_.SRB_BufLen     = dataOutLen;
              }
              else
              {
                 impl_->cmd10_.SRB_Flags      = SRBF_READ;
                 impl_->cmd10_.SRB_BufPointer = dataIn;
                 impl_->cmd10_.SRB_BufLen     = dataInLen;
              }

              
              (impl_->Sendcommand) ((char *) &impl_->cmd10_);

              while (i-- && impl_->cmd10_.SRB_Status != SS_COMP)
              {
                      while (!impl_->cmd10_.SRB_Status)
                          Sleep (10);
              }

	      // AM: Try to get sense data
	      if (impl_->cmd10_.SRB_Status == SS_ERR &&
		  impl_->cmd10_.SRB_TargStat == STATUS_CHKCOND) {
		memcpy(impl_->senseBuffer_, impl_->cmd10_.SenseArea10,
		       SENSE_LEN);

		if (showMessage)
		  printError();

		return 2;
	      }

              if (impl_->cmd10_.SRB_Status != SS_COMP)
                 return (1);

              break; 

     case 12: memset (&impl_->cmd12_, 0, sizeof (SRB_ExecSCSICmd12));
              memcpy (impl_->cmd12_.CDBByte, cmd, 12);

              impl_->cmd12_.SRB_Cmd        = SC_EXEC_SCSI_CMD;
              impl_->cmd12_.SRB_HaId       = impl_->haid_;
              impl_->cmd12_.SRB_Target     = impl_->scsi_id_;
              impl_->cmd12_.SRB_Lun        = impl_->lun_;
              impl_->cmd12_.SRB_SenseLen   = SENSE_LEN;
              impl_->cmd12_.SRB_CDBLen     = 12;

              if (dataOut && dataOutLen)
              {
                 impl_->cmd12_.SRB_Flags      = SRBF_WRITE;
                 impl_->cmd12_.SRB_BufPointer = dataOut;
                 impl_->cmd12_.SRB_BufLen     = dataOutLen;
              }
              else
              {
                 impl_->cmd12_.SRB_Flags      = SRBF_READ;
                 impl_->cmd12_.SRB_BufPointer = dataIn;
                 impl_->cmd12_.SRB_BufLen     = dataInLen;
              }


              
              (impl_->Sendcommand) ((char *) &impl_->cmd12_);

              while (i-- && impl_->cmd12_.SRB_Status != SS_COMP)
              {
                   while (!impl_->cmd12_.SRB_Status)
                         Sleep (10);
              }

	      // AM: Try to get sense data
	      if (impl_->cmd12_.SRB_Status == SS_ERR &&
		  impl_->cmd12_.SRB_TargStat == STATUS_CHKCOND) {
		memcpy(impl_->senseBuffer_, impl_->cmd12_.SenseArea12,
		       SENSE_LEN);

		if (showMessage)
		  printError();

		return 2;
	      }

              if (impl_->cmd12_.SRB_Status != SS_COMP)
                 return (1);

              break; 
  }

  return 0;
}

const unsigned char *ScsiIf::getSense(int &len) const
{
  len = SENSE_LEN;
  return impl_->senseBuffer_;
}

void ScsiIf::printError()
{
  decodeSense(impl_->senseBuffer_, SENSE_LEN);
}

int ScsiIf::inquiry()
{
  unsigned char cmd[6];
  unsigned char result[0x2c];
  int i;


  cmd[0] = 0x12; // INQUIRY
  cmd[1] = cmd[2] = cmd[3] = 0;
  cmd[4] = 0x2c;
  cmd[5] = 0;

  if (sendCmd (cmd, 6, NULL, 0, result, 0x2c, 1) != 0) {
    message (-2, "Inquiry command failed on '%s': ", impl_->dev_);
    return 1;
  }

  strncpy(vendor_, (char *)(result + 0x08), 8);
  vendor_[8] = 0;

  strncpy(product_, (char *)(result + 0x10), 16);
  product_[16] = 0;

  strncpy(revision_, (char *)(result + 0x20), 4);
  revision_[4] = 0;

  for (i = 7; i >= 0 && vendor_[i] == ' '; i--) 
  {
     vendor_[i] = 0;
  }

  for (i = 15; i >= 0 && product_[i] == ' '; i--) 
  {
     product_[i] = 0;
  }

  for (i = 3; i >= 0 && revision_[i] == ' '; i--) 
  {
     revision_[i] = 0;
  }

  return 0;
}
