/**************************************************************************
 **                                                                      **
 ** 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 <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QFileInfo>
#include <QSettings>
#include <QScreen>
#include <clocale>
#include <QSqlError>
#include <QSqlQuery>
#include <QImageReader>

#ifdef PQMEXIV2
#ifdef PQMEXIV2_ENABLE_BMFF
#define EXV_ENABLE_BMFF
#endif
#endif

// This needs to come early (in particular before the FreImage header)
#ifdef Q_OS_WIN
#include <windows.h>
#endif

#include <pqc_configfiles.h>
#include <pqc_singleinstance.h>
#include <pqc_validate.h>
#include <pqc_notify_cpp.h>
#include <pqc_messagehandler.h>
#include <pqc_providericon.h>
#include <pqc_providertheme.h>
#include <pqc_providerthumb.h>
#include <pqc_providertooltipthumb.h>
#include <pqc_providerfolderthumb.h>
#include <pqc_providerdragthumb.h>
#include <pqc_providerfull.h>
#include <pqc_providersvg.h>
#include <pqc_providersvgcolor.h>
#include <pqc_providermipmap.h>
#include <pqc_extensionshandler.h>
#include <pqc_extensionsettings.h>
#include <pqc_look.h>
#include <pqc_startuphandler.h>
#include <pqc_settingscpp.h>
#include <scripts/pqc_scriptslocalization.h>
#include <scripts/pqc_scriptsimages.h>

#if defined(PQMIMAGEMAGICK) || defined(PQMGRAPHICSMAGICK)
#include <Magick++.h>
#endif

#ifdef PQMDEVIL
#include <IL/il.h>
#endif

#ifdef PQMLIBVIPS
#include <vips/vips8>
#endif

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

#ifdef Q_OS_WIN
#include <QQuickWindow>
#include <QSGRendererInterface>
#endif

#ifdef PQMFLATPAKBUILD
#include <gio/gio.h>
#endif

#ifdef PQMPHOTOSPHEREQRHI
#include <rhi/qrhi.h>
#include <QQuickWindow>
#endif

int main(int argc, char **argv) {

#ifdef Q_OS_WIN

#ifdef PQMEXIV2
    // Exiv2 0.28.x and above needs this locale in order to support proper unicode (e.g., CJK characters) in file names/paths
    setlocale(LC_ALL, ".UTF8");
#endif

    QFileInfo info_exe(argv[0]);

    qputenv("PATH", QString("%1;%2").arg(qgetenv("PATH"),info_exe.absolutePath().replace("/", "\\")).toLocal8Bit());
    qputenv("MAGICK_CODER_MODULE_PATH", QString("%1").arg(info_exe.absolutePath().replace("/", "\\") + "\\imagemagick\\coders").toLocal8Bit());
    qputenv("MAGICK_FILTER_MODULE_PATH", QString("%1").arg(info_exe.absolutePath().replace("/", "\\") + "\\imagemagick\\filters").toLocal8Bit());

    // This allows for semi-transparent windows
    // By default Qt6 uses Direct3D which does not seem to support this
    QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
#endif

#ifdef PQMPORTABLETWEAKS
    // create directory and set hidden attribute
#ifdef Q_OS_WIN
    QString configLocation = argc > 1 ? QDir::fromNativeSeparators(argv[1]) : QCoreApplication::applicationDirPath();
    QFileInfo info(configLocation);
    if(info.isRelative())
        configLocation = info.absoluteFilePath();
    QString oldportablefolder = QString("%1/photoqt-data").arg(configLocation);
    QString portablefolder = QString("%1/PhotoQtData").arg(configLocation);
    QDir olddir(oldportablefolder);
    QDir newdir(portablefolder);
    if(olddir.exists() && !newdir.exists()) {
        SetFileAttributesA(olddir.absolutePath().toLocal8Bit(), FILE_ATTRIBUTE_NORMAL);
        // move old dir to new dir and remove hidden flag
        if(!olddir.rename(oldportablefolder, portablefolder))
            qWarning() << "Error renaming photoqt-data to PhotoQtData";
    } else {
        // make sure new dir exists
        newdir.mkdir(portablefolder);
    }
#else
    QString portablefolder = QString("%1/PhotoQtData").arg(argc > 1 ? argv[1] : QCoreApplication::applicationDirPath());
    QDir dir;
    dir.mkdir(portablefolder);
#endif
    qputenv("PHOTOQT_PORTABLE_DATA_LOCATION", portablefolder.toLocal8Bit());
#endif

    // avoids warning for customizing native styles (observed in particular on Windows)
    qputenv("QT_QUICK_CONTROLS_IGNORE_CUSTOMIZATION_WARNINGS", "1");

    // Set app information
    QApplication::setApplicationName("PhotoQt");
    QApplication::setOrganizationName("");
    QApplication::setOrganizationDomain("photoqt.org");
    QApplication::setApplicationVersion(PQMVERSION);
    QApplication::setQuitOnLastWindowClosed(true);

    // Set the desktop filename. On Wayland this is necessary to set the icon in the window title and panel
    QApplication::setDesktopFileName("org.photoqt.PhotoQt");

    // custom message handler for qDebug/qLog/qInfo/etc.
    qInstallMessageHandler(pqcMessageHandler);

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

    // only a single instance
    PQCSingleInstance app(argc, argv);

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

    // we take care of any startup checks and potential migrations
    PQCStartupHandler startupHandler(app.getForceShowWizard(), app.getForceSkipWizard());

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

    // we need to disable this limit early on to avoid any failures when loading images later-on
    QImageReader::setAllocationLimit(0);

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

#ifdef PQMVIDEOMPV
    // Qt sets the locale in the QApplication constructor, but libmpv
    // requires the LC_NUMERIC category to be set to "C", so change it back.
    std::setlocale(LC_NUMERIC, "C");
#endif

#ifdef PQMEXIV2
#if EXIV2_TEST_VERSION(0, 28, 0)
    // In this case Exiv2::enableBMFF() defaults to true
    // and the call to it is deprecated
#else
    #ifdef PQMEXIV2_ENABLE_BMFF
        Exiv2::enableBMFF(true);
    #endif
#endif
#endif

#ifdef PQMFLATPAKBUILD
#if !GLIB_CHECK_VERSION(2,35,0)
    g_type_init();
#endif
#endif

    // handle export/import commands
    if(!app.exportAndQuit.isEmpty()) {
        startupHandler.exportData(app.exportAndQuit);
        return 0;
    } else if(!app.importAndQuit.isEmpty()) {
        startupHandler.importData(app.importAndQuit);
        return 0;
    } else if(app.checkConfig) {
        startupHandler.setupDatabases();
        PQCValidate validate;
        validate.validate();
        return 0;
    } else if(app.resetConfig) {
        startupHandler.resetToDefaults();
        return 0;
    } else if(app.showInfo) {
        startupHandler.showInfo();
        return 0;
    } else if(!app.installExtensionFileName.isEmpty()) {
        PQCExtensionsHandler::get().installExtension(app.installExtensionFileName);
    }

    int updateStatus = startupHandler.performChecksAndUpdates();

    /***************************************/
    // figure out modern vs integrated without use of PQCSettings

    if(app.forceModernInterface || app.forceIntegratedInterface) {
        bool useModernInterface = !app.forceIntegratedInterface;
        QSqlDatabase dbtmp = QSqlDatabase::database("settings");
        QSqlQuery query(dbtmp);
        query.prepare("INSERT OR REPLACE INTO `general` (`name`,`value`,`datatype`) VALUES ('InterfaceVariant', :val, 'string')");
        query.bindValue(":val", (useModernInterface ? "modern" : "integrated"));
        if(!query.exec())
            qWarning() << "Unable to update value generalInterfaceVariant:" << query.lastError().text();
        query.clear();
        dbtmp.close();
        PQCSettingsCPP::get().forceInterfaceVariant((useModernInterface ? "modern" : "integrated"));
    }

    // this is important as otherwise the interface variant might not be set up properly leading to a mixed interface (ugly)
    PQCSettingsCPP::get().readDB();

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

    // Get screenshots for fake transparency if needed
    if(PQCSettingsCPP::get().getInterfaceBackgroundImageScreenshot() && qApp->platformName() != "wayland") {

        const QList<QScreen*> screens = QGuiApplication::screens();
        int index = 0;
        bool success = true;
        for(QScreen *screen : screens) {
            const QPixmap pixmap = screen->grabWindow(0);
            QDir d(PQCConfigFiles::get().CACHE_DIR() % "/screenshots/");
            d.mkdir(d.absolutePath());
            const QString fn = d.absolutePath() % "/" % QString::number(index) % ".jpg";
            if(pixmap.isNull() || !pixmap.save(fn)) {
                qWarning() << "Failed to store screenshot of screen" << index << "to" << fn;
                success = false;
                break;
            }
            index += 1;
        }

        PQCNotifyCPP::get().setHaveScreenshots(success);

    }

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

// only one of them will be defined at a time
#if defined(PQMGRAPHICSMAGICK) || defined(PQMIMAGEMAGICK)
    // Initialise Magick as early as possible
    // this needs to happen BEFORE startup check as this might call into Magick
    Magick::InitializeMagick(*argv);
#endif

#ifdef PQMDEVIL
    ilInit();
#endif

#ifdef PQMLIBVIPS
    VIPS_INIT(argv[0]);
#endif

    // on a fresh install this function is called from the wizard
    if(updateStatus != PQEUpdateCheck::FreshInstall)
        PQCScriptsLocalization::get().updateTranslation(PQCSettingsCPP::get().getInterfaceLanguage());

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

    QQmlApplicationEngine engine;

    QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QApplication::exit(-1); }, Qt::QueuedConnection);

#ifdef PQMPHOTOSPHEREQRHI
    // The max texture limit is read from the Rhi class (available in GuiPrivate)
    // this needs to be done in two nested steps:
    // 1) wait until the qml application engine has been created -> have access to window
    // 2) wait until scene graph of said window has been initialized
    // once the second condition is met the limit can be read.
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [&app, &engine]() {

        // no root objects -> something went wrong!
        if(engine.rootObjects().isEmpty()) {
            qWarning() << "no root object found";
            PQCScriptsImages::get().setMaxTextureLimit(0);
            return;
        }

        // find the top level window
        QQuickWindow *window = nullptr;
        const QList<QObject*> lst = engine.rootObjects();
        for(QObject *obj : lst) {
            window = qobject_cast<QQuickWindow *>(obj);
            if(window) break;
        }

        // no window found -> something went wrong!
        if(!window) {
            qWarning() << "no window found";
            PQCScriptsImages::get().setMaxTextureLimit(0);
            return;
        }

        // wait until the scene graph has been initialized
        QObject::connect(window, &QQuickWindow::sceneGraphInitialized, &app, [window]() {

            // get renderer interface
            auto *rif = window->rendererInterface();
            if(!rif) {
                qWarning() << "invalid renderer interface";
                PQCScriptsImages::get().setMaxTextureLimit(0);
                return;
            }

            // get rhi resource
            void *ptr = rif->getResource(window, QSGRendererInterface::RhiResource);
            auto *rhi = static_cast<QRhi *>(ptr);
            if(!rhi) {
                qWarning() << "invalid rhi resource";
                PQCScriptsImages::get().setMaxTextureLimit(0);
                return;
            }

            // read out texture max size limit
            const int limit = rhi->resourceLimit(QRhi::TextureSizeMax);
            qDebug() << "setting maximum texture limit to" << limit;
            PQCScriptsImages::get().setMaxTextureLimit(limit);

        }, Qt::DirectConnection);

    }, Qt::QueuedConnection);
#endif

    engine.addImageProvider("icon", new PQCProviderIcon);
    engine.addImageProvider("theme", new PQCProviderTheme);
    engine.addImageProvider("thumb", new PQCAsyncImageProviderThumb);
    engine.addImageProvider("tooltipthumb", new PQCAsyncImageProviderTooltipThumb);
    engine.addImageProvider("folderthumb", new PQCAsyncImageProviderFolderThumb);
    engine.addImageProvider("dragthumb", new PQCAsyncImageProviderDragThumb);
    engine.addImageProvider("full", new PQCProviderFull);
    engine.addImageProvider("svg", new PQCProviderSVG);
    engine.addImageProvider("svgcolor", new PQCProviderSVGColor);
    engine.addImageProvider("mipmap", new PQCAsyncImageProviderMipMap);

    // the extension settings item
    qmlRegisterType<ExtensionSettings>("ExtensionSettings", 1, 0, "ExtensionSettings");

#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
    engine.loadFromModule("PhotoQt", "PQMainWindow");
#else
    // In Qt 6.4 this path is not automatically added as import path meaning without this PhotoQt wont find any of its modules
    // We also cannot use loadFromModule() as that does not exist yet.
    engine.addImportPath(":/");
    engine.load("qrc:/PhotoQt/qml/PQMainWindow.qml");
#endif

    PQCScriptsLocalization::get().setQmlEngine(&engine);

    int currentExitCode = app.exec();

#ifdef PQMLIBVIPS
    vips_shutdown();
#endif

    return currentExitCode;

}
