/**************************************************************************
 **                                                                      **
 ** 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_startuphandler.h>
#include <pqc_configfiles.h>
#include <pqc_settingscpp.h>
#include <scripts/pqc_scriptsconfig.h>
#include <pqc_validate.h>
#include <pqc_migratesettings.h>
#include <pqc_migrateshortcuts.h>
#include <pqc_migrateimageplugins.h>
#include <pqc_wizard.h>
#include <pqc_singleinstance.h>
#include <QtDebug>
#include <QMessageBox>
#include <QApplication>
#include <QFile>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <iostream>

PQCStartupHandler::PQCStartupHandler(bool forceShowWizard, bool forceSkipWizard, QObject *parent) : QObject(parent),
                                                                                                    m_forceShowWizard(forceShowWizard),
                                                                                                    m_forceSkipWizard(forceSkipWizard) {

    // check if sqlite is available
    if(!QSqlDatabase::isDriverAvailable("QSQLITE3") && !QSqlDatabase::isDriverAvailable("QSQLITE")) {
        //: This is the window title of an error message box
        QMessageBox::critical(0, QApplication::translate("PQCStartup", "SQLite error"),
                              QApplication::translate("PQCStartup", "You seem to be missing the SQLite driver for Qt. This is needed though for a few different things, like reading and writing the settings. Without it, PhotoQt cannot function!"));
        qCritical() << "ERROR: SQLite driver not available. Available drivers are:" << QSqlDatabase::drivers().join(",");
        qCritical() << "PhotoQt cannot function without SQLite available.";
        qApp->quit();
    }

    m_allVersions << "4.0" << "4.1" << "4.2" << "4.3" << "4.4" << "4.5" << "4.6" << "4.7" << "4.8" << "4.8.1" << "4.9" << "4.9.1" << "4.9.2"
                  << "5.0" << "5.1" << "5.2" << "5.3" << "5.4" << "5.4.1";

}

void PQCStartupHandler::setupDatabases() {

    /****************************************************************************************************/
    // create all the databases used throughout PhotoQt that can be connected to from everywhere

    // it is possible that a connection to the settings db already exists

    QSqlDatabase dbcontextmenu, dblocation, dbshortcuts, dbsettings;

    if(QSqlDatabase::isDriverAvailable("QSQLITE3")) {

        if(!QSqlDatabase::contains("settings"))
            dbsettings = QSqlDatabase::addDatabase("QSQLITE3", "settings");
        dbshortcuts = QSqlDatabase::addDatabase("QSQLITE3", "shortcuts");
        dblocation = QSqlDatabase::addDatabase("QSQLITE3", "location");
        dbcontextmenu = QSqlDatabase::addDatabase("QSQLITE3", "contextmenu");

    } else if(QSqlDatabase::isDriverAvailable("QSQLITE")) {

        if(!QSqlDatabase::contains("settings"))
            dbsettings = QSqlDatabase::addDatabase("QSQLITE", "settings");
        dbshortcuts = QSqlDatabase::addDatabase("QSQLITE", "shortcuts");
        dblocation = QSqlDatabase::addDatabase("QSQLITE", "location");
        dbcontextmenu = QSqlDatabase::addDatabase("QSQLITE", "contextmenu");

    }

    dbsettings.setDatabaseName(PQCConfigFiles::get().USERSETTINGS_DB());
    dbshortcuts.setDatabaseName(PQCConfigFiles::get().SHORTCUTS_DB());
    dblocation.setDatabaseName(PQCConfigFiles::get().LOCATION_DB());
    dbcontextmenu.setDatabaseName(PQCConfigFiles::get().CONTEXTMENU_DB());

}

int PQCStartupHandler::performChecksAndUpdates() {

    qDebug() << "";

    // first we validate the structure of folder, files, and databases
    PQCValidate validate;
    validate.validateDirectories(PQCSettingsCPP::get().getThumbnailsCacheBaseDirLocation());

    /********************************************************************/
    /********************************************************************/
    // CHECK SETTINGS DB

    QString oldSettingsVersion = "";
    PQEUpdateCheck settingsChecker = PQEUpdateCheck::SameVersion;

    // if no settings db exist, then it is a fresh install
    if(!QFile::exists(PQCConfigFiles::get().USERSETTINGS_DB())) {
        if(!QFile::exists(PQCConfigFiles::get().OLDSETTINGS_DB()))
            settingsChecker = PQEUpdateCheck::FreshInstall;
        else {
            if(QFile::copy(PQCConfigFiles::get().OLDSETTINGS_DB(), PQCConfigFiles::get().USERSETTINGS_DB()))
                QFile::remove(PQCConfigFiles::get().OLDSETTINGS_DB());
            oldSettingsVersion = "4.8.1";
            settingsChecker = PQEUpdateCheck::Update;
        }
    }

    if(settingsChecker == PQEUpdateCheck::SameVersion) {

        setupDatabases();

        // last time a dev version was run
        // we need to figure this out WITHOUT using the PQCSettings class
        QSqlDatabase dbtmp;
        if(QSqlDatabase::contains("settings"))
            dbtmp = QSqlDatabase::database("settings");
        else {
            if(QSqlDatabase::isDriverAvailable("QSQLITE3"))
                dbtmp = QSqlDatabase::addDatabase("QSQLITE3", "settings");
            else if(QSqlDatabase::isDriverAvailable("QSQLITE"))
                dbtmp = QSqlDatabase::addDatabase("QSQLITE", "settings");
            dbtmp.setDatabaseName(PQCConfigFiles::get().USERSETTINGS_DB());
        }
        if(!dbtmp.open()) {
            qWarning() << "Unable to check old version number:" << dbtmp.lastError().text();
            qWarning() << "Assuming we came from and are on the current version";
        } else {
            QSqlQuery query(dbtmp);
            if(!query.exec("SELECT `value` FROM general WHERE `name`='Version'"))
                qWarning() << "Unable to check for generalVersion setting";
            else {
                if(query.next()) {
                    oldSettingsVersion = query.value(0).toString();
                    if(oldSettingsVersion != QString(PQMVERSION)) {
                        query.clear();
                        dbtmp.close();
                        settingsChecker = PQEUpdateCheck::Update;
                    }
                } else {
                    settingsChecker = PQEUpdateCheck::Update;
                }
            }
            query.clear();
            dbtmp.close();
        }

    }

    if(settingsChecker == PQEUpdateCheck::FreshInstall) {

        setupFresh();
        setupDatabases();

        if(!m_forceSkipWizard)
            showStartupWizard(true);

        // WE CAN STOP HERE!
        return PQEUpdateCheck::FreshInstall;

    } else if(settingsChecker == PQEUpdateCheck::Update) {

        setupDatabases();

        // do migrations
        PQCMigrateSettings::migrate(oldSettingsVersion, m_allVersions);
        validate.validateSettingsDatabase();
        validate.validateSettingsValues();

        // here we also do the image plugins migrations
        PQCMigrateImagePlugins::migrate(oldSettingsVersion, m_allVersions);

        if(!m_forceSkipWizard || m_forceShowWizard)
            showStartupWizard(false);

    } else if(m_forceShowWizard)
        showStartupWizard(false);

    /********************************************************************/
    /********************************************************************/
    // CHECK SHORTCUT DB

    QString oldShortcutsVersion = m_allVersions.at(m_allVersions.length()-2);
    PQEUpdateCheck shortcutsChecker = PQEUpdateCheck::SameVersion;

    // if no shortcuts db exist, then it is a fresh install
    if(!QFile::exists(PQCConfigFiles::get().SHORTCUTS_DB()))

        shortcutsChecker = PQEUpdateCheck::FreshInstall;

    else {

        QSqlDatabase dbtmp;
        if(QSqlDatabase::contains("shortcuts"))
            dbtmp = QSqlDatabase::database("shortcuts");
        else {
            if(QSqlDatabase::isDriverAvailable("QSQLITE3"))
                dbtmp = QSqlDatabase::addDatabase("QSQLITE3", "shortcuts");
            else if(QSqlDatabase::isDriverAvailable("QSQLITE"))
                dbtmp = QSqlDatabase::addDatabase("QSQLITE", "shortcuts");
            dbtmp.setDatabaseName(PQCConfigFiles::get().SHORTCUTS_DB());
        }
        if(!dbtmp.open()) {
            qWarning() << "Unable to check old version number:" << dbtmp.lastError().text();
            qWarning() << "Assuming we came from and are on the current version";
        } else {
            QSqlQuery query(dbtmp);
            if(!query.exec("SELECT `value` FROM config WHERE `name`='version'"))
                qWarning() << "Unable to check for version value";
            else {
                if(query.next()) {
                    oldShortcutsVersion = query.value(0).toString();
                    if(oldShortcutsVersion != QString(PQMVERSION)) {
                        query.clear();
                        dbtmp.close();
                        shortcutsChecker = PQEUpdateCheck::Update;
                    }
                }
            }
            query.clear();
            dbtmp.close();
        }

    }

    if(shortcutsChecker == PQEUpdateCheck::FreshInstall) {

        if(QFile::exists(PQCConfigFiles::get().SHORTCUTS_DB()))
            QFile::remove(PQCConfigFiles::get().SHORTCUTS_DB());
        if(!QFile::copy(":/shortcuts.db", PQCConfigFiles::get().SHORTCUTS_DB()))
            qWarning() << "Unable to create shortcuts database";
        else {
            QFile file(PQCConfigFiles::get().SHORTCUTS_DB());
            file.setPermissions(file.permissions()|QFileDevice::WriteOwner);
        }

    } else if(shortcutsChecker == PQEUpdateCheck::Update) {

        // do migrations
        PQCMigrateShortcuts::migrate(oldShortcutsVersion, m_allVersions);

        validate.validateShortcutsDatabase();

        // store shortcuts version number
        QSqlQuery query(QSqlDatabase::database("shortcuts"));
        query.prepare("UPDATE config SET `value`=:ver WHERE `name`='version'");
        query.bindValue(":ver", PQMVERSION);
        if(!query.exec())
            qWarning() << "ERROR storing version number in shortcut database";

    }

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

    // Finally we check whether last time a new interface variant was requested -> set it if so
    QFile file(PQCConfigFiles::get().CACHE_DIR() + "/nextstartupvariant");
    if(file.exists()) {
        if(!file.open(QIODevice::ReadOnly)) {
            qWarning() << "Unable to read file to set new interface variant";
        } else {

            QTextStream in(&file);
            QString variant = in.readAll().trimmed();

            QSqlQuery query(QSqlDatabase::database("settings"));
            query.prepare("INSERT OR REPLACE INTO `general` (`name`, `value`, `datatype`) VALUES ('InterfaceVariant', :val, 'string')");
            query.bindValue(":val", variant);

            if(!query.exec())
                qWarning() << "Unable to store interface selection:" << query.lastError().text();

            query.clear();
        }
        file.close();
        if(!file.remove()) {
            qWarning() << "Unable to remove file at:" << file.fileName();
            qWarning() << "Interface variant will be re-set again next time";
        }
    }

    // we base the update/fresh install/nothing status on the state of the setting db
    return settingsChecker;

}

QString PQCStartupHandler::getInterfaceVariant() {

    QSqlDatabase dbtmp = QSqlDatabase::database("settings");
    if(!dbtmp.open()) {
        qWarning() << "Unable to check what interface variant to use:" << dbtmp.lastError().text();
    } else {
        QSqlQuery query(dbtmp);
        if(!query.exec("SELECT `value` FROM general WHERE `name`='InterfaceVariant'"))
            qWarning() << "Unable to check for generalInterfaceVariant setting";
        else {
            if(query.next()) {
                const QString val = query.value(0).toString();
                if(val == "modern" || val == "integrated") {
                    return val;
                }
            }
        }
        query.clear();
        dbtmp.close();
    }

    return PQCSettingsCPP::get().getGeneralInterfaceVariant();

}

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

void PQCStartupHandler::exportData(QString path) {

    std::cout << std::endl
              << "PhotoQt v" << PQMVERSION << std::endl << std::endl;

    if(QFile::exists(path)) {

        std::cout << "> The specified file exists already and will be overwritten." << std::endl << std::endl
                  << "Continue? [yN] " << std::flush;

        // request input
        std::string choice;
        std::getline(std::cin, choice);

        // convert input to all lowercase
        std::transform(choice.begin(), choice.end(), choice.begin(), tolower);

        if(choice != "y" && choice != "yes") {
            std::cout << std::endl
                      << "> Cancelling request... Goodbye." << std::endl << std::endl;
            return;
        }

        QFile::remove(path);

    }

    if(!path.endsWith(".pqt"))
        path += ".pqt";

    // use plain cout as we don't want any log/debug info prepended
    std::cout << " > Exporting configuration to " << path.toStdString() << "... " << std::flush;

    if(PQCScriptsConfig::get().exportConfigTo(path))
        std::cout << "done! Goodbye." << std::endl << std::endl;
    else
        std::cout << "failed! Goodbye." << std::endl << std::endl;

}

void PQCStartupHandler::importData(QString path) {

    std::cout << std::endl
              << "PhotoQt v" << PQMVERSION << std::endl << std::endl;

    if(!QFile::exists(path)) {
        std::cout << "> ERROR: The specified file could not be found." << std::endl
                  << "> Stopping here... Goodbye." << std::endl << std::endl;
        return;
    }

    std::cout << "> This will overwrite the existing configuration with the" << std::endl
              << "  configuration found in the specified file." << std::endl
              << "> This step cannot be undone." << std::endl << std::endl
              << "Continue? [yN] " << std::flush;

    // request input
    std::string choice;
    std::getline(std::cin, choice);

    // convert input to all lowercase
    std::transform(choice.begin(), choice.end(), choice.begin(), tolower);

    if(choice == "y" || choice == "yes") {

        // use plain cout as we don't want any log/debug info prepended
        std::cout << std::endl
                  << "> Importing configuration from " << path.toStdString() << "... " << std::endl;

        if(PQCScriptsConfig::get().importConfigFrom(path))
            std::cout << "> Backup copied to temporary location." << std::endl;
        else {
            std::cout << "> Failed! Goodbye." << std::endl << std::endl;
            return;
        }

        if(PQCSingleInstance::importConfig())
            std::cout << "> Import done. Goodbye." << std::endl << std::endl;
        else
            std::cout << "> IMPORT FAILED. Goodbye." << std::endl << std::endl;

        return;

    }

    std::cout << std::endl
              << "> Cancelling request... Goodbye." << std::endl << std::endl;

}

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

void PQCStartupHandler::resetToDefaults() {

    std::cout << std::endl
              << "PhotoQt v" << PQMVERSION << std::endl << std::endl
              << "> This will reset PhotoQt to its default state." << std::endl
              << "> This step cannot be undone." << std::endl << std::endl
              << "Continue? [yN] " << std::flush;

    // request input
    std::string choice;
    std::getline(std::cin, choice);

    // convert input to all lowercase
    std::transform(choice.begin(), choice.end(), choice.begin(), tolower);

    if(choice == "y" || choice == "yes") {

        std::cout << std::endl
                  << " > Resetting to default configuration... " << std::flush;

        setupFresh();

        std::cout << "done! Goodbye." << std::endl << std::endl;

        return;

    }

    std::cout << std::endl
              << "> Cancelling request... Goodbye." << std::endl << std::endl;

}

void PQCStartupHandler::setupFresh() {

    qDebug() << "";

    /**************************************************************/
    // make sure necessary folder exist
    QDir dir;
    dir.mkpath(PQCConfigFiles::get().CONFIG_DIR());
    dir.mkpath(PQCConfigFiles::get().CACHE_DIR());
    dir.mkpath(QFileInfo(PQCConfigFiles::get().USER_PLACES_XBEL()).absolutePath());
    dir.mkpath(PQCConfigFiles::get().THUMBNAIL_CACHE_DIR());
    dir.mkpath(PQCConfigFiles::get().THUMBNAIL_CACHE_DIR() % "/normal/");
    dir.mkpath(PQCConfigFiles::get().THUMBNAIL_CACHE_DIR() % "/large/");
    dir.mkpath(PQCConfigFiles::get().THUMBNAIL_CACHE_DIR() % "/x-large/");
    dir.mkpath(PQCConfigFiles::get().THUMBNAIL_CACHE_DIR() % "/xx-large/");

    /**************************************************************/
    // create default settings database
    if(QFile::exists(PQCConfigFiles::get().USERSETTINGS_DB()))
        QFile::remove(PQCConfigFiles::get().USERSETTINGS_DB());
    if(!QFile::copy(":/usersettings.db", PQCConfigFiles::get().USERSETTINGS_DB()))
        qWarning() << "Unable to create settings database";
    else {
        QFile file(PQCConfigFiles::get().USERSETTINGS_DB());
        file.setPermissions(file.permissions()|QFileDevice::WriteOwner);
    }

    /**************************************************************/
    // create default location database
    if(QFile::exists(PQCConfigFiles::get().LOCATION_DB()))
        QFile::remove(PQCConfigFiles::get().LOCATION_DB());
    if(!QFile::copy(":/location.db", PQCConfigFiles::get().LOCATION_DB()))
        qWarning() << "Unable to create location database";
    else {
        QFile file(PQCConfigFiles::get().LOCATION_DB());
        file.setPermissions(file.permissions()|QFileDevice::WriteOwner);
    }

    /**************************************************************/
    // create default shortcuts database
    if(QFile::exists(PQCConfigFiles::get().SHORTCUTS_DB()))
        QFile::remove(PQCConfigFiles::get().SHORTCUTS_DB());
    if(!QFile::copy(":/shortcuts.db", PQCConfigFiles::get().SHORTCUTS_DB()))
        qWarning() << "Unable to create shortcuts database";
    else {
        QFile file(PQCConfigFiles::get().SHORTCUTS_DB());
        file.setPermissions(file.permissions()|QFileDevice::WriteOwner);
    }

    /**************************************************************/
    // create default contextmenu database
    if(QFile::exists(PQCConfigFiles::get().CONTEXTMENU_DB()))
        QFile::remove(PQCConfigFiles::get().CONTEXTMENU_DB());
    if(!QFile::copy(":/contextmenu.db", PQCConfigFiles::get().CONTEXTMENU_DB()))
        qWarning() << "Unable to create default contextmenu database";
    else {
        QFile file(PQCConfigFiles::get().CONTEXTMENU_DB());
        file.setPermissions(file.permissions()|QFileDevice::WriteOwner);
    }

    /**************************************************************/
    // make sure no old image format settings remain
    const QString imageplugindir = PQCConfigFiles::get().CONFIG_DIR() % "/imageplugins";
    if(QFileInfo::exists(imageplugindir)) {
        dir.setPath(imageplugindir);
        dir.removeRecursively();
    }
    dir.mkpath(imageplugindir);

}

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

void PQCStartupHandler::showInfo() {

    std::cout << std::endl
              << " ** PhotoQt configuration:"
              << std::endl << std::endl
              << PQCScriptsConfig::get().getConfigInfo().toStdString()
              << std::endl;

}

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

void PQCStartupHandler::showStartupWizard(bool freshInstall) {

    qDebug() << "";

    PQCWizard wizard(freshInstall);
    wizard.exec();

}
