/*  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 <string.h>
#include <assert.h>

#include "TeacCdr55.h"

#include "Toc.h"
#include "PQSubChannel16.h"
#include "util.h"

TeacCdr55::TeacCdr55(ScsiIf *scsiIf, unsigned long options)
  : CdrDriver(scsiIf, options)
{
  int i;
  driverName_ = "Teac CD-R50/55 - Version 0.1 (data)";
  
  speed_ = 0;
  simulate_ = 1;
  encodingMode_ = 1;

  scsiTimeout_ = 0;

  actMode_ = TrackData::AUDIO;
  writeEndLba_ = 0;

  memset(modeSelectData_, 0, 12);
  memset(&diskInfo_, 0, sizeof(DiskInfo));

  for (i = 0; i < maxScannedSubChannels_; i++)
    scannedSubChannels_[i] = new PQSubChannel16;

  // reads little endian samples
  audioDataByteOrder_ = 0;
}

TeacCdr55::~TeacCdr55()
{
  int i;

  for (i = 0; i < maxScannedSubChannels_; i++) {
    delete scannedSubChannels_[i];
    scannedSubChannels_[i] = NULL;
  }
}

// static constructor
CdrDriver *TeacCdr55::instance(ScsiIf *scsiIf, unsigned long options)
{
  return new TeacCdr55(scsiIf, options);
}

// sets speed
// return: 0: OK
//         1: illegal speed
int TeacCdr55::speed(int s)
{
  if (s >= 0 && s <= 4 && s != 3) {
    speed_ = s;
  }
  else if (s == 3) {
    speed_ = 2;
  }
  else if (s > 4) {
    speed_ = 4;
  }
  else {
    return 1;
  }

  return 0;
}

// loads ('unload' == 0) or ejects ('unload' == 1) tray
// return: 0: OK
//         1: scsi command failed
int TeacCdr55::loadUnload(int unload) const
{
  
 unsigned char cmd[6];

  memset(cmd, 0, 6);

  cmd[0] = 0x1b; // START/STOP UNIT
  cmd[4] = unload ? 0x02 : 0x03;

  if (sendCmd(cmd, 6, NULL, 0, NULL, 0) != 0) {
    message(-2, "Cannot load/unload medium.");
    return 1;
  }

  return 0;
}

// Retrieves mode select header and block descriptor and stores it in
// class member 'modeSelectData_' for later usage with various mode select
// commands.
// Return: 0: OK, 1: SCSI error
int TeacCdr55::getModeSelectData()
{
  unsigned char cmd[6];

  memset(cmd, 0, 6);
  memset(modeSelectData_, 0, 12);

  cmd[0] = 0x1a; // MODE SENSE
  cmd[4] = 12;

  if (sendCmd(cmd, 6, NULL, 0, modeSelectData_, 12, 1) != 0) {
    message(-2, "Mode Sense failed.");
    return 1;
  }

  return 0;
}

// sets read/write speed and simulation mode
// return: 0: OK
//         1: scsi command failed
int TeacCdr55::setWriteSpeed()
{
  unsigned char mp[4];
  
  mp[0] = 0x31;
  mp[1] = 2;
  mp[2] = 0;
  mp[3] = 0;

  switch (speed_) {
  case 1:
    mp[2] = 0;
    break;
  case 2:
    mp[2] = 1;
    break;
  case 0:
  case 4:
    mp[2] = 2;
    break;
  }

  if (setModePage6(mp, modeSelectData_, NULL, 1) != 0) {
    message(-2, "Cannot set speed mode page.");
    return 1;
  }
    
  return 0;
}

// Sets write parameters via mode page 0x22.
// return: 0: OK
//         1: scsi command failed
int TeacCdr55::setWriteParameters()
{
  unsigned char mp[9];

  memset(mp, 0, 9);

  mp[0] = 0x22;
  mp[1] = 7;

  if (multiSession()) {
    mp[4] = 4; // session at once, keep session open
  }
  else {
    if (diskInfo_.sessionCnt > 0)
      mp[4] = 0x84; // session at once and close disk
    else
      mp[4] = 2; // disk at once
  }

  if (setModePage6(mp, modeSelectData_, NULL, 1) != 0) {
    message(-2, "Cannot set write method mode page.");
    return 1;
  }

  return 0;
}

int TeacCdr55::setSimulationMode()
{
  unsigned char mp[3];

  mp[0] = 0x21;
  mp[1] = 1;

  if (simulate())
    mp[2] = 3; 
  else
    mp[2] = 0; 

  if (setModePage6(mp, modeSelectData_, NULL, 1) != 0) {
    message(-2, "Cannot set preview write mode page.");
    return 1;
  }

  return 0;
}

int TeacCdr55::setWriteDensity(TrackData::Mode mode)
{
  long blockLength = blockSize(mode, TrackData::SUBCHAN_NONE);
  unsigned char cmd[6];
  unsigned char data[12];

  memset(cmd, 0, 6);

  cmd[0] = 0x15; // MODE SELECT
  cmd[1] = 0x10;
  cmd[4] = 12;

  memcpy(data, modeSelectData_, 12);

  data[0] = 0;
  data[3] = 8;
  data[9] = 0;

  switch (mode) {
  case TrackData::AUDIO:
    data[4] = 0x04;
    break;
  case TrackData::MODE1:
  case TrackData::MODE1_RAW:
    data[4] = 0x01;
    break;
  case TrackData::MODE2:
    data[4] = 0xc1;
    break;
  case TrackData::MODE2_FORM1:
    data[4] = 0x81;
    break;
  case TrackData::MODE2_FORM2:
    data[4] = 0x82;
    break;
  case TrackData::MODE2_RAW:
  case TrackData::MODE2_FORM_MIX:
    data[4] = 0x83; // I'm not sure if this really allows writing of mixed
                    // form 1 and form 2 sectors.
    break;
  case TrackData::MODE0:
    message(-3, "Illegal mode in 'TeacCdr55::setWriteDensity()'.");
    return 0;
    break;
  }

  data[10] = blockLength >> 8;
  data[11] = blockLength;

  message(3, "Changing write density to 0x%02x/0x%02x%02x.", data[4], data[10],
	  data[11]);

  if (sendCmd(cmd, 6, data, 12, NULL, 0, 1) != 0) {
    message(-2, "Cannot set density/block size.");
    return 1;
  }

  return 0;
}


// Judges disc and performs power calibration if possible.
// judge: 1: judge disc, 0: don't judge disc
// Return 0: OK
//        1: judge disc rejected inserted medium
//        2: SCSI error occured
int TeacCdr55::executeOPC(int judge)
{
  unsigned char cmd[12];
  const unsigned char *senseCode;
  int senseLen;

  memset(cmd, 0, 12);

  cmd[0] = 0xec; // OPC EXECUTION

  if (judge) {
    cmd[1] = 1; // judge current disc

    message(2, "Judging disk...");
    switch (sendCmd(cmd, 12, NULL, 0, NULL, 0, 0)) {
    case 0: // OK, disc can be written at selected speed
      break;

    case 1:
      message(-2, "Judge disk command failed.");
      return 2;
      break;
      
    case 2: // Check sense code
      senseCode = scsiIf_->getSense(senseLen);
      if (senseLen > 12 && (senseCode[2] & 0x0f) == 5 && senseCode[7] != 0) {
	switch (senseCode[12]) {
	case 0xcd:
	  message(-2, "Cannot ensure reliable writing with inserted disk.");
	  return 1;
	  break;
	case 0xce:
	  message(-2, "Cannot ensure reliable writing at selected speed.");
	  return 1;
	  break;
	case 0xcf:
	  message(-2, "Cannot ensure reliable writing - disk has no ID code.");
	  return 1;
	  break;
	}
      }
      scsiIf_->printError();
      return 2;
      break;
    }
  }

  if (simulate()) {
    message(2, "Skipping optimum power calibration in simulation mode.");
    return 0;
  }

  cmd[1] = 0;

  message(2, "Performing optimum power calibration...");
  if (sendCmd(cmd, 12, NULL, 0, NULL, 0, 1) != 0) {
    message(-2, "Optimum power calibration failed.");
    return 2;
  }

  return 0;
}

// Clears the drive's subcode data.
// Return: 0: OK, 1: SCSI error
int TeacCdr55::clearSubcode()
{
  unsigned char cmd[12];

  memset(cmd, 0, 12);

  cmd[0] = 0xe4; // CLEAR SUBCODE
  cmd[5] = 0x80;

  if (sendCmd(cmd, 12, NULL, 0, NULL, 0, 1) != 0) {
    message(-2, "Clear subcode failed.");
    return 1;
  }
  
  return 0;
}

// Sets subcode data.
// start:   start LBA 
// end:     end LBA, one greater than last block with specfied subcode data
// ctl:     track mode flags in bits 4-7
// trackNr: track number
// indexNr: index number
// pflag:   P-channel flag
// return: 0: OK, 1: SCSI error
int TeacCdr55::setSubcode(long start, long end, unsigned char ctl, int trackNr,
			  int indexNr, int pflag)
{
  assert(start < end);

  unsigned char cmd[12];
  unsigned char data[4];

  long len = end - start;

  message(4,
	  "Setting subcode: start: %6ld, end: %6ld, len: %6ld, CTL: %02x P: %d, T: %2d I: %2d",
	  start, end, len, ctl, pflag, trackNr, indexNr);

  memset(cmd, 0, 12);

  cmd[0] = 0xb3; // SET LIMITS

  cmd[2] = start >> 24;
  cmd[3] = start >> 16;
  cmd[4] = start >> 8;
  cmd[5] = start;

  cmd[6] = len >> 24;
  cmd[7] = len >> 16;
  cmd[8] = len >> 8;
  cmd[9] = len;

  if (sendCmd(cmd, 12, NULL, 0, NULL, 0, 1) != 0) {
    message(-2, "Cannot set limits.");
    return 1;
  }

  memset(cmd, 0, 10);
  
  cmd[0] = 0xc2; // SET SUBCODE
  cmd[8] = 4; // parameter list length

  data[0] = pflag != 0 ? 1 : 0;
  data[1] = (ctl & 0xf0) | 0x01;
  data[2] = SubChannel::bcd(trackNr);
  data[3] = SubChannel::bcd(indexNr);

  if (sendCmd(cmd, 10, data, 4, NULL, 0, 1) != 0) {
    message(-2, "Cannot set subcode.");
    return 1;
  }
  
  return 0;
}
	
// Sets the subcodes for given track.
// track: actual track
// trackNr: number of current track
// start: LBA of track start (index 1), pre-gap length is taken from 'track'
// end:   LBA of track end, same as 'nstart' if next track has no pre-gap
// nstart: LBA of next track start (index 1)
// Return: 0: OK
//         1: SCSI command failed

struct SubCodeData {
  long start; // start LBA
  int index;  // index
  int pflag;  // P channel flag
};

int TeacCdr55::setSubcodes(const Track *track, int trackNr,
			   long start, long end, long nstart)
{
  assert(start < end);
  assert(end <= nstart);

  unsigned char ctl = trackCtl(track);
  SubCodeData subCodes[102]; // index 0-99 + P-channel entry + end-entry
  int n = 0;
  int i;

  if (track->start().lba() != 0) {
    // pre-gap
    subCodes[n].start = start - track->start().lba();
    subCodes[n].index = 0;
    subCodes[n].pflag = 1;
    n++;
  }

  // track start (index 1)
  subCodes[n].start = start;
  subCodes[n].index = 1;
  subCodes[n].pflag = 0;
  n++;

  // create index marks
  
  // We have to check if we have to add an entry for the P-channel flag if the
  // pre-gap of the following track is shorter than 2 seconds

  // calculate LBA where P-channel must be set for next track, if
  // it is behind 'end' it'll be ignored
  long pChangeLba = nstart - 150;

  int pChangeEntry = 0; // set to 1 if a P-channel entry was created
  long indexStart;

  for (i = 0; i < track->nofIndices(); i++) {
    indexStart = track->getIndex(i).lba() + start;

    if (indexStart > pChangeLba && pChangeEntry == 0) {
      subCodes[n].start = pChangeLba;
      subCodes[n].index = i + 1;
      subCodes[n].pflag = 1;
      n++;
      pChangeEntry = 1;
    }
    else if (indexStart == pChangeLba) {
      // index increment coincides with p channel change position
      // -> no entry required
      pChangeEntry = 1;
    }

    subCodes[n].start = indexStart;
    subCodes[n].index = i + 2;
    subCodes[n].pflag = indexStart >= pChangeLba ? 1 : 0;
    n++;
  }

  if (pChangeEntry == 0 && pChangeLba < end) {
    subCodes[n].start = pChangeLba;
    subCodes[n].index = i + 1;
    subCodes[n].pflag = 1;
    n++;
  }
  
  // add end of track as last entry
  subCodes[n].start = end;
  subCodes[n].index = 0; // not used
  subCodes[n].pflag = 0; // not used

  assert(n <= 101);

  // now issue the subcode commands
  for (i = 0; i < n; i++) {
    if (setSubcode(subCodes[i].start, subCodes[i+1].start, ctl, trackNr,
		   subCodes[i].index, subCodes[i].pflag) != 0) {
      return 1;
    }
  }

  return 0;
}

// Sets sub codes for all tracks.
// Return: 0: OK
//         1: toc contains no track
//         2: SCSI error
int TeacCdr55::setSubcodes()
{
  const Track *t, *nt;
  int trackNr;
  Msf start, end;
  Msf nstart, nend;
  long offset = diskInfo_.thisSessionLba;
  int first = 1;

  TrackIterator itr(toc_);

  trackNr = diskInfo_.lastTrackNr;
  if ((nt = itr.first(nstart, nend)) == NULL) {
    return 1;
  }

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

  do {
    trackNr += 1;
    t = nt;
    start = nstart;
    end = nend;

    if ((nt = itr.next(nstart, nend)) == NULL)
      nstart = Msf(toc_->length()); // start of lead-out

    if (first) {
      // set sub code for pre-gap of first track
      first = 0;
      if (setSubcode(offset - 150, offset, trackCtl(t), trackNr, 0, 1) != 0)
	return 2;
    }

    if (setSubcodes(t, trackNr, start.lba() + offset, end.lba() + offset,
		    nstart.lba() + offset) != 0) {
      return 2;
    }

  } while (nt != NULL);

  return 0;
}

// Sets toc data.
// Return: 0: OK
//         1: toc contains no track
//         2: SCSI command failed
int TeacCdr55::setToc()
{
  const Track *t;
  int trackNr;
  Msf start, end;
  long offset = diskInfo_.thisSessionLba + 150; // LBA offset
  int trackOffset = diskInfo_.lastTrackNr + 1;
  unsigned char firstTrackCtl;
  unsigned char lastTrackCtl;
  unsigned char ctl;
  int lastTrackNr = 0;
  int sessFormat;
  unsigned char cmd[10];
  unsigned char data[3 + 102 * 5]; // max. 99 tracks + 3 POINT entries
  unsigned char *p = data + 3;

  data[0] = 0;
  data[1] = 0;
  data[2] = 0;

  TrackIterator itr(toc_);

  for (t = itr.first(start, end), trackNr = trackOffset;
       t != NULL && trackNr <= 99;
       t = itr.next(start, end), trackNr++) {

    Msf s(start.lba() + offset);

    ctl = trackCtl(t);
    if (trackNr == trackOffset)
      firstTrackCtl = ctl;

    p[0] = ctl | 0x01;
    p[1] = SubChannel::bcd(trackNr);
    p[2] = SubChannel::bcd(s.min());
    p[3] = SubChannel::bcd(s.sec());
    p[4] = SubChannel::bcd(s.frac());
    p += 5;

    lastTrackNr = trackNr;
    lastTrackCtl = ctl;
  }

  if (lastTrackNr == 0)
    return 1;

  
  // Point A0 entry (first track nr and session format)
  if (diskInfo_.empty) {
    // disk is empty -> take toc type from toc-file
    sessFormat = sessionFormat();
  }
  else {
    // Disk has already a recorded session -> take toc type from
    // previous session if it is well defined.
    switch (diskInfo_.diskTocType) {
    case 0x00:
    case 0x10:
    case 0x20:
      sessFormat = diskInfo_.diskTocType;
      break;
    default:
      sessFormat = sessionFormat();
      break;
    }
  }

  p[0] = firstTrackCtl | 0x01;
  p[1] = 0xa0;
  p[2] = SubChannel::bcd(trackOffset);
  p[3] = SubChannel::bcd(sessFormat);
  p[4] = 0;
  p += 5;

  // Point A1 entry (last track nr)
  p[0] = lastTrackCtl | 0x01;
  p[1] = 0xa1;
  p[2] = SubChannel::bcd(lastTrackNr);
  p[3] = 0;
  p[4] = 0;
  p += 5;
  
  // Point A2 entry (start of lead-out)
  Msf los(toc_->length().lba() + offset);

  p[0] = lastTrackCtl | 0x01;
  p[1] = 0xa2;
  p[2] = SubChannel::bcd(los.min());
  p[3] = SubChannel::bcd(los.sec());
  p[4] = SubChannel::bcd(los.frac());
  p += 5;
  

  long dataLen = p - data;

#if 1
  message(4, "TOC data:");
  long i;
  for (i = 3; i < dataLen; i += 5) {
    message(4, "%02x %02x %02x:%02x:%02x", 
	    data[i], data[i+1], data[i+2], data[i+3], data[i+4]);
  }
#endif


  memset(cmd, 0, 10);

  cmd[0] = 0xc2; // SET SUBCODE
  cmd[7] = dataLen >> 8;
  cmd[8] = dataLen;

  if (sendCmd(cmd, 10, data, dataLen, NULL, 0, 1) != 0) {
    message(-2, "Cannot set TOC data.");
    return 2;
  }
   
  return 0;
}

// Sets catalog number.
// Return: 0: OK
//         1: SCSI command failed
int TeacCdr55::setCatalog()
{
  unsigned char cmd[10];
  unsigned char data[15];
  int i;

  if (!toc_->catalogValid())
    return 0;

  data[0] = 0;

  if (toc_->leadInMode() == TrackData::AUDIO)
    data[1] = 0x02;
  else
    data[1] = 0x42;

  for (i = 0; i < 13; i++)
    data[2 + i] = toc_->catalog(i);


  memset(cmd, 0, 10);

  cmd[0] = 0xc2; // SET SUBCODE
  cmd[8] = 15;

  if (sendCmd(cmd, 10, data, 15, NULL, 0, 1) != 0) {
    message(-2, "Cannot set catalog number data.");
    return 2;
  }

  return 0;
}

// Sets ISRC codes for all tracks
int TeacCdr55::setIsrc()
{
  const Track *t;
  int trackNr;
  Msf start, end;
  int trackOffset = diskInfo_.lastTrackNr + 1;
  unsigned char cmd[10];
  unsigned char data[15];

  data[0] = 0;

  memset(cmd, 0, 10);

  cmd[0] = 0xc2; // SET SUBCODE
  cmd[8] = 15;

  TrackIterator itr(toc_);

  for (t = itr.first(start, end), trackNr = trackOffset;
       t != NULL && trackNr <= 99;
       t = itr.next(start, end), trackNr++) {
    if (t->isrcValid()) {
      data[1] = trackCtl(t) | 0x03;
      data[2] = t->isrcCountry(0);
      data[3] = t->isrcCountry(1);
      data[4] = t->isrcOwner(0);
      data[5] = t->isrcOwner(1);
      data[6] = t->isrcOwner(2);
      data[7] = t->isrcYear(0) + '0';
      data[8] = t->isrcYear(1) + '0';
      data[9] = t->isrcSerial(0) + '0';
      data[10] = t->isrcSerial(1) + '0';
      data[11] = t->isrcSerial(2) + '0';
      data[12] = t->isrcSerial(3) + '0';
      data[13] = t->isrcSerial(4) + '0';
      data[14] = 0;

      cmd[6] = trackNr;
      if (sendCmd(cmd, 10, data, 15, NULL, 0, 1) != 0) {
	message(-2, "Cannot set ISRC code.");
	return 2;
      }
    }
  }

  return 0;
}


// Need to overload this function to set the WriteExtension flag. It'll
// also change the write density if the actual mode changes.
int TeacCdr55::writeData(TrackData::Mode mode, TrackData::SubChannelMode sm,
			 long &lba, const char *buf, long len)
{
  assert(blocksPerWrite_ > 0);
  int writeLen = 0;
  unsigned char cmd[10];
  long blockLength = blockSize(mode, TrackData::SUBCHAN_NONE);
  
#if 0
  long sum, i;

  sum = 0;

  for (i = 0; i < len * blockLength; i++) {
    sum += buf[i];
  }

  message(0, "W: %ld: %ld, %ld, %ld", lba, blockLength, len, sum);
#endif

  if (mode != actMode_) {
    actMode_ = mode;
    message(3, "Changed write density at LBA %ld.", lba);

    if (setWriteDensity(actMode_) != 0) {
      return 1;
    }
  }

  memset(cmd, 0, 10);
  cmd[0] = 0x2a; // WRITE10
  
  while (len > 0) {
    writeLen = (len > blocksPerWrite_ ? blocksPerWrite_ : len);

    cmd[2] = lba >> 24;
    cmd[3] = lba >> 16;
    cmd[4] = lba >> 8;
    cmd[5] = lba;

    cmd[7] = writeLen >> 8;
    cmd[8] = writeLen & 0xff;

    if (lba + writeLen >= writeEndLba_) {
      // last write command
      message(4, "Last write command at LBA: %ld", lba);
      cmd[9] = 0;
    }
    else {
      cmd[9] = 0x80; // extended write
    }

    if (sendCmd(cmd, 10, (unsigned char *)buf, writeLen * blockLength,
		NULL, 0) != 0) {
      message(-2, "Write data failed.");
      return 1;
    }

    buf += writeLen * blockLength;

    lba += writeLen;
    len -= writeLen;
  }
      
  return 0;
}


int TeacCdr55::initDao(const Toc *toc)
{
  long offset; // LBA offset for this session
  long n;
  
  toc_ = toc;

  blockLength_ = AUDIO_BLOCK_LEN;
  blocksPerWrite_ = scsiIf_->maxDataLen() / blockLength_;

  assert(blocksPerWrite_ > 0);

  diskInfo();

  if (!diskInfo_.valid.empty || !diskInfo_.valid.append) {
    message(-2, "Cannot determine status of inserted medium.");
    return 1;
  }

  if (!diskInfo_.append) {
    message(-2, "Inserted medium is not appendable.");
    return 1;
  }

  offset = diskInfo_.thisSessionLba;


  // allocate buffer for writing zeros
  n = blocksPerWrite_ * blockLength_;
  delete[] zeroBuffer_;
  zeroBuffer_ = new char[n];
  memset(zeroBuffer_, 0, n);


  writeEndLba_ = toc_->length().lba() + offset;
  actMode_ = toc_->leadInMode();
  
  if (getModeSelectData() != 0 ||
      setWriteDensity(actMode_) != 0 ||
      setSimulationMode() != 0 ||
      setWriteParameters() != 0 ||
      setWriteSpeed() != 0) {
    return 1;
  }

  return 0;
}

int TeacCdr55::startDao()
{
  scsiTimeout_ = scsiIf_->timeout(3 * 60);

  if (executeOPC(1) != 0 ||
      setSubcodes() != 0 ||
      setCatalog() != 0 ||
      setToc() != 0 ||
      setIsrc() != 0) {
    return 1;
  }

  long lba = diskInfo_.thisSessionLba - 150;

  if (writeZeros(actMode_, TrackData::SUBCHAN_NONE, lba, lba + 150, 150)
      != 0) {
    return 1;
  }
  
  return 0;
}

int TeacCdr55::finishDao()
{
  //message(0, "Writing lead-out...");

  // wait until writing of lead-out is finished ???

  scsiIf_->timeout(scsiTimeout_);

  delete[] zeroBuffer_, zeroBuffer_ = NULL;

  return 0;
}

void TeacCdr55::abortDao()
{
  unsigned char cmd[10];

  // Send a zero length write command with cleared extendened write bit.
  // I don't know if it has the expected effect.

  memset(cmd, 0, 10);
  cmd[0] = 0x2a; // WRITE10

  sendCmd(cmd, 10, NULL, 0, NULL, 0, 0);
}

DiskInfo *TeacCdr55::diskInfo()
{
  int i;
  unsigned char cmd[12];
  unsigned char data[12];

  memset(&diskInfo_, 0, sizeof(DiskInfo));

  diskInfo_.cdrw = 0;
  diskInfo_.valid.cdrw = 1;

  memset(cmd, 0, 10);
  memset(data, 0, 8);

  cmd[0] = 0x25; // READ CAPACITY
  cmd[9] = 0x80;

  if (sendCmd(cmd, 10, NULL, 0, data, 8, 1) != 0) {
    message(-1, "Cannot read CD-R capacity.");
  }
  else {
    diskInfo_.capacity = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) |
                        data[3];
    diskInfo_.valid.capacity = 1;
  }

  memset(cmd, 0, 12);
  memset(data, 0, 4);

  cmd[0] = 0xe6; // NEXT WRITABLE ADDRESS
  cmd[2] = 0xff;
  cmd[3] = 0xff;
  cmd[4] = 0xff;
  cmd[5] = 0xff;
  cmd[6] = 0xff;
  cmd[7] = 0xff;
  cmd[8] = 0xff;
  cmd[9] = 0xff;

  long nwa = 0;

  switch (sendCmd(cmd, 12, NULL, 0, data, 4, 0)) {
  case 0: // command succeed -> writable area exists
    diskInfo_.empty = 1;
    diskInfo_.valid.empty = 1;
    nwa = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
    message(4, "Next writable address: %ld", nwa);
    break;
  case 1: // SCSI command failed, no sense data -> cannot tell anything
    message(-1, "Get next writable address failed.");
    break;
  case 2: // SCSI command failed, sense data available
    // assume that disk is not writable
    message(4, "Cannot get next writable address.");
    diskInfo_.empty = 0;
    diskInfo_.valid.empty = 1;
    break;
  }

  // SCSI command failed -> stop further processing
  if (diskInfo_.valid.empty == 0)
    return &diskInfo_;

  memset(cmd, 0, 10);
  memset(data, 0, 4);

  cmd[0] = 0x43; // READ TOC
  cmd[6] = 1;
  cmd[8] = 4;

  if (sendCmd(cmd, 10, NULL, 0, data, 4, 0) == 0) {
    message(4, "First track %u, last track %u", data[2], data[3]);
    diskInfo_.lastTrackNr = data[3];
  }
  else {
    message(4, "READ TOC (format 0) failed.");
  }

  if (diskInfo_.lastTrackNr > 0) {
    diskInfo_.empty = 0;
    diskInfo_.diskTocType = 0xff; // undefined

    memset(cmd, 0, 10);
    memset(data, 0, 12);

    cmd[0] = 0x43; // READ TOC
    cmd[6] = 0;
    cmd[8] = 12;
    cmd[9] = 1 << 6;

    if (sendCmd(cmd, 10, NULL, 0, data, 12) == 0) {
      diskInfo_.sessionCnt = data[3];
      diskInfo_.lastSessionLba = (data[8] << 24) | (data[9] << 16) |
                                 (data[10] << 8) | data[11];

      message(4, "First session %u, last session %u, last session start %ld",
	      data[2], data[3], diskInfo_.lastSessionLba);
    }
    else {
      message(4, "READ TOC (format 1) failed.");
    }

    if (diskInfo_.sessionCnt > 0) {
      int len;
      CdRawToc *toc = getRawToc(diskInfo_.sessionCnt, &len);

      if (toc != NULL) {
	for (i = 0; i < len; i++) {
	  if (toc[i].sessionNr == diskInfo_.sessionCnt) {
	    if (toc[i].point == 0xb0 && toc[i].min != 0xff &&
		toc[i].sec != 0xff && toc[i].frame != 0xff) {
	      int m = SubChannel::bcd2int(toc[i].min);
	      int s = SubChannel::bcd2int(toc[i].sec);
	      int f = SubChannel::bcd2int(toc[i].frame);
	      
	      if (m < 90 && s < 60 && f < 75) 
		diskInfo_.thisSessionLba = Msf(m, s, f).lba(); // + 150 - 150
	    }

	    if (toc[i].point == 0xa0) {
	      diskInfo_.diskTocType = SubChannel::bcd2int(toc[i].psec);
	    }
	  }
	}

	if (diskInfo_.thisSessionLba > 0) {
	  if (diskInfo_.lastTrackNr < 99)
	    diskInfo_.append = 1;
	}
	else {
	  message(4, "Did not find BO pointer in session %d.",
		  diskInfo_.sessionCnt);
	}

	delete[] toc;
      }
      else {
	message(4, "getRawToc failed.");
      }
    }
  }
  else {
    // disk is empty and appendable
    diskInfo_.empty = 1;
    diskInfo_.append = 1;
  }

  diskInfo_.valid.append = 1;

  return &diskInfo_;
}


// tries to read catalog number from disk and adds it to 'toc'
// return: 1 if valid catalog number was found, else 0
int TeacCdr55::readCatalog(Toc *toc, long startLba, long endLba)
{
  unsigned char cmd[10];
  unsigned char data[24];
  char catalog[14];
  int i;

  memset(cmd, 0, 10);
  memset(data, 0, 24);

  cmd[0] = 0x42; // READ SUB-CHANNEL
  cmd[2] = 1 << 6;
  cmd[3] = 0x02; // get media catalog number
  cmd[8] = 24;   // transfer length

  if (sendCmd(cmd, 10, NULL, 0, data, 24) != 0) {
    message(-2, "Cannot get catalog number.");
    return 0;
  }

  if (data[8] & 0x80) {
    for (i = 0; i < 13; i++) {
      catalog[i] = data[0x09 + i];
    }
    catalog[13] = 0;

    if (toc->catalog(catalog) == 0) {
      return 1;
    }
  }

  return 0;
}

int TeacCdr55::readIsrc(int trackNr, char *buf)
{
  unsigned char cmd[10];
  unsigned char data[24];
  int i;

  buf[0] = 0;

  memset(cmd, 0, 10);
  memset(data, 0, 24);

  cmd[0] = 0x42; // READ SUB-CHANNEL
  cmd[2] = 1 << 6;
  cmd[3] = 0x03; // get media catalog number
  cmd[6] = trackNr;
  cmd[8] = 24;   // transfer length

  if (sendCmd(cmd, 10, NULL, 0, data, 24) != 0) {
    message(-2, "Cannot get ISRC code.");
    return 0;
  }

  if (data[8] & 0x80) {
    for (i = 0; i < 12; i++) {
      buf[i] = data[0x09 + i];
    }
    buf[12] = 0;
  }

  return 0;
}

int TeacCdr55::analyzeTrack(TrackData::Mode mode, int trackNr,
			    long startLba,
			    long endLba, Msf *indexIncrements,
			    int *indexIncrementCnt, long *pregap,
			    char *isrcCode, unsigned char *ctl)
{
  int ret = analyzeTrackScan(mode, trackNr, startLba, endLba,
			     indexIncrements, indexIncrementCnt, pregap,
			     isrcCode, ctl);

  if (mode == TrackData::AUDIO)
    readIsrc(trackNr, isrcCode);
  else
    *isrcCode = 0;

  return ret;
}

int TeacCdr55::readSubChannels(TrackData::SubChannelMode,
			       long lba, long len, SubChannel ***chans,
			       Sample *audioData)
{
  int retries = 5;
  unsigned char cmd[12];
  int i;

  cmd[0] = 0xd8;  // READ CD_DA
  cmd[1] = 0;
  cmd[2] = lba >> 24;
  cmd[3] = lba >> 16;
  cmd[4] = lba >> 8;
  cmd[5] = lba;
  cmd[6] = len >> 24;
  cmd[7] = len >> 16;
  cmd[8] = len >> 8;
  cmd[9] = len;
  cmd[10] = 0x01;  // Q sub-channel data
  cmd[11] = 0;

  while (1) {
    if (sendCmd(cmd, 12, NULL, 0,
		transferBuffer_, len * (AUDIO_BLOCK_LEN + 16),
		retries == 0 ? 1 : 0) != 0) {
      if (retries == 0) 
	return 1;
    }
    else {
      break;
    }

    retries--;
  }

  for (i = 0; i < len; i++) {
    PQSubChannel16 *chan = (PQSubChannel16*)scannedSubChannels_[i];
    unsigned char *p =
      transferBuffer_  + i * (AUDIO_BLOCK_LEN + 16) + AUDIO_BLOCK_LEN;

    // slightly reorder the sub-channel so it is suitable for 'PQSubChannel16'
    p[0] <<= 4;   // ctl/adr
    p[0] |= p[1];
    p[1] = p[2];  // track nr
    p[2] = p[3];  // index nr
    p[3] = p[4];  // min
    p[4] = p[5];  // sec
    p[5] = p[6];  // frame
    p[6] = 0;     // zero
    // p[7] is amin
    // p[8] is asec
    // p[9] is aframe
    
    chan->init(p);
    
    if (chan->checkConsistency())
      chan->calcCrc();
  }

  if (audioData != NULL) {
    unsigned char *p = transferBuffer_;

    for (i = 0; i < len; i++) {
      memcpy(audioData, p, AUDIO_BLOCK_LEN);

      p += AUDIO_BLOCK_LEN + 16;
      audioData += SAMPLES_PER_BLOCK;
    }
  }

  *chans = scannedSubChannels_;
  return 0;
}


CdRawToc *TeacCdr55::getRawToc(int sessionNr, int *len)
{
  unsigned char cmd[10];
  unsigned short dataLen;
  unsigned char *data = NULL;;
  unsigned char reqData[4]; // buffer for requestion the actual length
  unsigned char *p = NULL;
  int i, entries;
  CdRawToc *rawToc;
  
  assert(sessionNr >= 1);

  // read disk toc length
  memset(cmd, 0, 10);
  cmd[0] = 0x43; // READ TOC
  cmd[6] = sessionNr;
  cmd[8] = 4;
  cmd[9] |= 2 << 6; // get Q subcodes

  if (sendCmd(cmd, 10, NULL, 0, reqData, 4) != 0) {
    message(-2, "Cannot read raw disk toc.");
    return NULL;
  }

  dataLen = ((reqData[0] << 8) | reqData[1]) + 2;

  message(4, "Raw toc data len: %d", dataLen);

  if (dataLen == 4)
    return NULL;

  data = new (unsigned char)[dataLen];
  
  // read disk toc
  cmd[7] = dataLen >> 8;
  cmd[8] = dataLen;

  if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) {
    message(-2, "Cannot read raw disk toc.");
    delete[] data;
    return NULL;
  }

  entries = (((data[0] << 8) | data[1]) - 2) / 11;


  rawToc = new CdRawToc[entries];

  for (i = 0, p = data + 4; i < entries; i++, p += 11 ) {
#if 0
    message(0, "%d %02x %02d %2x %02x:%02x:%02x %02x %02x:%02x:%02x",
	    p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10]);
#endif
    rawToc[i].sessionNr = p[0];
    rawToc[i].adrCtl = p[1];
    rawToc[i].point = p[3];
    rawToc[i].min = p[4];
    rawToc[i].sec = p[5];
    rawToc[i].frame = p[6];
    rawToc[i].pmin = p[8];
    rawToc[i].psec = p[9];
    rawToc[i].pframe = p[10];
  }

  delete[] data;

  *len = entries;

  return rawToc;
}

long TeacCdr55::readTrackData(TrackData::Mode mode,
			      TrackData::SubChannelMode,
			      long lba, long len, unsigned char *buf)
{
  unsigned char cmd[10];
  long blockLen = 2340;
  long i;
  TrackData::Mode actMode;
  int ok = 0;
  int softError;
  const unsigned char *sense;
  int senseLen;

  if (setBlockSize(blockLen) != 0)
    return 0;

  memset(cmd, 0, 10);

  cmd[0] = 0x28; // READ10
  cmd[2] = lba >> 24;
  cmd[3] = lba >> 16;
  cmd[4] = lba >> 8;
  cmd[5] = lba;

  while (len > 0 && !ok) {
    cmd[7] = len >> 8;
    cmd[8] = len;

    memset(transferBuffer_, 0, len * blockLen);
    switch (sendCmd(cmd, 10, NULL, 0, transferBuffer_, len * blockLen, 0)) {
    case 0:
      ok = 1;
      break;

    case 2:
      softError = 0;
      sense = scsiIf_->getSense(senseLen);

      if (senseLen > 0x0c) {
	if ((sense[2] &0x0f) == 5) {
	  switch (sense[12]) {
	  case 0x63: // end of user area encountered on this track
	  case 0x64: // Illegal mode for this track
	    softError = 1;
	    break;
	  }
	}
	else if ((sense[2] & 0x0f) == 3) { // Medium error
	  switch (sense[12]) {
	  case 0x02: // No seek complete, sector not found
	  case 0x11: // L-EC error
	    return -2;
	    break;
	  }
	}
      }

      if (!softError) {
	scsiIf_->printError();
	return -1;
      }
      break;

    default:
      message(-2, "Read error at LBA %ld, len %ld", lba, len);
      return -1;
      break;
    }

    if (!ok) {
      len--;
    }
  }

  unsigned char *sector = transferBuffer_;
  for (i = 0; i < len; i++) {
    actMode = determineSectorMode(sector);

    if (!(actMode == mode ||
	  (mode == TrackData::MODE2_FORM_MIX &&
	   (actMode == TrackData::MODE2_FORM1 ||
	    actMode == TrackData::MODE2_FORM2)) ||

	  (mode == TrackData::MODE1_RAW && actMode == TrackData::MODE1) ||

	  (mode == TrackData::MODE2_RAW &&
	   (actMode == TrackData::MODE2 ||
	    actMode == TrackData::MODE2_FORM1 ||
	    actMode == TrackData::MODE2_FORM2)))) {
      return i;
    }

    if (buf != NULL) {
      switch (mode) {
      case TrackData::MODE1:
	memcpy(buf, sector + 4, MODE1_BLOCK_LEN);
	buf += MODE1_BLOCK_LEN;
	break;
      case TrackData::MODE2:
      case TrackData::MODE2_FORM_MIX:
	memcpy(buf, sector + 4, MODE2_BLOCK_LEN);
	buf += MODE2_BLOCK_LEN;
	break;
      case TrackData::MODE2_FORM1:
	memcpy(buf, sector + 12, MODE2_FORM1_DATA_LEN);
	buf += MODE2_FORM1_DATA_LEN;
	break;
      case TrackData::MODE2_FORM2:
	memcpy(buf, sector + 12, MODE2_FORM2_DATA_LEN);
	buf += MODE2_FORM2_DATA_LEN;
	break;
      case TrackData::MODE1_RAW:
      case TrackData::MODE2_RAW:
	memcpy(buf, syncPattern, 12);
	memcpy(buf + 12, sector, 2340);
	buf += AUDIO_BLOCK_LEN;
	break;
      case TrackData::MODE0:
      case TrackData::AUDIO:
	message(-3, "TeacCdr55::readTrackData: Illegal mode.");
	return 0;
	break;
      }
    }

    sector += blockLen;
  }  

  return len;
}

Toc *TeacCdr55::readDiskToc(int session, const char *audioFilename)
{
  Toc *toc = CdrDriver::readDiskToc(session, audioFilename);

  setBlockSize(MODE1_BLOCK_LEN);
  
  return toc;
}

Toc *TeacCdr55::readDisk(int session, const char *fname)
{
  Toc *toc = CdrDriver::readDisk(session, fname);

  setBlockSize(MODE1_BLOCK_LEN);

  return toc;
}

int TeacCdr55::readAudioRange(ReadDiskInfo *rinfo, int fd, long start,
			      long end, int startTrack,
			      int endTrack, TrackInfo *info)
{
  if (!onTheFly_) {
    int t;

    message(1, "Analyzing...");
    
    for (t = startTrack; t <= endTrack; t++) {
      long totalProgress;

      totalProgress = t * 1000;
      totalProgress /= rinfo->tracks;
      sendReadCdProgressMsg(RCD_ANALYZING, rinfo->tracks, t + 1, 0,
			    totalProgress);
      
      message(1, "Track %d...", t + 1);
      info[t].isrcCode[0] = 0;
      readIsrc(t + 1, info[t].isrcCode);
      if (info[t].isrcCode[0] != 0)
	message(2, "Found ISRC code.");

      totalProgress = (t + 1) * 1000;
      totalProgress /= rinfo->tracks;
      sendReadCdProgressMsg(RCD_ANALYZING, rinfo->tracks, t + 1, 1000,
			    totalProgress);
    }

    message(1, "Reading...");
  }

  return CdrDriver::readAudioRangeParanoia(rinfo, fd, start, end, startTrack,
					   endTrack, info);
}

