/***************************************************************************
                          dcfilebrowser.cpp  -  description
                             -------------------
    begin                : Fre Nov 29 2002
    copyright            : (C) 2002 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

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

#include "dcfilebrowser.h"
#include "config.h"
#include <QApplication>
#include <Q3Header>
#include <Q3ListView>
#include <QLabel>
#include <QCursor>
#include <QFileDialog>
#include <QUrl>
#include <QToolButton>
#include <QComboBox>
#include <QClipboard>
#include <QMessageBox>
#include <QStyle>
#include <QMdiArea>
#include <QList>
#include <QInputDialog>
#include <QtAlgorithms>
#include <QColor>

#include <set>
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#include "dcmenuhandler.h"
#include "dcconfig.h"
#include "dciconloader.h"
#include "dcfilebrowseritems.h"
#include "dcfilelistdecompressor.h"
#include "dcevent.h"

// for user menu commands
#include "dcclient.h"
#include "dcconnectionmanager.h"

// for searching by TTH
#include "dchubsearch.h"

#include "dcfiletool.h"

#include <dclib/dcos.h>
#include <dclib/core/cbytearray.h>
#include <dclib/core/cdir.h>
#include <dclib/core/cxml.h>

// for file type icons
#include <dclib/cfilemanager.h>

#include "dcguiutils.h"

DCFileBrowser::QStringHash DCFileBrowser::download_dirs;

/** */
DCFileBrowser::DCFileBrowser(QWidget * parent, bool allowopen)
: QWidget(parent) {
    setupUi(this);

    m_bAllowOpen = allowopen;
    m_bAllowDownload = true;

    // set caption
    setWindowTitle(tr("Filebrowser"));
    setWindowIcon(DCIconLoader::Instance()->GetPixmap(eiVIEW_SIDETREE));

    // set width mode
    ListView_DIRECTORY->setColumnWidthMode(0, Q3ListView::Maximum);
    ListView_FILES->setColumnWidthMode(0, Q3ListView::Maximum);
    ListView_FILES->setColumnWidthMode(1, Q3ListView::Maximum);
    ListView_FILES->setColumnWidthMode(2, Q3ListView::Maximum);
    ListView_FILES->setColumnWidthMode(3, Q3ListView::Maximum);
    ListView_FILES->setColumnWidthMode(4, Q3ListView::Maximum);

    // set right alignment on size columns
    ListView_DIRECTORY->setColumnAlignment(1, Qt::AlignRight);
    ListView_FILES->setColumnAlignment(1, Qt::AlignRight);
    ListView_FILES->setColumnAlignment(2, Qt::AlignRight);

    // hide the column header - only if not using size column
    if (DCConfig::Instance()->GetFolderSizesInLeftPane() == false) {
        ListView_DIRECTORY->header()->hide();
    }

    m_nShareSize = 0;
    m_nFileCount = 0;

    m_pDecomp = 0;
    m_pListData = 0;
    m_pFoundDir = 0;
    m_nFoundFile = 0;

    ToolButton_OPEN->setIcon(DCIconLoader::Instance()->GetPixmap(eiOPEN));
    ToolButton_SAVE->setIcon(DCIconLoader::Instance()->GetPixmap(eiSAVE));
    ToolButton_GOTO_USER->setIcon(DCIconLoader::Instance()->GetPixmap(eiUSERS));
    ToolButton_FIND->setIcon(DCIconLoader::Instance()->GetPixmap(eiFILEFIND));
    ToolButton_NEXT->setIcon(DCIconLoader::Instance()->GetPixmap(eiNEXT));

    connect(ListView_DIRECTORY, SIGNAL(selectionChanged()), this, SLOT(slotCurrentChangedDirectory()));
    connect(ListView_DIRECTORY, SIGNAL(expanded(Q3ListViewItem*)), this, SLOT(slotExpandedDirectory(Q3ListViewItem*)));
    connect(ListView_DIRECTORY, SIGNAL(collapsed(Q3ListViewItem*)), this, SLOT(slotCollapsedDirectory(Q3ListViewItem*)));
    connect(ListView_DIRECTORY, SIGNAL(contextMenuRequested(Q3ListViewItem *, const QPoint &, int)), this, SLOT(slotRightButtonClickedDirectory(Q3ListViewItem*, const QPoint &, int)));
    connect(ListView_FILES, SIGNAL(contextMenuRequested(Q3ListViewItem *, const QPoint &, int)), this, SLOT(slotRightButtonClickedFiles(Q3ListViewItem*, const QPoint &, int)));
    connect(ListView_FILES, SIGNAL(doubleClicked(Q3ListViewItem *, const QPoint &, int)), this, SLOT(slotItemDoubleClicked(Q3ListViewItem *, const QPoint &, int)));

    connect(ToolButton_OPEN, SIGNAL(clicked()), this, SLOT(slotFileOpen()));
    connect(ToolButton_SAVE, SIGNAL(clicked()), this, SLOT(slotFileSave()));
    connect(ToolButton_GOTO_USER, SIGNAL(clicked()), this, SLOT(slotGotoUser()));
    connect(ToolButton_FIND, SIGNAL(clicked()), this, SLOT(slotFind()));
    connect(ToolButton_NEXT, SIGNAL(clicked()), this, SLOT(slotFindNext()));

    ToolButton_OPEN->setEnabled(m_bAllowOpen);
    ToolButton_GOTO_USER->setEnabled(false);

    setAttribute(Qt::WA_DeleteOnClose);

    m_pContainer = NULL;

}

/** */
DCFileBrowser::~DCFileBrowser() {
    ClearView();

    delete m_pDecomp;
    delete m_pListData;

}

/** */
void DCFileBrowser::customEvent(QEvent * event) {
    if (event->type() == EVENT_OPEN_FILELIST_FOLDER) {
        DCFileBrowserListItem * item = ((DC_OpenFilelistFolderEvent*) event)->m_pItem;

        ListView_DIRECTORY->clearSelection();
        ListView_DIRECTORY->ensureItemVisible(item);
        ListView_DIRECTORY->setSelected(item, true);
        ListView_DIRECTORY->setCurrentItem(item);

        event->accept();
    } else {
        event->ignore();
    }
}

/** */
void DCFileBrowser::slotThreadFinishedLoading() {
    /* name may have been updated */
    m_sFileName = QString::fromAscii(m_pDecomp->getFileName().Data());

    CXml * xml = m_pDecomp->takeXml();
    if (xml) {
        CByteArray * ba = m_pDecomp->takeCompressed();
        if (ba) {
            delete m_pListData;
            m_pListData = ba;
        }

        ContinueInitTree(xml, NULL);
        delete xml;
    } else {
        CString * text = m_pDecomp->takeText();
        if (text) {
            CByteArray * ba = m_pDecomp->takeCompressed();
            if (ba) {
                delete m_pListData;
                m_pListData = ba;
            }

            ContinueInitTree(NULL, text);
            delete text;
        } else {
            QMessageBox::critical(
                    this,
                    tr("Failed to load filelist"),
                    tr("Unable to load") + " " + m_sFileName
                    );
            setWindowTitle(tr("Filebrowser"));
            EnableGuiElements();
        }
    }

    disconnect(m_pDecomp, SIGNAL(finished()), this, SLOT(slotThreadFinishedLoading()));
    delete m_pDecomp;
    m_pDecomp = NULL;
}

/** */
void DCFileBrowser::ClearView() {
    m_pFoundDir = 0;
    m_nFoundFile = 0;
    m_FoundFiles.clear();

    ListView_FILES->clear();

    Q3ListViewItemIterator it(ListView_DIRECTORY);

    for (; it.current(); it++) {
        // no longer have auto-delete with QT4 QList
        for (int i = 0; i < ((DCFileBrowserListItem*) it.current())->m_pFileList.size(); i++) {
            delete ((DCFileBrowserListItem*) it.current())->m_pFileList.at(i);
        }

        ((DCFileBrowserListItem*) it.current())->m_pFileList.clear();
    }

    ListView_DIRECTORY->clear();
}

/** */
void DCFileBrowser::slotFileOpen() {
    QString file;
    if (m_sFileName.isEmpty()) {
        file = QString::fromAscii(DCConfig::Instance()->GetFileListPath().Data());
    } else {
        file = m_sFileName;
    }

    QString s = QFileDialog::getOpenFileName(
            this,
            tr("Choose a file"),
            file,
            tr("Modern XML Filelists") + " (*.xml.bz2);;" +
            tr("Modern XML Filelists (uncompressed)") + " (*.xml);;" +
            tr("Old BZ2 Text Filelists") + " (*.bz2);;" +
            tr("Very old HE3 Text Filelists") + " (*.DcLst);;" +
            tr("Old Valknut Filelists") + " (*.filelist);;" +
            tr("All files") + " (*)"
            );

    if (!s.isEmpty()) {
        InitTree(QString::null, QString::null, QString::null, s);
    }
}

/** */
void DCFileBrowser::slotFileSave() {
    if ((m_pListData == NULL) || (m_pListData->Size() == 0)) {
        QMessageBox::critical(
                this,
                tr("Cannot save file"),
                tr("No data available to save.")
                );
        return;
    }

    QString s = QFileDialog::getSaveFileName(
            this,
            tr("Choose a filename to save under"),
            m_sFileName
            );

    if (s.isEmpty()) {
        return;
    }

    if (m_pListData->SaveToFile(s.toAscii().constData()) == false) {
        QMessageBox::critical(
                this,
                tr("Error saving file"),
                tr("Failed to save") + " " + s
                );
    }
}

/** */
void DCFileBrowser::slotCurrentChangedDirectory() {
    DCFileItem * FileItem;
    QString s;
    QList<Q3ListViewItem*> selitems;
    ulonglong size = 0;
    int items = 0;
    int citems = 0;
    Q3ListViewItem * item = 0;
    Q3ListViewItem * select = 0;

    // clear filelist
    ListView_FILES->clear();

    // check if only one dir selected
    if (DCGuiUtils::SelectedItems(ListView_DIRECTORY, selitems) == 1) {
        item = selitems.first();

        for (int i = 0; i < ((DCFileBrowserListItem*) item)->m_pFileList.size(); i++) {
            FileItem = ((DCFileBrowserListItem*) item)->m_pFileList.at(i);

            if ((DCConfig::Instance()->GetFoldersInRightPane() == false) && FileItem->m_bIsDir) {
                // do not show it
                continue;
            }

            size += FileItem->m_nSize;

            DC_FBListViewItem *item1 = new DC_FBListViewItem(ListView_FILES);
            item1->myvalue = FileItem->m_nSize;
            item1->mycol = 1;
            item1->m_bSortTop = false;
            item1->pDirItem = ((DCFileBrowserListItem*) item);

            if (FileItem->m_bIsDir) {
                s = tr("Folder");
                item1->setPixmap(0, m_FolderPixmap);

                if (DCConfig::Instance()->GetFoldersInRightPaneOnTop()) {
                    item1->m_bSortTop = true;
                }
            } else {
                s = CDir::Extension(FileItem->m_sName.toAscii().constData()).Data();

                if (!s.isEmpty()) {
                    s += " ";
                    s += tr("file");
                }

                eFileTypes type = CFileManager::Instance()->GetFileType(FileItem->m_sName.toAscii().constData());
                QPixmap icon;

                switch (type) {
                    case eftMP3:
                        icon = DCIconLoader::Instance()->GetPixmap(eiFILETYPE_MP3);
                        break;
                    case eftARCHIVE:
                        icon = DCIconLoader::Instance()->GetPixmap(eiFILETYPE_ARCHIVE);
                        break;
                    case eftDOCUMENT:
                        icon = DCIconLoader::Instance()->GetPixmap(eiFILETYPE_DOCUMENT);
                        break;
                    case eftAPPLICATION:
                        icon = DCIconLoader::Instance()->GetPixmap(eiFILETYPE_APPLICATION);
                        break;
                    case eftPICTURE:
                        icon = DCIconLoader::Instance()->GetPixmap(eiFILETYPE_PICTURE);
                        break;
                    case eftVIDEO:
                        icon = DCIconLoader::Instance()->GetPixmap(eiFILETYPE_VIDEO);
                        break;
                    default:
                        icon = DCIconLoader::Instance()->GetPixmap(eiFILETYPE_UNKNOWN);
                        break;
                }

                item1->setPixmap(0, icon);

                items++;

                if (!m_sJumpTo.isEmpty() && (FileItem->m_sName == m_sJumpTo)) {
                    select = item1;
                    m_sJumpTo = "";
                }
            }

            std::set<unsigned long> *my_set = CFileManager::Instance()->SearchHash(FileItem->m_sHash.toUtf8().data());

            item1->setText(0, QString(FileItem->m_sName));

            QColor color(0, 0, 0);

            if (my_set && my_set->size() > 0) {

                if (!m_bAllowDownload && !FileItem->m_bIsDir) { //own files
                    QColor cl = palette().text().color();
                    item1->setColor(cl);
                } else {
                    color.setRed(0);
                    color.setGreen(0x80);
                    color.setBlue(0x80);

                    item1->setColor(color);
                }

                delete my_set;

            } else if (!m_bAllowDownload) {
                color.setRed(0);
                color.setGreen(0);
                color.setBlue(0x80);
                item1->setColor(color);
            }else{
                color = DCGuiApp::Instance()->palette().text().color();
                item1->setColor(color);
            }


            item1->setText(1, DCGuiUtils::GetSizeString(FileItem->m_nSize));
            item1->setText(2, QString::number(FileItem->m_nSize));
            item1->setText(3, s);
            item1->setText(4, FileItem->m_sHash);
        }

        if (select) {
            ListView_FILES->clearSelection();
            ListView_FILES->ensureItemVisible(select);
            ListView_FILES->setSelected(select, true);
            ListView_FILES->setCurrentItem(select);
        }

        /* do not use it again */
        m_sJumpTo = "";
    }

    if (item) {
        citems = item->childCount();
    }

    TextLabel_STATUS->setText(QString().setNum(items + citems) + " " + tr("Items") + " - " +
            QString().setNum(items) + " " + tr("Files") +
            " (" + DCGuiUtils::GetSizeString(size) + " " + tr("Total") + ") - " +
            QString().setNum(citems) + " " + tr("Directories"));
    TextLabel_STATUS->setToolTip(QString::number(size) + " B");
}

/** */
void DCFileBrowser::slotExpandedDirectory(Q3ListViewItem * item) {
    if (item->parent() != NULL)
        item->setPixmap(0, DCIconLoader::Instance()->GetPixmap(eiFOLDER_BLUE_OPEN));
}

/** */
void DCFileBrowser::slotCollapsedDirectory(Q3ListViewItem * item) {
    if (item->parent() != NULL)
        item->setPixmap(0, DCIconLoader::Instance()->GetPixmap(eiFOLDER_BLUE));
}

/** */
void DCFileBrowser::slotRightButtonClickedDirectory(Q3ListViewItem * item, const QPoint &, int column) {
    CreateMenu(item, true, column);
}

/** */
void DCFileBrowser::slotRightButtonClickedFiles(Q3ListViewItem * item, const QPoint &, int column) {
    CreateMenu(item, false, column);
}

/** */
void DCFileBrowser::AddDirectory(Q3ListViewItem * item, QString name) {
    ((DCFileBrowserListItem*) item)->m_sName = name;

    item->setText(0, name);
    item->setPixmap(0, m_FolderPixmap);

    ((DCFileBrowserListItem*) item)->m_nBytes = 0;
}

/** */
void DCFileBrowser::AddFile(Q3ListViewItem * item, DCFileItem * fileitem) {
    // increment filecounter
    if (fileitem->m_bIsDir == false) {
        m_nFileCount++;
        m_nShareSize += fileitem->m_nSize;
    }

    // add fileitem to filelist
    ((DCFileBrowserListItem*) item)->m_pFileList.append(fileitem);

    // add to folder contents size
    ((DCFileBrowserListItem*) item)->m_nBytes += fileitem->m_nSize;

    DCFileItem * pFileItem = ((DCFileBrowserListItem*) item)->m_pFileItem;

    if (pFileItem != 0) {
        pFileItem->m_nSize = ((DCFileBrowserListItem*) item)->m_nBytes;
    }
}

/** */
void DCFileBrowser::InitTree(QString nick, QString hubname, QString hubhost, QString filename, QString jumpto, QStringList dirs) {
    if (filename.isEmpty()) {
        return;
    }

    if (m_pDecomp != 0) {
        QMessageBox::critical(
                this,
                tr("Cannot open list now"),
                tr("Filelist browser is busy")
                );
        return;
    }

    // store values
    m_sNick = nick;
    m_sHubName = hubname;
    m_sHubHost = hubhost;
    m_sJumpTo = jumpto;
    m_lDownloadDirs = dirs;

    // first check if filename is our own list
    if (filename == "/dev/null/ownfilelist") {
        m_bAllowDownload = false;
        m_sFileName = "Own Filelist"; /* temp value */

        DisableGuiElements();
        m_pDecomp = new DCFilelistDecompressor(this, CString());
    } else {
        m_bAllowDownload = true;
        m_sFileName = filename;

        if (!QFile::exists(filename)) {
            QMessageBox::critical(
                    this,
                    tr("Error opening filelist"),
                    tr("File") + " " + filename + " " + tr("does not exist.")
                    );

            return;
        }

        DisableGuiElements();
        m_pDecomp = new DCFilelistDecompressor(this, filename.toAscii().constData());
    }

    connect(m_pDecomp, SIGNAL(finished()), this, SLOT(slotThreadFinishedLoading()));
    m_pDecomp->start();
}

/** */
void DCFileBrowser::ContinueInitTree(CXml * xml, CString * text) {
    /* A bz2 file that decompressed to zero bytes probably... */
    if ((xml == NULL) && ((text == NULL) || (text->Length() == 0))) {
        QMessageBox::critical(
                this,
                tr("Error opening filelist"),
                m_sFileName + " " + tr("is likely not a valid filelist")
                );
        setWindowTitle(tr("Filebrowser"));
        EnableGuiElements();
        return;
    }

    Q3ListViewItem * item;

    // reset count values
    m_nShareSize = 0;
    m_nFileCount = 0;

    ClearView();

    // get folder pixmap
    m_FolderPixmap = DCIconLoader::Instance()->GetPixmap(eiFOLDER_BLUE);

    // disable updates
    ListView_DIRECTORY->setUpdatesEnabled(false);

    // parse filename

    QFileInfo fi(m_sFileName);
    QString fname = fi.fileName();

    if ((m_sNick.isEmpty()) || (m_sHubName.isEmpty()) || (m_sHubHost.isEmpty())) {
        int i = fname.indexOf('@');
        if (i != -1) {
            m_sNick = fname.left(i);
            m_sHubHost = fname.mid(i + 1);
            m_sHubHost.remove(".xml.bz2");
            m_sHubHost.remove(".bz2");
            m_sHubHost.remove(".xml");
            m_sHubHost.remove(".DcLst");
            m_sHubHost.remove(".filelist");

            // replace _ with : before port number
            int i2 = m_sHubHost.lastIndexOf('_');
            if (i2 != -1) {
                bool ok;
                m_sHubHost.mid(i2 + 1).toInt(&ok);
                if (ok) {
                    m_sHubHost = m_sHubHost.left(i2) + ":" + m_sHubHost.mid(i2 + 1);
                }
            }

            if (m_sHubName.isEmpty()) {
                m_sHubName = m_sHubHost;
            }
        }
    }

    // set dialog caption

    if (m_sNick.isEmpty()) {
        fname.remove(".xml.bz2");
        fname.remove(".bz2");
        fname.remove(".xml");
        fname.remove(".DcLst");
        fname.remove(".filelist");
        m_sNick = fname;
    }

    if (m_sHubName.isEmpty()) {
        setWindowTitle(m_sNick + " - " + tr("Filebrowser"));
    } else {
        setWindowTitle(m_sNick + " - " + tr("Filebrowser") + " [" + m_sHubName + "]");
    }

    // create root item
    item = new DCFileBrowserListItem(ListView_DIRECTORY);
    item->setText(0, tr("Root Directory"));
    item->setPixmap(0, DCIconLoader::Instance()->GetPixmap(eiFOLDER_RED));
    ((DCFileBrowserListItem*) item)->m_pFileItem = 0;

    if (xml) {
        InitXMLTree(item, xml);
    } else {
        InitTXTTree(item, text);
    }

    // update summary
    TextLabel_SUMMARY->setText(QString().setNum(m_nFileCount) + " " + tr("Files") + " (" +
            DCGuiUtils::GetSizeString(m_nShareSize) + " " + tr("Total") + ")");

    // update tooltip
    TextLabel_SUMMARY->setToolTip(QString().setNum(m_nShareSize) + " B");

    // update view
    ListView_DIRECTORY->setUpdatesEnabled(true);
    ListView_DIRECTORY->triggerUpdate();

    // open first tree
    ListView_DIRECTORY->setOpen(ListView_DIRECTORY->firstChild(), true);

    EnableGuiElements();

    /* download any directories */
    for (QStringList::const_iterator it = m_lDownloadDirs.constBegin(); it != m_lDownloadDirs.constEnd(); ++it) {
        DownloadDirectory(*it);
    }
    m_lDownloadDirs.clear();

    /* jump to target file or folder */
    if (!m_sJumpTo.isEmpty()) {
        JumpTo(m_sJumpTo);
    }

}

/** */
void DCFileBrowser::DisableGuiElements() {
    setWindowTitle(tr("Processing filelist..."));
    ToolButton_FIND->setEnabled(false);
    ToolButton_NEXT->setEnabled(false);
    ToolButton_OPEN->setEnabled(false);
    ToolButton_SAVE->setEnabled(false);
    ToolButton_GOTO_USER->setEnabled(false);
    ListView_DIRECTORY->setEnabled(false);
    ListView_FILES->setEnabled(false);
}

void DCFileBrowser::EnableGuiElements() {
    ToolButton_FIND->setEnabled(true);
    ToolButton_NEXT->setEnabled(true);
    ToolButton_OPEN->setEnabled(m_bAllowOpen);
    ToolButton_SAVE->setEnabled(true);
    ListView_DIRECTORY->setEnabled(true);
    ListView_FILES->setEnabled(true);

    if (!m_bAllowOpen || m_sNick.isEmpty() || m_sHubHost.isEmpty()) {
        ToolButton_GOTO_USER->setEnabled(false);
    } else {
        ToolButton_GOTO_USER->setEnabled(true);
    }
}

/** */
void DCFileBrowser::InitTXTTree(Q3ListViewItem * item, CString * data) {
    QString s1, sname, slength;
    long i, j, d;
    int depth;

    i = j = 0;

    while ((i = data->Find("\xD\xA", j)) >= 0) {
        s1 = data->Mid(j, i - j).Data();

        depth = 0;
        while ((s1.indexOf("\x9", depth)) != -1)
            depth++;
        s1.remove("\x9");

        if (item)
            while (depth < item->depth())
                if ((item = item->parent()) == 0)
                    break;

        if (s1.isEmpty())
            s1 = "\\";

        if (!s1.isEmpty()) {
            d = s1.lastIndexOf("|");

            if (d != -1) // parse file entry
            {
                DCFileItem * FileItem = new DCFileItem();

                FileItem->m_sName = s1.mid(0, d);
                FileItem->m_nSize = s1.right(s1.length() - d - 1).toULongLong();
                FileItem->m_bIsDir = false;

                AddFile(item, FileItem);
            } else // parse dir entry
            {
                DCFileItem * FileItem = new DCFileItem();

                FileItem->m_sName = s1;
                FileItem->m_nSize = 0;
                FileItem->m_bIsDir = true;

                AddFile(item, FileItem);

                if (item == 0)
                    item = new DCFileBrowserListItem(ListView_DIRECTORY);
                else
                    item = new DCFileBrowserListItem(item);

                ((DCFileBrowserListItem*) item)->m_pFileItem = FileItem;

                AddDirectory(item, s1);
            }
        }

        j = i + 2;
    }

    // fixup the folder sizes
    Q3ListViewItem * cur = ListView_DIRECTORY->firstChild();
    while (cur) {
        CalcDirSize(cur);

        cur = cur->nextSibling();
    }
}

/** */
void DCFileBrowser::InitXMLTree(Q3ListViewItem * item, CXml * xml) {
    if (xml && xml->DocFirstChild()) {
        // maybe we should just parse the first FileListing?
        do {
            if ((xml->Name() == "FileListing") && xml->FirstChild()) {
                ParseXMLTree(xml, item);
                xml->Parent();
            }
        } while (xml->NextNode());
    }
}

/** */
void DCFileBrowser::ParseXMLTree(CXml * xml, Q3ListViewItem * item) {
    CString s;

    do {
        if (xml->Name() == "File") {
            DCFileItem * FileItem = new DCFileItem();
            s = xml->Prop("Name");
            FileItem->m_sName = QString::fromUtf8(s.Data());
            FileItem->m_nSize = xml->Prop("Size").asULL();
            FileItem->m_sHash = xml->Prop("TTH").Data();
            FileItem->m_bIsDir = false;

            AddFile(item, FileItem);
        } else if (xml->Name() == "Directory") {
            Q3ListViewItem * item1;

            item1 = new DCFileBrowserListItem(item);
            ((DCFileBrowserListItem*) item1)->m_pFileItem = 0;

            s = xml->Prop("Name");
            AddDirectory(item1, QString::fromUtf8(s.Data()));

            if (xml->FirstChild()) {
                ParseXMLTree(xml, item1);
                xml->Parent();
            }

            DCFileItem * FileItem = new DCFileItem();
            FileItem->m_sName = QString::fromUtf8(s.Data());
            FileItem->m_nSize = ((DCFileBrowserListItem*) item1)->m_nBytes;
            FileItem->m_bIsDir = true;
            AddFile(item, FileItem);
            ((DCFileBrowserListItem*) item1)->m_pFileItem = FileItem;

            if (DCConfig::Instance()->GetFolderSizesInLeftPane()) {
                item1->setText(1, DCGuiUtils::GetSizeString(((DCFileBrowserListItem*) item1)->m_nBytes));
            }
        }
    } while (xml->NextNode());
}

/** */
QString DCFileBrowser::CreateRemotePath(Q3ListViewItem * item) {
    QString s;
    Q3ListViewItem * pitem;

    if (!item) {
        return s;
    }

    pitem = item;
    s = ((DCFileBrowserListItem*) pitem)->m_sName;

    while ((pitem = pitem->parent()) != 0) {
        // check for root entry
        if (pitem->parent() != 0) {
            s = ((DCFileBrowserListItem*) pitem)->m_sName + "\\" + s;
        }
    }

    s.replace("\\\\\\\\", "\\");

    return s;
}

/** */
void DCFileBrowser::CreateMenu(Q3ListViewItem *, bool direntry, int column) {
    int i;
    QString s;
    ulonglong size;
    QString rootPath = QString::null;
    QString localName = QString::null;
    QList<Q3ListViewItem*> selitems;
    Q3ListViewItem * curitem;
    bool hasFolderItems = false;
    DCClient * client = 0;
    QMap<QAction*, DC_UserMenuCommand*> addedcommands;

    if (direntry) {
        i = DCGuiUtils::SelectedItems(ListView_DIRECTORY, selitems);
    } else {
        i = DCGuiUtils::SelectedItems(ListView_FILES, selitems);

        QListIterator<Q3ListViewItem*> it(selitems);

        Q3ListViewItem * cur = 0;

        while (it.hasNext()) {
            cur = it.next();

            if (cur->text(3) == tr("Folder")) {
                hasFolderItems = true;
                break;
            }
        }
    }

    // nothing on no selection
    if (i == 0) {
        return;
    } else {
        curitem = selitems.first();
    }

    /* TODO readadd test
            if ( direntry && (item->parent() == 0) )
            {
                    return;
            }
     */
    QMenu * m = new QMenu(this);

    QAction * dl = DCMenuHandler::addAction(m, emiDOWNLOAD, m_bAllowDownload);
    QMenu * dl_to = DCMenuHandler::addMenu(m, emiDOWNLOAD_TO, m_bAllowDownload, tr("Download to"));
    // disable 'download as' on directories
    QAction * dl_as = DCMenuHandler::addAction(m, emiDOWNLOAD_AS, (m_bAllowDownload && !direntry && !hasFolderItems && (i == 1)));
    QAction * dl_in = DCMenuHandler::addAction(m, emiDOWNLOAD_IN, (m_bAllowDownload && !direntry && !hasFolderItems));
    DCMenuHandler::addAction(m, emiSEPARATOR);
    QAction * search_tth = m->addAction(QIcon(DCIconLoader::Instance()->GetPixmap(eiFILEFIND)), tr("Search by TTH"));
    search_tth->setEnabled(m_bAllowOpen && !direntry && curitem && !(curitem->text(4).isEmpty()));
    DCMenuHandler::addAction(m, emiSEPARATOR);
    QAction * copycol = DCMenuHandler::addAction(m, emiCOPY_COLUMN_TO_CLIPBOARD, !direntry);
    QAction * copyrow = DCMenuHandler::addAction(m, emiCOPY_ROW_TO_CLIPBOARD, !direntry);
    QAction * copymagnet = DCMenuHandler::addAction(m, emiCOPYMAGNETLINK, (!direntry && !hasFolderItems));

    client = DCConnectionManager::Instance()->GetClientForHub(m_sHubName.toAscii().constData(), m_sHubHost.toAscii().constData());

    if (client != 0) {
        addedcommands = client->AddMenuCommands(m, euccFilelist);
    }

    QList<DCDownloadAlias*> alias_list = DCConfig::Instance()->GetDownloadAliases();
    QList<QAction*> d_to_actions;

    for (int i = 0; i < alias_list.size(); i++){
	QAction *t = DCMenuHandler::addAction(dl_to, emiDOWNLOAD_FOLDER, true, alias_list.at(i)->alias);
	t->setToolTip(alias_list.at(i)->path);
	t->setText(alias_list.at(i)->alias);

	d_to_actions << t;
    }

    dl_to->addSeparator();

    if (!DCFileBrowser::GetDownloadDirs().isEmpty()){
        QStringHash &hash = DCFileBrowser::GetDownloadDirs();
        QHashIterator<QString, QString> it(hash);

        while (it.hasNext()){
            it.next();

            QAction *t = DCMenuHandler::addAction(dl_to, emiDOWNLOAD_FOLDER, true);
            t->setToolTip(it.key());
            t->setText(it.value());

            d_to_actions << t;
        }
    }

    QAction *dl_to_browse = DCMenuHandler::addAction(dl_to, emiDOWNLOAD_AS, true, tr("Browse"));
    dl_to_browse->setText(tr("Browse"));

    QAction * chosen = m->exec(QCursor::pos());
    QString t_tip = "";

    if (d_to_actions.contains(chosen))
        t_tip = chosen->toolTip();

    delete m;

    if (chosen == copycol) {
        QString s;

        for (int c = 0; c < selitems.size(); c++) {
            curitem = selitems.at(c);
            s += curitem->text(column);
            s += "\n";
        }

        s = s.trimmed();
        QApplication::clipboard()->setText(s);
    } else if (chosen == copyrow) {
        int idx;
        QString s;

        for (int c = 0; c < selitems.size(); c++) {
            curitem = selitems.at(c);

            for (idx = 0; idx < ListView_FILES->columns(); idx++) {
                s += curitem->text(idx);
                s += " ";
            }

            s += "\n";
        }

        s = s.trimmed();
        QApplication::clipboard()->setText(s);
    } else if (chosen == copymagnet) {
        //magnet:?xt=urn:tree:tiger:EOSA5AGTL5SD3VWCF3R2OH2WMGXV3S3R7SYN4YA&xl=708780032&dn=FC-6-i386-disc1.iso
        QString all;

        for (int c = 0; c < selitems.size(); c++) {
            curitem = selitems.at(c);

            // add tth
            all += "magnet:?xt=urn:tree:tiger:";
            all += curitem->text(4);

            // add filesize
            all += "&xl=";
            all += curitem->text(2);

            // add filename
            all += "&dn=";

            QString filename = QString(QUrl::toPercentEncoding(curitem->text(0)));

            //fixme DC++ replaces spaces with + character
            //filename = filename.replace(' ','+');

            filename.replace("%20", "+");
            all += filename;

            all += "\n";
        }

        // remove trailing "\n"
        all = all.trimmed();

        QApplication::clipboard()->setText(all);
    }        // Check if the user wants a non-default destination
    else if (chosen == dl_to_browse) {
        rootPath = QFileDialog::getExistingDirectory(this, tr("Select destination"));
        if (rootPath.isEmpty())
            return;

        QStringHash &hash = DCFileBrowser::GetDownloadDirs();

        if (!hash.contains(rootPath))
                hash.insert(rootPath, QDir(rootPath).dirName());
    }        // Should the file be renamed
    else if (chosen == dl_as) {
        QString save_root = DCConfig::Instance()->GetDownloadFinishedFolder().Data();

        if (save_root.isEmpty() || save_root.isNull())
            save_root = DCConfig::Instance()->GetDownloadFolder().Data();

        rootPath = QFileDialog::getSaveFileName(this, tr("Select a filename"),
                                                save_root + "/" + selitems.first()->text(0),
                                                tr("All files (*.*)"));
        if (rootPath.isEmpty()) {
            return;
        }

        QFileInfo fi(rootPath);
        rootPath = fi.path();
        localName = fi.fileName();

        if (rootPath.isEmpty() || localName.isEmpty()) {
            return;
        }
    } else if (chosen == dl_in) {
        QString localrootpath;
        QString localname;
        QString localpath;
        QString remotePath;

        if (!(curitem = selitems.first()))
            return;

        size = ((DC_QListViewItem *) curitem)->myvalue;
        QString tth = curitem->text(4);

        // all files need equal size and TTH
        for (int c = 0; c < selitems.size(); c++) {
            curitem = selitems.at(c);

            if (size != ((DC_QListViewItem *) curitem)->myvalue)
                return;
            if (tth != curitem->text(4))
                return;
        }

        if (DCFileTool::SelectFileSource(this, size, tth, localname, localrootpath, localpath) == false) {
            return;
        }

        QString remotePathAndFile;
        for (int c = 0; c < selitems.size(); c++) {
            curitem = selitems.at(c);

            s = curitem->text(0);

            // get the full remote path
            remotePath = CreateRemotePath(GetDirItem(curitem));

            remotePathAndFile = remotePath;
            remotePathAndFile += "\\";
            remotePathAndFile += s;

            // add transfer to the waitlist
            DCFileTool::CheckFile(this, m_sNick.toAscii().constData(), m_sHubName.toAscii().constData(), m_sHubHost.toAscii().constData(),
                    remotePathAndFile.toAscii().constData(), localname.toAscii().constData(), localpath.toAscii().constData(), localrootpath.toAscii().constData(), eltFILE,
                    size, curitem->text(4).toAscii().constData(), true);
        }
    } else if (chosen == search_tth) {
        DCHubSearch * hubsearch = new DCHubSearch(DCConnectionManager::Instance()->GetMdiArea());
        hubsearch->SetSearchForFile(curitem->text(4), eftHASH);
        hubsearch->show();
        hubsearch->StartSearchWithPrompt();
    } else if (addedcommands.contains(chosen)) {
        DC_UserMenuCommand * umc = addedcommands[ chosen ];

        QString usercommand = umc->m_sCommand;
        QString origUserCommand = usercommand;
        QString fullfilename, filesize, filesizeshort, filetth, filetype;

        for (int c = 0; c < selitems.size(); c++) {
            // filelist browsers are only for the one nick
            if ((umc->m_nType == euctRawOnce) && (c > 0)) {
                break;
            }

            curitem = selitems.at(c);

            usercommand = client->replaceCommandTags(origUserCommand, m_sNick);

            if (usercommand.isEmpty()) {
                // had a %[line:reason] but dialog was cancelled
                continue;
            }

            if (direntry) {
                fullfilename = CreateRemotePath(curitem);
                filesize.setNum(((DCFileBrowserListItem*) curitem)->m_nBytes);
                filesizeshort = DCGuiUtils::GetSizeString(((DCFileBrowserListItem*) curitem)->m_nBytes);
                filetth = tr("None");
                filetype = tr("Directory");
            } else {
                fullfilename = CreateRemotePath(GetDirItem(curitem));
                filesize = curitem->text(2);
                filesizeshort = curitem->text(1);
                filetth = curitem->text(4);
                if (filetth.isEmpty()) {
                    filetth = tr("None");
                }

                if (curitem->text(3) == tr("Folder")) {
                    filetype = tr("Directory");
                } else {
                    filetype = tr("File");
                    fullfilename += "\\";
                    fullfilename += curitem->text(0);
                }
            }

            usercommand.replace("%[file]", fullfilename);
            usercommand.replace("%[fileFN]", fullfilename);

            usercommand.replace("%[filesize]", filesize);
            usercommand.replace("%[fileSI]", filesize);

            usercommand.replace("%[filesizeshort]", filesizeshort);
            usercommand.replace("%[fileSIshort]", filesizeshort);

            usercommand.replace("%[tth]", filetth);
            usercommand.replace("%[fileTR]", filetth);

            usercommand.replace("%[type]", filetype);

            //client->m_pHubChat->AddStatus( usercommand.toAscii().constData() );
            client->SendString(usercommand.toAscii().constData());
        }
    }

    if (d_to_actions.contains(chosen)){
	if ((rootPath = t_tip) == "")
	    return;
    }

    // Is this a download?
    if ((chosen == dl) || (chosen == dl_to_browse) || (chosen == dl_as) || t_tip != "") {
        for (int c = 0; c < selitems.size(); c++) {
            curitem = selitems.at(c);

            // directory download from left pane
            if (direntry) {
                s = curitem->text(0);

                DownloadPath(rootPath, s, QString::null, curitem);
            } else if (curitem->text(3) == tr("Folder")) // directory download from right pane
            {
                Q3ListViewItem * diritem = GetDirItem(curitem);

                if (diritem == 0) {
                    QMessageBox::critical(this, tr("Cannot download"), tr("Could not find the path for ") + curitem->text(0));
                } else {
                    DownloadPath(rootPath, diritem->text(0), QString::null, diritem);
                }
            }                // Single file download
            else {
                QString remotePath;
                QString remoteName;

                // set localname
                if (chosen != dl_as) {
                    localName = curitem->text(0);
                }

                // get the full remote path
                remotePath = CreateRemotePath(GetDirItem(curitem));

                remoteName = curitem->text(0);

                DownloadFile(rootPath, QString(), localName, remotePath, remoteName, ((DC_QListViewItem *) curitem)->myvalue, curitem->text(4));
            }
        }
    }
}

/** */
void DCFileBrowser::DownloadPath(QString rootPath, QString localPath, QString localName, Q3ListViewItem * item) {
    QString remotePath;
    DCFileItem * FileItem = 0;
    QString s;
    QString remoteFile;

    if (!item) {
        return;
    }

    // get the full remote path
    remotePath = CreateRemotePath(item);

    for (int i = 0; i < ((DCFileBrowserListItem*) item)->m_pFileList.size(); i++) {
        FileItem = ((DCFileBrowserListItem*) item)->m_pFileList.at(i);

        if (FileItem->m_bIsDir == false) {
            remoteFile = FileItem->m_sName;
            DownloadFile(rootPath, localPath, localName, remotePath,
                    remoteFile, FileItem->m_nSize, FileItem->m_sHash);
        }
    }

    item = item->firstChild();

    while (item) {
        s = item->text(0);

        DownloadPath(rootPath, localPath + "\\" + s, localName, item);
        item = item->nextSibling();
    }
}

/** */
void DCFileBrowser::DownloadFile(QString rootPath, QString localPath, QString localName,
        QString remotePath, QString remoteName, ulonglong size,
        QString hash) {
    // append filename to the remote path
    remotePath += "\\";
    remotePath += remoteName;

    if (localName == QString::null) {
        localName = remoteName;
    }


    DCFileTool::CheckFile(this, m_sNick.toAscii().constData(),
            m_sHubName.toAscii().constData(),
            m_sHubHost.toAscii().constData(),
            remotePath.toAscii().constData(),
            localName.toAscii().constData(),
            localPath.toAscii().constData(),
            rootPath.toAscii().constData(), eltFILE,
            size, hash.toAscii().constData());
}

/** Double click to download the item, copied from DCFileBrowser::CreateMenu */
void DCFileBrowser::slotItemDoubleClicked(Q3ListViewItem * item, const QPoint &, int) {
    QString rootPath = QString::null;
    QString localName = QString::null;

    if (item == 0) // null pointer check, just in case
    {
        return;
    }

    Q3ListViewItem * diritem = GetDirItem(item);

    if (diritem == 0) {
        QMessageBox::critical(this, tr("Cannot download"), tr("Could not find the path for ") + item->text(0));
        return;
    }

    if (item->text(3) == tr("Folder")) {
        // we cannot open the folder since that would delete the existing
        // Q3ListViewItems, which is not allowed in this slot and causes
        // random crashes

        /* Q3ListViewItem * diritem = ListView_DIRECTORY->currentItem();
		
        if ( diritem == 0 )
        {
                printf("Double click a folder needs a ListView_DIRECTORY->currentItem()\n");
                return;
        }
		
        for ( diritem = diritem->firstChild(); diritem != 0; diritem = diritem->nextSibling() )
        {
                if ( diritem->text(0) == item->text(0) )
                {
                        ListView_DIRECTORY->clearSelection();
                        ListView_DIRECTORY->ensureItemVisible( diritem );
                        ListView_DIRECTORY->setSelected( diritem, true );
                        ListView_DIRECTORY->setCurrentItem( diritem );
                        break;
                }
        } */

        if (DCConfig::Instance()->GetOpenFoldersInRightPane()) {
            QApplication::postEvent(this, new DC_OpenFilelistFolderEvent((DCFileBrowserListItem*) diritem));
        } else if (m_bAllowDownload) {
            // ask for confirmation since this is unusual behaviour
            int confirm = QMessageBox::question(
                    this,
                    tr("Download contents?"),
                    tr("Download the contents of \"") + diritem->text(0) + "\" ?",
                    QMessageBox::Yes | QMessageBox::No,
                    QMessageBox::No
                    );

            if (confirm == QMessageBox::Yes) {
                DownloadPath(rootPath, diritem->text(0), localName, diritem);
            }
        }
    } else if (m_bAllowDownload) {
        QString remotePath;
        QString remoteName;

        // get the full remote path
        remotePath = CreateRemotePath(diritem);

        remoteName = item->text(0);

        DownloadFile(rootPath, QString(), localName, remotePath, remoteName, ((DC_QListViewItem *) item)->myvalue, item->text(4));
    }
}

/** Only needed for non-xml filelists */
ulonglong DCFileBrowser::CalcDirSize(Q3ListViewItem * item) {
    ulonglong total = 0;

    DCFileBrowserListItem * cur = (DCFileBrowserListItem*) item;

    QListIterator<DCFileItem*> it(cur->m_pFileList);

    DCFileItem * current = 0;

    while (it.hasNext()) {
        current = it.next();

        if (current->m_bIsDir) {
            Q3ListViewItem * child = item->firstChild();

            while (child) {
                if (child->text(0) == current->m_sName) {
                    total += CalcDirSize(child);
                    break;
                }

                child = child->nextSibling();
            }
        } else {
            total += current->m_nSize;
        }
    }

    DCFileItem * pFileItem = cur->m_pFileItem;
    if (pFileItem != 0) {
        pFileItem->m_nSize = total;
    }

    cur->m_nBytes = total;

    if (DCConfig::Instance()->GetFolderSizesInLeftPane()) {
        cur->setText(1, DCGuiUtils::GetSizeString(total));
    }

    //printf( "CalcDirSize for %s = %lld\n", item->text(0).toAscii().constData(), total );

    return total;
}

/** */
Q3ListViewItem * DCFileBrowser::GetDirItem(Q3ListViewItem * item) {
    if (item == 0) {
        return 0;
    }

    Q3ListViewItem * diritem = ((DC_FBListViewItem*) item)->pDirItem;

    if (item->text(3) != tr("Folder")) {
        return diritem;
    }

    if (diritem == 0) {
        return 0;
    }

    for (diritem = diritem->firstChild(); diritem != 0; diritem = diritem->nextSibling()) {
        if (diritem->text(0) == item->text(0)) {
            return diritem;
        }
    }

    return 0;
}

/** */
bool DCFileBrowser::DownloadDirectory(QString dir) {
    bool res = false;
    bool found = false;
    QStringList dirs = dir.split("\\", QString::SkipEmptyParts);

    qApp->processEvents();

    Q3ListViewItem * curitem = ListView_DIRECTORY->firstChild();

    if (!curitem) {
        return res;
    }

    for (QStringList::const_iterator it = dirs.constBegin(); it != dirs.constEnd(); ++it) {
        curitem = curitem->firstChild();

        while (curitem) {
            if (curitem->text(0) == *it) {
                found = true;
                break;
            }

            curitem = curitem->nextSibling();
        }

        if (!found) {
            return res;
        } else {
            found = false;
        }
    }

    if (curitem) {
        DownloadPath(QString::null, curitem->text(0), QString::null, curitem);
        res = true;
    }

    return res;
}

/** */
void DCFileBrowser::JumpTo(QString target) {
    QStringList parts = target.split("\\", QString::SkipEmptyParts);
    bool found = false;

    Q3ListViewItem * curitem = ListView_DIRECTORY->firstChild();
    Q3ListViewItem * parent;

    QStringList::const_iterator it = parts.constBegin();
    for (; it != parts.constEnd(); ++it) {
        parent = curitem;
        curitem = curitem->firstChild();

        while (curitem) {
            if (curitem->text(0) == *it) {
                found = true;
                break;
            }

            curitem = curitem->nextSibling();
        }

        if (!found) {
            curitem = parent;
            break;
        } else {
            found = false;
        }
    }

    if (curitem) {
        if (it != parts.constEnd()) {
            m_sJumpTo = *it;
        } else {
            m_sJumpTo = "";
        }

        ListView_DIRECTORY->clearSelection();
        ListView_DIRECTORY->ensureItemVisible(curitem);
        ListView_DIRECTORY->setSelected(curitem, true);
        ListView_DIRECTORY->setCurrentItem(curitem);
    } else {
        m_sJumpTo = "";
    }
}

/** */
void DCFileBrowser::slotGotoUser() {
    DCClient * client = 0;
    if (m_sNick.isEmpty() || m_sHubHost.isEmpty()) {
        client = 0;
    } else {
        client = DCConnectionManager::Instance()->GetClientForHub(m_sHubName.toAscii().constData(), m_sHubHost.toAscii().constData());
    }

    if (client) {
        if (client->jumpToNick(m_sNick)) {
            client->raise();
            client->setFocus();
        } else {
            QMessageBox::information(
                    this,
                    tr("Go to user"),
                    tr("Cannot find user")
                    );
        }
    } else {
        QMessageBox::information(
                this,
                tr("Go to user"),
                tr("Cannot find hub")
                );
    }
}

/** */
void DCFileBrowser::slotFind() {
    bool ok = false;
    QString text = QInputDialog::getText(
            this,
            tr("Find"),
            tr("Search string"),
            QLineEdit::Normal,
            m_sFindString,
            &ok
            );

    if (!ok || text.isEmpty()) {
        return;
    } else {
        m_sFindString = text;
        m_pFoundDir = 0;
        m_nFoundFile = 0;
        m_FoundFiles.clear();
        slotFindNext();
    }
}

bool NameLessThan(const DCFileItem * left, const DCFileItem * right) {
    return QString::localeAwareCompare(left->m_sName, right->m_sName) < 0;
}

bool NameGreaterThan(const DCFileItem * left, const DCFileItem * right) {
    return QString::localeAwareCompare(left->m_sName, right->m_sName) > 0;
}

bool SizeLessThan(const DCFileItem * left, const DCFileItem * right) {
    return left->m_nSize < right->m_nSize;
}

bool SizeGreaterThan(const DCFileItem * left, const DCFileItem * right) {
    return left->m_nSize > right->m_nSize;
}

bool TTHLessThan(const DCFileItem * left, const DCFileItem * right) {
    return QString::localeAwareCompare(left->m_sHash, right->m_sHash) < 0;
}

bool TTHGreaterThan(const DCFileItem * left, const DCFileItem * right) {
    return QString::localeAwareCompare(left->m_sHash, right->m_sHash) > 0;
}

bool ExtLessThan(const DCFileItem * left, const DCFileItem * right) {
    /* directory names searched separately so no need to add " file" */
    QString left_ext, right_ext;

    int pos = left->m_sName.lastIndexOf('.');
    if (pos != -1) {
        left_ext = left->m_sName.mid(pos + 1);
    }

    pos = right->m_sName.lastIndexOf('.');
    if (pos != -1) {
        right_ext = right->m_sName.mid(pos + 1);
    }

    return QString::localeAwareCompare(left_ext, right_ext) < 0;
}

bool ExtGreaterThan(const DCFileItem * left, const DCFileItem * right) {
    /* directory names searched separately so no need to add " file" */
    QString left_ext, right_ext;

    int pos = left->m_sName.lastIndexOf('.');
    if (pos != -1) {
        left_ext = left->m_sName.mid(pos + 1);
    }

    pos = right->m_sName.lastIndexOf('.');
    if (pos != -1) {
        right_ext = right->m_sName.mid(pos + 1);
    }

    return QString::localeAwareCompare(left_ext, right_ext) > 0;
}

/** */
void DCFileBrowser::slotFindNext() {
    /*
     * Cannot use QListView::findItem() because we need to
     * search in file names.
     *
     * The search starts from the first directory and
     * continues from the last found directory and file.
     */

    if (m_sFindString.isEmpty()) {
        /*
         * copied and pasted to avoid
         * ending up in a loop of
         * slotFind() -> slotFindNext() -> slotFind() ...
         */
        bool ok = false;
        QString text = QInputDialog::getText(
                this,
                tr("Find"),
                tr("Search string"),
                QLineEdit::Normal,
                m_sFindString,
                &ok
                );

        if (!ok || text.isEmpty()) {
            return;
        } else {
            m_sFindString = text;
            m_pFoundDir = 0;
            m_nFoundFile = 0;
            m_FoundFiles.clear();
        }
    }

    if (!m_pFoundDir) {
        m_pFoundDir = (DCFileBrowserListItem*) ListView_DIRECTORY->firstChild();

        if (!m_pFoundDir) {
            QMessageBox::information(
                    this,
                    tr("Find"),
                    tr("Empty list")
                    );
            return;
        }
    }

    /*
     * current directory - already searched,
     * select next item if there is one
     */
    if (m_pFoundDir == ListView_DIRECTORY->currentItem()) {
        Q3ListViewItemIterator selcount(ListView_FILES, Q3ListViewItemIterator::Selected);
        if (selcount.current()) {
            ++m_nFoundFile;
        }

        if (m_nFoundFile < m_FoundFiles.size()) {
            Q3ListViewItem * vi = ListView_FILES->findItem(m_FoundFiles.at(m_nFoundFile), 0);
            if (vi) {
                ListView_FILES->clearSelection();
                ListView_FILES->ensureItemVisible(vi);
                ListView_FILES->setSelected(vi, true);
                ListView_FILES->setCurrentItem(vi);

                return;
            }
        }
    }

    /*
     * No more matches in the current directory.
     * Start/resume walking the tree.
     */

    Q3ListViewItemIterator dir_it(m_pFoundDir);
    ++dir_it;

    bool dirmatches = false;
    m_nFoundFile = 0;
    m_FoundFiles.clear();

    while (dir_it.current()) {
        DCFileBrowserListItem * fbitem = (DCFileBrowserListItem*) dir_it.current();

        /* check if directory name matches */
        if (fbitem->text(0).contains(m_sFindString, Qt::CaseInsensitive)) {
            m_pFoundDir = fbitem;
            dirmatches = true;
        }

        /* search all file names */
        QList<DCFileItem*> founditems;
        for (QList<DCFileItem*>::const_iterator fnit = fbitem->m_pFileList.constBegin(); fnit != fbitem->m_pFileList.constEnd(); ++fnit) {
            /* directory names already searched */
            if (((*fnit)->m_bIsDir == false) && ((*fnit)->m_sName.contains(m_sFindString, Qt::CaseInsensitive))) {
                m_pFoundDir = fbitem;
                founditems.append(*fnit);
            }
        }

        /* sort the found items according to the view */
        if (ListView_FILES->sortOrder() == Qt::AscendingOrder) {
            switch (ListView_FILES->sortColumn()) {
                case 0:
                    qStableSort(founditems.begin(), founditems.end(), NameLessThan);
                    break;
                case 1:
                case 2:
                    qStableSort(founditems.begin(), founditems.end(), SizeLessThan);
                    break;
                case 3:
                    qStableSort(founditems.begin(), founditems.end(), ExtLessThan);
                    break;
                case 4:
                    qStableSort(founditems.begin(), founditems.end(), TTHLessThan);
                    break;
                default:
                    break;
            };
        } else {
            switch (ListView_FILES->sortColumn()) {
                case 0:
                    qStableSort(founditems.begin(), founditems.end(), NameGreaterThan);
                    break;
                case 1:
                case 2:
                    qStableSort(founditems.begin(), founditems.end(), SizeGreaterThan);
                    break;
                case 3:
                    qStableSort(founditems.begin(), founditems.end(), ExtGreaterThan);
                    break;
                case 4:
                    qStableSort(founditems.begin(), founditems.end(), TTHGreaterThan);
                    break;
                default:
                    break;
            };
        }

        for (QList<DCFileItem*>::const_iterator found_iter = founditems.constBegin(); found_iter != founditems.constEnd(); ++found_iter) {
            m_FoundFiles.append((*found_iter)->m_sName);
        }

        if (!dirmatches && !m_FoundFiles.isEmpty()) {
            m_sJumpTo = m_FoundFiles.first();
        }

        if (dirmatches || !m_FoundFiles.isEmpty()) {
            ListView_DIRECTORY->clearSelection();
            ListView_DIRECTORY->ensureItemVisible(fbitem);
            ListView_DIRECTORY->setSelected(fbitem, true);
            ListView_DIRECTORY->setCurrentItem(fbitem);

            return;
        }

        ++dir_it;
    }

    QMessageBox::information(
            this,
            tr("Find"),
            tr("No more matches.")
            );
    m_pFoundDir = 0;
    m_nFoundFile = 0;
    m_FoundFiles.clear();
}

void DCFileBrowser::closeEvent(QCloseEvent *e) {
    emit onDie(this);

    if (m_pContainer) {
        m_pContainer->setParent(0);
        m_pContainer->close();
    }

#if HAVE_MALLOC_TRIM
    malloc_trim(0);
#endif

    e->accept();
}
