
#include "convert.h"
//#include "conversionoptions.h"
#include "convertpluginloader.h"
#include "replaygainpluginloader.h"
#include "replaygain.h"
#include "ripperpluginloader.h"
#include "configuration.h"
#include "tagengine.h"
#include "cdmanager.h"
#include "logger.h"
#include "filelist.h"
#include "replaygainscanner.h"

#include <math.h>

#include <tdelocale.h>
#include <tdeglobal.h>
//#include <kdebug.h>
#include <tdetempfile.h>
#include <tdeio/job.h>
//#include <kprocess.h>
#include <kstandarddirs.h>

#include <tqfile.h>
#include <tqtimer.h>

ConvertItem::ConvertItem()
{
    // create a new item with the file list item pointer set to zero
    ConvertItem( (FileListItem*)0 );
}

ConvertItem::ConvertItem( FileListItem* item )
{
    fileListItem = item;
    getTime = getCorrectionTime = ripTime = decodeTime = encodeTime = replaygainTime = 0;
}

ConvertItem::~ConvertItem()
{}


Convert::Convert( Config* _config, TagEngine* _tagEngine, CDManager* _cdManager, FileList* _fileList, Logger* _logger )
{
    config = _config;
    tagEngine = _tagEngine;
    cdManager = _cdManager;
    fileList = _fileList;
    connect( fileList, TQ_SIGNAL(convertItem(FileListItem*)),
               this, TQ_SLOT(add(FileListItem*))
             );
    connect( fileList, TQ_SIGNAL(stopItem(FileListItem*)),
               this, TQ_SLOT(stop(FileListItem*))
             );
    connect( this, TQ_SIGNAL(finished(FileListItem*,int)),
               fileList, TQ_SLOT(itemFinished(FileListItem*,int))
             );
    connect( this, TQ_SIGNAL(rippingFinished(const TQString&)),
               fileList, TQ_SLOT(rippingFinished(const TQString&))
             );
    logger = _logger;
    connect( this, TQ_SIGNAL(finishedProcess(int,int)),
               logger, TQ_SLOT(processCompleted(int,int))
             );

    tUpdateProgressIndicator = new TQTimer( this, "tUpdateProgressIndicator" );
    connect( tUpdateProgressIndicator, TQ_SIGNAL(timeout()),
               this, TQ_SLOT(updateProgressIndicator())
             );
}

Convert::~Convert()
{}

void Convert::cleanUp()
{
    // TODO clean up
}

void Convert::get( ConvertItem* item )
{
    logger->log( item->logID, i18n("Getting file") );
    item->state = ConvertItem::get;

    item->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Getting file")+"... 00 %" );

    KURL source( item->fileListItem->options.filePathName.replace("?","%3f") );
    KURL destination( item->tempInFile->name() );

    if( source.isLocalFile() && destination.isLocalFile() ) {
        item->convertProcess->clearArguments();

        *(item->convertProcess) << "cp";
        *(item->convertProcess) << source.path();
        *(item->convertProcess) << destination.path();

        logger->log( item->logID, "cp \"" + source.path() + "\" \"" + destination.path() + "\"" );

        item->convertProcess->setPriority( config->data.general.priority );
        item->convertProcess->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput );
    }
    else {
        item->moveJob = new TDEIO::FileCopyJob( source, destination, -1, false, true, false, false );
        connect( item->moveJob, TQ_SIGNAL(percent(TDEIO::Job*,unsigned long)),
                this, TQ_SLOT(moveProgress(TDEIO::Job*,unsigned long))
                );
        connect( item->moveJob, TQ_SIGNAL(result(TDEIO::Job*)),
                this, TQ_SLOT(moveFinished(TDEIO::Job*))
                );
    }
}

void Convert::getCorrection( ConvertItem* item )
{
    logger->log( item->logID, i18n("Getting correction file") );
    item->state = ConvertItem::get_correction;

    // calculate the name of the correction input file
    TQFile file( OutputDirectory::changeExtension(item->fileListItem->options.filePathName,item->correctionInputExtension) );
    if( !file.exists() ) {
        logger->log( item->logID, " " + i18n("Aborting, file does not exist") + " (" + file.name() + ")" );
        executeNextStep( item );
        return;
    }

    KURL source( file.name() );
    KURL destination( item->correctionInFile );

    item->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Getting correction file")+"... 00 %" );

    if( source.isLocalFile() && destination.isLocalFile() ) {
        item->convertProcess->clearArguments();

        *(item->convertProcess) << "cp";
        *(item->convertProcess) << source.path();
        *(item->convertProcess) << destination.path();

        logger->log( item->logID, "cp \"" + source.path() + "\" \"" + destination.path() + "\"" );

        item->convertProcess->setPriority( config->data.general.priority );
        item->convertProcess->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput );
    }
    else {
        item->moveJob = new TDEIO::FileCopyJob( source, destination, -1, false, true, false, false );
        connect( item->moveJob, TQ_SIGNAL(percent(TDEIO::Job*,unsigned long)),
                this, TQ_SLOT(moveProgress(TDEIO::Job*,unsigned long))
                );
        connect( item->moveJob, TQ_SIGNAL(result(TDEIO::Job*)),
                this, TQ_SLOT(moveFinished(TDEIO::Job*))
                );
    }
}

void Convert::rip( ConvertItem* item )
{
    logger->log( item->logID, i18n("Ripping") );
    item->state = ConvertItem::rip;

/** kaudiocreator
	TQString wavFile;
	TQString args = job->device;
	if(!args.isEmpty())
		args = TQString("?device=%1").arg(args);
	args = args+"&fileNameTemplate=Track %{number}";
	if(job->track < 10)
		wavFile = TQString("audiocd:/Wav/Track 0%1.wav%2").arg(job->track).arg(args);
	else
		wavFile = TQString("audiocd:/Wav/Track %1.wav%2").arg(job->track).arg(args);
*/

    RipperPlugin* plugin = config->getCurrentRipper();

    if( plugin == 0 ) {
        // NOTE process devices like '/dev/cdrom' - seems to be done
        // TODO implement process priority (nice level)
        TQString src;
        if( item->fileListItem->track != 0 ) {
            // TODO does it work with cds with less than 10 tracks?
            src = TQString("audiocd:/Wav/Track %1.wav?device=%2&fileNameTemplate=Track %%{number}").
                arg(TQString::number(item->fileListItem->track).rightJustify(2, '0')).
                arg(item->fileListItem->device);
        }
        else {
            // FIXME implement ripping of full cds
            src = TQString("audiocd:/Full CD/Full CD.wav?device=%1&albumTemplate=Full CD").arg(item->fileListItem->device);
            item->tracks = 1;
        }
        KURL source( src );
        KURL dest( item->tempWavFile->name() );

        item->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Ripping")+"... 00 %" );
        item->fileListItem->ripping = true;

        item->moveJob = new TDEIO::FileCopyJob( source, dest, -1, false, true, false, false );
        connect( item->moveJob, TQ_SIGNAL(percent(TDEIO::Job*,unsigned long)),
                   this, TQ_SLOT(moveProgress(TDEIO::Job*,unsigned long))
                 );
        connect( item->moveJob, TQ_SIGNAL(result(TDEIO::Job*)),
                   this, TQ_SLOT(moveFinished(TDEIO::Job*))
                 );
    }
    else {
        TQStringList params;
        TQString param, paramSplinter;


        item->convertProcess->clearArguments();

        param = TQString();
        if( !plugin->rip.param.isEmpty() ) param.append( " " + plugin->rip.param );
        if( !plugin->rip.device.isEmpty() ) param.append( " " + plugin->rip.device );
        if( !plugin->rip.overwrite.isEmpty() ) param.append( " " + plugin->rip.overwrite );

        if( item->fileListItem->track != 0 ) {
            if( !plugin->rip.track.isEmpty() ) param.append( " " + plugin->rip.track );
        }
        else {
            if( !plugin->rip.full_disc.param.isEmpty() ) param.append( " " + plugin->rip.full_disc.param );
            item->tracks = cdManager->getTrackCount( item->fileListItem->device );
            item->track = 0;
        }

//         if( plugin->rip.out_file.find("%p") != -1 ) {
//             TQString t_str = plugin->rip.out_file;
//             t_str.replace( "%p", param );
//             param = plugin->rip.bin + " " + t_str;
//         }
//         else {
//             param = plugin->rip.bin + param + " " + plugin->rip.out_file;
//         }

        TQString t_str = plugin->rip.out_file;
        t_str.replace( "%p", param );
        param = config->binaries[plugin->rip.bin] + " " + t_str;

        param.simplifyWhiteSpace();

        params = TQStringList::split( ' ', param );

        for( TQStringList::Iterator it = params.begin(); it != params.end(); ++it )
        {
            paramSplinter = *it;
            paramSplinter.replace( "%d", item->fileListItem->device );
            paramSplinter.replace( "%t", TQString().sprintf("%i",item->fileListItem->track) );
            paramSplinter.replace( "%n", TQString().sprintf("%i",cdManager->getTrackCount(item->fileListItem->device)) );
            paramSplinter.replace( "%o", item->tempWavFile->name() );
            *(item->convertProcess) << paramSplinter;
        }

        param.replace( "%d", item->fileListItem->device );
        param.replace( "%t", TQString().sprintf("%i",item->fileListItem->track) );
        param.replace( "%n", TQString().sprintf("%i",cdManager->getTrackCount(item->fileListItem->device)) );
        param.replace( "%o", "\""+item->tempWavFile->name()+"\"" );
        logger->log( item->logID, param );

        //kdDebug() << " Executing: `" << param << "'" << endl;

        //item->readOutputTimer.start();
        item->lastOutputTimer.start();

        item->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Ripping")+"... 00 %" );
        item->fileListItem->ripping = true;

        item->convertProcess->setPriority( config->data.general.priority );
        item->convertProcess->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput );
    }
}

void Convert::decode( ConvertItem* item )
{
    logger->log( item->logID, i18n("Decoding") );
    item->state = ConvertItem::decode;

    TQStringList params;
    TQString param, paramSplinter;

    item->convertProcess->clearArguments();

    ConvertPlugin* plugin = config->decoderForFormat( item->fileListItem->mimeType );
    if( plugin == 0 ) {
        logger->log( item->logID, " NULL POINTER: Convert::decode( ... ) / plugin" );
        return;
    }

    param = "";
    if( !plugin->dec.param.isEmpty() ) param.append( " " + plugin->dec.param );
    if( !plugin->dec.overwrite.isEmpty() ) param.append( " " + plugin->dec.overwrite );

    TQString t_str = plugin->dec.in_out_files;
    t_str.replace( "%p", param );
    param = config->binaries[plugin->dec.bin] + " " + t_str;

    param = param.simplifyWhiteSpace();

    params = TQStringList::split( ' ', param );

    for( TQStringList::Iterator it = params.begin(); it != params.end(); ++it )
    {
        paramSplinter = *it;
        paramSplinter.replace( "%i", item->tempInFile->name() );
        paramSplinter.replace( "%o", item->tempWavFile->name() );
        *(item->convertProcess) << paramSplinter;
    }

    param.replace( "%i", "\""+item->tempInFile->name()+"\"" );
    param.replace( "%o", "\""+item->tempWavFile->name()+"\"" );
    //item->log = param;
    logger->log( item->logID, param );

    //kdDebug() << " Executing: `" << param << "'" << endl;

    //item->readOutputTimer.start();
    item->lastOutputTimer.start();

    item->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Decoding")+"... 00 %" );

    item->convertProcess->setPriority( config->data.general.priority );
    item->convertProcess->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput );
}

void Convert::encode( ConvertItem* item )
{
    // TODO test quality profiles (never done)

    TQString sStrength;
    TQString sBitrate;
    TQString sQuality;
    TQString sMinBitrate;
    TQString sMaxBitrate;
    TQString sSamplingRate;

    int t_int;
    float t_float;

    logger->log( item->logID, i18n("Encoding") );
    item->state = ConvertItem::encode;

    TQStringList params;
    TQString param, paramSplinter;

    item->convertProcess->clearArguments();

    // NOTE use mimetype
    FormatItem* formatItem = config->getFormatItem( item->fileListItem->options.encodingOptions.sFormat );
    if( formatItem == 0 ) {
        //kdDebug() << "NULL POINTER: `" << "Convert::encode( ... ) / formatItem" << "'" << endl;
        logger->log( 1000, "NULL POINTER: `Convert::encode( ... ) / formatItem'" );
        return;
    }
    ConvertPlugin* plugin = formatItem->encoder;
    if( plugin == 0 ) {
        //kdDebug() << "NULL POINTER: `" << "Convert::encode( ... ) / plugin" << "'" << endl;
        logger->log( 1000, "NULL POINTER: `Convert::encode( ... ) / plugin'" );
        return;
    }

//     item->binary = plugin->enc.bin;

    param = "";
    if( !plugin->enc.param.isEmpty() ) param.append( " " + plugin->enc.param );
    if( !plugin->enc.overwrite.isEmpty() ) param.append( " " + plugin->enc.overwrite );

    if( plugin->enc.strength.enabled ) {
        param.append( " " + plugin->enc.strength.param );
        int compressionLevel = formatItem->compressionLevel;

        if( plugin->enc.strength.profiles.empty() ) {
            if( plugin->enc.strength.step < 1 ) {
                if( plugin->enc.strength.range_max >= plugin->enc.strength.range_min )
                    sStrength = TQString::number( compressionLevel * plugin->enc.strength.step );
                else
                    sStrength = TQString::number( plugin->enc.strength.range_min - compressionLevel * plugin->enc.strength.step );
            }
            else {
                if( plugin->enc.strength.range_max >= plugin->enc.strength.range_min )
                    sStrength = TQString::number( int(compressionLevel * plugin->enc.strength.step) );
                else
                    sStrength = TQString::number( int(plugin->enc.strength.range_min - compressionLevel * plugin->enc.strength.step) );
            }
            if( plugin->enc.strength.separator != '.' ) sStrength.replace( TQChar('.'), plugin->enc.strength.separator );
        }
        else {
            TQStringList::Iterator it = plugin->enc.strength.profiles.at( compressionLevel );
            sStrength = *it;
        }
    }

    if( item->fileListItem->options.encodingOptions.sQualityMode == i18n("Bitrate") ) {
        if( item->fileListItem->options.encodingOptions.sBitrateMode == "cbr" && plugin->enc.lossy.bitrate.cbr.enabled ) {
            param.append( " " + plugin->enc.lossy.bitrate.cbr.param );
            sBitrate = TQString::number( item->fileListItem->options.encodingOptions.iQuality );
        }
        else if( item->fileListItem->options.encodingOptions.sBitrateMode == "abr" && plugin->enc.lossy.bitrate.abr.enabled ) {
            param.append( " " + plugin->enc.lossy.bitrate.abr.param );
            sBitrate = TQString::number( item->fileListItem->options.encodingOptions.iQuality );
            if( item->fileListItem->options.encodingOptions.bBitrateRange && plugin->enc.lossy.bitrate.abr.bitrate_range.enabled ) {
                param.append( " " + plugin->enc.lossy.bitrate.abr.bitrate_range.param_min );
                sMinBitrate = TQString::number( item->fileListItem->options.encodingOptions.iMinBitrate );
                param.append( " " + plugin->enc.lossy.bitrate.abr.bitrate_range.param_max );
                sMaxBitrate = TQString::number( item->fileListItem->options.encodingOptions.iMaxBitrate );
            }
        }
    }
    else if( item->fileListItem->options.encodingOptions.sQualityMode == i18n("Quality") && plugin->enc.lossy.quality.enabled ) {
        param.append( " " + plugin->enc.lossy.quality.param );
        if( plugin->enc.lossy.quality.profiles.empty() ) {
            if( plugin->enc.lossy.quality.step < 1 ) {
                if( plugin->enc.lossy.quality.range_max >= plugin->enc.lossy.quality.range_min)
                    t_float = ( (float)item->fileListItem->options.encodingOptions.iQuality * ( plugin->enc.lossy.quality.range_max - plugin->enc.lossy.quality.range_min ) / 100 ) + plugin->enc.lossy.quality.range_min;
                else
                    t_float = ( (100.0f - (float)item->fileListItem->options.encodingOptions.iQuality) * ( plugin->enc.lossy.quality.range_min - plugin->enc.lossy.quality.range_max ) / 100 ) + plugin->enc.lossy.quality.range_max;
                //t_float -= t_float%plugin->enc.quality.step;
                //sQuality = TQString().sprintf( "%.2f", t_float );
                sQuality = TQString::number( t_float );
            }
            else {
                if( plugin->enc.lossy.quality.range_max >= plugin->enc.lossy.quality.range_min)
                    t_int = ( item->fileListItem->options.encodingOptions.iQuality * (int)( plugin->enc.lossy.quality.range_max - plugin->enc.lossy.quality.range_min ) / 100) + (int)plugin->enc.lossy.quality.range_min;
                else
                    t_int = ( (100 - item->fileListItem->options.encodingOptions.iQuality) * (int)( plugin->enc.lossy.quality.range_min - plugin->enc.lossy.quality.range_max ) / 100) + (int)plugin->enc.lossy.quality.range_max;
                //t_int -= t_int%plugin->enc.quality.step;
                sQuality = TQString::number( t_int );
            }
            if( plugin->enc.bin == "oggenc" ) sQuality.replace(TQChar('.'),TDEGlobal::locale()->decimalSymbol()); // HACK make oggenc usable with all langauges
            else if( plugin->enc.lossy.quality.separator != '.' ) sQuality.replace(TQChar('.'),plugin->enc.lossy.quality.separator);
        }
        else {
            TQStringList::Iterator it = plugin->enc.lossy.quality.profiles.at( rint(item->fileListItem->options.encodingOptions.iQuality*plugin->enc.lossy.quality.range_max/100) );
            sQuality = *it;
        }
    }
    else if( item->fileListItem->options.encodingOptions.sQualityMode == i18n("Lossless") && plugin->enc.lossless.enabled ) {
        param.append( " " + plugin->enc.lossless.param );
    }
    else if( item->fileListItem->options.encodingOptions.sQualityMode == i18n("Hybrid") && plugin->enc.hybrid.enabled ) {
        param.append( " " + plugin->enc.hybrid.param );
        sBitrate = TQString::number( item->fileListItem->options.encodingOptions.iQuality );
    }

    if( item->fileListItem->options.encodingOptions.samplingRate.bEnabled && plugin->enc.lossy.samplingrate.enabled ) {
        param.append( " " + plugin->enc.lossy.samplingrate.param );
        if( plugin->enc.lossy.samplingrate.unit == PluginLoaderBase::Hz ) {
            sSamplingRate = TQString::number( item->fileListItem->options.encodingOptions.samplingRate.iSamplingRate );
        }
        else {
            sSamplingRate = TQString::number( (float)item->fileListItem->options.encodingOptions.samplingRate.iSamplingRate/1000 );
        }
    }

    if( item->fileListItem->options.encodingOptions.channels.bEnabled ) {
        if( item->fileListItem->options.encodingOptions.channels.sChannels == i18n("Mono") && plugin->enc.lossy.channels.mono_enabled ) {
            param.append( " " + plugin->enc.lossy.channels.mono_param );
        }
        else if( item->fileListItem->options.encodingOptions.channels.sChannels == i18n("Stereo") && plugin->enc.lossy.channels.stereo_enabled ) {
            param.append( " " + plugin->enc.lossy.channels.stereo_param );
        }
        else if( item->fileListItem->options.encodingOptions.channels.sChannels == i18n("Joint-Stereo") && plugin->enc.lossy.channels.joint_stereo_enabled ) {
            param.append( " " + plugin->enc.lossy.channels.joint_stereo_param );
        }
        else if( item->fileListItem->options.encodingOptions.channels.sChannels == i18n("Forced Joint-Stereo") && plugin->enc.lossy.channels.forced_joint_stereo_enabled ) {
            param.append( " " + plugin->enc.lossy.channels.forced_joint_stereo_param );
        }
        else if( item->fileListItem->options.encodingOptions.channels.sChannels == i18n("Dual Channels") && plugin->enc.lossy.channels.dual_channels_enabled ) {
            param.append( " " + plugin->enc.lossy.channels.dual_channels_param );
        }
    }

    if( item->fileListItem->options.encodingOptions.replaygain.bEnabled && plugin->enc.replaygain.enabled && !plugin->enc.replaygain.use.isEmpty() && formatItem->internalReplayGain ) {
        param.append( " " + plugin->enc.replaygain.use );
    }
    else if( plugin->enc.replaygain.enabled && !plugin->enc.replaygain.avoid.isEmpty() ) {
        param.append( " " + plugin->enc.replaygain.avoid );
    }

//     if( !tagEngine->canWrite(item->fileListItem->options.encodingOptions.sFormat) && item->fileListItem->tags && plugin->enc.tag.enabled ) {
    if( item->fileListItem->tags && plugin->enc.tag.enabled && item->fileListItem->options.encodingOptions.sFormat != "aac" ) { // HACK don't write metadata to aac
        if( !plugin->enc.tag.param.isEmpty() ) param.append( " " + plugin->enc.tag.param );
        if( !plugin->enc.tag.artist.isEmpty() && !item->fileListItem->tags->artist.isEmpty() ) param.append( " " + plugin->enc.tag.artist );
        if( !plugin->enc.tag.album.isEmpty() && !item->fileListItem->tags->album.isEmpty() ) param.append( " " + plugin->enc.tag.album );
        if( !plugin->enc.tag.comment.isEmpty() && !item->fileListItem->tags->comment.isEmpty() ) param.append( " " + plugin->enc.tag.comment );
        if( !plugin->enc.tag.disc.isEmpty() && item->fileListItem->tags->disc != 0 ) param.append( " " + plugin->enc.tag.disc );
        if( !plugin->enc.tag.genre.isEmpty() && !item->fileListItem->tags->genre.isEmpty() ) param.append( " " + plugin->enc.tag.genre );
        if( !plugin->enc.tag.track.isEmpty() && item->fileListItem->tags->track != 0 ) param.append( " " + plugin->enc.tag.track );
        if( !plugin->enc.tag.composer.isEmpty() && !item->fileListItem->tags->composer.isEmpty() ) param.append( " " + plugin->enc.tag.composer );
        if( !plugin->enc.tag.title.isEmpty() && !item->fileListItem->tags->title.isEmpty() ) param.append( " " + plugin->enc.tag.title );
        if( !plugin->enc.tag.year.isEmpty() && item->fileListItem->tags->year != 0 ) param.append( " " + plugin->enc.tag.year );
    }

    TQString sInOutFiles = item->fileListItem->options.encodingOptions.sInOutFiles;
    param = sInOutFiles.replace( "%p", param );

    // cosmetic surgery
    param = param.simplifyWhiteSpace();

    params = TQStringList::split( ' ', param );

    TQString inputFile;
    if( item->mode & ConvertItem::decode || item->mode & ConvertItem::rip ) inputFile = item->tempWavFile->name();
    else inputFile = item->tempInFile->name();

    for( TQStringList::Iterator it = params.begin(); it != params.end(); ++it )
    {
        paramSplinter = *it;
        paramSplinter.replace( "%i", inputFile );
        paramSplinter.replace( "%o", item->tempOutFile->name() );
        paramSplinter.replace( "%c", sStrength );
        paramSplinter.replace( "%b", sBitrate );
        paramSplinter.replace( "%q", sQuality );
        paramSplinter.replace( "%m", sMinBitrate );
        paramSplinter.replace( "%M", sMaxBitrate );
        paramSplinter.replace( "%s", sSamplingRate );

        if( item->fileListItem->tags ) {
            paramSplinter.replace( "%ta", ( item->fileListItem->tags->artist != "" ) ? item->fileListItem->tags->artist : i18n("Unknown") );
            paramSplinter.replace( "%tb", ( item->fileListItem->tags->album != "" ) ? item->fileListItem->tags->album : i18n("Unknown") );
            paramSplinter.replace( "%tc", ( item->fileListItem->tags->comment != "" ) ? item->fileListItem->tags->comment : i18n("Unknown") );
            paramSplinter.replace( "%td", ( TQString::number(item->fileListItem->tags->disc) != "" ) ? TQString::number(item->fileListItem->tags->disc) : "0" );
            paramSplinter.replace( "%tg", ( item->fileListItem->tags->genre != "" ) ? item->fileListItem->tags->genre : i18n("Unknown") );
            paramSplinter.replace( "%tn", ( TQString::number(item->fileListItem->tags->track) != "" ) ? TQString::number(item->fileListItem->tags->track) : "0" );
            paramSplinter.replace( "%tp", ( item->fileListItem->tags->composer != "" ) ? item->fileListItem->tags->composer : i18n("Unknown") );
            paramSplinter.replace( "%tt", ( item->fileListItem->tags->title != "" ) ? item->fileListItem->tags->title : i18n("Unknown") );
            paramSplinter.replace( "%ty", ( TQString::number(item->fileListItem->tags->year) != "" ) ? TQString::number(item->fileListItem->tags->year) : "0" );
        }

        if( paramSplinter != "" && paramSplinter != " " ) *(item->convertProcess) << paramSplinter; // NOTE fixes wavpack encoding
    }

    param.replace( "%i", "\""+inputFile+"\"" );
    param.replace( "%o", "\""+item->tempOutFile->name()+"\"" );
    param.replace( "%c", sStrength );
    param.replace( "%b", sBitrate );
    param.replace( "%q", sQuality );
    param.replace( "%m", sMinBitrate );
    param.replace( "%M", sMaxBitrate );
    param.replace( "%s", sSamplingRate );

    if( item->fileListItem->tags ) {
        param.replace( "%ta", "\""+item->fileListItem->tags->artist+"\"" );
        param.replace( "%tb", "\""+item->fileListItem->tags->album+"\"" );
        param.replace( "%tc", "\""+item->fileListItem->tags->comment+"\"" );
        param.replace( "%td", TQString::number(item->fileListItem->tags->disc) );
        param.replace( "%tg", "\""+item->fileListItem->tags->genre+"\"" );
        param.replace( "%tn", TQString::number(item->fileListItem->tags->track) );
        param.replace( "%tp", "\""+item->fileListItem->tags->composer+"\"" );
        param.replace( "%tt", "\""+item->fileListItem->tags->title+"\"" );
        param.replace( "%ty", TQString::number(item->fileListItem->tags->year) );
    }

    logger->log( item->logID, param );

    //kdDebug() << " Executing: `" << param << "'" << endl;

    //item->readOutputTimer.start();
    item->lastOutputTimer.start();

    item->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Encoding")+"... 00 %" );

    item->convertProcess->setPriority( config->data.general.priority );
    item->convertProcess->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput );
}

void Convert::replaygain( ConvertItem* item )
{
    logger->log( item->logID, i18n("Applying Replay Gain") );
    item->state = ConvertItem::replaygain;

    FormatItem* formatItem = config->getFormatItem( item->fileListItem->options.encodingOptions.sFormat );
    if( formatItem == 0 ) {
        logger->log( item->logID, " NULL POINTER: Convert::replaygain( ... ) / formatItem" );
        return;
    }
    ConvertPlugin* plugin = formatItem->encoder;
    if( plugin == 0 ) {
        logger->log( item->logID, " NULL POINTER: Convert::replaygain( ... ) / plugin" );
        return;
    }

    if( plugin->enc.replaygain.enabled && formatItem->internalReplayGain ) {
        executeNextStep( item );
        return;
    }

    item->replayGain = new ReplayGain( config, logger );
    bool ret = item->replayGain->apply( item->tempOutFile->name(), item->fileListItem->options.encodingOptions.sFormat, item->convertProcess, item->logID );

    if( !ret ) {
        executeNextStep( item );
        return;
    }

    //item->readOutputTimer.start();
    item->lastOutputTimer.start();

    item->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Replay Gain")+"... 00 %" );
}

void Convert::writeTags( ConvertItem* item )
{
    logger->log( item->logID, i18n("Writing tags") );
    item->state = ConvertItem::write_tags;

    if( item->mode & ConvertItem::encode ) {
        tagEngine->writeTags( item->tempOutFile->name(), item->fileListItem->tags );
        item->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Writing tags")+"... 00 %" );
    }

    executeNextStep( item );
}

void Convert::put( ConvertItem* item )
{
    logger->log( item->logID, i18n("Moving file") );
    item->state = ConvertItem::put;

    TQString src;
    if( item->mode & ConvertItem::encode ) src = item->tempOutFile->name();
    else src = item->tempWavFile->name();
    item->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Moving file")+"... 00 %" );

    item->outputFilePathName = OutputDirectory::makePath( OutputDirectory::uniqueFileName(OutputDirectory::calcPath(item->fileListItem,config)) ).replace("%2f","%252f");

    KURL source( src );
    KURL destination( item->outputFilePathName );

    if( source.isLocalFile() && destination.isLocalFile() ) {
        item->convertProcess->clearArguments();

        *(item->convertProcess) << "cp";
        *(item->convertProcess) << source.path();
        *(item->convertProcess) << destination.path();

        logger->log( item->logID, "cp \"" + source.path() + "\" \"" + destination.path() + "\"" );

        item->convertProcess->setPriority( config->data.general.priority );
        item->convertProcess->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput );
    }
    else {
        item->moveJob = new TDEIO::FileCopyJob( source, destination, -1, false, false, false, false );
        connect( item->moveJob, TQ_SIGNAL(percent(TDEIO::Job*,unsigned long)),
                this, TQ_SLOT(moveProgress(TDEIO::Job*,unsigned long))
                );
        connect( item->moveJob, TQ_SIGNAL(result(TDEIO::Job*)),
                this, TQ_SLOT(moveFinished(TDEIO::Job*))
                );
    }
}

void Convert::putCorrection( ConvertItem* item )
{
    logger->log( item->logID, i18n("Moving correction file") );
    item->state = ConvertItem::put_correction;

    TQString src = item->correctionOutFile;

    TQString dest = OutputDirectory::makePath( OutputDirectory::calcPath(item->fileListItem,config,item->correctionOutputExtension) ).replace("%2f","%252f");

    KURL source( src );
//     KURL destination( dest );
    KURL destination;
    destination.setPath( dest );

    item->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Moving correction file")+"... 00 %" );

    if( source.isLocalFile() && destination.isLocalFile() ) {
        item->convertProcess->clearArguments();

        *(item->convertProcess) << "cp";
        *(item->convertProcess) << source.path();
        *(item->convertProcess) << destination.path();

        logger->log( item->logID, "cp \"" + source.path() + "\" \"" + destination.path() + "\"" );

        item->convertProcess->setPriority( config->data.general.priority );
        item->convertProcess->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput );
    }
    else {
        item->moveJob = new TDEIO::FileCopyJob( source, destination, -1, false, false, false, false );
        connect( item->moveJob, TQ_SIGNAL(percent(TDEIO::Job*,unsigned long)),
                this, TQ_SLOT(moveProgress(TDEIO::Job*,unsigned long))
                );
        connect( item->moveJob, TQ_SIGNAL(result(TDEIO::Job*)),
                this, TQ_SLOT(moveFinished(TDEIO::Job*))
                );
    }
}

void Convert::executeUserScript( ConvertItem* item )
{
    logger->log( item->logID, i18n("Running user script") );
    item->state = ConvertItem::execute_userscript;

    KURL source( item->fileListItem->options.filePathName );
    KURL destination( item->outputFilePathName );

    item->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Running user script")+"... 00 %" );

    item->convertProcess->clearArguments();

    TQString userscript = locate( "data", "soundkonverter/userscript.sh" );
    if( userscript == "" ) executeNextStep( item );

    *(item->convertProcess) << userscript;
    *(item->convertProcess) << source.path();
    *(item->convertProcess) << destination.path();

    logger->log( item->logID, userscript + " \"" + source.path() + "\" \"" + destination.path() + "\"" );

    item->convertProcess->setPriority( config->data.general.priority );
    item->convertProcess->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput );
}

void Convert::executeNextStep( ConvertItem* item )
{
    logger->log( item->logID, i18n("Executing next step") );

    item->percent = 0;
    item->lastPercent = 0; // used for ripping a whole cd to one file

    switch( item->state )
    {
        case ConvertItem::get:
        {
            if( item->mode & ConvertItem::get_correction ) getCorrection( item );
            else if( item->mode & ConvertItem::rip ) rip( item );
            else if( item->mode & ConvertItem::decode ) decode( item );
            else if( item->mode & ConvertItem::encode ) encode( item );
            else if( item->mode & ConvertItem::replaygain ) replaygain( item );
            else if( item->mode & ConvertItem::write_tags ) writeTags( item );
            else if( item->mode & ConvertItem::put ) put( item );
            else if( item->mode & ConvertItem::put_correction ) putCorrection( item );
            else if( config->data.general.executeUserScript ) executeUserScript( item );
            else remove( item );
            break;
        }
        case ConvertItem::get_correction:
        {
            if( item->mode & ConvertItem::rip ) rip( item );
            else if( item->mode & ConvertItem::decode ) decode( item );
            else if( item->mode & ConvertItem::encode ) encode( item );
            else if( item->mode & ConvertItem::replaygain ) replaygain( item );
            else if( item->mode & ConvertItem::write_tags ) writeTags( item );
            else if( item->mode & ConvertItem::put ) put( item );
            else if( item->mode & ConvertItem::put_correction ) putCorrection( item );
            else if( config->data.general.executeUserScript ) executeUserScript( item );
            else remove( item );
            break;
        }
        case ConvertItem::rip:
        {
            if( item->mode & ConvertItem::decode ) decode( item );
            else if( item->mode & ConvertItem::encode ) encode( item );
            else if( item->mode & ConvertItem::replaygain ) replaygain( item );
            else if( item->mode & ConvertItem::write_tags ) writeTags( item );
            else if( item->mode & ConvertItem::put ) put( item );
            else if( item->mode & ConvertItem::put_correction ) putCorrection( item );
            else if( config->data.general.executeUserScript ) executeUserScript( item );
            else remove( item );
            break;
        }
        case ConvertItem::decode:
        {
            if( item->mode & ConvertItem::encode ) encode( item );
            else if( item->mode & ConvertItem::replaygain ) replaygain( item );
            else if( item->mode & ConvertItem::write_tags ) writeTags( item );
            else if( item->mode & ConvertItem::put ) put( item );
            else if( item->mode & ConvertItem::put_correction ) putCorrection( item );
            else if( config->data.general.executeUserScript ) executeUserScript( item );
            else remove( item );
            break;
        }
        case ConvertItem::encode:
        {
            if( item->mode & ConvertItem::replaygain ) replaygain( item );
            else if( item->mode & ConvertItem::write_tags ) writeTags( item );
            else if( item->mode & ConvertItem::put ) put( item );
            else if( item->mode & ConvertItem::put_correction ) putCorrection( item );
            else if( config->data.general.executeUserScript ) executeUserScript( item );
            else remove( item );
            break;
        }
        case ConvertItem::replaygain:
        {
            if( item->mode & ConvertItem::write_tags ) writeTags( item );
            else if( item->mode & ConvertItem::put ) put( item );
            else if( item->mode & ConvertItem::put_correction ) putCorrection( item );
            else if( config->data.general.executeUserScript ) executeUserScript( item );
            else remove( item );
            break;
        }
        case ConvertItem::write_tags:
        {
            if( item->mode & ConvertItem::put ) put( item );
            else if( item->mode & ConvertItem::put_correction ) putCorrection( item );
            else if( config->data.general.executeUserScript ) executeUserScript( item );
            else remove( item );
            break;
        }
        case ConvertItem::put:
        {
            if( item->mode & ConvertItem::put_correction ) putCorrection( item );
            else if( config->data.general.executeUserScript ) executeUserScript( item );
            else remove( item );
            break;
        }
        case ConvertItem::put_correction:
        {
            if( config->data.general.executeUserScript ) executeUserScript( item );
            else remove( item );
            break;
        }
        case ConvertItem::execute_userscript:
        {
            remove( item );
            break;
        }
        default: // case (ConvertItem::Mode)0x0000:
        {
            if( item->mode & ConvertItem::get ) get( item );
            else if( item->mode & ConvertItem::get_correction ) getCorrection( item );
            else if( item->mode & ConvertItem::rip ) rip( item );
            else if( item->mode & ConvertItem::decode ) decode( item );
            else if( item->mode & ConvertItem::encode ) encode( item );
            else if( item->mode & ConvertItem::replaygain ) replaygain( item );
            else if( item->mode & ConvertItem::write_tags ) writeTags( item );
            else if( item->mode & ConvertItem::put ) put( item );
            else if( item->mode & ConvertItem::put_correction ) putCorrection( item );
            else if( config->data.general.executeUserScript ) executeUserScript( item );
            else remove( item );
            break;
        }
    }
}

void Convert::moveProgress( TDEIO::Job* job, unsigned long percent )
{
    // search the item list for our item
    for( TQValueList<ConvertItem*>::Iterator item = items.begin(); item != items.end(); item++ ) {
        if( (*item)->moveJob == job ) {
            (*item)->percent = percent;
        }
    }
}

void Convert::moveFinished( TDEIO::Job* job )
{
    // search the item list for our item
    for( TQValueList<ConvertItem*>::Iterator item = items.begin(); item != items.end(); item++ ) {
        if( (*item)->moveJob == job ) {

            (*item)->percent = 0;
            if( (*item)->state == ConvertItem::get ) {
                if( job->error() != 0 ) {
                    remove( *item, 1 );
                    updateProgressIndicator();
                    return;
                }

                logger->log( (*item)->logID, " " + i18n("Got file") );

                // if file is remote or the tag reding failed previously, read the tags now
                if( (*item)->fileListItem->tags == 0 ) {
                    (*item)->fileListItem->tags = tagEngine->readTags( (*item)->tempInFile->name() );
                }

                emit countTime( (*item)->getTime );
            }
            else if( (*item)->state == ConvertItem::get_correction ) {
                if( job->error() != 0 ) {
                    emit uncountTime( (*item)->getTime );
                    remove( *item, 1 );
                    updateProgressIndicator();
                    return;
                }

                logger->log( (*item)->logID, " " + i18n("Got file") );
                emit countTime( (*item)->getCorrectionTime );
            }
            else if( (*item)->state == ConvertItem::rip ) {
                if( job->error() != 0 ) {
                    remove( *item, 1 );
                    updateProgressIndicator();
                    return;
                }

                //logger->log( (*item)->logID, " " + i18n("Ripped track") );
                (*item)->fileListItem->ripping = false;
                emit countTime( (*item)->ripTime );
                emit rippingFinished( (*item)->fileListItem->device );
            }
            else if( (*item)->state == ConvertItem::put ) {
                if( job->error() != 0 ) {
                    logger->log( (*item)->logID, i18n("Could not write to file: `%1'").arg(OutputDirectory::calcPath((*item)->fileListItem,config)) );
                    emit uncountTime( (*item)->getTime );
                    emit uncountTime( (*item)->getCorrectionTime );
                    emit uncountTime( (*item)->ripTime );
                    emit uncountTime( (*item)->decodeTime );
                    emit uncountTime( (*item)->encodeTime );
                    emit uncountTime( (*item)->replaygainTime );
                    remove( *item, 1 );
                    updateProgressIndicator();
                    return;
                }

                logger->log( (*item)->logID, " " + i18n("File moved") );
            }
            else if( (*item)->state == ConvertItem::put_correction ) {
                if( job->error() != 0 ) {
                    emit uncountTime( (*item)->getTime );
                    emit uncountTime( (*item)->getCorrectionTime );
                    emit uncountTime( (*item)->ripTime );
                    emit uncountTime( (*item)->decodeTime );
                    emit uncountTime( (*item)->encodeTime );
                    emit uncountTime( (*item)->replaygainTime );
                    remove( *item, 1 );
                    updateProgressIndicator();
                    return;
                }

                logger->log( (*item)->logID, " " + i18n("File moved") );
            }

            executeNextStep( *item );
            return;
        }
    }
}

void Convert::processOutput( TDEProcess* proc, char* data, int )
{
    int iPercent = 0, iTime = 0, iPos = 0, iNum = 0;

    // search the item list for our item
    for( TQValueList<ConvertItem*>::Iterator item = items.begin(); item != items.end(); item++ )
    {
        if( (*item)->convertProcess == proc )
        {
            TQString log_data = data;
/*            log_data.replace("\n","\\n");
            log_data.replace("\t","\\t");
            log_data.replace("\r","\\r");
            log_data.replace("\b","\\b");*/
            logger->log( (*item)->logID, " " + i18n("Output") + ": " + log_data );

            //if( (*item)->readOutputTimer.elapsed() < /*config->pauseTime*/ 100 ) return; // TODO use config value
            //(*item)->readOutputTimer.start();

            if( (*item)->state == ConvertItem::decode )
            {
                if( (*item)->fileListItem == 0 )  return;

                ConvertPlugin* plugin = config->decoderForFormat( (*item)->fileListItem->mimeType );
                // TODO null pointer check

                TQString outputPattern = plugin->dec.output;
                //outputPattern.replace( "%i", "%p" ); // for compatibility with old plugins

                if( outputPattern.find("%p") != -1 ) {
                     outputPattern.replace( "%p", "%i" );
                     sscanf( data, outputPattern.local8Bit().data(), &iPercent );
                }
                else if( outputPattern.find("%t") != -1 ) {
                     outputPattern.replace( "%t", "%i" );
                     sscanf( data, outputPattern.local8Bit().data(), &iTime );
                     iPercent = iTime * 100 / (*item)->fileListItem->time;
                }
                else if( outputPattern.find("%0") != -1 && outputPattern.find("%1") != -1 ) {
                     if( outputPattern.find("%0") < outputPattern.find("%1") ) {
                         outputPattern.replace( "%0", "%i" );
                         outputPattern.replace( "%1", "%i" );
                         sscanf( data, outputPattern.local8Bit().data(), &iPos, &iNum );
                     }
                     else {
                         outputPattern.replace( "%0", "%i" );
                         outputPattern.replace( "%1", "%i" );
                         sscanf( data, outputPattern.local8Bit().data(), &iNum, &iPos );
                     }
                     if( iPos != 0 && iNum != 0 ) iPercent = iPos * 100 / iNum;
                }

                if( iPercent > 0 && iPercent <= 100 )
                {
                    // TODO guess progress, when no signal is received
                    (*item)->lastOutputTimer.start();
                    (*item)->percent = iPercent;
                }
            }
            else if( (*item)->state == ConvertItem::encode )
            {
                if( (*item)->fileListItem == 0 )  return;

                // NOTE use mimetype
                ConvertPlugin* plugin = config->encoderForFormat( (*item)->fileListItem->options.encodingOptions.sFormat );
                // TODO null pointer check

                TQString outputPattern;
                if( (*item)->fileListItem->options.encodingOptions.sQualityMode == i18n("Quality") ) outputPattern = plugin->enc.lossy.quality.output;
                else if( (*item)->fileListItem->options.encodingOptions.sQualityMode == i18n("Bitrate") && (*item)->fileListItem->options.encodingOptions.sBitrateMode == "cbr" ) outputPattern = plugin->enc.lossy.bitrate.cbr.output;
                else if( (*item)->fileListItem->options.encodingOptions.sQualityMode == i18n("Bitrate") && (*item)->fileListItem->options.encodingOptions.sBitrateMode == "abr" ) outputPattern = plugin->enc.lossy.bitrate.abr.output;

                //outputPattern.replace( "%i", "%p" ); // for compatibility with old plugins

                if( outputPattern.find("%p") != -1 ) {
                     outputPattern.replace( "%p", "%i" );
                     sscanf( data, outputPattern.local8Bit().data(), &iPercent );
                }
                else if( outputPattern.find("%t") != -1 ) {
                     outputPattern.replace( "%t", "%i" );
                     sscanf( data, outputPattern.local8Bit().data(), &iTime );
                     iPercent = iTime * 100 / (*item)->fileListItem->time;
                }
                else if( outputPattern.find("%0") != -1 && outputPattern.find("%1") != -1 ) {
                     if( outputPattern.find("%0") < outputPattern.find("%1") ) {
                         outputPattern.replace( "%0", "%i" );
                         outputPattern.replace( "%1", "%i" );
                         sscanf( data, outputPattern.local8Bit().data(), &iPos, &iNum );
                     }
                     else {
                         outputPattern.replace( "%0", "%i" );
                         outputPattern.replace( "%1", "%i" );
                         sscanf( data, outputPattern.local8Bit().data(), &iNum, &iPos );
                     }
                     if( iPos != 0 && iNum != 0 ) iPercent = iPos * 100 / iNum;
                }

                if( iPercent > 0 && iPercent <= 100 )
                {
                    // TODO guess progress, when no signal is received
                    (*item)->lastOutputTimer.start();
                    (*item)->percent = iPercent;
                }
            }
            else if( (*item)->state == ConvertItem::rip ) // ### soundkonverter 0.4 make the progress dependent on the track length
            {
                if( (*item)->fileListItem == 0 )  return;

                RipperPlugin* plugin = config->getCurrentRipper();
                // TODO null pointer check

                TQString outputPattern;
                if( (*item)->fileListItem->track != 0 ) outputPattern = plugin->rip.output;
                else outputPattern = plugin->rip.full_disc.output;
                //outputPattern.replace( "%i", "%p" ); // for compatibility with old plugins

                if( outputPattern.find("%p") != -1 || outputPattern.find("%a") != -1 ) {
                     outputPattern.replace( "%p", "%i" );
                     outputPattern.replace( "%a", "%i" );
                     sscanf( data, outputPattern.local8Bit().data(), &iPercent );
                }
                else if( outputPattern.find("%t") != -1 ) {
                     outputPattern.replace( "%t", "%i" );
                     sscanf( data, outputPattern.local8Bit().data(), &iTime );
                     iPercent = iTime * 100 / (*item)->fileListItem->time;
                }
                else if( outputPattern.find("%0") != -1 && outputPattern.find("%1") != -1 ) {
                     if( outputPattern.find("%0") < outputPattern.find("%1") ) {
                         outputPattern.replace( "%0", "%i" );
                         outputPattern.replace( "%1", "%i" );
                         sscanf( data, outputPattern.local8Bit().data(), &iPos, &iNum );
                     }
                     else {
                         outputPattern.replace( "%0", "%i" );
                         outputPattern.replace( "%1", "%i" );
                         sscanf( data, outputPattern.local8Bit().data(), &iNum, &iPos );
                     }
                     if( iPos != 0 && iNum != 0 ) iPercent = iPos * 100 / iNum;
                }

                if( iPercent > 0 && iPercent <= 100 )
                {
                    // TODO guess progress, when no signal is received
                    (*item)->lastOutputTimer.start();
                    if( (*item)->fileListItem->track == 0 && plugin->rip.full_disc.output.find("%a") != -1 ) {
                        if( iPercent < (*item)->lastPercent ) (*item)->track++;
                        (*item)->lastPercent = iPercent;
                        (*item)->percent = (*item)->track * 100 / (*item)->tracks + iPercent / (*item)->tracks;
                    }
                    else {
                        (*item)->percent = iPercent;
                    }
                }
            }
            return;
        }
    }
}

void Convert::processExit( TDEProcess* proc )
{
    // search the item list for our item
    for( TQValueList<ConvertItem*>::Iterator item = items.begin(); item != items.end(); item++ ) {
//        if( (*item)->convertProcess == proc && (*item)->fileListItem != 0 ) {
        if( (*item)->convertProcess == proc ) {

            (*item)->percent = 0;
            if( (*item)->state == ConvertItem::rip ) {
                if( proc->signalled() ) { // FIXME does only check, whether the process has been killed
                    remove( *item, 1 );
                    updateProgressIndicator();
                    return;
                }
                else if( !proc->normalExit() ) {
                    remove( *item, -1 );
                    updateProgressIndicator();
                    return;
                }
                else {
                    (*item)->fileListItem->ripping = false;
                    emit countTime( (*item)->ripTime );
                    emit rippingFinished( (*item)->fileListItem->device );
                }
            }
            if( (*item)->state == ConvertItem::get ) {
                if( proc->signalled() ) {
                    remove( *item, 1 );
                    updateProgressIndicator();
                    return;
                }
                else if( !proc->normalExit() ) {
                    remove( *item, -1 );
                    updateProgressIndicator();
                    return;
                }
                else {
                    logger->log( (*item)->logID, " " + i18n("Got file") );

                    // if file is remote or the tag reding failed previously, read the tags now
                    if( (*item)->fileListItem->tags == 0 ) {
                        (*item)->fileListItem->tags = tagEngine->readTags( (*item)->tempInFile->name() );
                    }

                    emit countTime( (*item)->getTime );
                }
            }
            if( (*item)->state == ConvertItem::get_correction ) {
                if( proc->signalled() ) {
                    emit uncountTime( (*item)->getTime );
                    remove( *item, 1 );
                    updateProgressIndicator();
                    return;
                }
                else if( !proc->normalExit() ) {
                    emit uncountTime( (*item)->getTime );
                    remove( *item, -1 );
                    updateProgressIndicator();
                    return;
                }
                else {
                    logger->log( (*item)->logID, " " + i18n("Got file") );
                    emit countTime( (*item)->getCorrectionTime );
                }
            }
            if( (*item)->state == ConvertItem::decode ) {
                if( proc->signalled() ) {
                    emit uncountTime( (*item)->getTime );
                    emit uncountTime( (*item)->getCorrectionTime );
                    remove( *item, 1 );
                    updateProgressIndicator();
                    return;
                }
                else if( !proc->normalExit() ) {
                    emit uncountTime( (*item)->getTime );
                    emit uncountTime( (*item)->getCorrectionTime );
                    remove( *item, -1 );
                    updateProgressIndicator();
                    return;
                }
                else {
                    emit countTime( (*item)->decodeTime );
                }
            }
            if( (*item)->state == ConvertItem::encode ) {
                if( proc->signalled() ) {
                    emit uncountTime( (*item)->getTime );
                    emit uncountTime( (*item)->getCorrectionTime );
                    emit uncountTime( (*item)->ripTime );
                    emit uncountTime( (*item)->decodeTime );
                    remove( *item, 1 );
                    updateProgressIndicator();
                    return;
                }
                else if( !proc->normalExit() ) {
                    emit uncountTime( (*item)->getTime );
                    emit uncountTime( (*item)->getCorrectionTime );
                    emit uncountTime( (*item)->ripTime );
                    emit uncountTime( (*item)->decodeTime );
                    remove( *item, -1 );
                    updateProgressIndicator();
                    return;
                }
                else {
/*                    if( (*item)->binary == "faac" ) {
                        emit uncountTime( (*item)->getTime );
                        emit uncountTime( (*item)->getCorrectionTime );
                        emit uncountTime( (*item)->ripTime );
                        emit uncountTime( (*item)->decodeTime );
                        remove( *item, 1 );
                        updateProgressIndicator();
                        return;
                    }*/
                    emit countTime( (*item)->encodeTime );
                }
            }
            if( (*item)->state == ConvertItem::put ) {
                if( proc->signalled() ) {
                    logger->log( (*item)->logID, i18n("Could not write to file: `%1'").arg(OutputDirectory::calcPath((*item)->fileListItem,config)) );
                    emit uncountTime( (*item)->getTime );
                    emit uncountTime( (*item)->getCorrectionTime );
                    emit uncountTime( (*item)->ripTime );
                    emit uncountTime( (*item)->decodeTime );
                    emit uncountTime( (*item)->encodeTime );
                    emit uncountTime( (*item)->replaygainTime );
                    remove( *item, 1 );
                    updateProgressIndicator();
                    return;
                }
                if( !proc->normalExit() ) {
                    logger->log( (*item)->logID, i18n("Could not write to file: `%1'").arg(OutputDirectory::calcPath((*item)->fileListItem,config)) );
                    emit uncountTime( (*item)->getTime );
                    emit uncountTime( (*item)->getCorrectionTime );
                    emit uncountTime( (*item)->ripTime );
                    emit uncountTime( (*item)->decodeTime );
                    emit uncountTime( (*item)->encodeTime );
                    emit uncountTime( (*item)->replaygainTime );
                    remove( *item, -1 );
                    updateProgressIndicator();
                    return;
                }
                else {
                    logger->log( (*item)->logID, " " + i18n("File moved") );
                }
            }
            if( (*item)->state == ConvertItem::put_correction ) {
                if( proc->signalled() ) {
                    emit uncountTime( (*item)->getTime );
                    emit uncountTime( (*item)->getCorrectionTime );
                    emit uncountTime( (*item)->ripTime );
                    emit uncountTime( (*item)->decodeTime );
                    emit uncountTime( (*item)->encodeTime );
                    emit uncountTime( (*item)->replaygainTime );
                    remove( *item, 1 );
                    updateProgressIndicator();
                    return;
                }
                if( !proc->normalExit() ) {
                    emit uncountTime( (*item)->getTime );
                    emit uncountTime( (*item)->getCorrectionTime );
                    emit uncountTime( (*item)->ripTime );
                    emit uncountTime( (*item)->decodeTime );
                    emit uncountTime( (*item)->encodeTime );
                    emit uncountTime( (*item)->replaygainTime );
                    remove( *item, -1 );
                    updateProgressIndicator();
                    return;
                }
                else {
                    logger->log( (*item)->logID, " " + i18n("File moved") );
                }
            }
            if( (*item)->state == ConvertItem::replaygain ) {
                if( proc->signalled() ) {
                    emit uncountTime( (*item)->getTime );
                    emit uncountTime( (*item)->getCorrectionTime );
                    emit uncountTime( (*item)->ripTime );
                    emit uncountTime( (*item)->decodeTime );
                    emit uncountTime( (*item)->encodeTime );
                    remove( *item, 1 );
                    updateProgressIndicator();
                    return;
                }
                else if( !proc->normalExit() ) {
                    emit uncountTime( (*item)->getTime );
                    emit uncountTime( (*item)->getCorrectionTime );
                    emit uncountTime( (*item)->ripTime );
                    emit uncountTime( (*item)->decodeTime );
                    emit uncountTime( (*item)->encodeTime );
                    remove( *item, -1 );
                    updateProgressIndicator();
                    return;
                }
                else {
                    emit countTime( (*item)->replaygainTime );
                }
            }
            // TODO did we get errors? (complete)
            executeNextStep( *item );
            return;
        }
    }
}

void Convert::add( FileListItem* item )
{
    logger->log( 1000, i18n("Adding new item to conversion list: `%1'").arg(item->options.filePathName) );

    // append the item to the item list and store the iterator
    TQValueList<ConvertItem*>::Iterator newItem = items.append( new ConvertItem( item ) );

    // register at the logger
    (*newItem)->logID = logger->registerProcess( item->options.filePathName );
    logger->log( 1000, " " + i18n("Got log ID: %1").arg((*newItem)->logID) );

    logger->log( (*newItem)->logID, "Mime Type: " + (*newItem)->fileListItem->mimeType );
    if( (*newItem)->fileListItem->tags ) logger->log( (*newItem)->logID, i18n("Tags successfully read") );
    else logger->log( (*newItem)->logID, i18n("Reading tags failed") );

    // set some variables to default values
    (*newItem)->mode = (ConvertItem::Mode)0x0000;
    (*newItem)->state = (ConvertItem::Mode)0x0000;
    (*newItem)->convertProcess = 0;
    (*newItem)->moveJob = 0;
    (*newItem)->replayGain = 0;

    /* seems to be unnecessary
    (*newItem)->correctionInFile = TQString()();
    (*newItem)->correctionOutFile = TQString()();
    (*newItem)->correctionInputExtension = TQString()();
    (*newItem)->correctionOutputExtension = TQString()();*/

    // connect convertProcess of our new item with the slots of Convert
    (*newItem)->convertProcess = new TDEProcess();
    connect( (*newItem)->convertProcess, TQ_SIGNAL(receivedStdout(TDEProcess*,char*,int)),
               this, TQ_SLOT(processOutput(TDEProcess*,char*,int))
             );
    connect( (*newItem)->convertProcess, TQ_SIGNAL(receivedStderr(TDEProcess*,char*,int)),
               this, TQ_SLOT(processOutput(TDEProcess*,char*,int))
             );
    connect( (*newItem)->convertProcess, TQ_SIGNAL(processExited(TDEProcess*)),
               this, TQ_SLOT(processExit(TDEProcess*))
             );

    // NOTE the tempInFile is also created if the file is a audio cd track

    // set up the names of our temp files
    (*newItem)->tempInFile = new KTempFile( TQString(), "." + item->fileFormat );
    (*newItem)->tempInFile->setAutoDelete( true );
    (*newItem)->tempInFile->close();

    (*newItem)->tempWavFile = new KTempFile( TQString(), ".wav" );
    (*newItem)->tempWavFile->setAutoDelete( true );
    (*newItem)->tempWavFile->close();

    (*newItem)->tempOutFile = new KTempFile( TQString(), "." + item->options.encodingOptions.sFormat );
    (*newItem)->tempOutFile->setAutoDelete( true );
    (*newItem)->tempOutFile->close();

    if( item->track >= 0 ) // it's an audio cd track
    {
        (*newItem)->mode = ConvertItem::Mode( ConvertItem::rip );
    }
    else // it's a file
    {
        (*newItem)->mode = ConvertItem::Mode( ConvertItem::get );
        if( item->fileFormat != "wav" )
        {
            (*newItem)->mode = ConvertItem::Mode( (*newItem)->mode | ConvertItem::decode );
        }
    }

    if( item->options.encodingOptions.sFormat != "wav" )
    {
        (*newItem)->mode = ConvertItem::Mode( (*newItem)->mode | ConvertItem::encode );
    }
    if( item->options.encodingOptions.replaygain.bEnabled )
    {
        (*newItem)->mode = ConvertItem::Mode( (*newItem)->mode | ConvertItem::replaygain );
    }

    TQString extension;

    extension = config->getCorrectionExtension( item->mimeType );
    if( !extension.isEmpty() ) {
        (*newItem)->correctionInputExtension = extension;
        (*newItem)->correctionInFile = OutputDirectory::changeExtension( (*newItem)->tempInFile->name(), extension );
        (*newItem)->mode = ConvertItem::Mode( (*newItem)->mode | ConvertItem::get_correction );
        logger->log( (*newItem)->logID, " correctionInFile: `" + (*newItem)->correctionInFile + "'" );
    }

    extension = config->getCorrectionExtension( item->options.encodingOptions.sFormat );
    if( !extension.isEmpty() && item->options.encodingOptions.sQualityMode == i18n("Hybrid") ) {
        (*newItem)->correctionOutputExtension = extension;
        (*newItem)->correctionOutFile = OutputDirectory::changeExtension( (*newItem)->tempOutFile->name(), extension );
        (*newItem)->mode = ConvertItem::Mode( (*newItem)->mode | ConvertItem::put_correction );
        logger->log( (*newItem)->logID, " correctionOutFile: `" + (*newItem)->correctionOutFile + "'" );
    }

    (*newItem)->mode = ConvertItem::Mode( (*newItem)->mode | ConvertItem::write_tags | ConvertItem::put );

    // TODO use the values from the format info files !!!

    if( (*newItem)->mode & ConvertItem::get ) {
        if( !item->local ) {
            (*newItem)->getTime = 0.8; // TODO use the file size from the format info files
        }
    }
    if( (*newItem)->mode & ConvertItem::get_correction ) {
        if( !item->local ) {
            (*newItem)->getCorrectionTime = 2.0; // TODO use the file size from the format info files
        }
    }
    if( (*newItem)->mode & ConvertItem::rip ) {
        (*newItem)->ripTime = 1.0;
    }
    if( (*newItem)->mode & ConvertItem::decode ) {
        (*newItem)->decodeTime = 0.4;
    }
    if( (*newItem)->mode & ConvertItem::encode ) {
        (*newItem)->encodeTime = 1.0;
    }
    if( (*newItem)->mode & ConvertItem::replaygain ) {
        (*newItem)->replaygainTime = 0.2;
    }

    float sum = ( (*newItem)->getTime + (*newItem)->getCorrectionTime + (*newItem)->ripTime + (*newItem)->decodeTime + (*newItem)->encodeTime + (*newItem)->replaygainTime ) / item->time;

    (*newItem)->getTime /= sum;
    (*newItem)->getCorrectionTime /= sum;
    (*newItem)->ripTime /= sum;
    (*newItem)->decodeTime /= sum;
    (*newItem)->encodeTime /= sum;
    (*newItem)->replaygainTime /= sum;

    // visual feedback
    item->converting = true;

    if( !tUpdateProgressIndicator->isActive() ) {
        tUpdateProgressIndicator->start( config->data.general.updateDelay );
    }

    // and start
    executeNextStep( *newItem );
}

void Convert::stop( FileListItem* item )
{
    // search the item list for our item to stop 
    for( TQValueList<ConvertItem*>::Iterator stopItem = items.begin(); stopItem != items.end(); stopItem++ ) {
        // is fileListItem pointing at the same address, as item
        if( (*stopItem)->fileListItem == item ) {

            if( (*stopItem)->convertProcess->isRunning() ) {
                bool ret = (*stopItem)->convertProcess->kill( SIGKILL );
                //kdDebug() << "Killing process... (" << ret << ")" << endl;
                if( ret ) {
                    logger->log( (*stopItem)->logID, i18n("Killing process ...") );
                }
                else {
                    logger->log( (*stopItem)->logID, i18n("Killing process failed. Stopping after files are completed ...") );
                }
            }
            else if( (*stopItem)->moveJob != 0 ) {
                //kdDebug() << "Killing file copy..." << endl;
                // FIXME crashes sometimes - should be fixed by SIGKILL
                // FIXME crash if file_copy was stealthed
                logger->log( (*stopItem)->logID, i18n("Killing process ...") );
                (*stopItem)->moveJob->kill( false );
            }

            return;
        }
    }
}

void Convert::remove( ConvertItem* item, int state )
{
    // TODO "remove" (re-add) the times to the progress indicator
    //emit uncountTime( item->getTime + item->getCorrectionTime + item->ripTime +
    //                  item->decodeTime + item->encodeTime + item->replaygainTime );

    logger->log( item->logID, i18n("Removing file from conversion list. Exit code %1").arg(state) );

    if( item->fileListItem->notify != "" ) {
        TQString command = item->fileListItem->notify;
        command.replace( "%u", item->fileListItem->url );
        command.replace( "%i", item->fileListItem->options.filePathName.replace(" ","%20") );
        command.replace( "%o", item->outputFilePathName.replace(" ","%20") );
        logger->log( item->logID, " "+i18n("Executing command: \"%1\"").arg(command) );
        notify.clearArguments();
        TQString paramSplinter;
        // FIXME split correct (strings with spaces are splited by mistake)
        // FIXME only one command can be executed at once!?
        TQStringList params = TQStringList::split( ' ', item->fileListItem->notify );
        for( TQStringList::Iterator it = params.begin(); it != params.end(); ++it )
        {
            paramSplinter = *it;
            paramSplinter.replace( "%u", item->fileListItem->url );
            paramSplinter.replace( "%i", item->fileListItem->options.filePathName );
            paramSplinter.replace( "%o", item->outputFilePathName );
            notify << paramSplinter;
        }
        notify.start( TDEProcess::DontCare );
    }

    item->fileListItem->converting = false;
    emit finished( item->fileListItem, state ); // send signal to FileList
    emit finishedProcess( item->logID, state ); // send signal to Logger

    item->fileListItem = 0;
    if( item->convertProcess != 0 ) delete item->convertProcess;
    item->convertProcess = 0;
    //if( item->moveJob != 0 ) delete item->moveJob; // NOTE makes soundkonverter crash
    //item->moveJob = 0;
    if( item->replayGain != 0 ) delete item->replayGain;
    item->replayGain = 0;

    if( item->tempInFile != 0 ) delete item->tempInFile;
    item->tempInFile = 0;
    if( item->tempWavFile != 0 ) delete item->tempWavFile;
    item->tempWavFile = 0;
    if( item->tempOutFile != 0 ) delete item->tempOutFile;
    item->tempOutFile = 0;
    TQFile file;
    file.setName( item->correctionInFile );
    if( file.exists() ) file.remove();
    file.setName( item->correctionOutFile );
    if( file.exists() ) file.remove();

    for( TQValueList<ConvertItem*>::Iterator it = items.begin(); it != items.end(); it++ ) {
        if( (*it) == item ) {
            items.remove( it );
            break;
        }
    }

    delete item;
    item = 0;

    if( items.count() == 0 ) {
        tUpdateProgressIndicator->stop();
    }
}

void Convert::updateProgressIndicator()
{
    float time = 0;

    for( TQValueList<ConvertItem*>::Iterator it = items.begin(); it != items.end(); it++ ) {
        if( (*it)->state == ConvertItem::get ) {
            time += (*it)->getTime * (*it)->percent / 100;
            (*it)->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Getting file")+"... "+TQString().sprintf("%02i %%",(*it)->percent) );
        }
        else if( (*it)->state == ConvertItem::get_correction ) {
            time += (*it)->getCorrectionTime * (*it)->percent / 100;
            (*it)->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Getting correction file")+"... "+TQString().sprintf("%02i %%",(*it)->percent) );
        }
        else if( (*it)->state == ConvertItem::rip ) {
            RipperPlugin* plugin = config->getCurrentRipper();
            if( plugin != 0 && plugin->rip.output.isEmpty() ) {
                (*it)->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Ripping")+"... "+i18n("State")+": "+i18n("Unknown") );
            }
            else {
                time += (*it)->ripTime * (*it)->percent / 100;
                (*it)->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Ripping")+"... "+TQString().sprintf("%02i %%",(*it)->percent) );
            }
        }
        else if( (*it)->state == ConvertItem::decode ) {
            ConvertPlugin* plugin = config->decoderForFormat( (*it)->fileListItem->mimeType );
            if( plugin == 0 || plugin->dec.output.isEmpty() ) {
                (*it)->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Decoding")+"... "+i18n("State")+": "+i18n("Unknown") );
            }
            else {
                time += (*it)->decodeTime * (*it)->percent / 100;
                (*it)->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Decoding")+"... "+TQString().sprintf("%02i %%",(*it)->percent) );
            }
        }
        else if( (*it)->state == ConvertItem::encode ) {
            ConvertPlugin* plugin = config->encoderForFormat( (*it)->fileListItem->options.encodingOptions.sFormat );
            TQString outputPattern;
            if( plugin != 0 && (*it)->fileListItem->options.encodingOptions.sQualityMode == i18n("Quality") ) outputPattern = plugin->enc.lossy.quality.output;
            else if( plugin != 0 && (*it)->fileListItem->options.encodingOptions.sQualityMode == i18n("Bitrate") && (*it)->fileListItem->options.encodingOptions.sBitrateMode == "cbr" ) outputPattern = plugin->enc.lossy.bitrate.cbr.output;
            else if( plugin != 0 && (*it)->fileListItem->options.encodingOptions.sQualityMode == i18n("Bitrate") && (*it)->fileListItem->options.encodingOptions.sBitrateMode == "abr" ) outputPattern = plugin->enc.lossy.bitrate.abr.output;
            if( outputPattern.isEmpty() ) {
                (*it)->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Encoding")+"... "+i18n("State")+": "+i18n("Unknown") );
            }
            else {
                time += (*it)->encodeTime * (*it)->percent / 100;
                (*it)->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Encoding")+"... "+TQString().sprintf("%02i %%",(*it)->percent) );
            }
        }
        else if( (*it)->state == ConvertItem::replaygain ) {
            time += (*it)->replaygainTime * (*it)->percent / 100;
            (*it)->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Replay Gain")+"... "+TQString().sprintf("%02i %%",(*it)->percent) );
        }
        else if( (*it)->state == ConvertItem::put ) {
            (*it)->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Moving file")+"... "+TQString().sprintf("%02i %%",(*it)->percent) );
        }
        else if( (*it)->state == ConvertItem::put_correction ) {
            time += (*it)->getCorrectionTime * (*it)->percent / 100;
            (*it)->fileListItem->setText( fileList->columnByName(i18n("State")), i18n("Moving correction file")+"... "+TQString().sprintf("%02i %%",(*it)->percent) );
        }
    }
    emit update( time );
}

// void Convert::priorityChanged( int priority )
// { // FIXME setting a higher priority does not work
//     TDEProcess pChangePriority;
// 
//     for( TQValueList<ConvertItem*>::Iterator it = items.begin(); it != items.end(); it++ ) {
//         if( (*it)->convertProcess->isRunning() ) {
//             //(*it)->convertProcess->setPriority( priority );
//             pChangePriority.clearArguments();
//             pChangePriority << "renice";
//             TQString prio;
//             prio.sprintf( "%i", priority );
//             pChangePriority << prio;
//             TQString pid;
//             pid.sprintf( "%i", (*it)->convertProcess->pid() );
//             pChangePriority << pid;
//             //TQString cmd;
//             //cmd.sprintf( "renice %i %i",  );
//             pChangePriority.start( TDEProcess::Block );
//         }
//     }
// }


#include "convert.moc"
