/***************************************************************************
    begin                : Thu Oct 28 2004
    copyright            : (C) 2004 by Michael Pyne
                         : (c) 2003 Frerich Raabe <raabe@kde.org>
    email                : michael.pyne@kdemail.net
***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <algorithm>

#include <kdebug.h>
#include <kcombobox.h>
#include <kurl.h>
#include <kurlrequester.h>
#include <kiconloader.h>
#include <knuminput.h>
#include <kstandarddirs.h>
#include <tdeio/netaccess.h>
#include <tdeconfigbase.h>
#include <tdeconfig.h>
#include <tdeglobal.h>
#include <klineedit.h>
#include <tdelocale.h>
#include <kpushbutton.h>
#include <tdeapplication.h>
#include <tdemessagebox.h>
#include <ksimpleconfig.h>

#include <tqfile.h>
#include <tqhbox.h>
#include <tqvbox.h>
#include <tqscrollview.h>
#include <tqobjectlist.h>
#include <tqtimer.h>
#include <tqregexp.h>
#include <tqcheckbox.h>
#include <tqdir.h>
#include <tqlabel.h>
#include <tqlayout.h>
#include <tqsignalmapper.h>
#include <tqheader.h>

#include "tag.h"
#include "filehandle.h"
#include "filerenamer.h"
#include "exampleoptions.h"
#include "playlistitem.h"
#include "playlist.h"
#include "coverinfo.h"

class ConfirmationDialog : public KDialogBase
{
public:
    ConfirmationDialog(const TQMap<TQString, TQString> &files,
                       TQWidget *parent = 0, const char *name = 0)
        : KDialogBase(parent, name, true, i18n("Warning"), Ok | Cancel)
    {
        TQVBox *vbox = makeVBoxMainWidget();
        TQHBox *hbox = new TQHBox(vbox);

        TQLabel *l = new TQLabel(hbox);
        l->setPixmap(SmallIcon("messagebox_warning", 32));

        l = new TQLabel(i18n("You are about to rename the following files. "
                            "Are you sure you want to continue?"), hbox);
        hbox->setStretchFactor(l, 1);

        TDEListView *lv = new TDEListView(vbox);

        lv->addColumn(i18n("Original Name"));
        lv->addColumn(i18n("New Name"));

        int lvHeight = 0;

        TQMap<TQString, TQString>::ConstIterator it = files.begin();
        for(; it != files.end(); ++it) {
            TDEListViewItem *i = it.key() != it.data()
                ? new TDEListViewItem(lv, it.key(), it.data())
                : new TDEListViewItem(lv, it.key(), i18n("No Change"));
            lvHeight += i->height();
        }

        lvHeight += lv->horizontalScrollBar()->height() + lv->header()->height();
        lv->setMinimumHeight(TQMIN(lvHeight, 400));
        resize(TQMIN(width(), 500), TQMIN(minimumHeight(), 400));
    }
};

//
// Implementation of ConfigCategoryReader
//

ConfigCategoryReader::ConfigCategoryReader() : CategoryReaderInterface(),
    m_currentItem(0)
{
    TDEConfigGroup config(TDEGlobal::config(), "FileRenamer");

    TQValueList<int> categoryOrder = config.readIntListEntry("CategoryOrder");
    unsigned categoryCount[NumTypes] = { 0 }; // Keep track of each category encountered.

    // Set a default:

    if(categoryOrder.isEmpty())
        categoryOrder << Artist << Album << Title << Track;

    TQValueList<int>::ConstIterator catIt = categoryOrder.constBegin();
    for(; catIt != categoryOrder.constEnd(); ++catIt)
    {
        unsigned catCount = categoryCount[*catIt]++;
        TagType category = static_cast<TagType>(*catIt);
        CategoryID catId(category, catCount);

        m_options[catId] = TagRenamerOptions(catId);
        m_categoryOrder << catId;
    }

    m_folderSeparators.resize(m_categoryOrder.count() - 1, false);

    TQValueList<int> checkedSeparators = config.readIntListEntry("CheckedDirSeparators");

    TQValueList<int>::ConstIterator it = checkedSeparators.constBegin();
    for(; it != checkedSeparators.constEnd(); ++it) {
        unsigned index = static_cast<unsigned>(*it);
        if(index < m_folderSeparators.count())
            m_folderSeparators[index] = true;
    }

    m_musicFolder = config.readPathEntry("MusicFolder", "${HOME}/music");
    m_separator = config.readEntry("Separator", " - ");
}

TQString ConfigCategoryReader::categoryValue(TagType type) const
{
    if(!m_currentItem)
        return TQString();

    Tag *tag = m_currentItem->file().tag();

    switch(type) {
    case Track:
        return TQString::number(tag->track());

    case Year:
        return TQString::number(tag->year());

    case Title:
        return tag->title();

    case Artist:
        return tag->artist();

    case Album:
        return tag->album();

    case Genre:
        return tag->genre();

    default:
        return TQString();
    }
}

TQString ConfigCategoryReader::prefix(const CategoryID &category) const
{
    return m_options[category].prefix();
}

TQString ConfigCategoryReader::suffix(const CategoryID &category) const
{
    return m_options[category].suffix();
}

TagRenamerOptions::EmptyActions ConfigCategoryReader::emptyAction(const CategoryID &category) const
{
    return m_options[category].emptyAction();
}

TQString ConfigCategoryReader::emptyText(const CategoryID &category) const
{
    return m_options[category].emptyText();
}

TQValueList<CategoryID> ConfigCategoryReader::categoryOrder() const
{
    return m_categoryOrder;
}

TQString ConfigCategoryReader::separator() const
{
    return m_separator;
}

TQString ConfigCategoryReader::musicFolder() const
{
    return m_musicFolder;
}

int ConfigCategoryReader::trackWidth(unsigned categoryNum) const
{
    return m_options[CategoryID(Track, categoryNum)].trackWidth();
}

bool ConfigCategoryReader::hasFolderSeparator(unsigned index) const
{
    if(index >= m_folderSeparators.count())
        return false;
    return m_folderSeparators[index];
}

bool ConfigCategoryReader::isDisabled(const CategoryID &category) const
{
    return m_options[category].disabled();
}

//
// Implementation of FileRenamerWidget
//

FileRenamerWidget::FileRenamerWidget(TQWidget *parent) :
    FileRenamerBase(parent), CategoryReaderInterface(),
    m_exampleFromFile(false)
{
    TQLabel *temp = new TQLabel(0);
    m_exampleText->setPaletteBackgroundColor(temp->paletteBackgroundColor());
    delete temp;

    layout()->setMargin(0); // We'll be wrapped by KDialogBase
    
    // This must be created before createTagRows() is called.

    m_exampleDialog = new ExampleOptionsDialog(this);

    createTagRows();
    loadConfig();

    // Add correct text to combo box.
    m_category->clear();
    for(unsigned i = StartTag; i < NumTypes; ++i) {
        TQString category = TagRenamerOptions::tagTypeText(static_cast<TagType>(i));
        m_category->insertItem(category);
    }

    connect(m_exampleDialog, TQT_SIGNAL(signalShown()), TQT_SLOT(exampleDialogShown()));
    connect(m_exampleDialog, TQT_SIGNAL(signalHidden()), TQT_SLOT(exampleDialogHidden()));
    connect(m_exampleDialog, TQT_SIGNAL(dataChanged()), TQT_SLOT(dataSelected()));
    connect(m_exampleDialog, TQT_SIGNAL(fileChanged(const TQString &)),
            this,            TQT_SLOT(fileSelected(const TQString &)));

    exampleTextChanged();
}

void FileRenamerWidget::loadConfig()
{
    TQValueList<int> checkedSeparators;
    TDEConfigGroup config(TDEGlobal::config(), "FileRenamer");

    for(unsigned i = 0; i < m_rows.count(); ++i)
        m_rows[i].options = TagRenamerOptions(m_rows[i].category);

    checkedSeparators = config.readIntListEntry("CheckedDirSeparators");

    TQValueList<int>::ConstIterator it = checkedSeparators.begin();
    for(; it != checkedSeparators.end(); ++it) {
        unsigned separator = static_cast<unsigned>(*it);
        if(separator < m_folderSwitches.count())
            m_folderSwitches[separator]->setChecked(true);
    }

    TQString url = config.readPathEntry("MusicFolder", "${HOME}/music");
    m_musicFolder->setURL(url);

    m_separator->setCurrentText(config.readEntry("Separator", " - "));
}

void FileRenamerWidget::saveConfig()
{
    TDEConfigGroup config(TDEGlobal::config(), "FileRenamer");
    TQValueList<int> checkedSeparators;
    TQValueList<int> categoryOrder;

    for(unsigned i = 0; i < m_rows.count(); ++i) {
        unsigned rowId = idOfPosition(i); // Write out in GUI order, not m_rows order
        m_rows[rowId].options.saveConfig(m_rows[rowId].category.categoryNumber);
        categoryOrder += m_rows[rowId].category.category;
    }

    for(unsigned i = 0; i < m_folderSwitches.count(); ++i)
        if(m_folderSwitches[i]->isChecked() == true)
            checkedSeparators += i;

    config.writeEntry("CheckedDirSeparators", checkedSeparators);
    config.writeEntry("CategoryOrder", categoryOrder);
    config.writePathEntry("MusicFolder", m_musicFolder->url());
    config.writeEntry("Separator", m_separator->currentText());

    config.sync();
}

FileRenamerWidget::~FileRenamerWidget()
{
}

unsigned FileRenamerWidget::addRowCategory(TagType category)
{
    static TQPixmap up   = SmallIcon("go-up");
    static TQPixmap down = SmallIcon("go-down");

    // Find number of categories already of this type.
    unsigned categoryCount = 0;
    for(unsigned i = 0; i < m_rows.count(); ++i)
        if(m_rows[i].category.category == category)
            ++categoryCount;

    Row row;

    row.category = CategoryID(category, categoryCount);
    row.position = m_rows.count();
    unsigned id = row.position;

    TQHBox *frame = new TQHBox(m_mainFrame);
    frame->setPaletteBackgroundColor(frame->paletteBackgroundColor().dark(110));

    row.widget = frame;
    frame->setFrameShape(TQFrame::Box);
    frame->setLineWidth(1);
    frame->setMargin(3);

    m_mainFrame->setStretchFactor(frame, 1);

    TQVBox *buttons = new TQVBox(frame);
    buttons->setFrameStyle(TQFrame::Plain | TQFrame::Box);
    buttons->setLineWidth(1);

    row.upButton = new KPushButton(buttons);
    row.downButton = new KPushButton(buttons);

    row.upButton->setPixmap(up);
    row.downButton->setPixmap(down);
    row.upButton->setFlat(true);
    row.downButton->setFlat(true);

    upMapper->connect(row.upButton, TQT_SIGNAL(clicked()), TQT_SLOT(map()));
    upMapper->setMapping(TQT_TQOBJECT(row.upButton), id);
    downMapper->connect(row.downButton, TQT_SIGNAL(clicked()), TQT_SLOT(map()));
    downMapper->setMapping(TQT_TQOBJECT(row.downButton), id);

    TQString labelText = TQString("<b>%1</b>").arg(TagRenamerOptions::tagTypeText(category));
    TQLabel *label = new TQLabel(labelText, frame);
    frame->setStretchFactor(label, 1);
    label->setAlignment(AlignCenter);

    TQVBox *options = new TQVBox(frame);
    row.enableButton = new KPushButton(i18n("Remove"), options);
    toggleMapper->connect(row.enableButton, TQT_SIGNAL(clicked()), TQT_SLOT(map()));
    toggleMapper->setMapping(TQT_TQOBJECT(row.enableButton), id);

    row.optionsButton = new KPushButton(i18n("Options"), options);
    mapper->connect(row.optionsButton, TQT_SIGNAL(clicked()), TQT_SLOT(map()));
    mapper->setMapping(TQT_TQOBJECT(row.optionsButton), id);

    row.widget->show();
    m_rows.append(row);

    // Disable add button if there's too many rows.
    if(m_rows.count() == MAX_CATEGORIES)
        m_insertCategory->setEnabled(false);

    return id;
}

void FileRenamerWidget::moveSignalMappings(unsigned oldId, unsigned newId)
{
    mapper->setMapping(TQT_TQOBJECT(m_rows[oldId].optionsButton), newId);
    downMapper->setMapping(TQT_TQOBJECT(m_rows[oldId].downButton), newId);
    upMapper->setMapping(TQT_TQOBJECT(m_rows[oldId].upButton), newId);
    toggleMapper->setMapping(TQT_TQOBJECT(m_rows[oldId].enableButton), newId);
}

bool FileRenamerWidget::removeRow(unsigned id)
{
    if(id >= m_rows.count()) {
        kdWarning(65432) << "Trying to remove row, but " << id << " is out-of-range.\n";
        return false;
    }

    if(m_rows.count() == 1) {
        kdError(65432) << "Can't remove last row of File Renamer.\n";
        return false;
    }

    // Remove widget.  Don't delete it since it appears TQSignalMapper may still need it.
    m_rows[id].widget->deleteLater();
    m_rows[id].widget = 0;
    m_rows[id].enableButton = 0;
    m_rows[id].upButton = 0;
    m_rows[id].optionsButton = 0;
    m_rows[id].downButton = 0;

    unsigned checkboxPosition = 0; // Remove first checkbox.

    // If not the first row, remove the checkbox before it.
    if(m_rows[id].position > 0)
        checkboxPosition = m_rows[id].position - 1;

    // The checkbox is contained within a layout widget, so the layout
    // widget is the one the needs to die.
    delete m_folderSwitches[checkboxPosition]->parent();
    m_folderSwitches.erase(&m_folderSwitches[checkboxPosition]);

    // Go through all the rows and if they have the same category and a
    // higher categoryNumber, decrement the number.  Also update the
    // position identifier.
    for(unsigned i = 0; i < m_rows.count(); ++i) {
        if(i == id)
            continue; // Don't mess with ourself.

        if((m_rows[id].category.category == m_rows[i].category.category) &&
           (m_rows[id].category.categoryNumber < m_rows[i].category.categoryNumber))
        {
            --m_rows[i].category.categoryNumber;
        }

        // Items are moving up.
        if(m_rows[id].position < m_rows[i].position)
            --m_rows[i].position;
    }

    // Every row after the one we delete will have a different identifier, since
    // the identifier is simply its index into m_rows.  So we need to re-do the
    // signal mappings for the affected rows.
    for(unsigned i = id + 1; i < m_rows.count(); ++i)
        moveSignalMappings(i, i - 1);

    m_rows.erase(&m_rows[id]);

    // Make sure we update the buttons of affected rows.
    m_rows[idOfPosition(0)].upButton->setEnabled(false);
    m_rows[idOfPosition(m_rows.count() - 1)].downButton->setEnabled(false);

    // We can insert another row now, make sure GUI is updated to match.
    m_insertCategory->setEnabled(true);

    TQTimer::singleShot(0, this, TQT_SLOT(exampleTextChanged()));
    return true;
}

void FileRenamerWidget::addFolderSeparatorCheckbox()
{
    TQWidget *temp = new TQWidget(m_mainFrame);
    TQHBoxLayout *l = new TQHBoxLayout(temp);

    TQCheckBox *cb = new TQCheckBox(i18n("Insert folder separator"), temp);
    m_folderSwitches.append(cb);
    l->addWidget(cb, 0, AlignCenter);
    cb->setChecked(false);

    connect(cb, TQT_SIGNAL(toggled(bool)),
            TQT_SLOT(exampleTextChanged()));

    temp->show();
}

void FileRenamerWidget::createTagRows()
{
    TDEConfigGroup config(TDEGlobal::config(), "FileRenamer");
    TQValueList<int> categoryOrder = config.readIntListEntry("CategoryOrder");

    if(categoryOrder.isEmpty())
        categoryOrder << Artist << Album << Artist << Title << Track;
    
    // Setup arrays.
    m_rows.reserve(categoryOrder.count());
    m_folderSwitches.reserve(categoryOrder.count() - 1);

    mapper       = new TQSignalMapper(TQT_TQOBJECT(this), "signal mapper");
    toggleMapper = new TQSignalMapper(TQT_TQOBJECT(this), "toggle mapper");
    upMapper     = new TQSignalMapper(TQT_TQOBJECT(this), "up button mapper");
    downMapper   = new TQSignalMapper(TQT_TQOBJECT(this), "down button mapper");

    connect(mapper,       TQT_SIGNAL(mapped(int)), TQT_SLOT(showCategoryOption(int)));
    connect(toggleMapper, TQT_SIGNAL(mapped(int)), TQT_SLOT(slotRemoveRow(int)));
    connect(upMapper,     TQT_SIGNAL(mapped(int)), TQT_SLOT(moveItemUp(int)));
    connect(downMapper,   TQT_SIGNAL(mapped(int)), TQT_SLOT(moveItemDown(int)));

    m_mainFrame = new TQVBox(m_mainView->viewport());
    m_mainFrame->setMargin(10);
    m_mainFrame->setSpacing(5);

    m_mainView->addChild(m_mainFrame);
    m_mainView->setResizePolicy(TQScrollView::AutoOneFit);

    // OK, the deal with the categoryOrder variable is that we need to create
    // the rows in the order that they were saved in (the order given by categoryOrder).
    // The signal mappers operate according to the row identifier.  To find the position of
    // a row given the identifier, use m_rows[id].position.  To find the id of a given
    // position, use idOfPosition(position).

    TQValueList<int>::ConstIterator it = categoryOrder.constBegin();

    for(; it != categoryOrder.constEnd(); ++it) {
        if(*it < StartTag || *it >= NumTypes) {
            kdError(65432) << "Invalid category encountered in file renamer configuration.\n";
            continue;
        }

        if(m_rows.count() == MAX_CATEGORIES) {
            kdError(65432) << "Maximum number of File Renamer tags reached, bailing.\n";
            break;
        }

        TagType i = static_cast<TagType>(*it);

        addRowCategory(i);

        // Insert the directory separator checkbox if this isn't the last
        // item.

        TQValueList<int>::ConstIterator dup(it);

        // Check for last item
        if(++dup != categoryOrder.constEnd())
            addFolderSeparatorCheckbox();
    }

    m_rows.first().upButton->setEnabled(false);
    m_rows.last().downButton->setEnabled(false);

    // If we have maximum number of categories already, don't let the user
    // add more.
    if(m_rows.count() >= MAX_CATEGORIES)
        m_insertCategory->setEnabled(false);
}

void FileRenamerWidget::exampleTextChanged()
{
    // Just use .mp3 as an example

    if(m_exampleFromFile && (m_exampleFile.isEmpty() || 
                             !FileHandle(m_exampleFile).tag()->isValid()))
    {
        m_exampleText->setText(i18n("No file selected, or selected file has no tags."));
        return;
    }

    m_exampleText->setText(FileRenamer::fileName(*this) + ".mp3");
}

TQString FileRenamerWidget::fileCategoryValue(TagType category) const
{
    FileHandle file(m_exampleFile);
    Tag *tag = file.tag();

    switch(category) {
    case Track:
        return TQString::number(tag->track());

    case Year:
        return TQString::number(tag->year());

    case Title:
        return tag->title();

    case Artist:
        return tag->artist();

    case Album:
        return tag->album();

    case Genre:
        return tag->genre();

    default:
        return TQString();
    }
}

TQString FileRenamerWidget::categoryValue(TagType category) const
{
    if(m_exampleFromFile)
        return fileCategoryValue(category);

    const ExampleOptions *example = m_exampleDialog->widget();

    switch (category) {
    case Track:
        return example->m_exampleTrack->text();

    case Year:
        return example->m_exampleYear->text();

    case Title:
        return example->m_exampleTitle->text();

    case Artist:
        return example->m_exampleArtist->text();

    case Album:
        return example->m_exampleAlbum->text();

    case Genre:
        return example->m_exampleGenre->text();

    default:
        return TQString();
    }
}

TQValueList<CategoryID> FileRenamerWidget::categoryOrder() const
{
    TQValueList<CategoryID> list;

    // Iterate in GUI row order.
    for(unsigned i = 0; i < m_rows.count(); ++i) {
        unsigned rowId = idOfPosition(i);

        list += m_rows[rowId].category;
    }

    return list;
}

bool FileRenamerWidget::hasFolderSeparator(unsigned index) const
{
    if(index >= m_folderSwitches.count())
        return false;
    return m_folderSwitches[index]->isChecked();
}

void FileRenamerWidget::moveItem(unsigned id, MovementDirection direction)
{
    TQWidget *l = m_rows[id].widget;
    unsigned bottom = m_rows.count() - 1;
    unsigned pos = m_rows[id].position;
    unsigned newPos = (direction == MoveUp) ? pos - 1 : pos + 1;

    // Item we're moving can't go further down after this.

    if((pos == (bottom - 1) && direction == MoveDown) ||
       (pos == bottom && direction == MoveUp))
    {
        unsigned idBottomRow = idOfPosition(bottom);
        unsigned idAboveBottomRow = idOfPosition(bottom - 1);

        m_rows[idBottomRow].downButton->setEnabled(true);
        m_rows[idAboveBottomRow].downButton->setEnabled(false);
    }

    // We're moving the top item, do some button switching.

    if((pos == 0 && direction == MoveDown) || (pos == 1 && direction == MoveUp)) {
        unsigned idTopItem = idOfPosition(0);
        unsigned idBelowTopItem = idOfPosition(1);

        m_rows[idTopItem].upButton->setEnabled(true);
        m_rows[idBelowTopItem].upButton->setEnabled(false);
    }

    // This is the item we're swapping with.

    unsigned idSwitchWith = idOfPosition(newPos);
    TQWidget *w = m_rows[idSwitchWith].widget;

    // Update the table of widget rows.

    std::swap(m_rows[id].position, m_rows[idSwitchWith].position);

    // Move the item two spaces above/below its previous position.  It has to
    // be 2 spaces because of the checkbox.

    TQBoxLayout *layout = dynamic_cast<TQBoxLayout *>(m_mainFrame->layout());

    layout->remove(l);
    layout->insertWidget(2 * newPos, l);

    // Move the top item two spaces in the opposite direction, for a similar
    // reason.

    layout->remove(w);
    layout->insertWidget(2 * pos, w);
    layout->invalidate();

    TQTimer::singleShot(0, this, TQT_SLOT(exampleTextChanged()));
}

unsigned FileRenamerWidget::idOfPosition(unsigned position) const
{
    if(position >= m_rows.count()) {
        kdError(65432) << "Search for position " << position << " out-of-range.\n";
        return static_cast<unsigned>(-1);
    }

    for(unsigned i = 0; i < m_rows.count(); ++i)
        if(m_rows[i].position == position)
            return i;

    kdError(65432) << "Unable to find identifier for position " << position << endl;
    return static_cast<unsigned>(-1);
}

unsigned FileRenamerWidget::findIdentifier(const CategoryID &category) const
{
    for(unsigned index = 0; index < m_rows.count(); ++index)
        if(m_rows[index].category == category)
            return index;

    kdError(65432) << "Unable to find match for category " <<
        TagRenamerOptions::tagTypeText(category.category) <<
        ", number " << category.categoryNumber << endl;

    return MAX_CATEGORIES;
}

void FileRenamerWidget::enableAllUpButtons()
{
    for(unsigned i = 0; i < m_rows.count(); ++i)
        m_rows[i].upButton->setEnabled(true);
}

void FileRenamerWidget::enableAllDownButtons()
{
    for(unsigned i = 0; i < m_rows.count(); ++i)
        m_rows[i].downButton->setEnabled(true);
}

void FileRenamerWidget::showCategoryOption(int id)
{
    TagOptionsDialog *dialog = new TagOptionsDialog(this, m_rows[id].options, m_rows[id].category.categoryNumber);

    if(dialog->exec() == TQDialog::Accepted) {
        m_rows[id].options = dialog->options();
        exampleTextChanged();
    }

    delete dialog;
}

void FileRenamerWidget::moveItemUp(int id)
{
    moveItem(static_cast<unsigned>(id), MoveUp);
}

void FileRenamerWidget::moveItemDown(int id)
{
    moveItem(static_cast<unsigned>(id), MoveDown);
}

void FileRenamerWidget::toggleExampleDialog()
{
    m_exampleDialog->setShown(!m_exampleDialog->isShown());
}

void FileRenamerWidget::insertCategory()
{
    TagType category = TagRenamerOptions::tagFromCategoryText(m_category->currentText());
    if(category == Unknown) {
        kdError(65432) << "Trying to add unknown category somehow.\n";
        return;
    }

    // We need to enable the down button of the current bottom row since it
    // can now move down.
    unsigned idBottom = idOfPosition(m_rows.count() - 1);
    m_rows[idBottom].downButton->setEnabled(true);

    addFolderSeparatorCheckbox();

    // Identifier of new row.
    unsigned id = addRowCategory(category);

    // Set its down button to be disabled.
    m_rows[id].downButton->setEnabled(false);

    m_mainFrame->layout()->invalidate();
    m_mainView->update();

    // Now update according to the code in loadConfig().
    m_rows[id].options = TagRenamerOptions(m_rows[id].category);
    exampleTextChanged();
}

void FileRenamerWidget::exampleDialogShown()
{
    m_showExample->setText(i18n("Hide Renamer Test Dialog"));
}

void FileRenamerWidget::exampleDialogHidden()
{
    m_showExample->setText(i18n("Show Renamer Test Dialog"));
}

void FileRenamerWidget::fileSelected(const TQString &file)
{
    m_exampleFromFile = true;
    m_exampleFile = file;
    exampleTextChanged();
}

void FileRenamerWidget::dataSelected()
{
    m_exampleFromFile = false;
    exampleTextChanged();
}

TQString FileRenamerWidget::separator() const
{
    return m_separator->currentText();
}

TQString FileRenamerWidget::musicFolder() const
{
    return m_musicFolder->url();
}

void FileRenamerWidget::slotRemoveRow(int id)
{
    // Remove the given identified row.
    if(!removeRow(id))
        kdError(65432) << "Unable to remove row " << id << endl;
}

//
// Implementation of FileRenamer
//

FileRenamer::FileRenamer()
{
}

void FileRenamer::rename(PlaylistItem *item)
{
    PlaylistItemList list;
    list.append(item);

    rename(list);
}

void FileRenamer::rename(const PlaylistItemList &items)
{
    ConfigCategoryReader reader;
    TQStringList errorFiles;
    TQMap<TQString, TQString> map;
    TQMap<TQString, PlaylistItem *> itemMap;

    for(PlaylistItemList::ConstIterator it = items.begin(); it != items.end(); ++it) {
        reader.setPlaylistItem(*it);
        TQString oldFile = (*it)->file().absFilePath();
        TQString extension = (*it)->file().fileInfo().extension(false);
        TQString newFile = fileName(reader) + "." + extension;

        if(oldFile != newFile) {
            map[oldFile] = newFile;
            itemMap[oldFile] = *it;
        }
    }

    if(itemMap.isEmpty() || ConfirmationDialog(map).exec() != TQDialog::Accepted)
        return;

    TDEApplication::setOverrideCursor(TQt::waitCursor);
    for(TQMap<TQString, TQString>::ConstIterator it = map.begin();
        it != map.end(); ++it)
    {
        if(moveFile(it.key(), it.data())) {
            itemMap[it.key()]->setFile(it.data());
            itemMap[it.key()]->refresh();

            setFolderIcon(it.data(), itemMap[it.key()]);
        }
        else
            errorFiles << i18n("%1 to %2").arg(it.key()).arg(it.data());

        processEvents();
    }
    TDEApplication::restoreOverrideCursor();

    if(!errorFiles.isEmpty())
        KMessageBox::errorList(0, i18n("The following rename operations failed:\n"), errorFiles);
}

bool FileRenamer::moveFile(const TQString &src, const TQString &dest)
{
    kdDebug(65432) << "Moving file " << src << " to " << dest << endl;

    if(src == dest)
        return false;

    // Escape URL.
    KURL srcURL = KURL::fromPathOrURL(src);
    KURL dstURL = KURL::fromPathOrURL(dest);

    // Clean it.
    srcURL.cleanPath();
    dstURL.cleanPath();

    // Make sure it is valid.
    if(!srcURL.isValid() || !dstURL.isValid())
        return false;

    // Get just the directory.
    KURL dir = dstURL;
    dir.setFileName(TQString());

    // Create the directory.
    if(!TDEStandardDirs::exists(dir.path()))
        if(!TDEStandardDirs::makeDir(dir.path())) {
            kdError() << "Unable to create directory " << dir.path() << endl;
            return false;
        }

    // Move the file.
    return TDEIO::NetAccess::file_move(srcURL, dstURL);
}

void FileRenamer::setFolderIcon(const KURL &dst, const PlaylistItem *item)
{
    if(item->file().tag()->album().isEmpty() ||
       !item->file().coverInfo()->hasCover())
    {
        return;
    }

    KURL dstURL = dst;
    dstURL.cleanPath();

    // Split path, and go through each path element.  If a path element has
    // the album information, set its folder icon.
    TQStringList elements = TQStringList::split("/", dstURL.directory());
    TQString path;

    for(TQStringList::ConstIterator it = elements.begin(); it != elements.end(); ++it) {
        path.append("/" + (*it));

        kdDebug() << "Checking path: " << path << endl;
        if((*it).find(item->file().tag()->album()) != -1 &&
           !TQFile::exists(path + "/.directory"))
        {
            // Seems to be a match, let's set the folder icon for the current
            // path.  First we should write out the file.
            
            TQPixmap thumb = item->file().coverInfo()->pixmap(CoverInfo::Thumbnail);
            thumb.save(path + "/.juk-thumbnail.png", "PNG");

            KSimpleConfig config(path + "/.directory");
            config.setGroup("Desktop Entry");

            if(!config.hasKey("Icon")) {
                config.writeEntry("Icon", TQString("%1/.juk-thumbnail.png").arg(path));
                config.sync();
            }

            return;
        }
    }
}

/**
 * Returns iterator pointing to the last item enabled in the given list with
 * a non-empty value (or is required to be included).
 */
TQValueList<CategoryID>::ConstIterator lastEnabledItem(const TQValueList<CategoryID> &list,
                                                   const CategoryReaderInterface &interface)
{
    TQValueList<CategoryID>::ConstIterator it = list.constBegin();
    TQValueList<CategoryID>::ConstIterator last = list.constEnd();

    for(; it != list.constEnd(); ++it) {
        if(interface.isRequired(*it) || (!interface.isDisabled(*it) &&
              !interface.categoryValue((*it).category).isEmpty()))
        {
            last = it;
        }
    }

    return last;
}

TQString FileRenamer::fileName(const CategoryReaderInterface &interface)
{
    const TQValueList<CategoryID> categoryOrder = interface.categoryOrder();
    const TQString separator = interface.separator();
    const TQString folder = interface.musicFolder();
    TQValueList<CategoryID>::ConstIterator lastEnabled;
    unsigned i = 0;
    TQStringList list;
    TQChar dirSeparator = TQChar(TQDir::separator());

    // Use lastEnabled to properly handle folder separators.
    lastEnabled = lastEnabledItem(categoryOrder, interface);
    bool pastLast = false; // Toggles to true once we've passed lastEnabled.

    for(TQValueList<CategoryID>::ConstIterator it = categoryOrder.begin();
            it != categoryOrder.end();
            ++it, ++i)
    {
        if(it == lastEnabled)
            pastLast = true;

        if(interface.isDisabled(*it))
            continue;

        TQString value = interface.value(*it);

        // The user can use the folder separator checkbox to add folders, so don't allow
        // slashes that slip in to accidentally create new folders.  Should we filter this
        // back out when showing it in the GUI?
        value.replace('/', "%2f");

        if(!pastLast && interface.hasFolderSeparator(i))
            value.append(dirSeparator);

        if(interface.isRequired(*it) || !value.isEmpty())
            list.append(value);
    }

    // Construct a single string representation, handling strings ending in
    // '/' specially

    TQString result;

    for(TQStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); /* Empty */) {
        result += *it;

        ++it; // Manually advance iterator to check for end-of-list.

        // Add separator unless at a directory boundary
        if(it != list.constEnd() &&
           !(*it).startsWith(dirSeparator) && // Check beginning of next item.
           !result.endsWith(dirSeparator))
        {
            result += separator;
        }
    }

    return TQString(folder + dirSeparator + result);
}

#include "filerenamer.moc"
