/**************************************************************************
 **                                                                      **
 ** Copyright (C) 2011-2026 Lukas Spies                                  **
 ** Contact: https://photoqt.org                                         **
 **                                                                      **
 ** This file is part of PhotoQt.                                        **
 **                                                                      **
 ** PhotoQt 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.                                  **
 **                                                                      **
 ** PhotoQt is distributed in the hope that it will be useful,           **
 ** but WITHOUT ANY WARRANTY; without even the implied warranty of       **
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        **
 ** GNU General Public License for more details.                         **
 **                                                                      **
 ** You should have received a copy of the GNU General Public License    **
 ** along with PhotoQt. If not, see <http://www.gnu.org/licenses/>.      **
 **                                                                      **
 **************************************************************************/

#include <pqc_configfiles.h>
#include <scripts/pqc_scriptsfiledialog.h>
#include <pqc_imagehandler.h>
#include <QtDebug>
#include <QStorageInfo>
#include <QUrl>
#include <QFutureWatcher>
#include <QJSValue>
#include <QJSEngine>
#include <QtConcurrent/QtConcurrentRun>
#include <pqc_settingscpp.h>

#ifdef PQMPUGIXML
#include <pugixml.hpp>
#else
#include <QDomDocument>
#endif

PQCScriptsFileDialog::PQCScriptsFileDialog() {

}

PQCScriptsFileDialog::~PQCScriptsFileDialog() {

}

QVariantList PQCScriptsFileDialog::getDevices() {

    qDebug() << "";

    QVariantList ret;

    const QList<QStorageInfo> info = QStorageInfo::mountedVolumes();
    for(const QStorageInfo &s : info) {
        if(s.isValid()) {

            const QString tpe = QString(s.fileSystemType());
            const QString tpeLower = tpe.toLower();

            if((tpe.toLower() == "tmpfs" || tpeLower == "squashfs" || (tpeLower.startsWith("fuse") && tpe.endsWith("AppImage"))) && !PQCSettingsCPP::get().getFiledialogDevicesShowTmpfs())
                continue;

            QString name = s.name();
            if(name.isEmpty())
                name = QDir::toNativeSeparators(s.rootPath());

            QString p = s.rootPath();

            QVariantList vol;
            vol << name
                << s.bytesTotal()
                << tpe
                << p;

            ret.append(vol);

        }
    }

    return ret;

}

QVariantList PQCScriptsFileDialog::getPlaces(bool performEmptyCheck) {

    qDebug() << "";

    QVariantList ret;

    // if file does not exist yet then we create a sceleton file
    if(!QFile(PQCConfigFiles::get().USER_PLACES_XBEL()).exists()) {

        qDebug() << "Loading default user_places.xbel file";

        QString cont = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
        cont += "<xbel xmlns:mime=\"http://www.freedesktop.org/standards/shared-mime-info\" xmlns:bookmark=\"http://www.freedesktop.org/standards/desktop-bookmarks\">\n";
        cont += "</xbel>";

        QFile file(PQCConfigFiles::get().USER_PLACES_XBEL());
        if(file.open(QIODevice::WriteOnly)) {
            QTextStream out(&file);
            out << cont;
            file.close();
        }
    }

#ifdef PQMPUGIXML
    pugi::xml_document doc;
    pugi::xml_parse_result result = doc.load_file(PQCConfigFiles::get().USER_PLACES_XBEL().toUtf8());
    if(!result) {
        qWarning() << "Unable to read user places. Either file doesn't exist (yet) or cannot be read...";
        return ret;
    }

    bool docUpdated = false;

    pugi::xpath_node_set bookmarks = doc.select_nodes("/xbel/bookmark");

    for(pugi::xpath_node node: bookmarks) {

        QVariantList entry;

        pugi::xml_node bm = node.node();

        QString path = QUrl::fromPercentEncoding(bm.attribute("href").value());

// we need to check for the old syntax of two/three slashes
#ifdef Q_OS_WIN
        if(path.startsWith("file:///"))
            path = path.remove(0,8);
        else if(path.startsWith("file://"))
            path = path.remove(0,7);
        else if(path.startsWith("file:/"))
            path = path.remove(0,6);
        else if(path.startsWith("file:"))
            path = path.remove(0,5);
#else
        if(path.startsWith("file:////"))
            path = path.remove(0,8);
        else if(path.startsWith("file:///"))
            path = path.remove(0,7);
        else if(path.startsWith("file://"))
            path = path.remove(0,6);
        else if(path.startsWith("file:/"))
            path = path.remove(0,5);
        else if(path.startsWith("file:"))
            path = path.remove(0,4);
        else if(path == "trash:/" || path == "trash:")
            path = PQCConfigFiles::get().USER_TRASH_FILES();
#endif
        else
            continue;

        // name
        const QString nme = bm.select_node("title").node().child_value();
        entry << nme;

        // path
        entry << path;

        // icon
        const QString icn = bm.select_node("info/metadata/bookmark:icon").node().attribute("name").value();
        if((nme == "Home" || nme == QStandardPaths::displayName(QStandardPaths::HomeLocation)) && icn == "true")
            entry << "user-home";
        else
            entry << icn;

        // id
        QString id = bm.select_node("info/metadata/ID").node().child_value();
        // id doesn't exist (i.e., kde metadata part missing)
        if(id.isEmpty()) {

            id = getUniquePlacesId();

            pugi::xml_node info = bm.select_node("info").node();

            // <metadata> kde.org
            pugi::xml_node metadata = info.append_child("metadata");
            metadata.append_attribute("owner");
            metadata.attribute("owner").set_value("http://www.kde.org");

            // <ID>
            pugi::xml_node ID = metadata.append_child("ID");
            ID.text().set(id.toStdString().c_str());

            // <IsHidden>
            pugi::xml_node IsHidden = metadata.append_child("IsHidden");
            IsHidden.text().set("false");

            // <isSystemItem>
            pugi::xml_node isSystemItem = metadata.append_child("isSystemItem");
            isSystemItem.text().set("false");

            docUpdated = true;

        }
        entry << id;

        // hidden
        QString hidden = bm.select_node("info/metadata/IsHidden").node().child_value();
        if(hidden.isEmpty())
            hidden = "false";
        entry << hidden;

        ret.append(entry);

    }

    if(docUpdated)
        doc.save_file(PQCConfigFiles::get().USER_PLACES_XBEL().toUtf8(), " ");

#else

    QDomDocument doc;
    QFile file(PQCConfigFiles::get().USER_PLACES_XBEL());
    if(!file.open(QIODevice::ReadOnly)) {
        qWarning() << "Unable to open file to read user places. Either file doesn't exist (yet) or cannot be read...";
        return {};
    }
    if(!doc.setContent(&file)) {
        qWarning() << "Unable to read user places. Either file doesn't exist (yet) or cannot be read...";
        file.close();
        return{};
    }
    file.close();

    bool docUpdated = false;

    QDomNodeList bookmarksList = doc.elementsByTagName("bookmark");

    for(int i = 0; i < bookmarksList.count(); ++i) {

        QDomNode node = bookmarksList.at(i);
        QDomElement ele = node.toElement();

        if(ele.isNull()) {
            qWarning() << "Unable to find bookmark...";
            continue;
        }

        QVariantList entry;
        QString path = QUrl::fromPercentEncoding(ele.attribute("href").toUtf8());

// we need to check for the old syntax of two/three slashes
#ifdef Q_OS_WIN
        if(path.startsWith("file:///"))
            path = path.remove(0,8);
        else if(path.startsWith("file://"))
            path = path.remove(0,7);
        else if(path.startsWith("file:/"))
            path = path.remove(0,6);
        else if(path.startsWith("file:"))
            path = path.remove(0,5);
#else
        if(path.startsWith("file:////"))
            path = path.remove(0,8);
        else if(path.startsWith("file:///"))
            path = path.remove(0,7);
        else if(path.startsWith("file://"))
            path = path.remove(0,6);
        else if(path.startsWith("file:/"))
            path = path.remove(0,5);
        else if(path.startsWith("file:"))
            path = path.remove(0,4);
        else if(path == "trash:/" || path == "trash:")
            path = PQCConfigFiles::get().USER_TRASH_FILES();
#endif
        else
            continue;

        // name
        const QDomNodeList titleList = ele.elementsByTagName("title");
        const QString nme = (titleList.length() ? titleList.at(0).toElement().text() : "");
        entry << nme;

        // path
        entry << path;

        // icon
        const QDomNodeList iconList = ele.elementsByTagName("bookmark:icon");
        const QString icn = (iconList.length() ? iconList.at(0).toElement().attribute("name") : "");
        if((nme == "Home" || nme == QStandardPaths::displayName(QStandardPaths::HomeLocation)) && icn == "true")
            entry << "user-home";
        else
            entry << icn;

        // id
        const QDomNodeList idList = ele.elementsByTagName("ID");
        QString id = (idList.length() ? idList.at(0).toElement().text() : "");
        // id doesn't exist (i.e., kde metadata part missing)
        if(id.isEmpty()) {

            id = getUniquePlacesId();

            // if we don't have an info tag, add everything
            if(ele.elementsByTagName("info").length() == 0) {

                QDomElement infoEle = doc.createElement("info");

                // <metadata> freedesktop.org
                QDomElement metadataFDEle = doc.createElement("metadata");
                metadataFDEle.setAttribute("owner", "http://freedesktop.org");

                // <bookmark:icon>
                QDomElement iconFDEle = doc.createElement("bookmark:icon");
                iconFDEle.setAttribute("name", "folder");

                metadataFDEle.appendChild(iconFDEle);

                // <metadata> kde.org
                QDomElement metadataEle = doc.createElement("metadata");
                metadataEle.setAttribute("owner", "http://www.kde.org");

                // <ID>
                QDomElement idEle = doc.createElement("ID");
                QDomText idText = doc.createTextNode(id);
                idEle.appendChild(idText);

                // <IsHidden>
                QDomElement isHiddenEle = doc.createElement("IsHidden");
                QDomText isHiddenText = doc.createTextNode("false");
                isHiddenEle.appendChild(isHiddenText);

                // <isSystemItem>
                QDomElement isSystemItemEle = doc.createElement("isSystemItem");
                QDomText isSystemItemText = doc.createTextNode("false");
                isSystemItemEle.appendChild(isSystemItemText);

                metadataEle.appendChild(idEle);
                metadataEle.appendChild(isHiddenEle);
                metadataEle.appendChild(isSystemItemEle);

                infoEle.appendChild(metadataFDEle);
                infoEle.appendChild(metadataEle);

                node.appendChild(infoEle);

            // in this case we need to check whether a metadata tag with the right owner exists already
            } else {

                int existingMetadataIndex = -1;
                if(ele.elementsByTagName("metadata").length() > 0) {

                    QDomNodeList metadataList = ele.elementsByTagName("metadata");
                    for(int j = 0; j < metadataList.length(); ++j) {

                        QDomElement metaEle = metadataList.at(j).toElement();

                        if(metaEle.hasAttribute("owner") && metaEle.attribute("owner") == "http://www.kde.org") {
                            existingMetadataIndex = j;
                            break;
                        }

                    }
                // also create default folder icon
                } else {

                    // <metadata> freedesktop.org
                    QDomElement metadataEle = doc.createElement("metadata");
                    metadataEle.setAttribute("owner", "http://freedesktop.org");

                    // <bookmark:icon>
                    QDomElement iconEle = doc.createElement("bookmark:icon");
                    iconEle.setAttribute("name", "folder");

                    metadataEle.appendChild(iconEle);

                    ele.elementsByTagName("info").at(0).appendChild(metadataEle);

                }

                // we need to create this element
                if(existingMetadataIndex == -1) {

                    // we know at least one exists since we got here by now
                    QDomElement infoEle = ele.elementsByTagName("info").at(0).toElement();

                    // <metadata> kde.org
                    QDomElement metadataEle = doc.createElement("metadata");
                    metadataEle.setAttribute("owner", "http://www.kde.org");

                    // <ID>
                    QDomElement idEle = doc.createElement("ID");
                    QDomText idText = doc.createTextNode(id);
                    idEle.appendChild(idText);

                    // <IsHidden>
                    QDomElement isHiddenEle = doc.createElement("IsHidden");
                    QDomText isHiddenText = doc.createTextNode("false");
                    isHiddenEle.appendChild(isHiddenText);

                    // <isSystemItem>
                    QDomElement isSystemItemEle = doc.createElement("isSystemItem");
                    QDomText isSystemItemText = doc.createTextNode("false");
                    isSystemItemEle.appendChild(isSystemItemText);

                    metadataEle.appendChild(idEle);
                    metadataEle.appendChild(isHiddenEle);
                    metadataEle.appendChild(isSystemItemEle);

                    ele.elementsByTagName("info").at(0).appendChild(metadataEle);

                // otherwise we just add an id tag
                } else {

                    QDomElement metaEle = ele.elementsByTagName("metadata").at(existingMetadataIndex).toElement();

                    // <ID>
                    QDomElement idEle = doc.createElement("ID");
                    QDomText idText = doc.createTextNode(id);
                    idEle.appendChild(idText);

                    metaEle.appendChild(idEle);

                }

            }

            docUpdated = true;

        }
        entry << id;

        // hidden
        const QDomNodeList hiddenList = ele.elementsByTagName("IsHidden");
        const QString hidden = (hiddenList.length() ? hiddenList.at(0).toElement().text() : "false");
        entry << hidden;

        ret.append(entry);

    }

    // document was updated -> save file
    if(docUpdated) {
        QFile saveFile(PQCConfigFiles::get().USER_PLACES_XBEL());
        if(saveFile.open(QIODevice::WriteOnly)) {
            QTextStream out(&saveFile);
            out << doc.toString();
        }
        saveFile.close();
    }


#endif

    // When no entries are found, we fill in the four default entries
    // the `performEmptyCheck` boolean prevents a potential infinite loop if things go horribly wrong
    if(ret.length() == 0 && performEmptyCheck) {

        addPlacesEntry(QStandardPaths::writableLocation(QStandardPaths::HomeLocation),
                       0,
                       (QStandardPaths::displayName(QStandardPaths::HomeLocation).isEmpty() ? "Home" : QStandardPaths::displayName(QStandardPaths::HomeLocation)),
                       "user-home",
                       true);
        addPlacesEntry(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation),
                       0,
                       (QStandardPaths::displayName(QStandardPaths::DesktopLocation).isEmpty() ? "Desktop" : QStandardPaths::displayName(QStandardPaths::DesktopLocation)),
                       "user-desktop",
                       true);
        addPlacesEntry(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation),
                       0,
                       (QStandardPaths::displayName(QStandardPaths::PicturesLocation).isEmpty() ? "Pictures" : QStandardPaths::displayName(QStandardPaths::PicturesLocation)),
                       "folder-documents",
                       true);
        addPlacesEntry(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation),
                       0,
                       (QStandardPaths::displayName(QStandardPaths::DownloadLocation).isEmpty() ? "Downloads" : QStandardPaths::displayName(QStandardPaths::DownloadLocation)),
                       "folder-downloads",
                       true);

        return getPlaces(false);

    }

    return ret;

}

QString PQCScriptsFileDialog::getUniquePlacesId() {

    qDebug() << "";

#ifdef PQMPUGIXML

    pugi::xml_document doc;
    pugi::xml_parse_result result = doc.load_file(PQCConfigFiles::get().USER_PLACES_XBEL().toUtf8());
    if(!result) {
        qWarning() << "Unable to read user places. Either file doesn't exist (yet) or cannot be read...";
        return "";
    }

    pugi::xpath_node_set bookmarks = doc.select_nodes("/xbel/bookmark");

    QStringList allIds;
    for(pugi::xpath_node node : bookmarks) {
        pugi::xml_node cur = node.node();
        QString curId = cur.select_node("info/metadata/ID").node().child_value();
        QString curPath = QUrl::fromPercentEncoding(cur.attribute("href").value());
        if(curPath.startsWith("file:") || curPath == "trash:" || curPath == "trash:/")
            allIds.append(curId);
    }

    QString newid_base = QString::number(QDateTime::currentSecsSinceEpoch());

    int counter = 0;
    while(allIds.contains(QString("%1/%2").arg(newid_base).arg(counter)))
        ++counter;

    return QString("%1/%2").arg(newid_base).arg(counter);

#else

    QDomDocument doc;
    QFile file(PQCConfigFiles::get().USER_PLACES_XBEL());
    if(!file.open(QIODevice::ReadOnly)) {
        qWarning() << "Unable to open file to read user places. Either file doesn't exist (yet) or cannot be read...";
        return {};
    }
    if(!doc.setContent(&file)) {
        qWarning() << "Unable to read user places. Either file doesn't exist (yet) or cannot be read...";
        file.close();
        return{};
    }
    file.close();

    const QDomNodeList bookmarksList = doc.elementsByTagName("bookmark");

    QStringList allIds;
    for(int i = 0; i < bookmarksList.count(); ++i) {

        QDomElement ele = bookmarksList.at(i).toElement();

        if(ele.isNull()) {
            qWarning() << "Unable to find bookmark...";
            continue;
        }

        const QDomNodeList idList = ele.elementsByTagName("ID");
        const QString curId = (idList.length() ? idList.at(0).toElement().text() : "");
        const QString curPath = (idList.length() ? QUrl::fromPercentEncoding(idList.at(0).toElement().attribute("href").toUtf8()) : "");
        if(curPath.startsWith("file:") || curPath == "trash:" || curPath == "trash:/")
            allIds.append(curId);
    }

    QString newid_base = QString::number(QDateTime::currentSecsSinceEpoch());

    int counter = 0;
    while(allIds.contains(QString("%1/%2").arg(newid_base).arg(counter)))
        ++counter;

    return QString("%1/%2").arg(newid_base).arg(counter);

#endif

    return "";

}

unsigned int PQCScriptsFileDialog::getNumberOfFilesInFolder(const QString &path) {

    // cache key
    const QString key = QString("%1%2").arg(path,QFileInfo(path).lastModified().toString());

    // protect accesses to cacheNumberOfFilesInFolder
    QMutexLocker locker(&cacheMutex);

    // if already loaded before, read from cache
    if(cacheNumberOfFilesInFolder.contains(key))
        return cacheNumberOfFilesInFolder[key];

    // fresh count of files in folder

    QDir dir(path);
    QStringList checkForTheseFormats;
    const QSet<QString> lst = PQCImageHandler::get().getEnabledSuffixes();
    for(const QString &c : lst)
        checkForTheseFormats << QString("*.%1").arg(c);

    dir.setNameFilters(checkForTheseFormats);
    dir.setFilter(QDir::Files);

    const int count = dir.count();
    cacheNumberOfFilesInFolder.insert(key, count);

    return count;

}

QString PQCScriptsFileDialog::getLastLocation() {

    qDebug() << "";

    QFile file(PQCConfigFiles::get().FILEDIALOG_LAST_LOCATION());
    if(file.exists() && file.open(QIODevice::ReadOnly)) {
        QTextStream in(&file);
        QString ret = in.readAll().trimmed();
        file.close();
        QDir folder(ret);
        if(folder.exists())
            return ret;
    }

    return "";

}

// The following one ONLY needs to be called from the integrated ui IF the native filedialog is used!
void PQCScriptsFileDialog::setLastLocation(QString fname) {

    qDebug() << "args: fname =" << fname;

    QFile file(PQCConfigFiles::get().FILEDIALOG_LAST_LOCATION());
    if(file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
        QTextStream out(&file);
        out << fname;
        file.close();
    }

}

void PQCScriptsFileDialog::movePlacesEntry(QString id, bool moveDown, int howmany) {

    qDebug() << "args: id = " << id;
    qDebug() << "args: moveDown = " << moveDown;
    qDebug() << "args: howmany = " << howmany;

#ifdef PQMPUGIXML

    pugi::xml_document doc;
    pugi::xml_parse_result result = doc.load_file(PQCConfigFiles::get().USER_PLACES_XBEL().toUtf8());
    if(!result) {
        qWarning() << "ERROR: Unable to read user places. Either file doesn't exist (yet) or cannot be read...";
        return;
    }

    pugi::xpath_node_set bookmarks = doc.select_nodes("/xbel/bookmark");

    // first get a handle for this node
    QStringList allIds;
    for(pugi::xpath_node node : bookmarks) {
        pugi::xml_node cur = node.node();
        QString curId = cur.select_node("info/metadata/ID").node().child_value();
        QString curPath = QUrl::fromPercentEncoding(cur.attribute("href").value());
        if(curPath.startsWith("file:") || curPath == "trash:" || curPath == "trash:/")
            allIds.append(curId);
    }

    for(pugi::xpath_node nodeToBeMoved : bookmarks) {

        pugi::xml_node cur = nodeToBeMoved.node();
        QString curId = cur.select_node("info/metadata/ID").node().child_value();

        if(id == curId) {

            QString targetId = "";
            bool addAtBeginning = false;
            if(moveDown)
                targetId = allIds[qMin(allIds.length()-1, allIds.indexOf(id)+howmany)];
            else {
                int newid =allIds.indexOf(id)-howmany-1;
                if(newid < 0)
                    addAtBeginning = true;
                targetId = allIds[qMax(0, newid)];
            }

            for(pugi::xpath_node targetNode : bookmarks) {

                QString curId = targetNode.node().select_node("info/metadata/ID").node().child_value();

                if(curId == targetId) {
                    pugi::xml_node ret;
                    if(addAtBeginning)
                        ret = targetNode.node().parent().insert_move_before(nodeToBeMoved.node(), targetNode.node());
                    else
                        ret = targetNode.node().parent().insert_move_after(nodeToBeMoved.node(), targetNode.node());
                    if(ret == nullptr)
                        qWarning() << "ERROR: Reordering items in user-places.xbel failed...";
                    break;
                }

            }

            break;

        }

    }

    doc.save_file(PQCConfigFiles::get().USER_PLACES_XBEL().toUtf8(), " ");

#else

    QDomDocument doc;
    QFile file(PQCConfigFiles::get().USER_PLACES_XBEL());
    if(!file.open(QIODevice::ReadOnly)) {
        qWarning() << "Unable to open file to read user places. Either file doesn't exist (yet) or cannot be read...";
        return;
    }
    if(!doc.setContent(&file)) {
        qWarning() << "Unable to read user places. Either file doesn't exist (yet) or cannot be read...";
        file.close();
        return;
    }
    file.close();

    const QDomNodeList bookmarksList = doc.elementsByTagName("bookmark");

    QStringList allIds;
    for(int i = 0; i < bookmarksList.count(); ++i) {

        QDomElement ele = bookmarksList.at(i).toElement();

        if(ele.isNull()) {
            qWarning() << "Unable to find bookmark...";
            continue;
        }

        const QDomNodeList idList = ele.elementsByTagName("ID");
        const QString curId = (idList.length() ? idList.at(0).toElement().text() : "");
        const QString curPath = QUrl::fromPercentEncoding(ele.attribute("href").toUtf8());
        if(curPath.startsWith("file:") || curPath == "trash:" || curPath == "trash:/")
            allIds.append(curId);
    }

    for(int i = 0; i < bookmarksList.count(); ++i) {

        QDomNode node = bookmarksList.at(i);
        QDomElement ele = node.toElement();

        QDomNodeList idList = ele.elementsByTagName("ID");
        if(idList.length() == 0) continue;
        QString curId = idList.at(0).toElement().text();

        if(id == curId) {

            QString targetId = "";
            bool addAtBeginning = false;
            if(moveDown)
                targetId = allIds[qMin(allIds.length()-1, allIds.indexOf(id)+howmany)];
            else {
                int newid = allIds.indexOf(id)-howmany-1;
                if(newid < 0)
                    addAtBeginning = true;
                targetId = allIds[qMax(0, newid)];
            }

            for(int j = 0; j < bookmarksList.count(); ++j) {

                QDomNode targetNode = bookmarksList.at(j);
                QDomElement targetEle = targetNode.toElement();

                QDomNodeList targetList = targetEle.elementsByTagName("ID");
                if(targetList.length() == 0) continue;
                QString curId = targetList.at(0).toElement().text();

                if(curId == targetId) {

                    QDomNodeList xbel = doc.elementsByTagName("xbel");
                    if(xbel.length() != 1) {
                        qWarning() << "ERROR: Expected exactly 1 xbel tag, but found" << xbel.length();
                        return;
                    }

                    xbel.at(0).removeChild(node);
                    if(addAtBeginning)
                        xbel.at(0).insertBefore(node, targetNode);
                    else
                        xbel.at(0).insertAfter(node, targetNode);

                    break;
                }

            }

            break;

        }

    }

    QFile saveFile(PQCConfigFiles::get().USER_PLACES_XBEL());
    if(saveFile.open(QIODevice::WriteOnly)) {
        QTextStream out(&saveFile);
        out << doc.toString();
    }
    saveFile.close();

#endif

}

void PQCScriptsFileDialog::addPlacesEntry(QString path, int pos, QString titlestring, QString icon, bool isSystemItem) {

    qDebug() << "args: path =" << path;
    qDebug() << "args: pos =" << pos;
    qDebug() << "args: titlestring =" << titlestring;
    qDebug() << "args: icon =" << icon;
    qDebug() << "args: isSystemItem =" << isSystemItem;

#ifdef PQMPUGIXML

    pugi::xml_document doc;
    pugi::xml_parse_result result = doc.load_file(PQCConfigFiles::get().USER_PLACES_XBEL().toUtf8());
    if(!result) {
        qWarning() << "ERROR: Unable to read user places. Either file doesn't exist (yet) or cannot be read...";
        return;
    }

    pugi::xpath_node_set bookmarks = doc.select_nodes("/xbel/bookmark");

    QStringList allIds;
    for(pugi::xpath_node node : bookmarks) {

        pugi::xml_node cur = node.node();

        QString path = cur.attribute("href").value();

        if(path == "trash:" || path == "trash:/" || path.startsWith("file:"))
            allIds.push_back(cur.select_node("info/metadata/ID").node().child_value());

    }

    QString newid_base = QString::number(QDateTime::currentDateTime().toMSecsSinceEpoch());

    int counter = 0;
    while(allIds.contains(QString("%1/%2").arg(newid_base).arg(counter)))
        ++counter;

    // no items currently set
    if(allIds.length() == 0) {

        pugi::xpath_node_set toplevel = doc.select_nodes("/xbel");

        pugi::xml_node newnode = toplevel.first().node().append_child("bookmark");
        if(newnode == nullptr)
            qWarning() << "ERROR: Unable to add first node...";

        // <bookmark>
        newnode.set_name("bookmark");
        newnode.append_attribute("href");
        newnode.attribute("href").set_value(QString("file:%1").arg(QString::fromLatin1(QUrl::toPercentEncoding(path))).toStdString().c_str());

        // <title>
        pugi::xml_node title = newnode.append_child("title");
        title.text().set(titlestring.isEmpty() ? QFileInfo(path).fileName().toStdString().c_str() : titlestring.toStdString().c_str());

        // <info>
        pugi::xml_node info = newnode.append_child("info");

        // <metadata> freedesktop.org
        pugi::xml_node metadata1 = info.append_child("metadata");
        metadata1.append_attribute("owner");
        metadata1.attribute("owner").set_value("http://freedesktop.org");

        // <bookmark:icon>
        pugi::xml_node node_icon = metadata1.append_child("bookmark:icon");
        node_icon.append_attribute("name");
        node_icon.attribute("name").set_value(icon.toStdString().c_str());

        // <metadata> kde.org
        pugi::xml_node metadata2 = info.append_child("metadata");
        metadata2.append_attribute("owner");
        metadata2.attribute("owner").set_value("http://www.kde.org");

        // <ID>
        pugi::xml_node ID = metadata2.append_child("ID");
        ID.text().set(QString("%1/%2").arg(newid_base).arg(counter).toStdString().c_str());

        // <IsHidden>
        pugi::xml_node IsHidden = metadata2.append_child("IsHidden");
        IsHidden.text().set("false");

        // <isSystemItem>
        pugi::xml_node node_isSystemItem = metadata2.append_child("isSystemItem");
        node_isSystemItem.text().set(isSystemItem ? "true" : "false");

    } else {

        QString insertAfterId = allIds.length() == 0 ? "" : allIds[qMax(0, pos-2)];

        for(pugi::xpath_node node : bookmarks) {

            pugi::xml_node cur = node.node();

            if(insertAfterId.isEmpty() || cur.select_node("info/metadata/ID").node().child_value() == insertAfterId) {

                pugi::xml_node newnode = cur.parent().insert_child_after(pugi::node_element, cur);
                if(newnode == nullptr)
                    qWarning() << "ERROR: Unable to add new node...";

                // <bookmark>
                newnode.set_name("bookmark");
                newnode.append_attribute("href");
                newnode.attribute("href").set_value(QString("file:%1").arg(QString::fromLatin1(QUrl::toPercentEncoding(path))).toStdString().c_str());

                // <title>
                pugi::xml_node title = newnode.append_child("title");
                title.text().set(QFileInfo(path).fileName().toStdString().c_str());

                // <info>
                pugi::xml_node info = newnode.append_child("info");

                // <metadata> freedesktop.org
                pugi::xml_node metadata1 = info.append_child("metadata");
                metadata1.append_attribute("owner");
                metadata1.attribute("owner").set_value("http://freedesktop.org");

                // <bookmark:icon>
                pugi::xml_node node_icon = metadata1.append_child("bookmark:icon");
                node_icon.append_attribute("name");
                node_icon.attribute("name").set_value(icon.toStdString().c_str());

                // <metadata> kde.org
                pugi::xml_node metadata2 = info.append_child("metadata");
                metadata2.append_attribute("owner");
                metadata2.attribute("owner").set_value("http://www.kde.org");

                // <ID>
                pugi::xml_node ID = metadata2.append_child("ID");
                ID.text().set(QString("%1/%2").arg(newid_base).arg(counter).toStdString().c_str());

                // <IsHidden>
                pugi::xml_node IsHidden = metadata2.append_child("IsHidden");
                IsHidden.text().set("false");

                // <isSystemItem>
                pugi::xml_node node_isSystemItem = metadata2.append_child("isSystemItem");
                node_isSystemItem.text().set("false");

                break;

            }

        }

    }

    doc.save_file(PQCConfigFiles::get().USER_PLACES_XBEL().toUtf8(), " ");

#else

    QDomDocument doc;
    QFile file(PQCConfigFiles::get().USER_PLACES_XBEL());
    if(!file.open(QIODevice::ReadOnly)) {
        qWarning() << "Unable to open file to read user places. Either file doesn't exist (yet) or cannot be read...";
        return;
    }
    if(!doc.setContent(&file)) {
        qWarning() << "Unable to read user places. Either file doesn't exist (yet) or cannot be read...";
        file.close();
        return;
    }
    file.close();

    const QDomNodeList bookmarksList = doc.elementsByTagName("bookmark");

    QStringList allIds;
    for(int i = 0; i < bookmarksList.count(); ++i) {

        QDomElement ele = bookmarksList.at(i).toElement();

        if(ele.isNull()) {
            qWarning() << "Unable to find bookmark...";
            continue;
        }

        const QDomNodeList idList = ele.elementsByTagName("ID");
        const QString curId = (idList.length() ? idList.at(0).toElement().text() : "");
        const QString curPath = QUrl::fromPercentEncoding(ele.attribute("href").toUtf8());
        if(curPath.startsWith("file:") || curPath == "trash:" || curPath == "trash:/")
            allIds.append(curId);
    }

    QString newid_base = QString::number(QDateTime::currentMSecsSinceEpoch());

    int counter = 0;
    while(allIds.contains(QString("%1/%2").arg(newid_base).arg(counter)))
        ++counter;

    // no items currently set
    if(allIds.length() == 0) {

        QDomNodeList nodeList = doc.elementsByTagName("xbel");
        // xbel does not exist -> create it!
        if(nodeList.length() == 0) {

            QDomElement xbelEle = doc.createElement("xbel");
            xbelEle.setAttribute("xmlns:bookmark", "http://www.freedesktop.org/standards/desktop-bookmarks");
            xbelEle.setAttribute("xmlns:kdepriv", "http://www.kde.org/kdepriv");
            xbelEle.setAttribute("xmlns:mime", "http://www.freedesktop.org/standards/shared-mime-info");

            doc.appendChild(xbelEle);

            nodeList = doc.elementsByTagName("xbel");

        }

        QDomNode toplevel = nodeList.at(0);

        // <bookmark>
        QDomElement newEle = doc.createElement("bookmark");
        newEle.setAttribute("href", QString("file://%1").arg(path));

        // <title>
        QDomElement titleEle = doc.createElement("title");
        QDomText titleText = doc.createTextNode(titlestring.isEmpty() ? QFileInfo(path).fileName() : titlestring);
        titleEle.appendChild(titleText);

        // <info>
        QDomElement infoEle = doc.createElement("info");

        // <metadata> freedesktop.org
        QDomElement metadataFDEle = doc.createElement("metadata");
        metadataFDEle.setAttribute("owner", "http://freedesktop.org");

        // <bookmark:icon>
        QDomElement iconFDEle = doc.createElement("bookmark:icon");
        iconFDEle.setAttribute("name", icon);

        metadataFDEle.appendChild(iconFDEle);

        // <metadata> kde.org
        QDomElement metadataEle = doc.createElement("metadata");
        metadataEle.setAttribute("owner", "http://www.kde.org");

        // <ID>
        QDomElement idEle = doc.createElement("ID");
        QDomText idText = doc.createTextNode(QString("%1/%2").arg(newid_base).arg(counter));
        idEle.appendChild(idText);

        // <IsHidden>
        QDomElement isHiddenEle = doc.createElement("IsHidden");
        QDomText isHiddenText = doc.createTextNode("false");
        isHiddenEle.appendChild(isHiddenText);

        // <isSystemItem>
        QDomElement isSystemItemEle = doc.createElement("isSystemItem");
        QDomText isSystemItemText = doc.createTextNode(isSystemItem ? "true" : "false");
        isSystemItemEle.appendChild(isSystemItemText);

        metadataEle.appendChild(idEle);
        metadataEle.appendChild(isHiddenEle);
        metadataEle.appendChild(isSystemItemEle);

        infoEle.appendChild(metadataFDEle);
        infoEle.appendChild(metadataEle);

        newEle.appendChild(titleEle);
        newEle.appendChild(infoEle);

        toplevel.appendChild(newEle);

    } else {

        QDomNodeList nodeList = doc.elementsByTagName("xbel");
        // xbel does not exist -> create it!
        if(nodeList.length() == 0) {

            QDomElement xbelEle = doc.createElement("xbel");
            xbelEle.setAttribute("xmlns:bookmark", "http://www.freedesktop.org/standards/desktop-bookmarks");
            xbelEle.setAttribute("xmlns:kdepriv", "http://www.kde.org/kdepriv");
            xbelEle.setAttribute("xmlns:mime", "http://www.freedesktop.org/standards/shared-mime-info");

            doc.appendChild(xbelEle);

            nodeList = doc.elementsByTagName("xbel");

        }

        QDomNode toplevel = nodeList.at(0);

        QString insertAfterId = allIds.length() == 0 ? "" : allIds[qMax(0, pos-2)];

        for(int i = 0; i < bookmarksList.count(); ++i) {

            QDomNode node = bookmarksList.at(i);
            QDomElement ele = node.toElement();

            QDomNodeList idList = ele.elementsByTagName("ID");
            if(!insertAfterId.isEmpty() && idList.length() == 0) continue;
            QString curId = idList.at(0).toElement().text();

            if(insertAfterId.isEmpty() || curId == insertAfterId) {

                QDomElement newNode = doc.createElement("bookmark");

                // <bookmark>
                newNode.setAttribute("href", QString("file://%1").arg(path));

                // <title>
                qWarning() << " >>> title =" << QFileInfo(path).fileName();
                QDomElement titleEle = doc.createElement("title");
                QDomText titleText = doc.createTextNode(QFileInfo(path).fileName());
                titleEle.appendChild(titleText);

                // <info>
                QDomElement infoEle = doc.createElement("info");

                // <metadata> freedesktop.org
                QDomElement metadataFDEle = doc.createElement("metadata");
                metadataFDEle.setAttribute("owner", "http://freedesktop.org");

                // <bookmark:icon>
                QDomElement iconFDEle = doc.createElement("bookmark:icon");
                iconFDEle.setAttribute("name", icon);

                metadataFDEle.appendChild(iconFDEle);

                // <metadata> kde.org
                QDomElement metadataEle = doc.createElement("metadata");
                metadataEle.setAttribute("owner", "http://www.kde.org");

                // <ID>
                QDomElement idEle = doc.createElement("ID");
                QDomText idText = doc.createTextNode(QString("%1/%2").arg(newid_base).arg(counter));
                idEle.appendChild(idText);

                // <IsHidden>
                QDomElement isHiddenEle = doc.createElement("IsHidden");
                QDomText isHiddenText = doc.createTextNode("false");
                isHiddenEle.appendChild(isHiddenText);

                // <isSystemItem>
                QDomElement isSystemItemEle = doc.createElement("isSystemItem");
                QDomText isSystemItemText = doc.createTextNode("false");
                isSystemItemEle.appendChild(isSystemItemText);

                metadataEle.appendChild(idEle);
                metadataEle.appendChild(isHiddenEle);
                metadataEle.appendChild(isSystemItemEle);

                infoEle.appendChild(metadataFDEle);
                infoEle.appendChild(metadataEle);

                newNode.appendChild(titleEle);
                newNode.appendChild(infoEle);

                toplevel.insertAfter(newNode, node);

                break;

            }

        }

    }

    QFile saveFile(PQCConfigFiles::get().USER_PLACES_XBEL());
    if(saveFile.open(QIODevice::WriteOnly)) {
        QTextStream out(&saveFile);
        out << doc.toString();
    }
    saveFile.close();

#endif

}

void PQCScriptsFileDialog::hidePlacesEntry(QString id, bool hidden) {

    qDebug() << "args: id = " << id;
    qDebug() << "args: hidden = " << hidden;

#ifdef PQMPUGIXML

    pugi::xml_document doc;
    pugi::xml_parse_result result = doc.load_file(PQCConfigFiles::get().USER_PLACES_XBEL().toUtf8());
    if(!result) {
        qWarning() << "ERROR: Unable to read user places. Either file doesn't exist (yet) or cannot be read...";
        return;
    }

    pugi::xpath_node_set bookmarks = doc.select_nodes("/xbel/bookmark");

    for(pugi::xpath_node node : bookmarks) {

        pugi::xml_node cur = node.node();
        QString curId = cur.select_node("info/metadata/ID").node().child_value();

        if(curId == id) {
            if(QString(cur.select_node("info/metadata/IsHidden").node().child_value()).isEmpty()) {
                pugi::xml_node metadata = cur.select_node("info/metadata").node();
                pugi::xml_node isHidden = metadata.append_child("IsHidden");
                isHidden.text().set(hidden ? "true" : "false");
            } else
                if(!cur.select_node("info/metadata/IsHidden").node().text().set(hidden ? "true" : "false"))
                qWarning() << "ERROR: Unable to hide/show item with id" << id;
            break;
        }
    }

    doc.save_file(PQCConfigFiles::get().USER_PLACES_XBEL().toUtf8(), " ");

#else

    QDomDocument doc;
    QFile file(PQCConfigFiles::get().USER_PLACES_XBEL());
    if(!file.open(QIODevice::ReadOnly)) {
        qWarning() << "Unable to open file to read user places. Either file doesn't exist (yet) or cannot be read...";
        return;
    }
    if(!doc.setContent(&file)) {
        qWarning() << "Unable to read user places. Either file doesn't exist (yet) or cannot be read...";
        file.close();
        return;
    }
    file.close();

    QDomNodeList bookmarksList = doc.elementsByTagName("bookmark");

    for(int i = 0; i < bookmarksList.count(); ++i) {

        QDomNode node = bookmarksList.at(i);
        QDomElement ele = node.toElement();

        if(ele.isNull()) {
            qWarning() << "Unable to find bookmark...";
            continue;
        }

        const QDomNodeList idList = ele.elementsByTagName("ID");
        const QString curId = (idList.length() ? idList.at(0).toElement().text() : "");

        if(curId == id) {

            QDomNodeList hiddenNodeList = ele.elementsByTagName("IsHidden");
            if(hiddenNodeList.length() == 0) {

                QDomElement isHiddenEle = doc.createElement("IsHidden");
                QDomText isHiddenText = doc.createTextNode(hidden ? "true" : "false");
                isHiddenEle.appendChild(isHiddenText);

                QDomNodeList metadataList = ele.elementsByTagName("metadata");
                bool haveMetadataBlock = false;
                for(int j = 0; j < metadataList.length(); ++j) {

                    QDomNode metaNode = metadataList.at(j);
                    if(metaNode.toElement().attribute("owner") == "http://www.kde.org") {

                        haveMetadataBlock = true;
                        metaNode.appendChild(metaNode);
                        break;

                    }

                }

                if(!haveMetadataBlock) {

                    // <metadata> kde.org
                    QDomElement metadataEle = doc.createElement("metadata");
                    metadataEle.setAttribute("owner", "http://www.kde.org");

                    // <ID>
                    QDomElement idEle = doc.createElement("ID");
                    QDomText idText = doc.createTextNode(getUniquePlacesId());
                    idEle.appendChild(idText);

                    // <IsHidden>
                    QDomElement isHiddenEle = doc.createElement("IsHidden");
                    QDomText isHiddenText = doc.createTextNode(hidden ? "true" : "false");
                    isHiddenEle.appendChild(isHiddenText);

                    // <isSystemItem>
                    QDomElement isSystemItemEle = doc.createElement("isSystemItem");
                    QDomText isSystemItemText = doc.createTextNode("false");
                    isSystemItemEle.appendChild(isSystemItemText);

                    metadataEle.appendChild(idEle);
                    metadataEle.appendChild(isHiddenEle);
                    metadataEle.appendChild(isSystemItemEle);

                    QDomNodeList infoList = ele.elementsByTagName("info");
                    if(infoList.length() == 0) {
                        QDomElement infoEle = doc.createElement("info");
                        ele.appendChild(infoEle);
                        infoList = ele.elementsByTagName("info");
                    }

                    infoList.at(0).appendChild(metadataEle);

                }

            } else {

                hiddenNodeList.at(0).childNodes().at(0).toText().setData(hidden ? "true" : "false");
            }

        }

    }

    QFile saveFile(PQCConfigFiles::get().USER_PLACES_XBEL());
    if(saveFile.open(QIODevice::WriteOnly)) {
        QTextStream out(&saveFile);
        out << doc.toString();
    }
    saveFile.close();

#endif

}

void PQCScriptsFileDialog::deletePlacesEntry(QString id) {

    qDebug() << "args: id =" << id;

#ifdef PQMPUGIXML

    pugi::xml_document doc;
    pugi::xml_parse_result result = doc.load_file(PQCConfigFiles::get().USER_PLACES_XBEL().toUtf8());
    if(!result) {
        qWarning() << "ERROR: Unable to read user places. Either file doesn't exist (yet) or cannot be read...";
        return;
    }

    pugi::xpath_node_set bookmarks = doc.select_nodes("/xbel/bookmark");

    for(pugi::xpath_node node : bookmarks) {

        pugi::xml_node cur = node.node();
        QString curId = cur.select_node("info/metadata/ID").node().child_value();

        if(curId == id) {
            if(!cur.parent().remove_child(cur))
                qWarning() << "ERROR: Unable to remove item with id" << id;
            break;
        }
    }

    doc.save_file(PQCConfigFiles::get().USER_PLACES_XBEL().toUtf8(), " ");

#else

    QDomDocument doc;
    QFile file(PQCConfigFiles::get().USER_PLACES_XBEL());
    if(!file.open(QIODevice::ReadOnly)) {
        qWarning() << "Unable to open file to read user places. Either file doesn't exist (yet) or cannot be read...";
        return;
    }
    if(!doc.setContent(&file)) {
        qWarning() << "Unable to read user places. Either file doesn't exist (yet) or cannot be read...";
        file.close();
        return;
    }
    file.close();

    const QDomNodeList bookmarksList = doc.elementsByTagName("bookmark");

    for(int i = 0; i < bookmarksList.count(); ++i) {

        QDomNode node = bookmarksList.at(i);
        QDomElement ele = node.toElement();

        const QDomNodeList idList = ele.elementsByTagName("ID");
        if(idList.length() == 0) continue;
        const QString curId = idList.at(0).toElement().text();

        if(curId == id) {

            QDomNodeList xbel = doc.elementsByTagName("xbel");
            if(xbel.length() != 1) {
                qWarning() << "ERROR: Expected exactly 1 xbel tag, but found" << xbel.length();
                return;
            }

            xbel.at(0).removeChild(node);

            break;
        }

    }

    QFile saveFile(PQCConfigFiles::get().USER_PLACES_XBEL());
    if(saveFile.open(QIODevice::WriteOnly)) {
        QTextStream out(&saveFile);
        out << doc.toString();
    }
    saveFile.close();

#endif

}

QString PQCScriptsFileDialog::getSiblingFolder(QString currentFolder, const int direction) {

    QFileInfo info(currentFolder);
    if(info.isFile())
        currentFolder = info.absolutePath();

    QDir dir(currentFolder);
    QString origDirName = dir.dirName();

    dir.cdUp();

    const QStringList allFolders = dir.entryList(QDir::Dirs|QDir::NoDotAndDotDot);

    qDebug() << currentFolder;
    qDebug() << allFolders << "//" << origDirName;

    int currentIndex = allFolders.indexOf(origDirName);

    if(currentIndex == -1) {
        qWarning() << "Current folder not found... not sure what to do.";
        return "";
    }

    if(direction == -1 && currentIndex > 0)
        return dir.absolutePath() + "/" + allFolders.at(currentIndex-1);
    else if(direction == 1 && currentIndex < allFolders.length()-1)
        return dir.absolutePath() + "/" + allFolders.at(currentIndex+1);

    return "";

}
