/**************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
**************************************************************************/
#include "packagemanagerintroductionpage.h"
#include "packagemanagergui.h"

#include "component.h"
#include "componentmodel.h"
#include "errors.h"
#include "fileutils.h"
#include "messageboxhandler.h"
#include "packagemanagercore.h"
#include "progresscoordinator.h"
#include "performinstallationform.h"
#include "settings.h"
#include "utils.h"
#include "scriptengine.h"
#include "productkeycheck.h"

#include <QApplication>

#include <QString>
#include <QSettings>
#include <QtCore/QDir>
#include <QtCore/QPair>
#include <QtCore/QProcess>
#include <QtCore/QTimer>
#include <QTranslator>
#include <QDir>
#include <QDirIterator>
#include <QTextCodec>
#include <QFileInfo>
#include <QStringList>
#include <QScopedPointer>

#include <QCheckBox>
#include <QDesktopServices>
#include <QFileDialog>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QListWidgetItem>
#include <QMessageBox>
#include <QProgressBar>
#include <QPushButton>
#include <QRadioButton>
#include <QTextBrowser>
#include <QTreeView>
#include <QVBoxLayout>
#include <QShowEvent>
#include <QComboBox>

#ifdef Q_OS_WIN
# include <qt_windows.h>
# include <QWinTaskbarButton>
# include <QWinTaskbarProgress>
#endif

using namespace KDUpdater;
using namespace QInstaller;

// -- IntroductionPage

/*!
    \class QInstaller::IntroductionPage
    \inmodule QtInstallerFramework
    \brief The IntroductionPage class displays information about the product to
    install.
*/

/*!
    \fn IntroductionPage::packageManagerCoreTypeChanged()

    This signal is emitted when the package manager core type changes.
*/

/*!
    Constructs an introduction page with \a core as parent.
*/
IntroductionPage::IntroductionPage(PackageManagerCore *core)
    : PackageManagerPage(core)
    , m_updatesFetched(false)
    , m_allPackagesFetched(false)
    , m_label(0)
    , m_msgLabel(0)
    , m_errorLabel(0)
    , m_progressBar(0)
    , m_packageManager(0)
    , m_updateComponents(0)
    , m_removeAllComponents(0)
{
    setObjectName(QLatin1String("IntroductionPage"));

    QVBoxLayout *layout = new QVBoxLayout(this);
    setLayout(layout);

    m_msgLabel = new QLabel(this);
    m_msgLabel->setWordWrap(true);
    m_msgLabel->setObjectName(QLatin1String("MessageLabel"));

    QWidget *widget = new QWidget(this);
    QVBoxLayout *boxLayout = new QVBoxLayout(widget);

    m_packageManager = new QRadioButton(this);
    m_packageManager->setObjectName(QLatin1String("PackageManagerRadioButton"));
    boxLayout->addWidget(m_packageManager);
    m_packageManager->setChecked(core->isPackageManager());
    connect(m_packageManager, &QAbstractButton::toggled, this, &IntroductionPage::setPackageManager);

    m_updateComponents = new QRadioButton(this);
    m_updateComponents->setObjectName(QLatin1String("UpdaterRadioButton"));
    boxLayout->addWidget(m_updateComponents);
    m_updateComponents->setChecked(core->isUpdater());
    connect(m_updateComponents, &QAbstractButton::toggled, this, &IntroductionPage::setUpdater);

    m_removeAllComponents = new QRadioButton(this);
    m_removeAllComponents->setObjectName(QLatin1String("UninstallerRadioButton"));
    boxLayout->addWidget(m_removeAllComponents);
    m_removeAllComponents->setChecked(core->isUninstaller());
    connect(m_removeAllComponents, &QAbstractButton::toggled,
            this, &IntroductionPage::setUninstaller);
    connect(m_removeAllComponents, &QAbstractButton::toggled,
            core, &PackageManagerCore::setCompleteUninstallation);

    boxLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding));

    m_label = new QLabel(this);
    m_label->setWordWrap(true);
    m_label->setObjectName(QLatin1String("InformationLabel"));
    m_label->setText(tr("Retrieving information..."));
    boxLayout->addWidget(m_label);

    m_progressBar = new QProgressBar(this);
    m_progressBar->setRange(0, 0);
    boxLayout->addWidget(m_progressBar);
    m_progressBar->setObjectName(QLatin1String("InformationProgressBar"));

    boxLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding));

    m_errorLabel = new QLabel(this);
    m_errorLabel->setWordWrap(true);
    boxLayout->addWidget(m_errorLabel);
    m_errorLabel->setObjectName(QLatin1String("ErrorLabel"));

    layout->addWidget(m_msgLabel);
    layout->addWidget(widget);
    layout->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding));

    core->setCompleteUninstallation(core->isUninstaller());

    connect(core, &PackageManagerCore::metaJobProgress, this, &IntroductionPage::onProgressChanged);
    connect(core, &PackageManagerCore::metaJobInfoMessage, this, &IntroductionPage::setMessage);
    connect(core, &PackageManagerCore::coreNetworkSettingsChanged,
            this, &IntroductionPage::onCoreNetworkSettingsChanged);

    m_updateComponents->setEnabled(ProductKeyCheck::instance()->hasValidKey());

#ifdef Q_OS_WIN
    if (QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS7) {
        m_taskButton = new QWinTaskbarButton(this);
        connect(core, &PackageManagerCore::metaJobProgress,
                m_taskButton->progress(), &QWinTaskbarProgress::setValue);
    } else {
        m_taskButton = 0;
    }
#endif
}

/*!
    Determines which page should be shown next depending on whether the
    application is being installed, updated, or uninstalled.
*/
int IntroductionPage::nextId() const
{
    if (packageManagerCore()->isUninstaller())
        return PackageManagerCore::ReadyForInstallation;

    if (packageManagerCore()->isUpdater() || packageManagerCore()->isPackageManager())
        return PackageManagerCore::ComponentSelection;

    return PackageManagerPage::nextId();
}

/*!
    For an uninstaller, always returns \c true. For the package manager and updater, at least
    one valid repository is required. For the online installer, package manager, and updater, valid
    meta data has to be fetched successfully to return \c true.
*/
bool IntroductionPage::validatePage()
{
    PackageManagerCore *core = packageManagerCore();
    if (core->isUninstaller())
        return true;

    setComplete(false);
    if (!validRepositoriesAvailable()) {
        setErrorMessage(QLatin1String("<font color=\"red\">") + tr("A valid folder is required.") + QLatin1String("</font>"));
        return isComplete();
    }

    gui()->setSettingsButtonEnabled(false);
    const bool maintenance = core->isUpdater() || core->isPackageManager();
    if (maintenance) {
        showAll();
        setMaintenanceToolsEnabled(false);
    } else {
        showMetaInfoUpdate();
    }

#ifdef Q_OS_WIN
    if (m_taskButton) {
        if (!m_taskButton->window()) {
            if (QWidget *widget = QApplication::activeWindow())
                m_taskButton->setWindow(widget->windowHandle());
        }

        m_taskButton->progress()->reset();
        m_taskButton->progress()->resume();
        m_taskButton->progress()->setVisible(true);
    }
#endif

    // fetch updater packages
    if (core->isUpdater()) {
        if (!m_updatesFetched) {
            m_updatesFetched = core->fetchRemotePackagesTree();
            if (!m_updatesFetched)
                setErrorMessage(core->error());
        }

        if (m_updatesFetched) {
            if (core->components(QInstaller::PackageManagerCore::ComponentType::Root).count() <= 0)
                setErrorMessage(QString::fromLatin1("<b>%1</b>").arg(tr("Your software is up to date.")));
            else
                setComplete(true);
        }
    }

    // fetch common packages
    if (core->isInstaller() || core->isPackageManager()) {
        bool localPackagesTreeFetched = false;
        if (!m_allPackagesFetched) {
            // first try to fetch the server side packages tree
            m_allPackagesFetched = core->fetchRemotePackagesTree();
            if (!m_allPackagesFetched) {
                QString error = core->error();
                if (core->isPackageManager() && core->status() != PackageManagerCore::ForceUpdate) {
                    // if that fails and we're in maintenance mode, try to fetch local installed tree
                    localPackagesTreeFetched = core->fetchLocalPackagesTree();
                    if (localPackagesTreeFetched) {
                        // if that succeeded, adjust error message
                        error = QLatin1String("<font color=\"red\">") + error + tr(" Local package management only.") + QLatin1String("</font>");
                    }
                }
                setErrorMessage(error);
            }
        }

        if (m_allPackagesFetched || localPackagesTreeFetched)
            setComplete(true);
    }

    if (maintenance) {
        showMaintenanceTools();
        setMaintenanceToolsEnabled(true);
    } else {
        hideAll();
    }
    gui()->setSettingsButtonEnabled(true);

#ifdef Q_OS_WIN
    if (m_taskButton)
        m_taskButton->progress()->setVisible(!isComplete());
#endif
    return isComplete();
}

/*!
    Shows all widgets on the page.
*/
void IntroductionPage::showAll()
{
    showWidgets(true);
}

/*!
    Hides all widgets on the page.
*/
void IntroductionPage::hideAll()
{
    showWidgets(false);
}

/*!
    Hides the widgets on the page except a text label and progress bar.
*/
void IntroductionPage::showMetaInfoUpdate()
{
    showWidgets(false);
    m_label->setVisible(true);
    m_progressBar->setVisible(true);
}

/*!
    Shows the options to install, add, and unistall components on the page.
*/
void IntroductionPage::showMaintenanceTools()
{
    showWidgets(true);
    m_label->setVisible(false);
    m_progressBar->setVisible(false);
}

/*!
    Sets \a enable to \c true to enable the options to install, add, and
    uninstall components on the page.
*/
void IntroductionPage::setMaintenanceToolsEnabled(bool enable)
{
    m_packageManager->setEnabled(enable);
    m_updateComponents->setEnabled(enable && ProductKeyCheck::instance()->hasValidKey());
    m_removeAllComponents->setEnabled(enable);
}

// -- public slots

/*!
    Displays the message \a msg on the page.
*/
void IntroductionPage::setMessage(const QString &msg)
{
    m_label->setText(msg);
}

/*!
    Updates the value of \a progress on the progress bar.
*/
void IntroductionPage::onProgressChanged(int progress)
{
    m_progressBar->setRange(0, 100);
    m_progressBar->setValue(progress);
}

/*!
    Displays the error message \a error on the page.
*/
void IntroductionPage::setErrorMessage(const QString &error)
{
    QPalette palette;
    const PackageManagerCore::Status s = packageManagerCore()->status();
    if (s == PackageManagerCore::Failure || s == PackageManagerCore::Failure) {
        palette.setColor(QPalette::WindowText, Qt::red);
    } else {
        palette.setColor(QPalette::WindowText, palette.color(QPalette::WindowText));
    }

    m_errorLabel->setText(error);
    m_errorLabel->setPalette(palette);

#ifdef Q_OS_WIN
    if (m_taskButton) {
        m_taskButton->progress()->stop();
        m_taskButton->progress()->setValue(100);
    }
#endif
}

/*!
    Returns \c true if at least one valid and enabled repository is available.
*/
bool IntroductionPage::validRepositoriesAvailable() const
{
    const PackageManagerCore *const core = packageManagerCore();
    bool valid = (core->isInstaller() && core->isOfflineOnly()) || core->isUninstaller();

    if (!valid) {
        foreach (const Repository &repo, core->settings().repositories()) {
            if (repo.isEnabled() && repo.isValid()) {
                valid = true;
                break;
            }
        }
    }
    return valid;
}

void IntroductionPage::setUpdater(bool value)
{
    if (value) {
        entering();
        gui()->showSettingsButton(true);
        packageManagerCore()->setUpdater();
        emit packageManagerCoreTypeChanged();
    }
}

void IntroductionPage::setUninstaller(bool value)
{
    if (value) {
        entering();
        gui()->showSettingsButton(false);
        packageManagerCore()->setUninstaller();
        emit packageManagerCoreTypeChanged();
    }
}

void IntroductionPage::setPackageManager(bool value)
{
    if (value) {
        entering();
        gui()->showSettingsButton(true);
        packageManagerCore()->setPackageManager();
        emit packageManagerCoreTypeChanged();
    }
}

/*!
    Resets the internal page state, so that on clicking \uicontrol Next the metadata needs to be
    fetched again.
*/
void IntroductionPage::onCoreNetworkSettingsChanged()
{
    m_updatesFetched = false;
    m_allPackagesFetched = false;
}

/*!
    Initializes the page's fields.
*/
void IntroductionPage::entering()
{
    setComplete(true);
    showWidgets(false);
    setMessage(QString());
    setErrorMessage(QString());
    setButtonText(QWizard::CancelButton, tr("Cancel"));

    m_packageManager->setText(tr("Add or remove app components"));
    m_updateComponents->setText(tr("Update app components"));
    m_removeAllComponents->setText(tr("Remove all app components"));
    m_label->setText(tr("Retrieving information..."));

#if defined(Q_OS_OSX)
    setColoredTitle(tr("Welcome to %1").arg(productName()));
#elif defined(Q_OS_WIN)
    setColoredTitle(tr("Welcome to the %1 Setup Wizard.").arg(productName()));
#endif

    m_progressBar->setValue(0);
    m_progressBar->setRange(0, 0);
    PackageManagerCore *core = packageManagerCore();

    if (core->isUninstaller()) {
        gui()->next();
    }

    const bool maintenance = core->isUpdater() || core->isPackageManager();

    if (maintenance) {
        showMaintenanceTools();
        setMaintenanceToolsEnabled(true);
    }

    if (core->isInstaller()) {
        gui()->setOption(QWizard::IgnoreSubTitles, true);
#if defined(Q_OS_OSX)
        int fontSize = m_msgLabel->font().pointSize();
        QString titleMessage = QString(QStringLiteral("<span style=\" font-size:%1pt;\"><b>")).arg(fontSize + 5) + tr("%1 for Mac OS").arg(productName()) + QStringLiteral("</b></span>") ;
        QString message = tr("This will install %1 on your computer.<br><br> You must have administration rights to install the application.").arg(productName());
        m_msgLabel->setText(titleMessage + QStringLiteral("<br>") + message);

        if (PackageManagerGui *w = gui()) {
            w->button(QWizard::BackButton)->setVisible(false);

            // Change default actions
            w->button(QWizard::NextButton)->disconnect();
            connect(w->button(QWizard::NextButton), &QAbstractButton::clicked,
                    this, &IntroductionPage::onShowModalDialog);
        }
#elif defined(Q_OS_WIN)
        m_msgLabel->setText(tr("This will install %1 on your computer.\n\nIt is recommended that you close all other applications before continuing.\n\nClick Next to continue, or Cancel to exit Setup").arg(productName()));

        if (PackageManagerGui *w = gui()) {
            w->button(QWizard::BackButton)->setVisible(false);

            // Change default actions
            w->button(QWizard::NextButton)->disconnect();
            connect(w->button(QWizard::NextButton), &QAbstractButton::clicked,
                    this, &IntroductionPage::onShowModalDialog);
        }
#endif
    }

    setSettingsButtonRequested((!core->isOfflineOnly()) && (!core->isUninstaller()));
}

/*!
    Called when end users leave the page and the PackageManagerGui:currentPageChanged()
    signal is triggered.
*/
void IntroductionPage::leaving()
{
    m_progressBar->setValue(0);
    m_progressBar->setRange(0, 0);
    setButtonText(QWizard::CancelButton, gui()->defaultButtonText(QWizard::CancelButton));
}

/*!
    Displays widgets on the page.
*/
void IntroductionPage::showWidgets(bool show)
{
    m_label->setVisible(show);
    m_progressBar->setVisible(show);
    m_packageManager->setVisible(show);
    m_updateComponents->setVisible(show);
    m_removeAllComponents->setVisible(show);
}

/*!
    Displays the text \a text on the page.
*/
void IntroductionPage::setText(const QString &text)
{
    m_msgLabel->setText(text);
}

#if (defined Q_OS_WIN) || (defined Q_OS_OSX)
void IntroductionPage::onShowModalDialog() {
    QMessageBox msgBox(MessageBoxHandler::currentBestSuitParent());
    msgBox.setWindowTitle(productName());
    msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
    msgBox.setDefaultButton(QMessageBox::Ok);
    msgBox.setButtonText(QMessageBox::Cancel, tr("Cancel"));
    msgBox.setButtonText(QMessageBox::Ok, tr("Continue"));
    msgBox.setWindowFlags(Qt::WindowFlags(Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowTitleHint | Qt::CustomizeWindowHint));

    QString txt1 = tr("When this software finishes installing, you must restart your computer.");
    QString txt2 = tr("Are you sure you want to install the software now?");
#if (defined Q_OS_OSX)
    QPixmap icn (QLatin1Literal(":/InstallerIconX.png"));
    msgBox.setIconPixmap(icn.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
    msgBox.setWindowModality(Qt::WindowModal);
#endif
    msgBox.setText(txt1 + QLatin1Literal("<br>") + txt2);

    int execCode = msgBox.exec();

    if (execCode == -1 || execCode == QMessageBox::Cancel) {
        if (PackageManagerGui *w = gui()) {
            w->close();
        }
        return;
    }

    if (execCode == QMessageBox::Ok) {
        if (PackageManagerGui *w = gui()) {
            w->next();
        }
    }
}
#endif
