
#include "replaygainfilelist.h"
#include "tagengine.h"
#include "logger.h"
#include "configuration.h"
#include "replaygain.h"
#include "replaygainpluginloader.h"
#include "filelist.h" //< for color shift functions

#include <tqdir.h>
#include <tqpainter.h>
#include <tqsimplerichtext.h>
#include <tqapplication.h>
#include <tqheader.h>
#include <tqlayout.h>
#include <tqtimer.h>

#include <tdelocale.h>
#include <kiconloader.h>
#include <kurldrag.h>
#include <tdepopupmenu.h>
#include <tdeaction.h>
#include <tdeactioncollection.h>
#include <tdemessagebox.h>
#include <kmountpoint.h>
#include <kprogress.h>
#include <kprocess.h>
#include <kmimetype.h>
#include <tdeapplication.h>
#include <kuser.h>

// TODO move listDir, addFiles, addDir, etc -- done?

// ### soundkonverter 0.4: give the 'track' and 'album' column a minimum width and fill the rest of the space with the 'file' column - make some margins, too


ReplayGainFileListItem::ReplayGainFileListItem( TQListView* parent )
    : TDEListViewItem( parent )
{
    m_type = File;
    mimeType = "application/octet-stream";
    addingReplayGain = false;
    queued = false;
}

// ReplayGainFileListItem::ReplayGainFileListItem( TQListView* parent, TQListViewItem* after )
//     : TDEListViewItem( parent, after )
// {
//     m_type = File;
//     mimeType = "application/octet-stream";
//     addingReplayGain = false;
//     queued = false;
// }

ReplayGainFileListItem::ReplayGainFileListItem( ReplayGainFileListItem* parent )
    : TDEListViewItem( parent )
{
    m_type = File;
    mimeType = "application/octet-stream";
    addingReplayGain = false;
    queued = false;
}

ReplayGainFileListItem::~ReplayGainFileListItem()
{}

void ReplayGainFileListItem::paintCell( TQPainter *p, const TQColorGroup &cg, int column, int width, int alignment )
{
    // NOTE speed up this function

    if( column == ((ReplayGainFileList*)listView())->columnByName(i18n("File")) )
    {
        int margin = listView()->itemMargin();
        int w = width - 2*margin;
        int h = height();
        TQRect textRect = p->boundingRect( margin, 0, w, h, alignment, text(column) );

        if( textRect.width() > w ) {
            alignment = TQt::AlignRight | TQt::SingleLine;
        }
    }

    if( addingReplayGain ) {
        TQColorGroup _cg( cg );
        if ( isSelected() ) {
            _cg.setColor( TQColorGroup::Highlight, shiftColorToRed(_cg.highlight(), 40) );
        } else {
            _cg.setColor( TQColorGroup::Base, shiftColorToRed(backgroundColor(column), 40));
        }
        TQListViewItem::paintCell( p, _cg, column, width, alignment );
    } else if (queued) {
        TQColorGroup _cg( cg );
        if ( isSelected() ) {
            _cg.setColor( TQColorGroup::Highlight, shiftColorToYellow(_cg.highlight(), 40) );
        } else {
            _cg.setColor( TQColorGroup::Base, shiftColorToYellow(backgroundColor(column), 40));
        }
        TQListViewItem::paintCell( p, _cg, column, width, alignment );
    } else {
        TDEListViewItem::paintCell( p, cg, column, width, alignment );
    }
}

void ReplayGainFileListItem::setType( Type type )
{
    if( type == m_type ) return;

    m_type = type;

    if( type == Album ) {
        setOpen( true );
        setPixmap( 0, TDEGlobal::iconLoader()->loadIcon("media-optical-cdrom-unmounted",TDEIcon::Small) );
    }
}

void ReplayGainFileListItem::updateReplayGainCells( TagData* tags )
{
    if( !tags ) {
        setText( ((ReplayGainFileList*)listView())->columnByName(i18n("Track")), i18n("Unknown") );
        setText( ((ReplayGainFileList*)listView())->columnByName(i18n("Album")), i18n("Unknown") );
    }
    else {
        if( tags->track_gain != 210588 ) {
            setText( ((ReplayGainFileList*)listView())->columnByName(i18n("Track")), TQString().sprintf("%+.2f dB",tags->track_gain) );
        }
        else {
            setText( ((ReplayGainFileList*)listView())->columnByName(i18n("Track")), i18n("Unknown") );
        }
        if( tags->album_gain != 210588 ) {
            setText( ((ReplayGainFileList*)listView())->columnByName(i18n("Album")), TQString().sprintf("%+.2f dB",tags->album_gain) );
        }
        else {
            setText( ((ReplayGainFileList*)listView())->columnByName(i18n("Album")), i18n("Unknown") );
        }
    }
}

int ReplayGainFileListItem::compare( TQListViewItem* item, int column, bool ascending ) const
{
    // NOTE looking at the types, not the strings would be better
    if( text(1) == "" && item->text(1) != "" ) return -1;
    else if( text(1) != "" && item->text(1) == "" ) return 1;
    else return TDEListViewItem::compare( item, column, ascending );
}

ReplayGainFileList::ReplayGainFileList( TagEngine* _tagEngine, Config* _config, Logger* _logger, TQWidget *parent, const char *name )
    : TDEListView( parent, name )
{
    tagEngine = _tagEngine;
    config = _config;
    logger = _logger;

    processing = false;
    queue = false;

    time = 0;
    processedTime = 0;

    addColumn( i18n("File"), 390 );
    setColumnWidthMode( 0, TQListView::Manual );
    addColumn( i18n("Track"), 90 );
    setColumnAlignment( 1, TQt::AlignRight );
    addColumn( i18n("Album"), 90 );
    setColumnAlignment( 2, TQt::AlignRight );

    setSelectionMode( TQListView::Extended );
    setAllColumnsShowFocus( true );
    setResizeMode( TQListView::LastColumn );
    setShowSortIndicator( true );
    setSorting( 0 );
    setRootIsDecorated( true );

    setDragEnabled( true );
    setAcceptDrops( true );

    TQGridLayout* grid = new TQGridLayout( this, 2, 1, 11, 6 );
    grid->setRowStretch( 0, 1 );
    grid->setRowStretch( 2, 1 );
    grid->setColStretch( 0, 1 );
    grid->setColStretch( 2, 1 );
    pScanStatus = new KProgress( this, "pScanStatus" );
    pScanStatus->setMinimumHeight( pScanStatus->height() );
    pScanStatus->setFormat( "%v / %m" );
    pScanStatus->hide();
    grid->addWidget( pScanStatus, 1, 1 );
    grid->setColStretch( 1, 2 );

    contextMenu = new TDEPopupMenu( this );
    connect( this, TQ_SIGNAL(contextMenuRequested(TQListViewItem*,const TQPoint&,int)),
               this, TQ_SLOT(showContextMenu(TQListViewItem*,const TQPoint&,int))
             );

    // we haven't got access to the action collection of soundKonverter, so let's create a new one
    actionCollection = new TDEActionCollection( this );

    calc_gain = new TDEAction( i18n("Calculate Replay Gain tags"), "apply", 0, this, TQ_SLOT(calcSelectedItemsGain()), actionCollection, "calc_album" );
    remove_gain = new TDEAction( i18n("Remove Replay Gain tags"), "cancel", 0, this, TQ_SLOT(removeSelectedItemsGain()), actionCollection, "remove_gain" );
    remove = new TDEAction( i18n("Remove"), "edittrash", Key_Delete, this, TQ_SLOT(removeSelectedItems()), actionCollection, "remove" );
    paste = new TDEAction( i18n("Paste"), "edit-paste", 0, this, 0, actionCollection, "paste" );
    newalbum = new TDEAction( i18n("New album"), "document-new", 0, this, TQ_SLOT(createNewAlbum()), actionCollection, "newalbum" );
    open_albums = new TDEAction( i18n("Open all albums"), "view_tree", 0, this, TQ_SLOT(openAlbums()), actionCollection, "open_albums" );
    close_albums = new TDEAction( i18n("Cloase all albums"), "view_text", 0, this, TQ_SLOT(closeAlbums()), actionCollection, "close_albums" );

    replayGain = new ReplayGain( config, logger );

    bubble = new TQSimpleRichText( i18n( "<div align=center>"
                                 "<h3>Replay Gain Tool</h3>"
                                 "With this tool you can add Replay Gain tags to your audio files and remove them."
                                 //"<br>Replay Gain adds a volume correction information to the files for playing them at the same volume."
                                 //"Replay Gain allows you to play all audio files at the same volume level without modifying the audio data."
                                 "<br>Replay Gain adds a volume correction information to the files so that they can be played at an equal volume level."
//                                  "<br><a href=\"documenation:replaygaintool\">Learn more about Replay Gain ...</a><br/>"
                                 "</div>" ), TQApplication::font() );

    connect( header(), TQ_SIGNAL(sizeChange( int, int, int )),
               TQ_SLOT(columnResizeEvent( int, int, int ))
             );
    connect( this, TQ_SIGNAL( dropped(TQDropEvent*, TQListViewItem*, TQListViewItem*) ),
               TQ_SLOT( slotDropped(TQDropEvent*, TQListViewItem*, TQListViewItem*) )
             );

    process = new TDEProcess();
    connect( process, TQ_SIGNAL(receivedStdout(TDEProcess*,char*,int)),
               this, TQ_SLOT(processOutput(TDEProcess*,char*,int))
             );
    connect( process, TQ_SIGNAL(receivedStderr(TDEProcess*,char*,int)),
               this, TQ_SLOT(processOutput(TDEProcess*,char*,int))
             );
    connect( process, TQ_SIGNAL(processExited(TDEProcess*)),
               this, TQ_SLOT(processExit(TDEProcess*))
             );

    tUpdateProgress = new TQTimer( this, "tUpdateProgress" );
    connect( tUpdateProgress, TQ_SIGNAL(timeout()),
               this, TQ_SLOT(update())
             );
}

ReplayGainFileList::~ReplayGainFileList()
{
    delete replayGain;
}

int ReplayGainFileList::columnByName( const TQString& name )
{
    for( int i = 0; i < columns(); ++i ) {
        if( columnText( i ) == name ) return i;
    }
    return -1;
}

void ReplayGainFileList::viewportPaintEvent( TQPaintEvent* e )
{
    TDEListView::viewportPaintEvent( e );

    // the bubble help
    if( childCount() == 0 ) {
        TQPainter p( viewport() );

        bubble->setWidth( width() - 50 );

        const uint w = bubble->width() + 20;
        const uint h = bubble->height() + 20;

        p.setBrush( colorGroup().background() );
        p.drawRoundRect( 15, 15, w, h, (8*200)/w, (8*200)/h );
        bubble->draw( &p, 20, 20, TQRect(), colorGroup() );
    }
}

void ReplayGainFileList::viewportResizeEvent( TQResizeEvent* )
{
    // needed for correct redraw of bubble help
    triggerUpdate();
}

void ReplayGainFileList::columnResizeEvent( int, int, int )
{
    // needed for correct redraw of bubble help
    triggerUpdate();
}

bool ReplayGainFileList::acceptDrag( TQDropEvent* e ) const
{
    return ( e->source() == viewport() || KURLDrag::canDecode(e) ); // TODO verify the files
}

void ReplayGainFileList::slotDropped( TQDropEvent* e, TQListViewItem*, TQListViewItem* )
{
    TQString file;
    KURL::List list;
    if( KURLDrag::decode( e, list ) )
    {
        for( KURL::List::Iterator it = list.begin(); it != list.end(); ++it )
        {
            // TODO verify the files (necessary when multiple files are being dropped)
            file = TQDir::convertSeparators( (*it).pathOrURL() );
            TQFileInfo fileInfo( file );
            if( fileInfo.isDir() )
            {
                addDir( file );
            }
            else
            {
                addFile( (*it).url() );
            }
        }
    }
}

void ReplayGainFileList::contentsDragEnterEvent( TQDragEnterEvent *e )
{
    e->accept( e->source() == viewport() || KURLDrag::canDecode(e) );
}

void ReplayGainFileList::contentsDragMoveEvent( TQDragMoveEvent *e )
{ // the mouse has moved while dragging some stuff

    // if the file is added from an external app, don't let the user select the position to drop
    if( e->source() != viewport() ) {
        setDropHighlighter( false );
        setDropVisualizer( false );
        cleanItemHighlighter();
        cleanDropVisualizer();

        // the rest is handled by TDEListView
        TDEListView::contentsDragMoveEvent( e );

        return;
    }

    // translate the coordinates of the mouse pointer
    TQPoint vp = contentsToViewport( e->pos() );
    // and get the list view item below the pointer
    ReplayGainFileListItem* item = itemAt( vp );

    if( item && item->type() == ReplayGainFileListItem::Album ) { // the pointer is above an 'album' element
        // draw a nice rect around the item
        setDropHighlighter( true );
        setDropVisualizer( false );
        cleanDropVisualizer();
    }
    else if( item ) { // the pointer is above an 'file' element
        // draw a line above or below the item
        setDropVisualizer( true );
        setDropHighlighter( false );
        cleanItemHighlighter();
    }

    // the rest is handled by TDEListView
    TDEListView::contentsDragMoveEvent( e );
}

void ReplayGainFileList::contentsDropEvent( TQDropEvent *e )
{ // the stuff has been dropped

    emit dropped( e, 0, 0 ); // NOTE it works this way

    bool formatError = false;

    cleanDropVisualizer();
    cleanItemHighlighter();

    // get the item below the mouse pointer, where the stuff has been dropped
    TQPoint vp = contentsToViewport( e->pos() );
    ReplayGainFileListItem* newParent = itemAt( vp );

    // if the item is a 'file', use the parent item
    if( newParent && newParent->type() != ReplayGainFileListItem::Album ) {
        newParent = newParent->parent();
    }

    // TODO if the mouse is on the left side under the parent but not under the sibling item, use the parent
/*    if( newParent == 0 && vp.x() >= 2*treeStepSize() ) {
        TQPoint p = vp;
        int height = 0;
        if( firstChild() ) {
            height = firstChild()->height();
        }
        p.setY( p.y() - height );
        ReplayGainFileListItem* it = itemAt( p );
        if( it && it->type() != ReplayGainFileListItem::Album ) {
            newParent = it->parent();
        }
        else if( it ) {
            newParent = it;
        }
    }*/

    ReplayGainFileListItem* i;

    // iterate through all items and move all selected ones
    for( ReplayGainFileListItem* item = firstChild(); item != 0; ) {
        if( item->type() == ReplayGainFileListItem::File ) {
            if( newParent == 0 ) {
                item = item->nextSibling();
                continue;
            }
            if( item->isSelected() ) {
                if( newParent == 0 || newParent->mimeType == item->mimeType || newParent->mimeType == "application/octet-stream" ) {
                    if( newParent->mimeType == "application/octet-stream" ) newParent->mimeType = item->mimeType;
                    i = item;
                    item = item->nextSibling();
                    takeItem( i );
                    if( newParent ) newParent->insertItem( i );
                    else insertItem( i );
                }
                else {
                    item = item->nextSibling();
                    formatError = true;
                }
            }
            else {
                item = item->nextSibling();
            }
        }
        else {
            if( item == newParent ) {
                item = item->nextSibling();
                continue;
            }
            if( item->isSelected() ) {
                if( newParent == 0 || newParent->mimeType == item->mimeType || newParent->mimeType == "application/octet-stream" ) {
                    if( newParent->mimeType == "application/octet-stream" ) newParent->mimeType = item->mimeType;
                    for( ReplayGainFileListItem* sub_item = item->firstChild(); sub_item != 0;  ) {
                        i = sub_item;
                        sub_item = sub_item->nextSibling();
                        item->takeItem( i );
                        if( newParent ) newParent->insertItem( i );
                        else insertItem( i );
                    }
                    i = item;
                    item = item->nextSibling();
                    delete i;
                }
                else {
                    item = item->nextSibling();
                    formatError = true;
                }
            }
            else {
                for( ReplayGainFileListItem* sub_item = item->firstChild(); sub_item != 0; ) {
                    if( sub_item->isSelected() ) {
                        if( newParent == 0 || newParent->mimeType == item->mimeType || newParent->mimeType == "application/octet-stream" ) {
                            if( newParent && newParent->mimeType == "application/octet-stream" ) newParent->mimeType = item->mimeType;
                            i = sub_item;
                            sub_item = sub_item->nextSibling();
                            item->takeItem( i );
                            if( newParent ) newParent->insertItem( i );
                            else insertItem( i );
                        }
                        else {
                            sub_item = sub_item->nextSibling();
                            formatError = true;
                        }
                    }
                    else {
                        sub_item = sub_item->nextSibling();
                    }
                }
                if( item->childCount() == 0 ) {
                    i = item;
                    item = item->nextSibling();
                    delete i;
                }
                else {
                    item = item->nextSibling();
                }
            }
        }
    }

    // FIXME make the mouse pointer look normal

    if( formatError ) {
        KMessageBox::information( this,
            i18n("You can't place files of different formats in the same \'album\'."), i18n("Different file formats") );
    }
}

int ReplayGainFileList::listDir( const TQString& directory, TQStringList filter, bool recursive, bool fast, int count )
{ // NOTE speed up?
    TQDir dir( directory );
    dir.setFilter( TQDir::Files | TQDir::Dirs | TQDir::NoSymLinks | TQDir::Readable );

    TQStringList list = dir.entryList();

    for( TQStringList::Iterator it = list.begin(); it != list.end(); ++it ) {
        if( *it == "." || *it == ".." ) continue;
        TQFileInfo fileInfo( directory + "/" + *it );
        if( fast ) {
            if( fileInfo.isDir() && recursive ) {
                count = listDir( directory + "/" + *it, filter, recursive, fast, count );
            }
            else if( !fileInfo.isDir() || !recursive ) { // NOTE checking for isFile may not work with all file names
                // NOTE filter feature
                for( TQStringList::Iterator jt = filter.begin(); jt != filter.end(); ++jt ) {
                    if( (*it).endsWith("."+(*jt),false) ) {
                        count++;
                        pScanStatus->setTotalSteps( count );
                        break;
                    }
                }
                if( filter.first() == "" ) {
                    count++;
                    pScanStatus->setTotalSteps( count );
                }
            }
        }
        else {
            if( fileInfo.isDir() && recursive ) {
                count = listDir( directory + "/" + *it, filter, recursive, fast, count );
            }
            else if( !fileInfo.isDir() || !recursive ) { // NOTE checking for isFile may not work with all file names
                // NOTE filter feature
                for( TQStringList::Iterator jt = filter.begin(); jt != filter.end(); ++jt ) {
                    if( (*it).endsWith("."+(*jt),false) ) {
                        addFile( KURL::encode_string(directory + "/" + *it) );
                        count++;
                        pScanStatus->setProgress( count );
                        break;
                    }
                }
                if( filter.first() == "" ) {
                    addFile( KURL::encode_string(directory + "/" + *it) );
                    count++;
                    pScanStatus->setProgress( count );
                }
            }
        }
    }

    return count;
}

void ReplayGainFileList::showContextMenu( TQListViewItem* item, const TQPoint& point, int )
{
    // remove all items from the context menu
    contextMenu->clear();

    // add a tilte to our context manu
    //contextMenu->insertTitle( static_cast<FileListItem*>(item)->fileName );

    // if item is null, we can abort here
    if( item ) {
        if( !processing ) {
            calc_gain->plug( contextMenu );
            if( item->text(columnByName(i18n("Track"))) != i18n("Unknown") || item->text(columnByName(i18n("Album"))) != i18n("Unknown") ) {
                remove_gain->plug( contextMenu );
            }
        }
        newalbum->plug( contextMenu );
        remove->plug( contextMenu );
    }
    else {
        newalbum->plug( contextMenu );
    }
    paste->plug( contextMenu );
    contextMenu->insertSeparator();
    open_albums->plug( contextMenu );
    close_albums->plug( contextMenu );

    // show the popup menu
    contextMenu->popup( point );
}

void ReplayGainFileList::addFile( const TQString& file )
{
    TQString filename = file;
    TQString filePathName;
    TQString device;

    if( filename.left( 1 ) == "/" ) {
        filePathName = filename;
    }
    else if( filename.left( 7 ) == "file://" ) {
        filePathName = filename;
        filePathName.remove( 0, 7 );
    }
    else if( filename.left( 13 ) == "system:/home/" ) {
        filePathName = filename;
        filePathName.remove( 0, 13 );
        filePathName = TQDir::homeDirPath() + "/" + filePathName;
    }
    else if( filename.left( 14 ) == "system:/users/" || filename.left( 6 ) == "home:/" ) {
        int length = ( filename.left(6) == "home:/" ) ? 6 : 14;
        TQString username = filename;
        username.remove( 0, length );
        username = username.left( username.find("/") );
        filePathName = filename;
        filePathName.remove( 0, length + username.length() );
        KUser user( username );
        filePathName = user.homeDir() + filePathName;
    }
    else if( filename.left( 14 ) == "system:/media/" || filename.left( 7 ) == "media:/" ) {
        int length = ( filename.left(7) == "media:/" ) ? 7 : 14;
        device = filename;
        device.remove( 0, length );
        device = "/dev/" + device.left( device.find( "/" ) );

        KMountPoint::List mountPoints = KMountPoint::possibleMountPoints();

        for( KMountPoint::List::ConstIterator jt = mountPoints.begin(); jt != mountPoints.end(); ++jt )
        {
            const TDESharedPtr<KMountPoint> mp = *jt;
            if( mp->mountedFrom() == device )
            {
                filePathName = ( mp->mountPoint() == "/" ) ? mp->mountPoint() : mp->mountPoint() + "/";
                filePathName += filename.right( filename.length() - device.length() - length + 4 );
            }
        }
    }
    else {
        return;
    }

    TagData* tags = tagEngine->readTags( KURL::decode_string(filePathName) );
    if( !tags || tags->album.isEmpty() ) {
        ReplayGainFileListItem* item = new ReplayGainFileListItem( this );
        item->originalFileFormat = filePathName.right( filePathName.length() - filePathName.findRev(".") - 1 );
        item->mimeType = KMimeType::findByFileContent( filePathName )->name();
        item->fileFormat = KMimeType::findByFileContent( filePathName )->patterns().first();
        if( item->mimeType.isEmpty() || item->mimeType == "application/octet-stream" || item->mimeType == "text/plain" ) {
            item->mimeType = KMimeType::findByURL( filePathName.lower() )->name();
            item->fileFormat = KMimeType::findByURL( filePathName.lower() )->patterns().first();
        }
        // check whether the mime type has a decoder registered
        if( !config->acceptReplayGainFile( item->mimeType ) ) {
            delete item;
            return;
        }
        item->filePathName = filePathName;
        if( tags ) item->time = tags->length;
        else {
            FormatItem* formatItem = config->getFormatItem( item->mimeType );
            if( formatItem && formatItem->size > 0 ) {
                TQFileInfo fileInfo( filePathName );
                item->time = fileInfo.size() / formatItem->size;
            }
            else {
                item->time = 210;
            }
        }
        item->setText( columnByName(i18n("File")), KURL::decode_string(filePathName).replace("%2f","/").replace("%%","%") );
        if( tags && tags->track_gain != 210588 ) {
            item->setText( columnByName(i18n("Track")), TQString().sprintf("%+.2f dB",tags->track_gain) );
        }
        else {
            item->setText( columnByName(i18n("Track")), i18n("Unknown") );
        }
        if( tags && tags->album_gain != 210588 ) {
            item->setText( columnByName(i18n("Album")), TQString().sprintf("%+.2f dB",tags->album_gain) );
        }
        else {
            item->setText( columnByName(i18n("Album")), i18n("Unknown") );
        }
    }
    else {
        TQString mimeType = KMimeType::findByFileContent( filePathName )->name();
        TQString fileFormat = KMimeType::findByFileContent( filePathName )->patterns().first();
        if( mimeType.isEmpty() || mimeType == "application/octet-stream" || mimeType == "text/plain" ) {
            mimeType = KMimeType::findByURL( filePathName.lower() )->name();
            fileFormat = KMimeType::findByURL( filePathName.lower() )->patterns().first();
        }
        // check whether the mime type has a decoder registered
        if( !config->acceptReplayGainFile( mimeType ) ) {
            return;
        }

        for( ReplayGainFileListItem* it = firstChild(); it != 0; it = it->nextSibling() ) {
            //if( it->text(0) == TQString(tags->artist+" - "+tags->album) ) {
            if( it->text(columnByName(i18n("File"))) == tags->album && it->type() == ReplayGainFileListItem::Album && it->mimeType == mimeType ) {
                ReplayGainFileListItem* item = new ReplayGainFileListItem( it );
                item->originalFileFormat = filePathName.right( filePathName.length() - filePathName.findRev(".") - 1 );
                item->filePathName = filePathName;
                item->mimeType = mimeType;
                item->fileFormat = fileFormat;
                item->time = tags->length;
                item->setText( columnByName(i18n("File")), KURL::decode_string(filePathName).replace("%2f","/").replace("%%","%") );
                if( tags->track_gain != 210588 ) {
                    item->setText( columnByName(i18n("Track")), TQString().sprintf("%+.2f dB",tags->track_gain) );
                }
                else {
                    item->setText( columnByName(i18n("Track")), i18n("Unknown") );
                }
                if( tags->album_gain != 210588 ) {
                    item->setText( columnByName(i18n("Album")), TQString().sprintf("%+.2f dB",tags->album_gain) );
                }
                else {
                    item->setText( columnByName(i18n("Album")), i18n("Unknown") );
                }
                tags = 0;  // <--,
                break;     //    |
            }              //    |
        }                  //    |
        if( tags ) {       // <--'
            ReplayGainFileListItem* parent = new ReplayGainFileListItem( this );
            //parent->setText( 0, tags->artist+" - "+tags->album );
            parent->setText( columnByName(i18n("File")), tags->album );
            parent->setType( ReplayGainFileListItem::Album );
            parent->mimeType = mimeType;
            parent->fileFormat = fileFormat;
            ReplayGainFileListItem* item = new ReplayGainFileListItem( parent );
            item->originalFileFormat = filePathName.right( filePathName.length() - filePathName.findRev(".") - 1 );
            item->filePathName = filePathName;
            item->mimeType = mimeType;
            item->fileFormat = fileFormat;
            item->time = tags->length;
            item->setText( columnByName(i18n("File")), KURL::decode_string(filePathName).replace("%2f","/").replace("%%","%") );
            if( tags->track_gain != 210588 ) {
                item->setText( columnByName(i18n("Track")), TQString().sprintf("%+.2f dB",tags->track_gain) );
            }
            else {
                item->setText( columnByName(i18n("Track")), i18n("Unknown") );
            }
            if( tags->album_gain != 210588 ) {
                item->setText( columnByName(i18n("Album")), TQString().sprintf("%+.2f dB",tags->album_gain) );
            }
            else {
                item->setText( columnByName(i18n("Album")), i18n("Unknown") );
            }
        }
    }
}

void ReplayGainFileList::addDir( const TQString& directory, const TQStringList& filter, bool recursive )
{
    pScanStatus->setProgress( 0 );
    pScanStatus->setTotalSteps( 0 );
    pScanStatus->show(); // show the status while scanning the directories
    kapp->processEvents();

    int count = listDir( directory, filter, recursive, true );
    listDir( directory, filter, recursive );

    pScanStatus->hide(); // hide the status bar, when the scan is done
}

void ReplayGainFileList::openAlbums()
{
    // iterate through all items and open all albums
    for( ReplayGainFileListItem* item = firstChild(); item != 0; item = item->nextSibling() ) {
        if( item->type() == ReplayGainFileListItem::Album ) {
            item->setOpen( true );
        }
    }
}

void ReplayGainFileList::closeAlbums()
{
    // iterate through all items and close all albums
    for( ReplayGainFileListItem* item = firstChild(); item != 0; item = item->nextSibling() ) {
        if( item->type() == ReplayGainFileListItem::Album ) {
            item->setOpen( false );
        }
    }
}

void ReplayGainFileList::removeSelectedItems()
{
    ReplayGainFileListItem* i;

    // iterate through all items and remove all selected ones
    for( ReplayGainFileListItem* item = firstChild(); item != 0; ) {
        if( item->type() == ReplayGainFileListItem::File ) {
            if( item->isSelected() ) {
                i = item;
                item = item->nextSibling();
                delete i;
            }
            else {
                item = item->nextSibling();
            }
        }
        else {
            if( item->isSelected() ) {
                i = item;
                item = item->nextSibling();
                delete i;
            }
            else {
                for( ReplayGainFileListItem* sub_item = item->firstChild(); sub_item != 0; ) {
                    if( sub_item->isSelected() ) {
                        i = sub_item;
                        sub_item = sub_item->nextSibling();
                        delete i;
                    }
                    else {
                        sub_item = sub_item->nextSibling();
                    }
                }
                if( item->childCount() == 0 ) {
                    i = item;
                    item = item->nextSibling();
                    delete i;
                }
                else {
                    item = item->nextSibling();
                }
            }
        }
    }
}

void ReplayGainFileList::createNewAlbum()
{
    ReplayGainFileListItem* item = new ReplayGainFileListItem( this );
    item->setText( columnByName(i18n("File")), i18n("New album") );
    item->setType( ReplayGainFileListItem::Album );
    item->mimeType = "application/octet-stream";
}

void ReplayGainFileList::calcSelectedItemsGain()
{
    if( processing ) return;

    // iterate through all items and remove the replay gain from all selected ones
    for( ReplayGainFileListItem* item = firstChild(); item != 0; item = item->nextSibling() ) {
        if( item->type() == ReplayGainFileListItem::File ) {
            if( item->isSelected() ) {
                item->queued = true;
                item->repaint();
                item->mode = ReplayGainFileListItem::force;
            }
        }
        else {
            if( item->isSelected() ) {
                item->queued = true;
                item->repaint();
                item->mode = ReplayGainFileListItem::force;
                for( ReplayGainFileListItem* sub_item = item->firstChild(); sub_item != 0; sub_item = sub_item->nextSibling() ) {
                    sub_item->queued = true;
                    sub_item->repaint();
                    sub_item->mode = ReplayGainFileListItem::force;
                }
            }
            else {
                for( ReplayGainFileListItem* sub_item = item->firstChild(); sub_item != 0; sub_item = sub_item->nextSibling() ) {
                    if( sub_item->isSelected() ) {
                        item->queued = true;
                        item->repaint();
                        item->mode = ReplayGainFileListItem::force;
                        for( ReplayGainFileListItem* sub_item2 = item->firstChild(); sub_item2 != 0; sub_item2 = sub_item2->nextSibling() ) {
                            sub_item2->queued = true;
                            sub_item2->repaint();
                            sub_item2->mode = ReplayGainFileListItem::force;
                        }
                        break;
                    }
                }
            }
        }
    }
    startProcess();
}

void ReplayGainFileList::removeSelectedItemsGain()
{
    // iterate through all items and remove the replay gain from all selected ones
    for( ReplayGainFileListItem* item = firstChild(); item != 0; item = item->nextSibling() ) {
        if( item->type() == ReplayGainFileListItem::File ) {
            if( item->isSelected() && !item->addingReplayGain ) {
                item->queued = true;
                item->repaint();
                item->mode = ReplayGainFileListItem::remove;
            }
        }
        else {
            if( item->isSelected() && !item->addingReplayGain ) {
                item->queued = true;
                item->repaint();
                item->mode = ReplayGainFileListItem::remove;
                for( ReplayGainFileListItem* sub_item = item->firstChild(); sub_item != 0; sub_item = sub_item->nextSibling() ) {
                    sub_item->queued = true;
                    sub_item->repaint();
                    sub_item->mode = ReplayGainFileListItem::remove;
                }
            }
            else {
                for( ReplayGainFileListItem* sub_item = item->firstChild(); sub_item != 0; sub_item = sub_item->nextSibling() ) {
                    if( sub_item->isSelected() && !sub_item->addingReplayGain ) {
                        sub_item->queued = true;
                        sub_item->repaint();
                        sub_item->mode = ReplayGainFileListItem::remove;
                    }
                }
            }
        }
    }
    startProcess();
}

void ReplayGainFileList::calcReplayGain( ReplayGainFileListItem* item )
{
    logID = logger->registerProcess( KURL::encode_string(item->text(columnByName(i18n("File")))) );
    logger->log( logID, "Mime Type: " + item->mimeType );
    logger->log( logID, i18n("Applying Replay Gain") );

    TQStringList fileList;
    bool force = false;
    if( mode & ReplayGainFileListItem::force ) force = true;
    TQString album_gain = "";
    bool skip = true;

    timeCount = 0;
    file = 0;
    files = 0;

    if( item->type() == ReplayGainFileListItem::Album ) {
        item->queued = false;
        item->addingReplayGain = true;
        item->repaint();
        for( ReplayGainFileListItem* sub_item = item->firstChild(); sub_item != 0; sub_item = sub_item->nextSibling() ) {
            if( sub_item->queued && sub_item->mode & ReplayGainFileListItem::force ) force = true; // NOTE can this be replaced by checking item?
            sub_item->queued = false;
            sub_item->addingReplayGain = true;
            sub_item->repaint();

            fileList += sub_item->filePathName;
            files++;
            timeCount += sub_item->time;

            TQString current_gain = sub_item->text( columnByName(i18n("Album")) );
            if( album_gain == "" ) album_gain = current_gain;
            else if( album_gain != current_gain ) skip = false;
        }

        if( !skip || album_gain == i18n("Unknown") || force ) {
            if( force ) {
                replayGain->apply( fileList, item->mimeType, process, logID, ReplayGain::Mode(ReplayGain::calc_album|ReplayGain::force) );
            }
            else {
                replayGain->apply( fileList, item->mimeType, process, logID );
            }
        }
        else {
            logger->processCompleted( logID, 0 );
            item->addingReplayGain = false;
            item->repaint();
            for( ReplayGainFileListItem* sub_item = item->firstChild(); sub_item != 0; sub_item = sub_item->nextSibling() ) {
                sub_item->addingReplayGain = false;
                sub_item->repaint();
            }
            processNextFile();
        }
    }
    else {
        if( item->queued && item->mode & ReplayGainFileListItem::force ) force = true;

        item->queued = false;
        item->addingReplayGain = true;
        item->repaint();

        files = 1;
        timeCount = item->time;

        if( force ) {
            replayGain->apply( item->filePathName, item->mimeType, process, logID, ReplayGain::Mode(ReplayGain::calc_album|ReplayGain::force) );
        }
        else {
            replayGain->apply( item->filePathName, item->mimeType, process, logID );
        }
    }
}

void ReplayGainFileList::removeReplayGain( ReplayGainFileListItem* item )
{
    logID = logger->registerProcess( KURL::encode_string(item->text(columnByName(i18n("File")))) );
    logger->log( logID, "Mime Type: " + item->mimeType );
    logger->log( logID, i18n("Removing Replay Gain") );

    if( item->type() == ReplayGainFileListItem::File ) {
        item->queued = false;
        item->addingReplayGain = true;
        item->repaint();
        timeCount = item->time;
        replayGain->apply( item->filePathName, item->mimeType, process, logID, ReplayGain::remove );
    }
    else {
        item->queued = false;
        item->repaint();
        processNextFile();
    }
}

void ReplayGainFileList::calcAllReplayGain( bool force )
{
    queue = true;
    if( force ) mode = ReplayGainFileListItem::force;
    else mode = ReplayGainFileListItem::Mode(0x0000);
    startProcess();
}

void ReplayGainFileList::removeAllReplayGain()
{
    queue = true;
    mode = ReplayGainFileListItem::remove;
    startProcess();
}

void ReplayGainFileList::cancelProcess()
{
    queue = false;
    if( process->isRunning() )
    {
        bool ret = process->kill( SIGKILL );
        if( ret ) {
            logger->log( logID, i18n("Killing process ...") );
        }
        else {
            logger->log( logID, i18n("Killing process failed. Stopping after files are completed ...") );
        }
    }
}

void ReplayGainFileList::startProcess()
{
    emit processStarted();
    processing = true;
    time = 0;

    for( ReplayGainFileListItem* item = firstChild(); item != 0; item = item->nextSibling() ) {
        if( item->type() == ReplayGainFileListItem::File ) {
            if( queue ) {
                time += item->time;
            }
            else if( item->queued ) {
                time += item->time;
            }
        }
        else {
            for( ReplayGainFileListItem* sub_item = item->firstChild(); sub_item != 0; sub_item = sub_item->nextSibling() ) {
                if( queue ) {
                    time += sub_item->time;
                }
                else if( sub_item->queued ) {
                    time += sub_item->time;
                }
            }
        }
    }

    emit updateProgress( 0, 100 );
    if( !tUpdateProgress->isActive() ) {
        tUpdateProgress->start( 200 ); // TODO use config value
    }

    currentItem = 0;
    processNextFile();
}

void ReplayGainFileList::processNextFile()
{
    percent = 0;
    lastPercent = 0;

    ReplayGainFileListItem* currentSubItem = 0;

    if( !currentItem ) { currentItem = firstChild(); }
    else if( currentItem->type() == ReplayGainFileListItem::File && currentItem->parent() == 0 ) { currentItem = currentItem->nextSibling(); }
    else if( currentItem->type() == ReplayGainFileListItem::Album ) { currentItem = currentItem->nextSibling(); }
    else { currentSubItem = currentItem->nextSibling(); currentItem = currentItem->parent(); if( !currentSubItem ) { currentItem = currentItem->nextSibling(); } }

    for( ReplayGainFileListItem* item = currentItem; item != 0; item = item->nextSibling() ) {
        if( item->type() == ReplayGainFileListItem::File ) {
            if( queue ) {
                currentItem = item;
                if( mode & ReplayGainFileListItem::remove ) removeReplayGain( item );
                else calcReplayGain( item );
                return;
            }
            else if( item->queued ) {
                currentItem = item;
                if( item->mode & ReplayGainFileListItem::remove ) removeReplayGain( item );
                else calcReplayGain( item );
                return;
            }
        }
        else {
            if( queue ) {
                currentItem = item;
                if( mode & ReplayGainFileListItem::remove ) {}
                else { calcReplayGain( item ); return; }
            }
            else if( item->queued ) {
                currentItem = item;
                if( item->mode & ReplayGainFileListItem::remove ) { item->queued = false; }
                else { calcReplayGain( item ); return; }
            }

            if( !currentSubItem ) currentSubItem = item->firstChild();
            for( ReplayGainFileListItem* sub_item = currentSubItem; sub_item != 0; sub_item = sub_item->nextSibling() ) {
                if( queue ) {
                    currentItem = sub_item;
                    if( mode & ReplayGainFileListItem::remove ) removeReplayGain( sub_item );
                    return;
                }
                else if( sub_item->queued ) {
                    currentItem = sub_item;
                    if( sub_item->mode & ReplayGainFileListItem::remove ) removeReplayGain( sub_item );
                    return;
                }
            }

            currentSubItem = 0;
        }
    }
    queue = false;
    tUpdateProgress->stop();
    processedTime = 0;
    processing = false;
    emit processStopped();
}

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

    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( logID, " " + i18n("Output") + ": " + log_data );

    ReplayGainPlugin* plugin = config->replaygainForFormat( currentItem->mimeType );
    if( plugin == 0 ) { // shouldn't happen
        logger->log( logID, " NULL POINTER: ReplayGainScanner::processOutput( ... ) / plugin" );
        return;
    }
    if( plugin->info.name == i18n("built-in") ) { // shouldn't happen // TODO implement a check for this
        logger->log( logID, " Backend is an encoder" );
        return;
    }

    TQString outputPattern = ( files > 1 ) ? plugin->replaygain.output_multiple : plugin->replaygain.output_single;
    //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" ); // for compatibility with old plugins
        sscanf( data, outputPattern.local8Bit().data(), &iPercent );
    }
    /*else if( outputPattern.find("%t") != -1 ) { // NOTE a little bit complicated and not necessary
        outputPattern.replace( "%t", "%i" );
        sscanf( data, outputPattern, &iTime );
        iPercent = iTime * 100 / currentItem->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
        //lastOutputTimer.start();
        if( files > 1 ) {
            if( iPercent < lastPercent ) file++;
            lastPercent = iPercent;
            percent = file * 100 / files + iPercent / files;
        }
        else {
            percent = iPercent;
        }
    }
}

void ReplayGainFileList::processExit( TDEProcess* proc )
{
    logger->processCompleted( logID, ( proc->signalled() ) ? -1 : 0 );

    for( ReplayGainFileListItem* item = firstChild(); item != 0; item = item->nextSibling() ) {
        if( item->type() == ReplayGainFileListItem::File ) {
            if( item->addingReplayGain ) {
                processedTime += item->time;
                item->addingReplayGain = false;
                item->repaint();
                item->updateReplayGainCells( tagEngine->readTags(KURL::decode_string(item->filePathName)) );
            }
            if( item->queued && proc->signalled() ) {
                item->queued = false;
                item->repaint();
            }
        }
        else {
            if( item->addingReplayGain ) {
                item->addingReplayGain = false;
                item->repaint();
                for( ReplayGainFileListItem* sub_item = item->firstChild(); sub_item != 0; sub_item = sub_item->nextSibling() ) {
                    processedTime += sub_item->time;
                    sub_item->addingReplayGain = false;
                    sub_item->repaint();
                    sub_item->updateReplayGainCells( tagEngine->readTags(KURL::decode_string(sub_item->filePathName)) );
                }
            }
            if( item->queued && proc->signalled() ) {
                item->queued = false;
                item->repaint();
                for( ReplayGainFileListItem* sub_item = item->firstChild(); sub_item != 0; sub_item = sub_item->nextSibling() ) {
                    sub_item->queued = false;
                    sub_item->repaint();
                }
            }
            for( ReplayGainFileListItem* sub_item = item->firstChild(); sub_item != 0; sub_item = sub_item->nextSibling() ) {
                if( sub_item->addingReplayGain ) {
                    processedTime += sub_item->time;
                    sub_item->addingReplayGain = false;
                    sub_item->repaint();
                    sub_item->updateReplayGainCells( tagEngine->readTags(KURL::decode_string(sub_item->filePathName)) );
                }
                if( sub_item->queued && proc->signalled() ) {
                    sub_item->queued = false;
                    sub_item->repaint();
                }
            }
        }
    }
    if( proc->signalled() ) {
        queue = false;
        tUpdateProgress->stop();
        processedTime = 0;
        processing = false;
        emit processStopped();
        return;
    }
    else {
        processNextFile();
    }
}

void ReplayGainFileList::update()
{
    emit updateProgress( int(processedTime) + percent * int(timeCount) / 100, int(time) );
}

#include "replaygainfilelist.moc"
