/*  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 "SonyCDU948.h"
#include "PWSubChannel96.h"

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

SonyCDU948::SonyCDU948(ScsiIf *scsiIf, unsigned long options)
  : SonyCDU920(scsiIf, options)
{
  driverName_ = "Sony CDU948 - Version 0.1 (data) (alpha)";

  speed_ = 4;

  cdTextEncoder_ = NULL;
}

SonyCDU948::~SonyCDU948()
{
  delete cdTextEncoder_;
  cdTextEncoder_ = NULL;
}

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

int SonyCDU948::checkToc(const Toc *toc)
{
  int err = SonyCDU920::checkToc(toc);
  int e;

  if ((e = toc->checkCdTextData()) > err)
    err = e;

  return err;
}

int SonyCDU948::multiSession(int m)
{
  multiSession_ = m != 0 ? 1 : 0;

  return 0;
}

// sets speed
// return: 0: OK
//         1: illegal speed
int SonyCDU948::speed(int s)
{
  if (s == 0 || s == 1 || s == 2 || s == 4)
    speed_ = s;
  else if (s == 3)
    speed_ = 2;
  else if (s > 4)
    speed_ = 4;
  else
    return 1;

  return 0;
}

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

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

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

  return 0;
}

// Sets write parameters.
// return: 0: OK
//         1: scsi command failed
int SonyCDU948::setWriteParameters()
{
  unsigned char cmd[10];
  unsigned char data[52];

  if (SonyCDU920::setWriteParameters() != 0)
    return 1;

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

  cmd[0] = 0xf8; // SET WRITE PARAMETERS
  cmd[7] = 0;
  cmd[8] = 52;

  data[1] = 50;
  data[3] = (multiSession_ != 0 ? (3 << 6) : 0);
  
  if (sendCmd(cmd, 10, data, 52, NULL, 0, 1) != 0) {
    message(-1, "Cannot set write parameters.");
    return 1;
  }

  return 0;
}

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

  delete cdTextEncoder_;
  cdTextEncoder_ = new CdTextEncoder(toc);
  if (cdTextEncoder_->encode() != 0) {
    message(-2, "CD-TEXT encoding failed.");
    return 1;
  }

  if (cdTextEncoder_->getSubChannels(&n) == NULL || n == 0) {
    // there is no CD-TEXT data to write
    delete cdTextEncoder_;
    cdTextEncoder_ = NULL;
  }

  return SonyCDU920::initDao(toc);
}

int SonyCDU948::startDao()
{
  unsigned char leadInDataForm = 0x00; // CD-DA, generate data by device
  scsiTimeout_ = scsiIf_->timeout(3 * 60);

  if (cdTextEncoder_ != NULL)
    leadInDataForm = 0xc0; // CD-DA with P-W sub-channel data

  if (setWriteParameters() != 0 ||
      sendCueSheet(leadInDataForm) != 0)
    return 1;

  if (writeCdTextLeadIn() != 0) {
    return 1;
  }

  long lba = -150;

  // write mandatory pre-gap after lead-in
  if (writeZeros(toc_->leadInMode(), TrackData::SUBCHAN_NONE, lba, 0, 150)
      != 0) {
    return 1;
  }
  
  return 0;
}

int SonyCDU948::writeCdTextLeadIn()
{
  unsigned char cmd[10];
  const PWSubChannel96 **cdTextSubChannels;
  long cdTextSubChannelCount;
  long channelsPerCmd = scsiIf_->maxDataLen() / 96;
  long scp = 0;
  long byteLen;
  long len = leadInLen_;
  long n;
  long i;
  unsigned char *p;

  if (cdTextEncoder_ == NULL)
    return 0;

  if (leadInLen_ == 0) {
    message(-2, "Cannot write CD-TEXT lead-in because lead-in length is not known.");
    return 1;
  }

  cdTextSubChannels = cdTextEncoder_->getSubChannels(&cdTextSubChannelCount);

  assert(channelsPerCmd > 0);
  assert(cdTextSubChannels != NULL);
  assert(cdTextSubChannelCount > 0);

  message(2, "Writing CD-TEXT lead-in...");

  memset(cmd, 0, 10);
  cmd[0] = 0xe1; // WRITE CONTINUE

  while (len > 0) {
    n = (len > channelsPerCmd) ? channelsPerCmd : len;

    byteLen = n * 96;

    cmd[1] = (byteLen >> 16) & 0x1f;
    cmd[2] = byteLen >> 8;
    cmd[3] = byteLen;

    p = transferBuffer_;
    
    for (i = 0; i < n; i++) {
      memcpy(p, cdTextSubChannels[scp]->data(), 96);
      p += 96;

      scp++;
      if (scp >= cdTextSubChannelCount)
	scp = 0;
    }

    if (sendCmd(cmd, 10, transferBuffer_, byteLen, NULL, 0) != 0) {
      message(-2, "Writing of CD-TEXT data failed.");
      return 1;
    }

    len -= n;
  }
    
  return 0;
}
