/*  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.
 */

/* Driver for the TaiyoYuden drive created by Henk-Jan Slotboom.
 * Very similar to the Philips CDD2x00 drives.
 */

#include <config.h>

#include <string.h>
#include <assert.h>

#include "TaiyoYuden.h"
#include "SubChannel.h"

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

TaiyoYuden::TaiyoYuden(ScsiIf *scsiIf, unsigned long options)
  : PlextorReader(scsiIf, options), CDD2600Base(this)
{
  driverName_ = "Taiyo-Yuden - Version 0.1(alpha)";
  
  leadInLength_ = leadOutLength_ = 0;
  speed_ = 2;
  simulate_ = 1;
  encodingMode_ = 0;

  audioDataByteOrder_ = 0; // little endian

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

TaiyoYuden::~TaiyoYuden()
{
}

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


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

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

  memset(cmd, 0, 10);

  cmd[0] = 0xe7; // MEDIUM LOAD/UNLOAD
  if (unload) {
    cmd[8] |= 0x01;
  }

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

  return 0;
};

int TaiyoYuden::initDao(const Toc *toc)
{
  long n;

  toc_ = toc;

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

  assert(blocksPerWrite_ > 0);

  if (modeSelectBlockSize(blockLength_, 1) != 0 ||
      modeSelectSpeed(-1, speed_, simulate_, 1) != 0 ||
      modeSelectCatalog(toc_) != 0 ||
      readSessionInfo(&leadInLength_, &leadOutLength_, 1) != 0) {
    return 1;
  }

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

  return 0;
}

int TaiyoYuden::startDao()
{
  long lba = -leadInLength_ - 150; // Value is not really important since the
                                   // LBA is not used by 'writeData'.
 
  if (writeSession(toc_, multiSession_, 0) != 0) {
    return 1;
  }

  message(2, "Writing lead-in and gap...");

  // write lead-in
  if (writeZeros(toc_->leadInMode(), TrackData::SUBCHAN_NONE, lba, 0,
		 leadInLength_) != 0) {
    flushCache();
    return 1;
  }

  // write gap (2 seconds)
  if (writeZeros(toc_->leadInMode(), TrackData::SUBCHAN_NONE, lba, 0, 150)
      != 0) {
    flushCache();
    return 1;
  }


  message(2, "");

  return 0;
}

int TaiyoYuden::finishDao()
{
  long lba = toc_->length().lba();

  message(2, "Writing lead-out...");

  // write lead-out
  if (writeZeros(toc_->leadOutMode(), TrackData::SUBCHAN_NONE, lba, lba + 150,
		 leadOutLength_) != 0) {
    flushCache();
    return 1;
  }

  message(2, "\nFlushing cache...");
  
  if (flushCache() != 0) {
    return 1;
  }

  message(2, "");

  delete[] zeroBuffer_, zeroBuffer_ = NULL;

  return 0;
}

void TaiyoYuden::abortDao()
{
  flushCache();
}

// Writes data to target, the block length depends on the actual writing mode
// and is stored internally. 'len' is number of blocks to write.
// 'lba' specifies the next logical block address for writing and is updated
// by this function but not used for writing
// return: 0: OK
//         1: scsi command failed
int TaiyoYuden::writeData(TrackData::Mode mode, TrackData::SubChannelMode sm,
			  long &lba, const char *buf, long len)
{
  assert(blocksPerWrite_ > 0);
  assert(blockLength_ > 0);
  assert(mode == TrackData::AUDIO);
  int nwritten = 0;
  int writeLen = 0;
  unsigned char cmd[10];

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

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

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

    lba += writeLen;

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

// Retrieve disk information.
// return: DiskInfo structure or 'NULL' on error
DiskInfo *TaiyoYuden::diskInfo()
{
  unsigned char cmd[10];
  unsigned long dataLen = 34;
  unsigned char data[34];
  
  memset(&diskInfo_, 0, sizeof(DiskInfo));
  
  if (readCapacity(&(diskInfo_.capacity), 0) == 0) {
    diskInfo_.valid.capacity = 1;
  }
  
  if (readSessionInfo(&leadInLength_, &leadOutLength_, 0) == 0) {
    diskInfo_.valid.manufacturerId = 1;
  			        // start time of lead-in
    diskInfo_.manufacturerId = Msf(450150 - leadInLength_ - 150 );
    diskInfo_.empty = 1;
  }
  else {
    diskInfo_.empty = 0;
  }

  diskInfo_.valid.empty = 0; // does not work for this drive
  
  memset(cmd, 0, 10);
  memset(data, 0, dataLen);
  
  cmd[0] = 0x51; // READ DISK INFORMATION
  cmd[7] = dataLen >> 8;
  cmd[8] = dataLen;
   
  if (sendCmd(cmd, 10, NULL, 0, data, dataLen, 0) == 0) {
    diskInfo_.empty = (data[2] & 0x03) == 0 ? 1 : 0;
    diskInfo_.cdrw = (data[2] & 0x10) != 0 ? 1 : 0;
    diskInfo_.valid.cdrw = 1;
  }

  return &diskInfo_;
}

// Special toc check for Taiyo-Yuden drives. Every track must have a pre-gap
// to create a correct disk. The pre-gap length may be only 1 block.
// Only warnings are created.
// Return: 0: OK
//         1: at least one warning was given
//         2: at least on error was given

int TaiyoYuden::checkToc(const Toc *toc)
{
  int err = PlextorReader::checkToc(toc);
  int trackNr;
  Msf start, end;
  const Track *t;
  int warningGiven = 0;

  TrackIterator itr(toc);

  for (t = itr.first(start, end), trackNr = 1;
       t != NULL;
       t = itr.next(start, end), trackNr++) {
    if (t->start().lba() == 0 && trackNr > 1) {
      if (!warningGiven) {
	warningGiven = 1;
	message(-1, "Each track must have a minimal pre-gap to create a useful disk.");
	message(-1, "This is not fulfilled for following track(s):");
      }
      message(-1, "Track %d", trackNr);
    }
  }

  if (warningGiven) {
    message(0,"");
    if (err < 1)
      err = 1;
  }

  return err;
}
