/**************************************************************************
 **                                                                      **
 ** 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 <QCollator>
#include <QDir>
#include <QFileInfo>
#include <QFileSystemWatcher>
#include <QMimeDatabase>
#include <QSize>
#include <QTimer>
#include <QtConcurrent/QtConcurrentRun>
#include <QtDebug>
#include <QImage>
#include <QDirIterator>
#include <pqc_configfiles.h>
#include <pqc_filefoldermodel.h>
#include <pqc_notify_cpp.h>
#include <pqc_resolutioncache.h>
#include <pqc_settingscpp.h>
#include <scripts/pqc_scriptsimages.h>
#include <pqc_helper.h>
#include <pqc_imagehandler.h>

#ifdef PQMLIBARCHIVE
#include <archive.h>
#include <archive_entry.h>
#endif

#ifdef PQMQTPDF
#include <QtPdf/QPdfDocument>
#endif

#ifdef PQMPOPPLER
#include <poppler/qt6/poppler-qt6.h>
#endif

#ifdef PQMEXIV2
#include <exiv2/exiv2.hpp>
#endif

PQCFileFolderModel::PQCFileFolderModel(QObject *parent) : QObject(parent) {

    m_firstFolderMainViewLoaded = false;

    m_fileInFolderMainView = "";
    m_folderFileDialog = "";
    m_folderFileDialogExists = false;
    m_currentIndex = -1;
    m_countMainView = 0;
    m_countFoldersFileDialog = 0;
    m_countFilesFileDialog = 0;

    m_readDocumentOnly = false;
    m_readArchiveOnly = false;
    m_includeFilesInSubFolders = false;
    m_isARC = false;
    m_isPDF = false;
    m_activeViewerMode = false;
    m_viewerModeBaseFile = "";

    m_entriesMainView.clear();
    m_entriesFileDialog.clear();

    m_nameFilters = QStringList();
    m_restrictToSuffixes = PQCImageHandler::get().getEnabledSuffixes();
    m_filenameFilters = QStringList();
    m_restrictToMimeTypes = PQCImageHandler::get().getEnabledMimetypes();
    m_imageResolutionFilter = QSize(0,0);
    m_fileSizeFilter = 0;
    m_justLeftViewerMode = false;
    m_ratingsFilter = 0;

    m_virtualFolders = PQCNotifyCPP::get().getVirtualFolders();
    m_virtualFiles = PQCNotifyCPP::get().getVirtualFiles();
    m_loadVirtualFolderFileDialog = (m_virtualFolders.length()+m_virtualFiles.length() > 0);
    m_loadVirtualFolderMainView = (m_virtualFolders.length()+m_virtualFiles.length() > 0);

    watcherMainView = new QFileSystemWatcher;
    watcherFileDialog = new QFileSystemWatcher;

    loadDelayMainView = new QTimer;
    loadDelayMainView->setInterval(100);
    loadDelayMainView->setSingleShot(true);
    connect(loadDelayMainView, &QTimer::timeout, this, &PQCFileFolderModel::loadDataMainView);

    loadDelayFileDialog = new QTimer;
    loadDelayFileDialog->setInterval(100);
    loadDelayFileDialog->setSingleShot(true);
    connect(loadDelayFileDialog, &QTimer::timeout, this, &PQCFileFolderModel::loadDataFileDialog);

    timerNotifyCurrentIndexChanged = new QTimer;
    timerNotifyCurrentIndexChanged->setInterval(100);
    timerNotifyCurrentIndexChanged->setSingleShot(true);
#if __cplusplus >= 202002L
    connect(timerNotifyCurrentIndexChanged, &QTimer::timeout, this, [=, this]() {
#else
    connect(timerNotifyCurrentIndexChanged, &QTimer::timeout, this, [=]() {
#endif

        Q_EMIT currentIndexChanged();
        Q_EMIT currentFileChanged();

        const int pdfIdx = m_currentFile.indexOf("::PDF::");
        bool ispdf = pdfIdx>-1;
        if(m_isPDF != ispdf) {
            m_isPDF = ispdf;
            m_isARC = false;
            if(ispdf) {
                m_pdfNum = m_currentFile.mid(0,pdfIdx).toInt();
                m_pdfName = m_currentFile.mid(pdfIdx+7);
            }
            Q_EMIT isPDFChanged();
            Q_EMIT pdfNameChanged();
            Q_EMIT pdfNumChanged();
        }

        const int arcIdx = m_currentFile.indexOf("::ARC::");
        bool isarc = arcIdx>-1;
        if(m_isARC != isarc) {
            m_isPDF = false;
            m_isARC = isarc;
            if(m_isARC) {
                m_arcFile = m_currentFile.mid(0,arcIdx);
                m_arcName = m_currentFile.mid(arcIdx+7);
            }
            Q_EMIT isARCChanged();
            Q_EMIT arcNameChanged();
            Q_EMIT arcFileChanged();
        }
    });

    timerResetJustLeftViewerMode = new QTimer;
    timerResetJustLeftViewerMode->setInterval(100);
    timerResetJustLeftViewerMode->setSingleShot(true);
#if __cplusplus >= 202002L
    connect(timerResetJustLeftViewerMode, &QTimer::timeout, this, [=, this]() {
#else
    connect(timerResetJustLeftViewerMode, &QTimer::timeout, this, [=]() {
#endif
        m_justLeftViewerMode = false;
    });

    // we add a tiny delay to this signal to make sure that when the directory has changed all files are fully written
    // not having this delay can cause faulty thumbnails to be loaded
#if __cplusplus >= 202002L
    connect(watcherMainView, &QFileSystemWatcher::directoryChanged, this, [=, this]() { m_fileInFolderMainView = m_currentFile; loadDelayMainView->start(500); });
    connect(watcherFileDialog, &QFileSystemWatcher::directoryChanged, this, [=, this]() { loadDelayFileDialog->start(500); });
#else
    connect(watcherMainView, &QFileSystemWatcher::directoryChanged, this, [=]() { m_fileInFolderMainView = m_currentFile; loadDelayMainView->start(500); });
    connect(watcherFileDialog, &QFileSystemWatcher::directoryChanged, this, [=]() { loadDelayFileDialog->start(500); });
#endif

    m_advancedSortDone = 0;

    connect(this, &PQCFileFolderModel::newDataLoadedMainView, this, &PQCFileFolderModel::handleNewDataLoadedMainView);

    connect(&PQCSettingsCPP::get(), &PQCSettingsCPP::imageviewSortImagesAscendingChanged, this, &PQCFileFolderModel::loadDataFileDialog);
    connect(&PQCSettingsCPP::get(), &PQCSettingsCPP::imageviewSortImagesAscendingChanged, this, &PQCFileFolderModel::loadDataMainView);
    connect(&PQCSettingsCPP::get(), &PQCSettingsCPP::imageviewSortImagesByChanged, this, &PQCFileFolderModel::loadDataFileDialog);
    connect(&PQCSettingsCPP::get(), &PQCSettingsCPP::imageviewSortImagesByChanged, this, &PQCFileFolderModel::loadDataMainView);
    connect(&PQCSettingsCPP::get(), &PQCSettingsCPP::filedialogShowHiddenFilesFoldersChanged, this, &PQCFileFolderModel::loadDataFileDialog);
    connect(&PQCSettingsCPP::get(), &PQCSettingsCPP::filedialogShowHiddenFilesFoldersChanged, this, &PQCFileFolderModel::loadDataMainView);

    connect(&PQCNotifyCPP::get(), &PQCNotifyCPP::resetSessionData, this, &PQCFileFolderModel::resetModel);

#if __cplusplus >= 202002L
    connect(&PQCNotifyCPP::get(), &PQCNotifyCPP::virtualFoldersChanged,
            this, [=, this](QStringList val) { m_virtualFolders = val; Q_EMIT virtualFoldersChanged(); });
    connect(&PQCNotifyCPP::get(), &PQCNotifyCPP::virtualFilesChanged,
            this, [=, this](QStringList val) { m_virtualFiles = val; Q_EMIT virtualFilesChanged(); });
#else
    connect(&PQCNotifyCPP::get(), &PQCNotifyCPP::virtualFoldersChanged,
            this, [=](QStringList val) { m_virtualFolders = val; Q_EMIT virtualFoldersChanged(); });
    connect(&PQCNotifyCPP::get(), &PQCNotifyCPP::virtualFilesChanged,
            this, [=](QStringList val) { m_virtualFiles = val; Q_EMIT virtualFilesChanged(); });
#endif

    /********************************************/
    /********************************************/

    // these are READ FROM PQCFileFolderModelCPP

#if __cplusplus >= 202002L
    connect(&PQCFileFolderModelCPP::get(), &PQCFileFolderModelCPP::setFileInFolderMainView, this, [=, this](QString val) { setFileInFolderMainView(val); });
#else
    connect(&PQCFileFolderModelCPP::get(), &PQCFileFolderModelCPP::setFileInFolderMainView, this, [=](QString val) { setFileInFolderMainView(val); });
#endif

    /********************************************/
    /********************************************/

#if __cplusplus >= 202002L
    connect(this, &PQCFileFolderModel::ratingsFilterChanged, this, [=, this]() {
#else
    connect(this, &PQCFileFolderModel::ratingsFilterChanged, this, [=]() {
#endif
        loadDelayMainView->start();
        loadDelayFileDialog->start();
        checkFilterActive();
    });

}

PQCFileFolderModel::~PQCFileFolderModel() {

    delete loadDelayMainView;
    delete loadDelayFileDialog;

    delete watcherMainView;
    delete watcherFileDialog;

    delete timerNotifyCurrentIndexChanged;
    delete timerResetJustLeftViewerMode;

}

/********************************************/
/********************************************/

QString PQCFileFolderModel::getFileInFolderMainView() {
    return m_fileInFolderMainView;
}
void PQCFileFolderModel::setFileInFolderMainView(QString val) {

    if(m_fileInFolderMainView == val)
        return;
    QFileInfo oldfile(m_fileInFolderMainView);
    QFileInfo newfile(val);
    if(oldfile.dir() == newfile.dir() && !m_fileInFolderMainView.isEmpty()) {
        m_currentFile = val;
        PQCFileFolderModelCPP::get().setCurrentFile(m_currentFile);
        m_currentFileNoDelay = m_currentFile;
        m_currentIndex = m_entriesMainView.indexOf(val);
        m_currentIndexNoDelay = m_currentIndex;
        m_fileInFolderMainView = val;
        Q_EMIT fileInFolderMainViewChanged();
        Q_EMIT currentFileChanged();
        Q_EMIT currentIndexChanged();
        Q_EMIT currentFileNoDelayChanged();
        Q_EMIT currentIndexNoDelayChanged();
    } else {
        m_fileInFolderMainView = val;
        loadDelayMainView->start();
        Q_EMIT fileInFolderMainViewChanged();
    }
}

QString PQCFileFolderModel::getFolderFileDialog() {
    return m_folderFileDialog;
}
void PQCFileFolderModel::setFolderFileDialog(QString val) {
    if(m_folderFileDialog == val)
        return;
    m_folderFileDialog = val;
    m_folderFileDialogExists = QFileInfo::exists(val);
    Q_EMIT folderFileDialogChanged();
    Q_EMIT folderFileDialogExistsChanged();
    loadDelayFileDialog->start();

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

}

bool PQCFileFolderModel::getFirstFolderMainViewLoaded() {
    return m_firstFolderMainViewLoaded;
}
void PQCFileFolderModel::setFirstFolderMainViewLoaded(bool val) {
    if(val != m_firstFolderMainViewLoaded) {
        m_firstFolderMainViewLoaded = val;
        Q_EMIT firstFolderMainViewLoadedChanged();
    }
}

/********************************************/
/********************************************/

int PQCFileFolderModel::getCountMainView() {
    return m_countMainView;
}
void PQCFileFolderModel::setCountMainView(int c) {

    PQCFileFolderModelCPP::get().setCountMainView(c);

    if(m_countMainView == c)
        return;
    m_countMainView = c;
    Q_EMIT countMainViewChanged();
}

int PQCFileFolderModel::getCountFoldersFileDialog() {
    return m_countFoldersFileDialog;
}
void PQCFileFolderModel::setCountFoldersFileDialog(int c) {
    if(m_countFoldersFileDialog == c)
        return;
    m_countFoldersFileDialog = c;
    m_countAllFileDialog = m_countFilesFileDialog+c;
    Q_EMIT countFoldersFileDialogChanged();
    Q_EMIT countAllFileDialogChanged();
}

int PQCFileFolderModel::getCountFilesFileDialog() {
    return m_countFilesFileDialog;
}
void PQCFileFolderModel::setCountFilesFileDialog(int c) {
    if(m_countFilesFileDialog == c)
        return;
    m_countFilesFileDialog = c;
    m_countAllFileDialog = m_countFoldersFileDialog+c;
    Q_EMIT countFilesFileDialogChanged();
    Q_EMIT countAllFileDialogChanged();
}

int PQCFileFolderModel::getCountAllFileDialog() {
    return m_countAllFileDialog;
}

/********************************************/
/********************************************/

QStringList PQCFileFolderModel::getEntriesFileDialog() {
    return m_entriesFileDialog;
}

QStringList PQCFileFolderModel::getEntriesMainView() {
    return m_entriesMainView;
}

bool PQCFileFolderModel::getIncludeFilesInSubFolders() {
    return m_includeFilesInSubFolders;
}
void PQCFileFolderModel::setIncludeFilesInSubFolders(bool c) {
    if(m_includeFilesInSubFolders == c)
        return;
    m_includeFilesInSubFolders = c;
    Q_EMIT includeFilesInSubFoldersChanged();
    loadDelayMainView->start();
}

bool PQCFileFolderModel::getReadDocumentOnly() {
    return m_readDocumentOnly;
}
void PQCFileFolderModel::setReadDocumentOnly(bool c) {
    if(m_readDocumentOnly == c)
        return;
    m_readDocumentOnly = c;
    Q_EMIT readDocumentOnlyChanged();
    loadDelayMainView->start();
}

bool PQCFileFolderModel::getReadArchiveOnly() {
    return m_readArchiveOnly;
}
void PQCFileFolderModel::setReadArchiveOnly(bool c) {
    if(m_readArchiveOnly == c)
        return;
    m_readArchiveOnly = c;
    Q_EMIT readArchiveOnlyChanged();
    loadDelayMainView->start();
}

QStringList PQCFileFolderModel::getVirtualFolders() {
    return m_virtualFolders;
}
void PQCFileFolderModel::setVirtualFolders(QStringList val) {
    m_virtualFolders = val;
    Q_EMIT virtualFoldersChanged();
}

QStringList PQCFileFolderModel::getVirtualFiles() {
    return m_virtualFiles;
}
void PQCFileFolderModel::setVirtualFiles(QStringList val) {
    m_virtualFiles = val;
    Q_EMIT virtualFilesChanged();
}

bool PQCFileFolderModel::getLoadVirtualFolderFileDialog() {
    return m_loadVirtualFolderFileDialog;
}
void PQCFileFolderModel::setLoadVirtualFolderFileDialog(bool val) {
    m_loadVirtualFolderFileDialog = val;
    Q_EMIT loadVirtualFolderFileDialogChanged();
}

bool PQCFileFolderModel::getLoadVirtualFolderMainView() {
    return m_loadVirtualFolderMainView;
}
void PQCFileFolderModel::setLoadVirtualFolderMainView(bool val) {
    m_loadVirtualFolderMainView = val;
    Q_EMIT loadVirtualFolderMainViewChanged();
}

/********************************************/
/********************************************/

QSet<QString> PQCFileFolderModel::getRestrictToSuffixes() {
    return m_restrictToSuffixes;
}
void PQCFileFolderModel::setRestrictToSuffixes(QSet<QString> val) {
    if(m_restrictToSuffixes == val)
        return;
    m_restrictToSuffixes = val;
    Q_EMIT restrictToSuffixesChanged();
    loadDelayMainView->start();
    loadDelayFileDialog->start();
}

QStringList PQCFileFolderModel::getNameFilters() {
    return m_nameFilters;
}
void PQCFileFolderModel::setNameFilters(QStringList val) {
    if(m_nameFilters == val)
        return;
    m_nameFilters = val;
    Q_EMIT nameFiltersChanged();
    loadDelayMainView->start();
    loadDelayFileDialog->start();
    checkFilterActive();
}

QStringList PQCFileFolderModel::getFilenameFilters() {
    return m_filenameFilters;
}
void PQCFileFolderModel::setFilenameFilters(QStringList val) {
    if(m_filenameFilters == val)
        return;
    m_filenameFilters = val;
    Q_EMIT filenameFiltersChanged();
    loadDelayMainView->start();
    loadDelayFileDialog->start();
    checkFilterActive();
}

QSet<QString> PQCFileFolderModel::getRestrictToMimeTypes() {
    return m_restrictToMimeTypes;
}
void PQCFileFolderModel::setRestrictToMimeTypes(QSet<QString> val) {
    if(m_restrictToMimeTypes == val)
        return;
    m_restrictToMimeTypes = val;
    Q_EMIT restrictToMimeTypesChanged();
    loadDelayMainView->start();
    loadDelayFileDialog->start();
}

QSize PQCFileFolderModel::getImageResolutionFilter() {
    return m_imageResolutionFilter;
}
void PQCFileFolderModel::setImageResolutionFilter(QSize val) {
    if(m_imageResolutionFilter == val)
        return;
    m_imageResolutionFilter = val;
    Q_EMIT imageResolutionFilterChanged();
    loadDelayMainView->start();
    loadDelayFileDialog->start();
    checkFilterActive();
}

qint64 PQCFileFolderModel::getFileSizeFilter() {
    return m_fileSizeFilter;
}
void PQCFileFolderModel::setFileSizeFilter(qint64 val) {
    if(m_fileSizeFilter == val)
        return;
    m_fileSizeFilter = val;
    Q_EMIT fileSizeFilterChanged();
    loadDelayMainView->start();
    loadDelayFileDialog->start();
    checkFilterActive();
}

bool PQCFileFolderModel::getFilterCurrentlyActive() {
    return m_filterCurrentlyActive;
}

void PQCFileFolderModel::checkFilterActive() {

    if(m_nameFilters.length() > 0 || m_filenameFilters.length() > 0 ||
        m_imageResolutionFilter.width() > 0 || m_imageResolutionFilter.height() > 0 ||
        m_fileSizeFilter > 0 || m_ratingsFilter != 0) {

        if(!m_filterCurrentlyActive) {
            m_filterCurrentlyActive = true;
            Q_EMIT filterCurrentlyActiveChanged();
        }

    } else {

        if(m_filterCurrentlyActive) {
            m_filterCurrentlyActive = false;
            Q_EMIT filterCurrentlyActiveChanged();
        }

    }

}

/********************************************/
/********************************************/

int PQCFileFolderModel::getAdvancedSortDone() {
    return m_advancedSortDone;
}

/********************************************/

void PQCFileFolderModel::advancedSortMainView(QString advSortCriteria, bool advSortAscending, QString advSortQuality, QStringList advDateCriteria) {

    qDebug() << "";

    // if nothing changed, reload folder
    QFileInfo info(m_fileInFolderMainView);
    if(info.absolutePath() == cacheAdvancedSortFolderName
        && advSortCriteria == cacheAdvancedSortCriteria
        && info.lastModified().toMSecsSinceEpoch() == cacheAdvancedSortLastModified
        && advSortAscending == cacheAdvancedSortAscending) {

        // we first make sure the count is set to 0
        // to force a refresh of the folder
        const int tmp = getCountMainView();
        setCountMainView(0);
        setCountMainView(tmp);

        m_entriesMainView = cacheAdvancedSortFolder;
        PQCFileFolderModelCPP::get().setEntriesMainView(cacheAdvancedSortFolder);
        Q_EMIT newDataLoadedMainView();
        Q_EMIT advancedSortingComplete();
        return;

    }

    advancedSortKeepGoing = true;
    m_advancedSortDone = 0;
    Q_EMIT advancedSortDoneChanged();

#if __cplusplus >= 202002L
    QFuture<void> f = QtConcurrent::run([=, this]() {
#else
    QFuture<void> f = QtConcurrent::run([=]() {
#endif

        QMap<qint64, QStringList> sortedWithKey;

        for(int i = 0; i < m_countMainView; ++i) {

            const QString fn = m_entriesMainView[i];

            if(!advancedSortKeepGoing) {
                return;
            }

            // the key used for sorting
            // depending on the criteria, it is computed in different ways
            qint64 key = 0;

            if(advSortCriteria == "resolution") {

                QSize size = PQCResolutionCache::get().getResolution(fn);
                if(!size.isValid()) {
                    size = PQCImageHandler::get().getSize(fn);
                    if(size.isValid())
                        PQCResolutionCache::get().saveResolution(fn, size);
                }

                key = size.width()+size.height();

            } else if(advSortCriteria == "luminosity") {

                QSize requestedSize = QSize(512,512);
                if(advSortQuality == "medium")
                    requestedSize = QSize(1024,1024);
                else if(advSortQuality == "high")
                    requestedSize = QSize(-1,-1);

                QSize origSize;
                QString err = "";
                QImage img = PQCImageHandler::get().getImage(fn, requestedSize, origSize, err);
                if(!err.isEmpty()) {
                    qWarning() << "Error loading image:" << err;
                    continue;
                }
                PQCResolutionCache::get().saveResolution(fn, origSize);

                QRgb *rgb = reinterpret_cast<QRgb*>(img.bits());

                quint64 pixelCount = img.width() * img.height();

                double val = 0;
                for(int i = 0; i < img.height(); ++i) {
                    qint64 tmpval = 0;
                    for(int j = 0; j < img.width(); ++j) {
                        int h,s,v;
                        QColor col(rgb[i*img.width()+j]);
                        col.getHsv(&h,&s,&v);
                        tmpval += v;
                    }
                    val += static_cast<double>(tmpval)/static_cast<double>(pixelCount);

                    if(!advancedSortKeepGoing)
                        return;

                }

                key = val;

            } else if(advSortCriteria == "exifdate") {

                bool foundvalue = false;

                for(int j = 0; j < advDateCriteria.length(); ++j) {

                    QString item = advDateCriteria.at(j);

                    if(item == "exiforiginal" || item == "exifdigital") {

#ifdef PQMEXIV2

#if EXIV2_TEST_VERSION(0, 28, 0)
                        Exiv2::Image::UniquePtr image;
#else
                        Exiv2::Image::AutoPtr image;
#endif
                        try {
                            image  = Exiv2::ImageFactory::open(m_entriesMainView[i].toStdString());
                            image->readMetadata();
                        } catch (Exiv2::Error& e) {
                            // An error code of 11 means unknown file type
                            // Since we always try to read any file's meta data, this happens a lot
#if EXIV2_TEST_VERSION(0, 28, 0)
                            if(e.code() != Exiv2::ErrorCode::kerFileContainsUnknownImageType)
#else
                            if(e.code() != 11)
#endif
                                qWarning() << "ERROR reading exiv data (caught exception):" << e.what();
                            else
                                qDebug() << "ERROR reading exiv data (caught exception):" << e.what();
                            continue;
                        }


                        /*******************
                        * Obtain EXIF data *
                        ********************/

                        Exiv2::ExifData exifData;

                        try {
                            exifData = image->exifData();
                        } catch(Exiv2::Error &e) {
                            qWarning() << "ERROR: Unable to read exif metadata:" << e.what();
                            continue;
                        }


                        try {

                            Exiv2::ExifMetadata::const_iterator iter;

                            if(item == "exiforiginal")
                                iter = exifData.findKey(Exiv2::ExifKey("Exif.Photo.DateTimeOriginal"));
                            else if(item == "exifdigital")
                                iter = exifData.findKey(Exiv2::ExifKey("Exif.Photo.DateTimeDigitized"));

                            if(iter != exifData.end()) {
                                key = QDateTime::fromString(QString::fromStdString(Exiv2::toString(iter->value())), "yyyy:MM:dd hh:mm:ss").toMSecsSinceEpoch();
                                // If key is 0 then the conversion failed
                                if(key > 0)
                                    foundvalue = true;
                            }

                        } catch(Exiv2::Error &) {
                            // ignore exception -> most likely thrown as key does not exist
                            continue;
                        }

#endif

                    } else if(item == "filecreation") {

#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
                        QFileInfo info(m_entriesMainView[i]);
                        QDateTime bd = info.birthTime();
                        if(bd.isValid()) {
                            key = info.birthTime().toMSecsSinceEpoch();
                            // If key is 0 then the birthTime is unavailable
                            if(key > 0)
                                foundvalue = true;
                        }
#endif

                    } else if(item == "filemodification") {

                        QFileInfo info(m_entriesMainView[i]);
                        key = info.lastModified().toMSecsSinceEpoch();
                        foundvalue = true;

                    } else {

                        qWarning() << "ERROR unknown item:" << item;
                        key = i;

                    }

                    if(foundvalue)
                        break;

                }

                // if no usable value was found, then we simply list them in order
                if(!foundvalue)
                    key = i;

            } else if(advSortCriteria == "rating") {

                key = PQCScriptsImages::get().getStarRating(fn);

            } else {

                QSize requestedSize = QSize(512,512);
                if(advSortQuality == "medium")
                    requestedSize = QSize(1024,1024);
                else if(advSortQuality == "high")
                    requestedSize = QSize(-1,-1);

                QSize origSize;
                QString err = "";
                QImage img = PQCImageHandler::get().getImage(fn, requestedSize, origSize, err);
                if(!err.isEmpty()) {
                    qWarning() << "Error loading image:" << err;
                    continue;
                }
                PQCResolutionCache::get().saveResolution(fn, origSize);
                if(img.format() != QImage::Format_RGB32)
                    img.convertTo(QImage::Format_RGB32);

                // Prepare the lists for the levels
                QVector<qint64> red(256);
                QVector<qint64> green(256);
                QVector<qint64> blue(256);

                // Loop over all rows of the image
                for(int i = 0; i < img.height(); ++i) {

                    // Get the pixel data of row i of the image
                    QRgb *rowData = (QRgb*)img.scanLine(i);

                    // Loop over all columns
                    for(int j = 0; j < img.width(); ++j) {

                        // Get pixel data of pixel at column j in row i
                        QRgb pixelData = rowData[j];

                        ++red[qRed(pixelData)];
                        ++green[qGreen(pixelData)];
                        ++blue[qBlue(pixelData)];

                    }

                }

                if(!advancedSortKeepGoing)
                    return;

                qint64 red_val = 0;
                qint64 green_val = 0;
                qint64 blue_val = 0;

                if(advSortCriteria == "dominantcolor") {

                    QVector<qint64> redSteps(26);
                    QVector<qint64> greenSteps(26);
                    QVector<qint64> blueSteps(26);
                    for(int j = 0; j < 256; ++j) {
                        redSteps[j/10] += red[j];
                        greenSteps[j/10] += green[j];
                        blueSteps[j/10] += blue[j];
                    }

                    red_val = 10*redSteps.indexOf(*std::max_element(redSteps.constBegin(), redSteps.constEnd()));
                    green_val = 10*greenSteps.indexOf(*std::max_element(greenSteps.constBegin(), greenSteps.constEnd()));
                    blue_val = 10*blueSteps.indexOf(*std::max_element(blueSteps.constBegin(), blueSteps.constEnd()));

                } else {

                    // we divide before accumulating to minimize the risk of overflow
                    for(int j = 0; j < red.size(); ++j) red[j] /= static_cast<double>(red.size());
                    for(int j = 0; j < green.size(); ++j) green[j] /= static_cast<double>(green.size());
                    for(int j = 0; j < blue.size(); ++j) blue[j] /= static_cast<double>(blue.size());

                    red_val = red.indexOf(std::accumulate(red.begin(), red.end(), 0));
                    green_val = green.indexOf(std::accumulate(green.begin(), green.end(), 0));
                    blue_val = blue.indexOf(std::accumulate(blue.begin(), blue.end(), 0));

                }

                key = red_val*1000000 + green_val*1000 + blue_val;

            }

            sortedWithKey[key].push_back(m_entriesMainView[i]);

            ++m_advancedSortDone;
            Q_EMIT advancedSortDoneChanged();

        }

        if(!advancedSortKeepGoing) return;

        QList<qint64> allKeys = sortedWithKey.keys();
        if(advSortAscending)
            std::sort(allKeys.begin(), allKeys.end(), std::less<int>());
        else
            std::sort(allKeys.begin(), allKeys.end(), std::greater<int>());

        cacheAdvancedSortFolder.clear();
        for(auto entry : std::as_const(allKeys)) {
            QStringList curVals = sortedWithKey[entry];
            curVals.sort(Qt::CaseInsensitive);
            if(!advSortAscending)
                std::reverse(curVals.begin(), curVals.end());
            for(const auto &e : std::as_const(curVals))
                cacheAdvancedSortFolder << e;
        }

        // we first make sure the count is set to 0
        // to force a refresh of the folder
        const int tmp = getCountMainView();
        setCountMainView(0);
        setCountMainView(tmp);

        m_currentIndex = cacheAdvancedSortFolder.indexOf(m_currentFile);
        m_currentIndexNoDelay = m_currentIndex;

        m_entriesMainView = cacheAdvancedSortFolder;
        PQCFileFolderModelCPP::get().setEntriesMainView(cacheAdvancedSortFolder);
        Q_EMIT currentIndexChanged();
        Q_EMIT currentIndexNoDelayChanged();
        Q_EMIT newDataLoadedMainView();
        Q_EMIT entriesMainViewChanged();
        Q_EMIT advancedSortingComplete();

        QFileInfo info(m_fileInFolderMainView);
        cacheAdvancedSortFolderName = info.absolutePath();
        cacheAdvancedSortLastModified = info.lastModified().toMSecsSinceEpoch();
        cacheAdvancedSortCriteria = advSortCriteria;
        cacheAdvancedSortAscending = advSortAscending;

    });

}

void PQCFileFolderModel::advancedSortMainViewCANCEL() {
    advancedSortKeepGoing = false;
}

void PQCFileFolderModel::forceReloadMainView() {
    loadDelayMainView->stop();
    loadDataMainView();
}

void PQCFileFolderModel::forceReloadFileDialog() {
    loadDelayFileDialog->stop();
    loadDataFileDialog();
}

int PQCFileFolderModel::getIndexOfMainView(QString filepath) {
    for(int i = 0; i < m_entriesMainView.length(); ++i) {
        if(m_entriesMainView[i] == filepath)
            return i;
    }
    return -1;
}

void PQCFileFolderModel::removeEntryMainView(int index) {

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

    QFileInfo info(m_fileInFolderMainView);
    if(info.absolutePath() == cacheAdvancedSortFolderName) {
        QString oldentry = m_entriesMainView[index];
        cacheAdvancedSortFolder.removeOne(oldentry);
    }

    if(m_currentIndex < m_countMainView-1)
        setFileInFolderMainView(m_entriesMainView[index+1]);
    else if(m_countMainView > 1)
        setFileInFolderMainView(m_entriesMainView[index-1]);
    else
        setFileInFolderMainView("");

}

void PQCFileFolderModel::resetModel() {

    qDebug() << "";

    delete watcherFileDialog;
    watcherFileDialog = new QFileSystemWatcher;
    delete watcherMainView;
    watcherMainView = new QFileSystemWatcher;
    m_entriesMainView.clear();
    PQCFileFolderModelCPP::get().setEntriesMainView({});

    setFileInFolderMainView("");
    setCountMainView(0);

    m_readDocumentOnly = false;
    m_readArchiveOnly = false;
    m_includeFilesInSubFolders = false;

    m_entriesMainView.clear();
    PQCFileFolderModelCPP::get().setEntriesMainView({});

    cache.resetData();

    setCurrentIndex(-1);

}

/********************************************/
/********************************************/


int PQCFileFolderModel::getCurrentIndex() {
    return m_currentIndex;
}

int PQCFileFolderModel::getCurrentIndexNoDelay() {
    return m_currentIndexNoDelay;
}

void PQCFileFolderModel::setCurrentIndex(int val) {

    PQCFileFolderModelCPP::get().setCurrentIndex(val);

    if(m_currentIndex != val) {
        m_currentIndex = val;
        if(m_currentIndex == -1)
            m_currentFile = "";
        else
            m_currentFile = m_entriesMainView[m_currentIndex];
        PQCFileFolderModelCPP::get().setCurrentFile(m_currentFile);

        m_currentIndexNoDelay = m_currentIndex;
        m_currentFileNoDelay = m_currentFile;

        Q_EMIT currentIndexNoDelayChanged();
        Q_EMIT currentFileNoDelayChanged();

        timerNotifyCurrentIndexChanged->start();

    }


}

int PQCFileFolderModel::getIndexOf(QString file) {
    return m_entriesMainView.indexOf(file);
}

QString PQCFileFolderModel::getCurrentFile() {
    return m_currentFile;
}

QString PQCFileFolderModel::getCurrentFileNoDelay() {
    return m_currentFileNoDelay;
}

bool PQCFileFolderModel::getIsPDF() {
    return m_isPDF;
}

bool PQCFileFolderModel::getIsARC() {
    return m_isARC;
}

QString PQCFileFolderModel::getPdfName() {
    return m_pdfName;
}

int PQCFileFolderModel::getPdfNum() {
    return m_pdfNum;
}

QString PQCFileFolderModel::getArcName() {
    return m_arcName;
}

QString PQCFileFolderModel::getArcFile() {
    return m_arcFile;
}


/********************************************/
/********************************************/

void PQCFileFolderModel::loadDataMainView() {

    qDebug() << "";

    ////////////////////////
    // virtual folder

    bool virtualFolderLoaded = false;
    bool virtualFolderEmpty = false;
    if(m_loadVirtualFolderMainView) {

        qDebug() << "Loading virtual folder:" << m_virtualFolders << "/" << m_virtualFiles;

        QStringList allFiles = m_virtualFiles;
        for(const QString &f : std::as_const(m_virtualFolders)) {

            allFiles.append(getAllFiles(f));

        }

        m_entriesMainView = allFiles;

        PQCFileFolderModelCPP::get().setEntriesMainView(m_entriesMainView);
        setCountMainView(m_entriesMainView.length());

        if(allFiles.length() == 0)
            virtualFolderEmpty = true;
        else
            virtualFolderLoaded = true;

    }


    ////////////////////////
    // no new directory

    if(virtualFolderEmpty || m_fileInFolderMainView.isEmpty()) {

        qDebug() << "No file in main view -> nothing here";

        // clear old entries
        m_entriesMainView.clear();
        PQCFileFolderModelCPP::get().setEntriesMainView({});
        if(watcherMainView->directories().length())
            watcherMainView->removePaths(watcherMainView->directories());
        if(watcherMainView->files().length())
            watcherMainView->removePaths(watcherMainView->files());
        setCountMainView(0);

        // reset variables
        m_currentFile = "";
        PQCFileFolderModelCPP::get().setCurrentFile(m_currentFile);
        m_currentIndex = -1;
        m_currentIndexNoDelay = -1;
        m_currentFileNoDelay = "";
        Q_EMIT newDataLoadedMainView();
        Q_EMIT countMainViewChanged();
        Q_EMIT currentFileChanged();
        Q_EMIT currentIndexChanged();
        Q_EMIT currentFileNoDelayChanged();
        Q_EMIT currentIndexNoDelayChanged();
        if(!m_firstFolderMainViewLoaded)
            setFirstFolderMainViewLoaded(true);
        return;
    }

    if(!virtualFolderLoaded) {

        ////////////////////////
        // load files

        int numberPageDocument = 0;
        const int pdfIdx = m_fileInFolderMainView.indexOf("::PDF::");
        if(pdfIdx > -1) {
            m_readDocumentOnly = true;
            numberPageDocument = m_fileInFolderMainView.mid(0,pdfIdx).toInt();
            m_fileInFolderMainView = m_fileInFolderMainView.mid(pdfIdx+7);
        } else {
            const int arcIdx = m_fileInFolderMainView.indexOf("::ARC::");
            if(arcIdx > -1) {
                m_readArchiveOnly = true;
                numberPageDocument = m_fileInFolderMainView.mid(0,arcIdx).toInt();
                m_fileInFolderMainView = m_fileInFolderMainView.mid(arcIdx+7);
            }
        }

        const bool isFolder = (QFile::exists(m_fileInFolderMainView) && !QFileInfo(m_fileInFolderMainView).isFile()) || m_fileInFolderMainView.endsWith("/");

        // clear any old watchers
        if(watcherMainView->directories().length())
            watcherMainView->removePaths(watcherMainView->directories());
        if(watcherMainView->files().length())
            watcherMainView->removePaths(watcherMainView->files());

        // and set the current one
        watcherMainView->addPath(isFolder ? m_fileInFolderMainView : QFileInfo(m_fileInFolderMainView).absolutePath());

        if(m_readDocumentOnly) {

            m_entriesMainView = listPDFPages(m_fileInFolderMainView);
            PQCFileFolderModelCPP::get().setEntriesMainView(m_entriesMainView);
            setCountMainView(m_entriesMainView.length());
            m_readDocumentOnly = false;
            setCurrentIndex(numberPageDocument);

            qDebug() << "Found" << m_entriesMainView.length() << "pages in document";

        } else if(m_readArchiveOnly) {

            if(archiveContentPreloaded.length() > 0) {
                m_entriesMainView.clear();
                for(const QString &f : std::as_const(archiveContentPreloaded))
                    m_entriesMainView.append(QString("%1::ARC::%2").arg(f, m_viewerModeBaseFile));
                PQCFileFolderModelCPP::get().setEntriesMainView(m_entriesMainView);
                archiveContentPreloaded.clear();
            }
            setCountMainView(m_entriesMainView.length());
            m_readArchiveOnly = false;
            setCurrentIndex(numberPageDocument);

            qDebug() << "Found" << m_entriesMainView.length() << "files in archive";

        } else {

            m_entriesMainView = getAllFiles(isFolder ? m_fileInFolderMainView : QFileInfo(m_fileInFolderMainView).absolutePath());
            PQCFileFolderModelCPP::get().setEntriesMainView(m_entriesMainView);
            setCountMainView(m_entriesMainView.length());

            qDebug() << "Found" << m_entriesMainView.length() << "files";

            if(isFolder && m_entriesMainView.length())
                m_fileInFolderMainView = m_entriesMainView[0];

        }

    }

    QFileInfo info(m_fileInFolderMainView);
    if(info.absolutePath() == cacheAdvancedSortFolderName)

        advancedSortMainView(PQCSettingsCPP::get().getImageviewAdvancedSortCriteria(),
                             PQCSettingsCPP::get().getImageviewAdvancedSortAscending(),
                             PQCSettingsCPP::get().getImageviewAdvancedSortQuality(),
                             PQCSettingsCPP::get().getImageviewAdvancedSortDateCriteria());

    else {

        cacheAdvancedSortFolderName = "";

        // we first make sure the count is set to 0
        // to force a refresh of the folder
        const int tmp = getCountMainView();
        setCountMainView(0);
        setCountMainView(tmp);

        m_currentIndex = m_entriesMainView.indexOf(m_fileInFolderMainView);
        m_currentFile = m_fileInFolderMainView;
        PQCFileFolderModelCPP::get().setCurrentFile(m_currentFile);

        m_currentIndexNoDelay = m_currentIndex;
        m_currentFileNoDelay = m_currentFile;

        Q_EMIT newDataLoadedMainView();
        Q_EMIT currentIndexChanged();
        Q_EMIT currentFileChanged();
        Q_EMIT currentIndexNoDelayChanged();
        Q_EMIT currentFileNoDelayChanged();

    }

    if(!m_firstFolderMainViewLoaded)
        setFirstFolderMainViewLoaded(true);

}

void PQCFileFolderModel::loadDataFileDialog() {

    qDebug() << "";

    ////////////////////////
    // virtual folder

    bool virtualFolderLoaded = false;
    bool virtualFolderEmpty = false;
    if(m_loadVirtualFolderFileDialog) {

        qDebug() << "Loading virtual folder:" << m_virtualFolders << "/" << m_virtualFiles;

        QStringList allFiles = m_virtualFiles;
        for(const QString &f : std::as_const(m_virtualFolders)) {

            allFiles.append(getAllFiles(f));

        }

        m_entriesFileDialog = allFiles;
        m_countFoldersFileDialog = 0;
        m_countFilesFileDialog = allFiles.length();

        if(allFiles.length() == 0)
            virtualFolderEmpty = true;
        else
            virtualFolderLoaded = true;

    }

    ////////////////////////
    // no new directory

    if(virtualFolderEmpty || m_folderFileDialog.isEmpty()) {

        // clear old entries
        m_entriesFileDialog.clear();
        m_countFoldersFileDialog = 0;
        m_countFilesFileDialog = 0;
        m_countAllFileDialog = 0;

        Q_EMIT newDataLoadedFileDialog();
        Q_EMIT countFoldersFileDialogChanged();
        Q_EMIT countFilesFileDialogChanged();
        Q_EMIT countAllFileDialogChanged();

        return;
    }

    if(!virtualFolderLoaded) {

        ////////////////////////
        // watch directory for changes

        if(watcherFileDialog->directories().length())
            watcherFileDialog->removePaths(watcherFileDialog->directories());
        watcherFileDialog->addPath(m_folderFileDialog);

        ////////////////////////
        // load folders

        m_entriesFileDialog = getAllFolders(m_folderFileDialog);
        m_countFoldersFileDialog = m_entriesFileDialog.length();

        ////////////////////////
        // load files

        m_entriesFileDialog.append(getAllFiles(m_folderFileDialog, true, true));
        m_countFilesFileDialog = m_entriesFileDialog.length()-m_countFoldersFileDialog;

    }

    m_countAllFileDialog = m_countFoldersFileDialog+m_countFilesFileDialog;

    qDebug() << "Found" << m_countFoldersFileDialog << "folders and" << m_countFilesFileDialog << "files";

    Q_EMIT newDataLoadedFileDialog();
    Q_EMIT countFoldersFileDialogChanged();
    Q_EMIT countFilesFileDialogChanged();
    Q_EMIT countAllFileDialogChanged();

}

QStringList PQCFileFolderModel::getAllFolders(const QString folder, const bool forceShowHidden) {

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

    QStringList ret;

    const bool sortReversed = !PQCSettingsCPP::get().getImageviewSortImagesAscending();
    QString sortBy = PQCSettingsCPP::get().getImageviewSortImagesBy();
    const bool showHidden = (PQCSettingsCPP::get().getFiledialogShowHiddenFilesFolders() || forceShowHidden);

#ifdef PQMWITHOUTICU
    if(sortBy == "naturalname")
        sortBy = "name";
#endif

    QDir::SortFlags sortFlags = QDir::IgnoreCase;
    if(sortReversed)
        sortFlags |= QDir::Reversed;
    if(sortBy == "name")
        sortFlags |= QDir::Name;
    else if(sortBy == "time")
        sortFlags |= QDir::Time;
    else if(sortBy == "size")
        sortFlags |= QDir::Size;
    else if(sortBy == "type")
        sortFlags |= QDir::Type;

    const size_t cacheKey = cache.composeCacheKey(folder, false, showHidden, sortReversed, sortBy,
                                                  m_restrictToSuffixes, m_nameFilters, m_filenameFilters, m_restrictToMimeTypes,
                                                  m_imageResolutionFilter, m_fileSizeFilter, false);

    // ratings filters might not be stored in the file so we have to assume the content might have changed
    if(m_ratingsFilter || !cache.loadFoldersFromCache(cacheKey, ret)) {

        QDir dir(folder);

        if(!dir.exists()) {
            qWarning() << "ERROR: Folder location does not exist:" << folder;
            return ret;
        }

        if(showHidden)
            dir.setFilter(QDir::Dirs|QDir::NoDotAndDotDot|QDir::Hidden);
        else
            dir.setFilter(QDir::Dirs|QDir::NoDotAndDotDot);

        if(sortBy != "naturalname")
            dir.setSorting(sortFlags);

        const QFileInfoList lst = dir.entryInfoList();
        ret.reserve(lst.size());
        for(const QFileInfo &f : lst)
            ret.append(f.filePath());

        if(sortBy == "naturalname") {
            QCollator collator;
            collator.setLocale(QLocale::system());
#ifndef PQMWITHOUTICU
            collator.setCaseSensitivity(Qt::CaseInsensitive);
            collator.setIgnorePunctuation(true);
            collator.setNumericMode(true);
#endif
            if(sortReversed)
                std::sort(ret.begin(), ret.end(), [&collator](const QString &file1, const QString &file2) { return collator.compare(QFileInfo(file2).fileName(), QFileInfo(file1).fileName()) < 0; });
            else
                std::sort(ret.begin(), ret.end(), [&collator](const QString &file1, const QString &file2) { return collator.compare(QFileInfo(file1).fileName(), QFileInfo(file2).fileName()) < 0; });
        }

        // ratings might not be stored in the file, so don't cache this
        if(!m_ratingsFilter)
            cache.saveFoldersToCache(cacheKey, ret);

    }

    return ret;

}

QStringList PQCFileFolderModel::getAllFiles(const QString folder, const bool ignoreFiltersExceptDefault, const bool enforceOnlyIncludingThisFolder) {

    qDebug() << "args: folder =" << folder;
    qDebug() << "args: ignoreFiltersExceptDefault =" << ignoreFiltersExceptDefault;

    QStringList ret;

    const bool sortReversed = !PQCSettingsCPP::get().getImageviewSortImagesAscending();
    QString sortBy = PQCSettingsCPP::get().getImageviewSortImagesBy();
    const bool showHidden = PQCSettingsCPP::get().getFiledialogShowHiddenFilesFolders();

#ifdef PQMWITHOUTICU
    if(sortBy == "naturalname")
        sortBy = "name";
#endif

    QDir::SortFlags sortFlags = QDir::IgnoreCase;
    if(sortReversed)
        sortFlags |= QDir::Reversed;
    if(sortBy == "name")
        sortFlags |= QDir::Name;
    else if(sortBy == "time")
        sortFlags |= QDir::Time;
    else if(sortBy == "size")
        sortFlags |= QDir::Size;
    else if(sortBy == "type")
        sortFlags |= QDir::Type;

    // In order to properly sort the resulting list (sorting by directory first and by chosen sorting criteria second (on a per-directory basis)
    // we need to consider each directory on its own before adding it to the resulting list at the end

    QStringList foldersToScan;
    foldersToScan << folder;

    if(!enforceOnlyIncludingThisFolder) {

        if(m_includeFilesInSubFolders) {
            QDirIterator iter(folder, QDir::Dirs|QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
            int count = 0;
            while(iter.hasNext()) {
                iter.next();
                foldersToScan << iter.filePath();

                // we limit the number of subfolders to avoid getting stuck
                ++count;
                if(count > 100)
                    break;
            }
        }

    }

    const bool ignoreLargeArchive = PQCSettingsCPP::get().getFiletypesArchiveIgnoreLargerThan();
    const qint64 ignoreLargeArchiveSize = PQCSettingsCPP::get().getFiletypesArchiveIgnoreLargerThanSize()*1024*1024;

    for(const QString &f : std::as_const(foldersToScan)) {

        const size_t cacheKey = cache.composeCacheKey(f, true, showHidden, sortReversed, sortBy,
                                                      m_restrictToSuffixes, m_nameFilters, m_filenameFilters, m_restrictToMimeTypes,
                                                      m_imageResolutionFilter, m_fileSizeFilter, ignoreFiltersExceptDefault,
                                                      PQCImageHandler::get().getNumFormatsEnabled());

        // ratings filters might not be stored in the file so we have to assume the content might have changed
        if(m_ratingsFilter || !cache.loadFilesFromCache(cacheKey, ret)) {

            QStringList ret_cur;

            QDir dir(f);

            if(!dir.exists()) {
                qWarning() << "ERROR: Folder location does not exist:" << f;
                continue;
            }

            if(showHidden)
                dir.setFilter(QDir::Files|QDir::NoDotAndDotDot|QDir::Hidden);
            else
                dir.setFilter(QDir::Files|QDir::NoDotAndDotDot);

            if(sortBy != "naturalname")
                dir.setSorting(sortFlags);

            if(m_nameFilters.size() == 0 && m_restrictToSuffixes.size() == 0 && m_restrictToMimeTypes.size() == 0 && m_imageResolutionFilter.isNull() && m_fileSizeFilter == 0 && m_ratingsFilter == 0) {
                const QFileInfoList lst = dir.entryInfoList();
                if(PQCSettingsCPP::get().getFiletypesLoadAppleLivePhotos()) {
                    for(const auto &f: lst) {
                        QFileInfo info(f);
                        // we need to exclude video files connected to Apple Live Videos (if support enabled)
                        if(info.suffix().toLower() == "mov" && QFileInfo::exists(info.absolutePath()+"/"+info.baseName()+".heic"))
                            continue;
                        ret_cur << f.filePath();
                    }
                } else {
                    for(const auto &f: lst) {
                        const QString fp = f.filePath();
                        // ignore large archives
                        if(PQCScriptsImages::get().isArchive(fp) && ignoreLargeArchive && f.size() > ignoreLargeArchiveSize)
                            continue;
                        ret_cur << fp;
                    }
                }
            } else {

                const QFileInfoList lst = dir.entryInfoList();
                for(const auto &f : lst) {

                    if(!ignoreFiltersExceptDefault) {

                        if(m_fileSizeFilter != 0) {

                            // only show images greater than -> fails check
                            if(m_fileSizeFilter > 0 && f.size() < m_fileSizeFilter)
                                continue;

                            // only show images less than -> fails check
                            if(m_fileSizeFilter < 0 && f.size() > -m_fileSizeFilter)
                                continue;

                        }

                        if(!m_imageResolutionFilter.isNull()) {

                            const bool greater = (m_imageResolutionFilter.width()>0);
                            const int width = m_imageResolutionFilter.width();
                            const int height = m_imageResolutionFilter.height();

                            QSize origSize = PQCImageHandler::get().getSize(f.absoluteFilePath());

                            if(greater && ((origSize.width() < width && width > 0) || (origSize.height() < height && height > 0)))
                                continue;

                            if(!greater && ((origSize.width() > -width && width < 0) || (origSize.height() > -height && width < 0)))
                                continue;

                        }

                        if(m_ratingsFilter != 0) {

                            int stars = PQCScriptsImages::get().getStarRating(f.absoluteFilePath());
                            if(stars == 0) {
                                if(m_ratingsFilter != 100)
                                    continue;
                            } else if((m_ratingsFilter < 0 && stars > -m_ratingsFilter) || m_ratingsFilter == 100) {
                                continue;
                            } else if((m_ratingsFilter > 0 && stars < m_ratingsFilter) || m_ratingsFilter == 100) {
                                continue;
                            }

                        }

                    }

                    QString suffix1 = f.suffix().toLower();
                    QString suffix2 = f.completeSuffix().toLower();
                    if(f.isSymLink() && f.exists()) {
                        suffix1 = QFileInfo(f.symLinkTarget()).suffix().toLower();
                        suffix2 = QFileInfo(f.symLinkTarget()).completeSuffix().toLower();
                    }
                    if((m_nameFilters.size() == 0 || (!ignoreFiltersExceptDefault && (m_nameFilters.contains(suffix1) || m_nameFilters.contains(suffix2)))) &&
                        (m_restrictToSuffixes.size() == 0 || m_restrictToSuffixes.contains(suffix1) || m_restrictToSuffixes.contains(suffix2))) {
                        if(m_filenameFilters.length() == 0 || ignoreFiltersExceptDefault) {
                            const QString fp = f.absoluteFilePath();
                            // we need to exclude video files connected to Apple Live Videos (if support enabled)
                            if(PQCSettingsCPP::get().getFiletypesLoadAppleLivePhotos() && suffix1 == "mov" && QFileInfo::exists(f.absolutePath()+"/"+f.baseName()+".heic"))
                                continue;
                            // ignore large archives
                            if(PQCScriptsImages::get().isArchive(fp) && ignoreLargeArchive && f.size() > ignoreLargeArchiveSize)
                                continue;
                            ret_cur << fp;
                        } else {
                            const QString fp = f.absoluteFilePath();
                            // we need to exclude video files connected to Apple Live Videos (if support enabled)
                            if(PQCSettingsCPP::get().getFiletypesLoadAppleLivePhotos() && suffix1 == "mov" && QFileInfo::exists(f.absolutePath()+"/"+f.baseName()+".heic"))
                                continue;
                            // ignore large archives
                            if(PQCScriptsImages::get().isArchive(fp) && ignoreLargeArchive && f.size() > ignoreLargeArchiveSize)
                                continue;
                            for(const QString &fil : std::as_const(m_filenameFilters)) {
                                if(f.baseName().contains(fil)) {
                                    ret_cur << fp;
                                    break;
                                }
                            }
                        }
                    }
                    // if not the ending, then check the mime type
                    else if(m_nameFilters.size() == 0 && m_restrictToMimeTypes.contains(db.mimeTypeForFile(f.absoluteFilePath()).name())) {
                        if(m_filenameFilters.length() == 0 || ignoreFiltersExceptDefault) {
                            const QString fp = f.absoluteFilePath();
                            // ignore large archives
                            if(PQCScriptsImages::get().isArchive(fp) && ignoreLargeArchive && f.size() > ignoreLargeArchiveSize)
                                continue;
                            ret_cur << fp;
                        } else {
                            const QString fp = f.absoluteFilePath();
                            // ignore large archives
                            if(PQCScriptsImages::get().isArchive(fp) && ignoreLargeArchive && f.size() > ignoreLargeArchiveSize)
                                continue;
                            for(const QString &fil : std::as_const(m_filenameFilters)) {
                                if(f.baseName().contains(fil)) {
                                    ret_cur << fp;
                                    break;
                                }
                            }
                        }
                    }

                }

            }

            if(sortBy == "naturalname") {
                QCollator collator;
                collator.setLocale(QLocale::system());
#ifndef PQMWITHOUTICU
                collator.setCaseSensitivity(Qt::CaseInsensitive);
                collator.setIgnorePunctuation(true);
                collator.setNumericMode(true);
#endif
                if(sortReversed)
                    std::sort(ret_cur.begin(), ret_cur.end(), [&collator](const QString &file1, const QString &file2) { return collator.compare(file2, file1) < 0; });
                else
                    std::sort(ret_cur.begin(), ret_cur.end(), [&collator](const QString &file1, const QString &file2) { return collator.compare(file1, file2) < 0; });
            }

            // add current list, sorted, to global result list
            ret << ret_cur;

            // ratings might not be stored in the file, so don't cache this
            if(!m_ratingsFilter)
                cache.saveFilesToCache(cacheKey, ret_cur);

        }

    }

    return ret;

}

QStringList PQCFileFolderModel::listPDFPages(QString path) {

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

    QStringList ret;

#ifdef PQMPOPPLER

    std::unique_ptr<Poppler::Document> document = Poppler::Document::load(path);
    if(document && !document->isLocked()) {
        int numPages = document->numPages();
        for(int i = 0; i < numPages; ++i)
            ret.append(QString("%1::PDF::%2").arg(i).arg(path));
    }

#endif

#ifdef PQMQTPDF
    QPdfDocument doc;
    doc.load(path);

    QPdfDocument::Status err = doc.status();
    if(err == QPdfDocument::Status::Ready) {
        const int numPages = doc.pageCount();
        for(int i = 0; i < numPages; ++i)
            ret.append(QString("%1::PDF::%2").arg(i).arg(path));
    }
#endif

    return ret;

}

void PQCFileFolderModel::handleNewDataLoadedMainView() {

    bool curset = false;

    int newIndex = m_currentIndex;

    // make sure the index is valid
    if(newIndex >= m_countMainView)
        newIndex = m_countMainView-1;
    else if(newIndex == -1 && m_countMainView > 0)
        newIndex = 0;
    else if(newIndex != -1 && m_countMainView == 0)
            newIndex = -1;

    setCurrentIndex(newIndex);

}

void PQCFileFolderModel::removeAllUserFilter() {

    qDebug() << "";

    m_nameFilters.clear();
    m_filenameFilters.clear();
    m_imageResolutionFilter = QSize(0,0);
    m_fileSizeFilter = 0;
    m_ratingsFilter = 0;

    Q_EMIT nameFiltersChanged();
    Q_EMIT filenameFiltersChanged();
    Q_EMIT imageResolutionFilterChanged();
    Q_EMIT fileSizeFilterChanged();
    Q_EMIT ratingsFilterChanged();

    setFileInFolderMainView(getCurrentFile());

}

bool PQCFileFolderModel::isUserFilterSet() {
    return (m_nameFilters.length()>0 ||
            m_filenameFilters.length()>0 ||
            !m_imageResolutionFilter.isNull() ||
            m_fileSizeFilter > 0 ||
            m_ratingsFilter != 0);
}

void PQCFileFolderModel::enableViewerMode(QString baseFile, int page) {

    qDebug() << "args: baseFile =" << baseFile;
    qDebug() << "args: page =" << page;

    m_viewerModeBaseFile = baseFile;
    m_activeViewerMode = true;
    Q_EMIT activeViewerModeChanged();

    if(PQCScriptsImages::get().isPDFDocument(baseFile))
        setFileInFolderMainView(QString("%1::PDF::%2").arg(page).arg(baseFile));
    else {
        archiveContentPreloaded = PQCScriptsImages::get().listArchiveContentWithoutThread(baseFile);
        setFileInFolderMainView(QString("%1::ARC::%2").arg(archiveContentPreloaded[page], baseFile));
    }
    forceReloadMainView();
    setCurrentIndex(page);
}

void PQCFileFolderModel::disableViewerMode(bool bufferDisabling) {

    qDebug() << "";

    m_activeViewerMode = false;
    m_viewerModeBaseFile = "";
    Q_EMIT activeViewerModeChanged();

    if(bufferDisabling)
        m_justLeftViewerMode = true;

    QString tmp = getCurrentFile();

    setFileInFolderMainView(PQCHelper::extractInsidePDFFilename(tmp));
    setFileInFolderMainView(PQCHelper::extractInsideARCFilename(tmp));
    forceReloadMainView();

    m_isPDF = false;
    m_isARC = false;
    Q_EMIT isARCChanged();
    Q_EMIT isPDFChanged();

    if(bufferDisabling)
        timerResetJustLeftViewerMode->start();

}

QString PQCFileFolderModel::getFirstMatchFileDialog(QString partial) {

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

    QFileInfo info(partial);
    QString typed = info.fileName();
    QString parent = partial.chopped(typed.length());

    QStringList folders = getAllFolders(parent, true);

    for(const auto &f : std::as_const(folders)) {
        if(f.sliced(parent.length()).startsWith(typed))
            return QString("%1/").arg(f);

    }

    QStringList files = getAllFiles(parent, true, true);

    for(const auto &f : std::as_const(files)) {
        if(f.sliced(parent.length()).startsWith(typed))
            return f;

    }

    return "";

}

void PQCFileFolderModel::loadNextMatchOfSearch(const QString search) {

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

    if(search.isEmpty()) {
        Q_EMIT PQCNotifyCPP::get().showNotificationMessage("Nothing to search", "No search term to search for yet.");
        return;
    }

    // first look forward
    for(int i = m_currentIndex+1; i < m_countMainView; ++i) {
        const QString cur = m_entriesMainView[i];
        QFileInfo info(cur);
        if(info.fileName().toLower().contains(search.toLower())) {
            setFileInFolderMainView(cur);
            return;
        }
    }

    // then wrap around
    for(int i = 0; i < m_currentIndex+1; ++i) {
        const QString cur = m_entriesMainView[i];
        QFileInfo info(cur);
        if(info.fileName().toLower().contains(search.toLower())) {
            setFileInFolderMainView(cur);
            Q_EMIT PQCNotifyCPP::get().showNotificationMessage("Wrapped around", "Search wrapped around from the beginning.");
            return;
        }
    }

    Q_EMIT PQCNotifyCPP::get().showNotificationMessage("Nothing found", "The current search term returned no results.");

}

QString PQCFileFolderModel::getFilenameOfMostRecentFile(const QString folder) {

    QDir dir(folder);
    const QStringList lst = dir.entryList(QDir::Files|QDir::NoDotAndDotDot, QDir::Time);
    qDebug() << folder;
    qDebug() << lst;
    if(lst.length()) return QString("%1/%2").arg(folder, lst[0]);

    return "";

}
