/*
 *
 * $Id: k3bmixedjob.cpp 528555 2006-04-11 12:17:49Z trueg $
 * Copyright (C) 2003 Sebastian Trueg <trueg@k3b.org>
 *
 * This file is part of the K3b project.
 * Copyright (C) 1998-2004 Sebastian Trueg <trueg@k3b.org>
 *
 * 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.
 * See the file "COPYING" for the exact licensing terms.
 */




#include "k3bmixedjob.h"
#include "k3bmixeddoc.h"

#include <k3bdatadoc.h>
#include <k3bisoimager.h>
#include <k3bmsinfofetcher.h>
#include <k3baudioimager.h>
#include <k3baudiodoc.h>
#include <k3baudiotrack.h>
#include <k3baudionormalizejob.h>
#include <k3baudiojobtempdata.h>
#include <k3baudiomaxspeedjob.h>
#include <k3bdevicemanager.h>
#include <k3bdevice.h>
#include <k3bdevicehandler.h>
#include <k3bmsf.h>
#include <k3bglobals.h>
#include <k3bexternalbinmanager.h>
#include <k3bversion.h>
#include <k3bcore.h>
#include <k3bcdrecordwriter.h>
#include <k3bcdrdaowriter.h>
#include <k3btocfilewriter.h>
#include <k3binffilewriter.h>
#include <k3bglobalsettings.h>

#include <qfile.h>
#include <qdatastream.h>
#include <qapplication.h>

#include <kdebug.h>
#include <klocale.h>
#include <ktempfile.h>
#include <kio/netaccess.h>
#include <kio/global.h>
#include <kmessagebox.h>


class K3bMixedJob::Private
{
public:
  Private()
    : maxSpeedJob(0) {
  }

  int copies;
  int copiesDone;

  K3bAudioMaxSpeedJob* maxSpeedJob;
  bool maxSpeed;
};


K3bMixedJob::K3bMixedJob( K3bMixedDoc* doc, K3bJobHandler* hdl, QObject* parent )
  : K3bBurnJob( hdl, parent ),
    m_doc( doc ),
    m_normalizeJob(0)
{
  d = new Private;

  m_isoImager = new K3bIsoImager( doc->dataDoc(), this, this );
  connect( m_isoImager, SIGNAL(sizeCalculated(int, int)), this, SLOT(slotSizeCalculationFinished(int, int)) );
  connect( m_isoImager, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) );
  connect( m_isoImager, SIGNAL(percent(int)), this, SLOT(slotIsoImagerPercent(int)) );
  connect( m_isoImager, SIGNAL(finished(bool)), this, SLOT(slotIsoImagerFinished(bool)) );
  connect( m_isoImager, SIGNAL(debuggingOutput(const QString&, const QString&)),
	   this, SIGNAL(debuggingOutput(const QString&, const QString&)) );

  m_audioImager = new K3bAudioImager( doc->audioDoc(), this, this );
  connect( m_audioImager, SIGNAL(infoMessage(const QString&, int)), 
	   this, SIGNAL(infoMessage(const QString&, int)) );
  connect( m_audioImager, SIGNAL(percent(int)), this, SLOT(slotAudioDecoderPercent(int)) );
  connect( m_audioImager, SIGNAL(subPercent(int)), this, SLOT(slotAudioDecoderSubPercent(int)) );
  connect( m_audioImager, SIGNAL(finished(bool)), this, SLOT(slotAudioDecoderFinished(bool)) );
  connect( m_audioImager, SIGNAL(nextTrack(int, int)), this, SLOT(slotAudioDecoderNextTrack(int, int)) );

  m_msInfoFetcher = new K3bMsInfoFetcher( this, this );
  connect( m_msInfoFetcher, SIGNAL(finished(bool)), this, SLOT(slotMsInfoFetched(bool)) );
  connect( m_msInfoFetcher, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) );

  m_writer = 0;
  m_tocFile = 0;
  m_tempData = new K3bAudioJobTempData( m_doc->audioDoc(), this );
}


K3bMixedJob::~K3bMixedJob()
{
  delete m_tocFile;
  delete d;
}


K3bDevice::Device* K3bMixedJob::writer() const
{
  return m_doc->burner();
}


K3bDoc* K3bMixedJob::doc() const
{
  return m_doc;
}


void K3bMixedJob::start()
{
  emit started();

  m_canceled = false;
  m_errorOccuredAndAlreadyReported = false;
  d->copiesDone = 0;
  d->copies = m_doc->copies();
  m_currentAction = PREPARING_DATA;
  d->maxSpeed = false;

  if( m_doc->dummy() )
    d->copies = 1;

  prepareProgressInformation();

  // set some flags that are needed
  m_doc->audioDoc()->setOnTheFly( m_doc->onTheFly() );  // for the toc writer
  m_doc->audioDoc()->setHideFirstTrack( false );   // unsupported
  m_doc->dataDoc()->setBurner( m_doc->burner() );  // so the isoImager can read ms data

  emit newTask( i18n("Preparing data") );

  m_doc->dataDoc()->prepareFilenames();
  if( m_doc->dataDoc()->needToCutFilenames() ) {
    if( !questionYesNo( i18n("Some filenames need to be shortened due to the %1 char restriction "
			     "of the Joliet extensions. Continue anyway?")
			.arg( m_doc->dataDoc()->isoOptions().jolietLong() ? 103 : 64 ) ) ) {
      emit canceled();
      emit finished( false );
      return;
    }
  }

  determineWritingMode();

  // depending on the mixed type and if it's onthefly or not we
  // decide what to do first

  //
  // in case we write two sessions we need to check if the resulting data will really fit on the Cd
  //
  if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION )
    determinePreliminaryDataImageSize();
  else
    startFirstCopy();
}



void K3bMixedJob::determinePreliminaryDataImageSize()
{
  // we do not have msinfo yet
  m_isoImager->setMultiSessionInfo( QString::null );
  m_isoImager->calculateSize();
}


void K3bMixedJob::startFirstCopy()
{
  //
  // if not onthefly create the iso image and then the wavs
  // and write then
  // if onthefly calculate the iso size
  //
  if( m_doc->onTheFly() ) {
    if( m_doc->speed() == 0 ) {
      emit newSubTask( i18n("Determining maximum writing speed") );

      //
      // try to determine the max possible speed
      // no need to check the data track's max speed. Most current systems are able
      // to handle the maxium possible
      //
      if( !d->maxSpeedJob ) {
	// the maxspeed job gets the device from the doc:
	m_doc->audioDoc()->setBurner( m_doc->burner() );
	d->maxSpeedJob = new K3bAudioMaxSpeedJob( m_doc->audioDoc(), this, this );
	connect( d->maxSpeedJob, SIGNAL(percent(int)), 
		 this, SIGNAL(subPercent(int)) );
	connect( d->maxSpeedJob, SIGNAL(finished(bool)), 
		 this, SLOT(slotMaxSpeedJobFinished(bool)) );
      }
      d->maxSpeedJob->start();
    }
    else if( m_doc->mixedType() != K3bMixedDoc::DATA_SECOND_SESSION ) {
      m_isoImager->calculateSize();
    }
    else {
      // we cannot calculate the size since we don't have the msinfo yet
      // so first write the audio session
      writeNextCopy();
    }
  }
  else {
    emit burning(false);

    emit infoMessage( i18n("Creating audio image files in %1").arg(m_doc->tempDir()), INFO );

    m_tempFilePrefix = K3b::findUniqueFilePrefix( ( !m_doc->audioDoc()->title().isEmpty()
						    ? m_doc->audioDoc()->title()
						    : m_doc->dataDoc()->isoOptions().volumeID() ),
						  m_doc->tempDir() );

    m_tempData->prepareTempFileNames( m_doc->tempDir() );      
    QStringList filenames;
    for( K3bAudioTrack* track = m_doc->audioDoc()->firstTrack(); track; track = track->next() )
      filenames += m_tempData->bufferFileName( track );
    m_audioImager->setImageFilenames( filenames );
    
    if( m_doc->mixedType() != K3bMixedDoc::DATA_SECOND_SESSION ) {
      createIsoImage();
    }
    else {
      emit newTask( i18n("Creating audio image files") );
      m_currentAction = CREATING_AUDIO_IMAGE;
      m_audioImager->start();
    }
  }
}


void K3bMixedJob::slotMaxSpeedJobFinished( bool success )
{
  d->maxSpeed = success;
  if( !success )
    emit infoMessage( i18n("Unable to determine maximum speed for some reason. Ignoring."), WARNING );

  if( m_doc->mixedType() != K3bMixedDoc::DATA_SECOND_SESSION ) {
    m_isoImager->calculateSize();
  }
  else {
    // we cannot calculate the size since we don't have the msinfo yet
    // so first write the audio session
    writeNextCopy();
  }
}


void K3bMixedJob::writeNextCopy()
{
  if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) {
    m_currentAction = WRITING_AUDIO_IMAGE;
    if( !prepareWriter() || !startWriting() ) {
      cleanupAfterError();
      emit finished(false);
    }
    else if( m_doc->onTheFly() )
      m_audioImager->start();
  }
  else {
    // the prepareWriter method needs the action to be set
    if( m_doc->mixedType() == K3bMixedDoc::DATA_LAST_TRACK )
      m_currentAction = WRITING_AUDIO_IMAGE;
    else
      m_currentAction = WRITING_ISO_IMAGE;

    if( !prepareWriter() || !startWriting() ) {
      cleanupAfterError();
      emit finished(false);
    }
    else if( m_doc->onTheFly() ) {
      if( m_doc->mixedType() == K3bMixedDoc::DATA_LAST_TRACK )
	m_audioImager->start();
      else
	m_isoImager->start();
    }
  }
}


void K3bMixedJob::cancel()
{
  m_canceled = true;

  if( d->maxSpeedJob )
    d->maxSpeedJob->cancel();

  if( m_writer )
    m_writer->cancel();
  m_isoImager->cancel();
  m_audioImager->cancel();
  m_msInfoFetcher->cancel();
  emit infoMessage( i18n("Writing canceled."), K3bJob::ERROR );
  removeBufferFiles();
  emit canceled();
  emit finished(false);
}


void K3bMixedJob::slotMsInfoFetched( bool success )
{
  if( m_canceled || m_errorOccuredAndAlreadyReported )
    return;

  if( success ) {
    if( m_usedDataWritingApp == K3b::CDRECORD )
      m_isoImager->setMultiSessionInfo( m_msInfoFetcher->msInfo() );
    else  // cdrdao seems to write a 150 blocks pregap that is not used by cdrecord
      m_isoImager->setMultiSessionInfo( QString("%1,%2")
					.arg(m_msInfoFetcher->lastSessionStart())
					.arg(m_msInfoFetcher->nextSessionStart()+150) );

    if( m_doc->onTheFly() ) {
      m_isoImager->calculateSize();
    }
    else {
      createIsoImage();
    }
  }
  else {
    // the MsInfoFetcher already emitted failure info
    cleanupAfterError();
    emit finished(false);
  }
}


void K3bMixedJob::slotSizeCalculationFinished( int status, int size )
{
  kdDebug() << "(K3bMixedJob) size calculated: " << size << endl;
  emit debuggingOutput( "K3b", QString( "Size of filesystem calculated: %1" ).arg(size) );

  if( status != ERROR ) {

    if( m_currentAction == PREPARING_DATA ) {
      // check the size
      m_projectSize  = size + m_doc->audioDoc()->length();
      if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION )
	m_projectSize += 11400; // the session gap
      
      startFirstCopy();
    }

    // 1. data in first track:
    //    start isoimager and writer
    //    when isoimager finishes start audiodecoder

    // 2. data in last track
    //    start audiodecoder and writer
    //    when audiodecoder finishes start isoimager

    // 3. data in second session
    //    start audiodecoder and writer
    //    start isoimager and writer

    else if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) {
      if( !prepareWriter() || !startWriting() ) {
	cleanupAfterError();
	emit finished(false);
      }
      else
	m_isoImager->start();
    }
    else
      writeNextCopy();
  }
  else {
    cleanupAfterError();
    emit finished( false );
  }
}


void K3bMixedJob::slotIsoImagerFinished( bool success )
{
  if( m_canceled || m_errorOccuredAndAlreadyReported )
    return;

  if( !success ) {
    emit infoMessage( i18n("Error while creating ISO image."), ERROR );
    cleanupAfterError();

    emit finished( false );
    return;
  }

  if( m_doc->onTheFly() ) {
    if( m_doc->mixedType() == K3bMixedDoc::DATA_FIRST_TRACK ) {
      m_currentAction = WRITING_AUDIO_IMAGE;
      m_audioImager->start();
    }
  }
  else {
    emit infoMessage( i18n("ISO image successfully created."), SUCCESS );

    if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) {
      m_currentAction = WRITING_ISO_IMAGE;

      if( !prepareWriter() || !startWriting() ) {
	cleanupAfterError();
	emit finished(false);
      }
    }
    else {
      emit newTask( i18n("Creating audio image files") );
      m_currentAction = CREATING_AUDIO_IMAGE;
      m_audioImager->start();
    }
  }
}


void K3bMixedJob::slotWriterFinished( bool success )
{
  if( m_canceled || m_errorOccuredAndAlreadyReported )
    return;

  if( !success ) {
    cleanupAfterError();
    emit finished(false);
    return;
  }

  emit burning(false);

  if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION && m_currentAction == WRITING_AUDIO_IMAGE ) {
    // reload the media (as a subtask so the user does not see the "Flushing cache" or "Fixating" messages while
    // doing so
    emit newSubTask( i18n("Reloading the media") );
    connect( K3bDevice::reload( m_doc->burner() ), SIGNAL(finished(bool)),
	     this, SLOT(slotMediaReloadedForSecondSession(bool)) );
  }
  else {
    d->copiesDone++;
    if( d->copiesDone < d->copies ) {
      K3bDevice::eject( m_doc->burner() );
      writeNextCopy();
    }
    else {
      if( !m_doc->onTheFly() && m_doc->removeImages() )
	removeBufferFiles();
      
      emit finished(true);
    }
  }
}


void K3bMixedJob::slotMediaReloadedForSecondSession( bool success )
{
  if( !success )
    KMessageBox::information( 0, i18n("Please reload the medium and press 'ok'"),
			      i18n("Unable to close the tray") );

  // start the next session
  m_currentAction = WRITING_ISO_IMAGE;
  if( d->copiesDone > 0 ) {
    // we only create the image once. This should not be a problem???
    if( !prepareWriter() || !startWriting() ) {
      cleanupAfterError();
      emit finished(false);
    }
    else if( m_doc->onTheFly() ) {
      m_isoImager->start();
    }
  }
  else if( m_doc->dummy() ) {
    // do not try to get ms info in simulation mode since the cd is empty!
    if( m_doc->onTheFly() )
      m_isoImager->calculateSize();
    else 
      createIsoImage();
  }
  else {
    m_msInfoFetcher->setDevice( m_doc->burner() );
    m_msInfoFetcher->start();
  }
}


void K3bMixedJob::slotAudioDecoderFinished( bool success )
{
  if( m_canceled || m_errorOccuredAndAlreadyReported )
    return;

  if( !success ) {
    emit infoMessage( i18n("Error while decoding audio tracks."), ERROR );
    cleanupAfterError();
    emit finished(false);
    return;
  }

  if( m_doc->onTheFly() ) {
    if( m_doc->mixedType() == K3bMixedDoc::DATA_LAST_TRACK ) {
      m_currentAction = WRITING_ISO_IMAGE;
      m_isoImager->start();
    }
  }
  else {
    emit infoMessage( i18n("Audio images successfully created."), SUCCESS );

    if( m_doc->audioDoc()->normalize() ) {
      normalizeFiles();
    }
    else {
      if( m_doc->mixedType() == K3bMixedDoc::DATA_FIRST_TRACK )
	m_currentAction = WRITING_ISO_IMAGE;
      else
	m_currentAction = WRITING_AUDIO_IMAGE;

      if( !prepareWriter() || !startWriting() ) {
	cleanupAfterError();
	emit finished(false);
      }
    }
  }
}


void K3bMixedJob::slotAudioDecoderNextTrack( int t, int tt )
{
  if( m_doc->onlyCreateImages() || !m_doc->onTheFly() ) {
    K3bAudioTrack* track = m_doc->audioDoc()->getTrack(t);
    emit newSubTask( i18n("Decoding audio track %1 of %2%3")
		     .arg(t)
		     .arg(tt)
		     .arg( track->title().isEmpty() || track->artist().isEmpty() 
			   ? QString::null
			   : " (" + track->artist() + " - " + track->title() + ")" ) );
  }
}


bool K3bMixedJob::prepareWriter()
{
  if( m_writer ) delete m_writer;

  if( ( m_currentAction == WRITING_ISO_IMAGE && m_usedDataWritingApp == K3b::CDRECORD ) ||
      ( m_currentAction == WRITING_AUDIO_IMAGE && m_usedAudioWritingApp == K3b::CDRECORD ) )  {

    if( !writeInfFiles() ) {
      kdDebug() << "(K3bMixedJob) could not write inf-files." << endl;
      emit infoMessage( i18n("IO Error"), ERROR );

      return false;
    }

    K3bCdrecordWriter* writer = new K3bCdrecordWriter( m_doc->burner(), this, this );

    // only write the audio tracks in DAO mode
    if( m_currentAction == WRITING_ISO_IMAGE )
      writer->setWritingMode( m_usedDataWritingMode );
    else
      writer->setWritingMode( m_usedAudioWritingMode );

    writer->setSimulate( m_doc->dummy() );
    writer->setBurnSpeed( m_doc->speed() );

    if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) {
      if( m_currentAction == WRITING_ISO_IMAGE ) {
	if( m_doc->onTheFly() )
	  writer->addArgument("-waiti");

	addDataTrack( writer );
      }
      else {
	writer->addArgument("-multi");
	addAudioTracks( writer );
      }
    }
    else {
      if( m_doc->mixedType() == K3bMixedDoc::DATA_FIRST_TRACK )
	addDataTrack( writer );
      addAudioTracks( writer );
      if( m_doc->mixedType() == K3bMixedDoc::DATA_LAST_TRACK )
	addDataTrack( writer );
    }

    m_writer = writer;
  }
  else {
    if( !writeTocFile() ) {
      kdDebug() << "(K3bDataJob) could not write tocfile." << endl;
      emit infoMessage( i18n("IO Error"), ERROR );

      return false;
    }

    // create the writer
    // create cdrdao job
    K3bCdrdaoWriter* writer = new K3bCdrdaoWriter( m_doc->burner(), this, this );
    writer->setSimulate( m_doc->dummy() );
    writer->setBurnSpeed( m_doc->speed() );

    // multisession only for the first session
    writer->setMulti( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION
		      && m_currentAction == WRITING_AUDIO_IMAGE );

    writer->setTocFile( m_tocFile->name() );

    m_writer = writer;
  }

  connect( m_writer, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) );
  connect( m_writer, SIGNAL(percent(int)), this, SLOT(slotWriterJobPercent(int)) );
  connect( m_writer, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) );
  connect( m_writer, SIGNAL(subPercent(int)), this, SIGNAL(subPercent(int)) );
  connect( m_writer, SIGNAL(processedSubSize(int, int)), this, SIGNAL(processedSubSize(int, int)) );
  connect( m_writer, SIGNAL(nextTrack(int, int)), this, SLOT(slotWriterNextTrack(int, int)) );
  connect( m_writer, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) );
  connect( m_writer, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) );
  connect( m_writer, SIGNAL(writeSpeed(int, int)), this, SIGNAL(writeSpeed(int, int)) );
  connect( m_writer, SIGNAL(finished(bool)), this, SLOT(slotWriterFinished(bool)) );
  //  connect( m_writer, SIGNAL(newTask(const QString&)), this, SIGNAL(newTask(const QString&)) );
  connect( m_writer, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) );
  connect( m_writer, SIGNAL(debuggingOutput(const QString&, const QString&)),
	   this, SIGNAL(debuggingOutput(const QString&, const QString&)) );

  return true;
}


bool K3bMixedJob::writeInfFiles()
{
  K3bInfFileWriter infFileWriter;
  K3bAudioTrack* track = m_doc->audioDoc()->firstTrack();
  while( track ) {

    infFileWriter.setTrack( track->toCdTrack() );
    infFileWriter.setTrackNumber( track->trackNumber() );
    if( !m_doc->onTheFly() )
      infFileWriter.setBigEndian( false );

    if( !infFileWriter.save( m_tempData->infFileName(track) ) )
      return false;

    track = track->next();
  }
  return true;
}


bool K3bMixedJob::writeTocFile()
{
  // FIXME: create the tocfile in the same directory like all the other files.

  if( m_tocFile ) delete m_tocFile;
  m_tocFile = new KTempFile( QString::null, "toc" );
  m_tocFile->setAutoDelete(true);

  // write the toc-file
  if( QTextStream* s = m_tocFile->textStream() ) {

    K3bTocFileWriter tocFileWriter;

    //
    // TOC
    //
    tocFileWriter.setData( m_doc->toToc( m_usedDataMode == K3b::MODE2
					 ? K3bDevice::Track::XA_FORM1
					 : K3bDevice::Track::MODE1,
					 m_doc->onTheFly() 
					 ? m_isoImager->size()
					 : m_doc->dataDoc()->length() ) );

    //
    // CD-Text
    //
    if( m_doc->audioDoc()->cdText() ) {
      K3bDevice::CdText text = m_doc->audioDoc()->cdTextData();
      // if data in first track we need to add a dummy cdtext
      if( m_doc->mixedType() == K3bMixedDoc::DATA_FIRST_TRACK )
	text.insert( text.begin(), K3bDevice::TrackCdText() );
    
      tocFileWriter.setCdText( text );
    }

    //
    // Session to write
    //
    tocFileWriter.setSession( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION &&
			      m_currentAction == WRITING_ISO_IMAGE ? 2 : 1 );

    //
    // image filenames
    //
    if( !m_doc->onTheFly() ) {
      QStringList files;
      K3bAudioTrack* track = m_doc->audioDoc()->firstTrack();
      while( track ) {      
	files += m_tempData->bufferFileName( track );
	track = track->next();
      }
      if( m_doc->mixedType() == K3bMixedDoc::DATA_FIRST_TRACK )
	files.prepend( m_isoImageFilePath );
      else
	files.append( m_isoImageFilePath );

      tocFileWriter.setFilenames( files );
    }

    bool success = tocFileWriter.save( *s );

    m_tocFile->close();

    // backup for debugging
//    KIO::NetAccess::del("/tmp/trueg/tocfile_debug_backup.toc",0L);
//    KIO::NetAccess::copy( m_tocFile->name(), "/tmp/trueg/tocfile_debug_backup.toc",0L );

    return success;
  }
  else
    return false;
}


void K3bMixedJob::addAudioTracks( K3bCdrecordWriter* writer )
{
  writer->addArgument( "-useinfo" );

  // add raw cdtext data
  if( m_doc->audioDoc()->cdText() ) {
    writer->setRawCdText( m_doc->audioDoc()->cdTextData().rawPackData() );
  }

  writer->addArgument( "-audio" );

  // we always pad because although K3b makes sure all tracks' lenght are multible of 2352
  // it seems that normalize sometimes corrupts these lengths
  writer->addArgument( "-pad" );

  // Allow tracks shorter than 4 seconds
  writer->addArgument( "-shorttrack" );

  // add all the audio tracks
  K3bAudioTrack* track = m_doc->audioDoc()->firstTrack();
  while( track ) {
    if( m_doc->onTheFly() ) {
      // this is only supported by cdrecord versions >= 2.01a13
      writer->addArgument( QFile::encodeName( m_tempData->infFileName( track ) ) );
    }
    else {
      writer->addArgument( QFile::encodeName( m_tempData->bufferFileName( track ) ) );
    }
    track = track->next();
  }
}

void K3bMixedJob::addDataTrack( K3bCdrecordWriter* writer )
{
  // add data track
  if(  m_usedDataMode == K3b::MODE2 ) {
    if( k3bcore->externalBinManager()->binObject("cdrecord") && 
	k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "xamix" ) )
      writer->addArgument( "-xa" );
    else
      writer->addArgument( "-xa1" );
  }
  else
    writer->addArgument( "-data" );

  if( m_doc->onTheFly() )
    writer->addArgument( QString("-tsize=%1s").arg(m_isoImager->size()) )->addArgument("-");
  else
    writer->addArgument( m_isoImageFilePath );
}


void K3bMixedJob::slotWriterNextTrack( int t, int )
{
  K3bAudioTrack* track = 0;

  if( m_doc->mixedType() == K3bMixedDoc::DATA_FIRST_TRACK ) {
    if( t > 1 )
      track = m_doc->audioDoc()->getTrack(t-1);
  }
  else if( m_doc->mixedType() == K3bMixedDoc::DATA_LAST_TRACK ) {
    if( t < m_doc->audioDoc()->numOfTracks()+1 )
      track = m_doc->audioDoc()->getTrack(t);
  }
  else if( m_currentAction == WRITING_AUDIO_IMAGE )
    track = m_doc->audioDoc()->getTrack(t);
  else
    t = m_doc->numOfTracks();

  if( track )
    emit newSubTask( i18n("Writing track %1 of %2%3")
		     .arg(t)
		     .arg(m_doc->numOfTracks())
		     .arg( track->title().isEmpty() || track->artist().isEmpty() 
			   ? QString::null
			   : " (" + track->artist() + " - " + track->title() + ")" ) );
  else
    emit newSubTask( i18n("Writing track %1 of %2 (%3)").arg(t).arg(m_doc->numOfTracks()).arg(i18n("ISO9660 data")) );
}


void K3bMixedJob::slotWriterJobPercent( int p )
{
  double totalTasks = d->copies;
  double tasksDone = d->copiesDone;
  if( m_doc->audioDoc()->normalize() ) {
    totalTasks+=1.0;
    tasksDone+=1.0;
  }
  if( !m_doc->onTheFly() ) {
    totalTasks+=1.0;
  }

  if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) {
    if( m_currentAction == WRITING_AUDIO_IMAGE ) {
      // the audio imager has finished in all cases
      // the iso imager only if this is not the first copy
      if( d->copiesDone > 0 )
	tasksDone += 1.0;
      else if( !m_doc->onTheFly() )
	tasksDone += m_audioDocPartOfProcess;

      p = (int)((double)p*m_audioDocPartOfProcess);
    }
    else {
      // all images have been created
      if( !m_doc->onTheFly() )
	tasksDone += 1.0;

      p = (int)(100.0*m_audioDocPartOfProcess + (double)p*(1.0-m_audioDocPartOfProcess));
    }
  }
  else if( !m_doc->onTheFly() )
    tasksDone += 1.0;

  emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) );
}


void K3bMixedJob::slotAudioDecoderPercent( int p )
{
  // the only thing finished here might be the isoimager which is part of this task
  if( !m_doc->onTheFly() ) {
    double totalTasks = d->copies+1;
    if( m_doc->audioDoc()->normalize() )
      totalTasks+=1.0;

    if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION )
      p = (int)((double)p*m_audioDocPartOfProcess);
    else
      p = (int)(100.0*(1.0-m_audioDocPartOfProcess) + (double)p*m_audioDocPartOfProcess);
    
    emit percent( (int)((double)p / totalTasks) );
  }
}


void K3bMixedJob::slotAudioDecoderSubPercent( int p )
{
  if( !m_doc->onTheFly() ) {
    emit subPercent( p );
  }
}


void K3bMixedJob::slotIsoImagerPercent( int p )
{
  if( !m_doc->onTheFly() ) {
    emit subPercent( p );
    if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) {

      double totalTasks = d->copies+1.0;
      double tasksDone = d->copiesDone;
      if( m_doc->audioDoc()->normalize() ) {
	totalTasks+=1.0;
	// the normalizer finished
	tasksDone+=1.0;
      }

      // the writing of the audio part finished
      tasksDone += m_audioDocPartOfProcess;

      // the audio decoder finished (which is part of this task in terms of progress)
      p = (int)(100.0*m_audioDocPartOfProcess + (double)p*(1.0-m_audioDocPartOfProcess));

      emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) );
    }
    else {
      double totalTasks = d->copies+1.0;
      if( m_doc->audioDoc()->normalize() )
	totalTasks+=1.0;
      
      emit percent( (int)((double)(p*(1.0-m_audioDocPartOfProcess)) / totalTasks) );
    }
  }
}


bool K3bMixedJob::startWriting()
{
  if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) {
    if( m_currentAction == WRITING_ISO_IMAGE) {
      if( m_doc->dummy() )
	emit newTask( i18n("Simulating second session") );
      else if( d->copies > 1 )
	emit newTask( i18n("Writing second session of copy %1").arg(d->copiesDone+1) );
      else
	emit newTask( i18n("Writing second session") );
    }
    else {
      if( m_doc->dummy() )
	emit newTask( i18n("Simulating first session") );
      else if( d->copies > 1 )
	emit newTask( i18n("Writing first session of copy %1").arg(d->copiesDone+1) );
      else
	emit newTask( i18n("Writing first session") );
    }
  }
  else if( m_doc->dummy() )
    emit newTask( i18n("Simulating") );
  else
    emit newTask( i18n("Writing Copy %1").arg(d->copiesDone+1) );


  // if we append the second session the cd is already in the drive
  if( !(m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION
	&& m_currentAction == WRITING_ISO_IMAGE) ) {

    emit newSubTask( i18n("Waiting for media") );
    if( waitForMedia( m_doc->burner() ) < 0 ) {
      cancel();
      return false;
    }

    // just to be sure we did not get canceled during the async discWaiting
    if( m_canceled )
      return false;

    // check if the project will fit on the CD
    if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) {
      // the media is in and has been checked so this should be fast (hopefully)
      K3b::Msf mediaSize = m_doc->burner()->diskInfo().capacity();
      if( mediaSize < m_projectSize ) {
	if( k3bcore->globalSettings()->overburn() ) {
	  emit infoMessage( i18n("Trying to write more than the official disk capacity"), K3bJob::WARNING );
	}
	else {
	  emit infoMessage( i18n("Data does not fit on disk."), ERROR );
	  return false;
	}
      }
    }
  }

  // in case we determined the max possible writing speed we have to reset the speed on the writer job
  // here since an inserted media is neccessary
  // the Max speed job will compare the max speed value with the supported values of the writer
  if( d->maxSpeed )
    m_writer->setBurnSpeed( d->maxSpeedJob->maxSpeed() );

  emit burning(true);
  m_writer->start();

  if( m_doc->onTheFly() ) {
    // now the writer is running and we can get it's stdin
    // we only use this method when writing on-the-fly since
    // we cannot easily change the audioDecode fd while it's working
    // which we would need to do since we write into several
    // image files.
    m_audioImager->writeToFd( m_writer->fd() );
    m_isoImager->writeToFd( m_writer->fd() );
  }

  return true;
}


void K3bMixedJob::createIsoImage()
{
  m_currentAction = CREATING_ISO_IMAGE;

  // prepare iso image file
  m_isoImageFilePath = m_tempFilePrefix + "_datatrack.iso";

  if( !m_doc->onTheFly() )
    emit newTask( i18n("Creating ISO image file") );
  emit newSubTask( i18n("Creating ISO image in %1").arg(m_isoImageFilePath) );
  emit infoMessage( i18n("Creating ISO image in %1").arg(m_isoImageFilePath), INFO );

  m_isoImager->writeToImageFile( m_isoImageFilePath );
  m_isoImager->start();
}


void K3bMixedJob::cleanupAfterError()
{
  m_errorOccuredAndAlreadyReported = true;
  //  m_audioImager->cancel();
  m_isoImager->cancel();
  if( m_writer )
    m_writer->cancel();

  if( m_tocFile ) delete m_tocFile;
  m_tocFile = 0;

  // remove the temp files
  removeBufferFiles();
}


void K3bMixedJob::removeBufferFiles()
{
  emit infoMessage( i18n("Removing buffer files."), INFO );

  if( QFile::exists( m_isoImageFilePath ) )
      if( !QFile::remove( m_isoImageFilePath ) )
	emit infoMessage( i18n("Could not delete file %1.").arg(m_isoImageFilePath), ERROR );

  m_tempData->cleanup();
}


void K3bMixedJob::determineWritingMode()
{
  // we don't need this when only creating image and it is possible
  // that the burn device is null
  if( m_doc->onlyCreateImages() )
    return;

  // at first we determine the data mode
  // --------------------------------------------------------------
  if( m_doc->dataDoc()->dataMode() == K3b::DATA_MODE_AUTO ) {
    if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION )
      m_usedDataMode = K3b::MODE2;
    else
      m_usedDataMode = K3b::MODE1;
  }
  else
    m_usedDataMode = m_doc->dataDoc()->dataMode();


  // we try to use cdrecord if possible
  bool cdrecordOnTheFly = false;
  bool cdrecordCdText = false;
  bool cdrecordUsable = false;
  
  if( k3bcore->externalBinManager()->binObject("cdrecord") ) {
    cdrecordOnTheFly =
      k3bcore->externalBinManager()->binObject("cdrecord")->version
      >= K3bVersion( 2, 1, -1, "a13" );
    cdrecordCdText =
      k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "cdtext" );
    cdrecordUsable =
      !( !cdrecordOnTheFly && m_doc->onTheFly() ) &&
      !( m_doc->audioDoc()->cdText() && !cdrecordCdText );
  }

  // Writing Application
  // --------------------------------------------------------------
  // cdrecord seems to have problems writing xa 1 disks in dao mode? At least on my system!
  if( writingApp() == K3b::DEFAULT ) {
    if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) {
      if( m_doc->writingMode() == K3b::DAO ||
	  ( m_doc->writingMode() == K3b::WRITING_MODE_AUTO && !cdrecordUsable ) ) {
	m_usedAudioWritingApp = K3b::CDRDAO;
	m_usedDataWritingApp = K3b::CDRDAO;
      }
      else {
	m_usedAudioWritingApp = K3b::CDRECORD;
	m_usedDataWritingApp = K3b::CDRECORD;
      }
    }
    else {
      if( cdrecordUsable ) {
	m_usedAudioWritingApp = K3b::CDRECORD;
	m_usedDataWritingApp = K3b::CDRECORD;
      }
      else {
	m_usedAudioWritingApp = K3b::CDRDAO;
	m_usedDataWritingApp = K3b::CDRDAO;
      }
    }
  }
  else {
    m_usedAudioWritingApp = writingApp();
    m_usedDataWritingApp = writingApp();
  }

  // TODO: use K3bExceptions::brokenDaoAudio

  // Writing Mode (TAO/DAO/RAW)
  // --------------------------------------------------------------
  if( m_doc->writingMode() == K3b::WRITING_MODE_AUTO ) {

    if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) {
      if( m_usedDataWritingApp == K3b::CDRECORD )
	m_usedDataWritingMode = K3b::TAO;
      else
	m_usedDataWritingMode = K3b::DAO;

      // default to Session at once for the audio part
      m_usedAudioWritingMode = K3b::DAO;
    }
    else if( writer()->dao() ) {
      m_usedDataWritingMode = K3b::DAO;
      m_usedAudioWritingMode = K3b::DAO;
    }
    else {
      m_usedDataWritingMode = K3b::TAO;
      m_usedAudioWritingMode = K3b::TAO;
    }
  }
  else {
    m_usedAudioWritingMode = m_doc->writingMode();
    m_usedDataWritingMode = m_doc->writingMode();
  }


  if( m_usedDataWritingApp == K3b::CDRECORD ) {
    if( !cdrecordOnTheFly && m_doc->onTheFly() ) {
      m_doc->setOnTheFly( false );
      emit infoMessage( i18n("On-the-fly writing with cdrecord < 2.01a13 not supported."), ERROR );
    }

    if( m_doc->audioDoc()->cdText() ) {
      if( !cdrecordCdText ) {
	m_doc->audioDoc()->writeCdText( false );
	emit infoMessage( i18n("Cdrecord %1 does not support CD-Text writing.").arg(k3bcore->externalBinManager()->binObject("cdrecord")->version), ERROR );
      }
      else if( m_usedAudioWritingMode == K3b::TAO ) {
	emit infoMessage( i18n("It is not possible to write CD-Text in TAO mode. Try DAO or RAW."), WARNING );
      }
    }
  }
}


void K3bMixedJob::normalizeFiles()
{
  if( !m_normalizeJob ) {
    m_normalizeJob = new K3bAudioNormalizeJob( this, this );

    connect( m_normalizeJob, SIGNAL(infoMessage(const QString&, int)),
	     this, SIGNAL(infoMessage(const QString&, int)) );
    connect( m_normalizeJob, SIGNAL(percent(int)), this, SLOT(slotNormalizeProgress(int)) );
    connect( m_normalizeJob, SIGNAL(subPercent(int)), this, SLOT(slotNormalizeSubProgress(int)) );
    connect( m_normalizeJob, SIGNAL(finished(bool)), this, SLOT(slotNormalizeJobFinished(bool)) );
    connect( m_normalizeJob, SIGNAL(newTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) );
    connect( m_normalizeJob, SIGNAL(debuggingOutput(const QString&, const QString&)),
	     this, SIGNAL(debuggingOutput(const QString&, const QString&)) );
  }

  // add all the files
  QValueVector<QString> files;
  K3bAudioTrack* track = m_doc->audioDoc()->firstTrack();
  while( track ) {
    files.append( m_tempData->bufferFileName(track) );
    track = track->next();
  }

  m_normalizeJob->setFilesToNormalize( files );

  emit newTask( i18n("Normalizing volume levels") );
  m_normalizeJob->start();
}

void K3bMixedJob::slotNormalizeJobFinished( bool success )
{
  if( m_canceled || m_errorOccuredAndAlreadyReported )
    return;

  if( success ) {
    if( m_doc->mixedType() == K3bMixedDoc::DATA_FIRST_TRACK )
      m_currentAction = WRITING_ISO_IMAGE;
    else
      m_currentAction = WRITING_AUDIO_IMAGE;

    if( !prepareWriter() || !startWriting() ) {
      cleanupAfterError();
      emit finished(false);
    }
  }
  else {
    cleanupAfterError();
    emit finished(false);
  }
}

void K3bMixedJob::slotNormalizeProgress( int p )
{
  double totalTasks = d->copies+2.0;
  double tasksDone = 0;

  if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) {
    // the audio imager finished (m_audioDocPartOfProcess*1 task)
    // plus the normalize progress
    tasksDone = m_audioDocPartOfProcess;
  }
  else {
    // the iso and audio imagers already finished (one task)
    // plus the normalize progress
    tasksDone = 1.0;
  }

  emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) );
}


void K3bMixedJob::slotNormalizeSubProgress( int p )
{
  emit subPercent( p );
}


void K3bMixedJob::prepareProgressInformation()
{
  // calculate percentage of audio and data
  // this is also used in on-the-fly mode
  double ds = (double)m_doc->dataDoc()->length().totalFrames();
  double as = (double)m_doc->audioDoc()->length().totalFrames();
  m_audioDocPartOfProcess = as/(ds+as);
}


QString K3bMixedJob::jobDescription() const
{
  if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION )
    return i18n("Writing Enhanced Audio CD")
      + ( m_doc->audioDoc()->title().isEmpty()
	  ? QString::null
	  : QString( " (%1)" ).arg(m_doc->audioDoc()->title()) );
  else
    return i18n("Writing Mixed Mode CD")
      + ( m_doc->audioDoc()->title().isEmpty()
	  ? QString::null
	  : QString( " (%1)" ).arg(m_doc->audioDoc()->title()) );
}


QString K3bMixedJob::jobDetails() const
{
  return ( i18n("%1 tracks (%2 minutes audio data, %3 ISO9660 data)")
	   .arg(m_doc->numOfTracks())
	   .arg(m_doc->audioDoc()->length().toString())
	   .arg(KIO::convertSize(m_doc->dataDoc()->size()))
	   + ( m_doc->copies() > 1 && !m_doc->dummy()
	       ? i18n(" - %n copy", " - %n copies", m_doc->copies()) 
	       : QString::null ) );
}

#include "k3bmixedjob.moc"
