/**************************************************************************
**
** 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 "packagemanagerreadyforinstallationpage.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 "constants.h"

#include "kdsysinfo.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;

// -- ReadyForInstallationPage

/*!
    \class QInstaller::ReadyForInstallationPage
    \inmodule QtInstallerFramework
    \brief The ReadyForInstallationPage class informs end users that the
    installation can begin.
*/

/*!
    Constructs a ready for installation page with \a core as parent.
*/
ReadyForInstallationPage::ReadyForInstallationPage(PackageManagerCore *core)
    : PackageManagerPage(core)
    , m_msgLabel(new QLabel)
    , m_deleteUserData(new QCheckBox)
    , m_deleteUserDataSpacer1(new QSpacerItem(10, 10, QSizePolicy::Preferred, QSizePolicy::Expanding))
    , m_deleteUserDataSpacer2(new QSpacerItem(10, 10, QSizePolicy::Preferred, QSizePolicy::Expanding))
{
    setPixmap(QWizard::WatermarkPixmap, QPixmap());
    setObjectName(QLatin1String("ReadyForInstallationPage"));

    QVBoxLayout *baseLayout = new QVBoxLayout();
    baseLayout->setObjectName(QLatin1String("BaseLayout"));

    QVBoxLayout *topLayout = new QVBoxLayout();
    topLayout->setObjectName(QLatin1String("TopLayout"));

    m_msgLabel->setWordWrap(true);
    m_msgLabel->setObjectName(QLatin1String("MessageLabel"));
    m_msgLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
    topLayout->addWidget(m_msgLabel);

    topLayout->addItem(m_deleteUserDataSpacer1);

    connect(m_deleteUserData, &QCheckBox::stateChanged, this, &ReadyForInstallationPage::onDeleteUserDataChanged);
    m_deleteUserData->setCheckState(Qt::Checked);    
    topLayout->addWidget(m_deleteUserData);

    topLayout->addItem(m_deleteUserDataSpacer2);

    baseLayout->addLayout(topLayout);

    QVBoxLayout *bottomLayout = new QVBoxLayout();
    bottomLayout->setObjectName(QLatin1String("BottomLayout"));
    bottomLayout->addStretch();

    m_taskDetailsBrowser = new QTextBrowser(this);
    m_taskDetailsBrowser->setReadOnly(true);
    m_taskDetailsBrowser->setObjectName(QLatin1String("TaskDetailsBrowser"));
    m_taskDetailsBrowser->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    m_taskDetailsBrowser->setVisible(false);
    bottomLayout->addWidget(m_taskDetailsBrowser);
    bottomLayout->setStretch(1, 10);
    baseLayout->addLayout(bottomLayout);

    setCommitPage(true);
    setLayout(baseLayout);
}

/*!
    Initializes the page's fields based on values from fields on previous
    pages. The text to display depends on whether the page is being used in an
    installer, updater, or uninstaller.
*/
void ReadyForInstallationPage::entering()
{
    setComplete(false);

    m_deleteUserData->setText(tr("Remove user data and settings"));

    if (packageManagerCore()->isUninstaller()) {
        m_taskDetailsBrowser->setVisible(false);
        setButtonText(QWizard::CommitButton, tr("U&ninstall"));
        setColoredTitle(tr("Ready to Uninstall"));

        const QString messageStr0 = tr("Setup is ready to begin removing %1 from your computer.").arg(productName());
        const QString messageStr1 = tr("All content in program directory %2 will be removed.")
            .arg(QDir::toNativeSeparators(QDir(packageManagerCore()->value(scTargetDir)).absolutePath()));
        const QString messageStr2 = tr("To avoid potential data loss, please eject all LaCie RAID volumes from your computer before continuing the uninstall process.");

        QString resultStr = QString("%1<br><font color='red'>%2</font><br>&nbsp;<br><font color='red'>%3</font>")
                .arg(messageStr0)
                .arg(messageStr1)
                .arg(messageStr2);

        m_msgLabel->setText(resultStr);
        setComplete(true);

        // We removed maintain page, where we can select Remove all, update and etc
        if (QAbstractButton *back = gui()->button(QWizard::BackButton))
            back->setVisible(false);

        return;
    } else if (packageManagerCore()->isUpdater() || packageManagerCore()->isPackageManager()) {
        setButtonText(QWizard::CommitButton, tr("U&pdate"));
        setColoredTitle(tr("Ready to Update Packages"));
        m_msgLabel->setText(tr("Ready to update the installer."));
    } else {
        Q_ASSERT(packageManagerCore()->isInstaller());
        setButtonText(QWizard::CommitButton, tr("&Install"));
        setColoredTitle(tr("Ready to Install"));
        m_msgLabel->setText(tr("Ready to install %1 on your computer.")
            .arg(productName()));
    }

    QString htmlOutput;
    bool componentsOk = calculateComponents(&htmlOutput);
    m_taskDetailsBrowser->setHtml(htmlOutput);
    m_taskDetailsBrowser->setVisible(!componentsOk || isVerbose());
    setComplete(componentsOk);

    const VolumeInfo tempVolume = VolumeInfo::fromPath(QDir::tempPath());
    const VolumeInfo targetVolume = VolumeInfo::fromPath(packageManagerCore()->value(scTargetDir));

    const quint64 tempVolumeAvailableSize = tempVolume.availableSize();
    const quint64 installVolumeAvailableSize = targetVolume.availableSize();

    // at the moment there is no better way to check this
    if (targetVolume.size() == 0 && installVolumeAvailableSize == 0) {
        qDebug().nospace() << "Cannot determine available space on device. "
                              "Volume descriptor: " << targetVolume.volumeDescriptor()
                           << ", Mount path: " << targetVolume.mountPath() << ". Continue silently.";
        return;     // TODO: Shouldn't this also disable the "Next" button?
    }

    const bool tempOnSameVolume = (targetVolume == tempVolume);
    if (tempOnSameVolume) {
        qDebug() << "Tmp and install directories are on the same volume. Volume mount point:"
            << targetVolume.mountPath() << "Free space available:"
            << humanReadableSize(installVolumeAvailableSize);
    } else {
        qDebug() << "Tmp is on a different volume than the installation directory. Tmp volume mount point:"
            << tempVolume.mountPath() << "Free space available:"
            << humanReadableSize(tempVolumeAvailableSize) << "Install volume mount point:"
            << targetVolume.mountPath() << "Free space available:"
            << humanReadableSize(installVolumeAvailableSize);
    }

    const quint64 extraSpace = 256 * 1024 * 1024LL;
    quint64 required(packageManagerCore()->requiredDiskSpace());
    quint64 tempRequired(packageManagerCore()->requiredTemporaryDiskSpace());
    if (required < extraSpace) {
        required += 0.1 * required;
        tempRequired += 0.1 * tempRequired;
    } else {
        required += extraSpace;
        tempRequired += extraSpace;
    }

    quint64 repositorySize = 0;
    const bool createLocalRepository = packageManagerCore()->createLocalRepositoryFromBinary();
    if (createLocalRepository && packageManagerCore()->isInstaller()) {
        repositorySize = QFile(QCoreApplication::applicationFilePath()).size();
        // if we create a local repository, take that space into account as well
        required += repositorySize;
    }

    qDebug() << "Installation space required:" << humanReadableSize(required) << "Temporary space "
        "required:" << humanReadableSize(tempRequired) << "Local repository size:"
        << humanReadableSize(repositorySize);

    if (tempOnSameVolume && (installVolumeAvailableSize <= (required + tempRequired))) {
        m_msgLabel->setText(tr("Insufficient space for the temporary files. Available: %1, Required: %2")
            .arg(humanReadableSize(installVolumeAvailableSize),
            humanReadableSize(required + tempRequired)));
        setComplete(false);
        return;
    }

    if (installVolumeAvailableSize < required) {
        m_msgLabel->setText(tr("Insufficient space for all selected app components. Available: %1, Required: %2").arg(humanReadableSize(installVolumeAvailableSize),
            humanReadableSize(required)));
        setComplete(false);
        return;
    }

    if (tempVolumeAvailableSize < tempRequired) {
        m_msgLabel->setText(tr("Insufficient space for the temporary files. Available: %1, Required: %2").arg(humanReadableSize(tempVolumeAvailableSize),
            humanReadableSize(tempRequired)));
        setComplete(false);
        return;
    }

    if (installVolumeAvailableSize - required < 0.01 * targetVolume.size()) {
        // warn for less than 1% of the volume's space being free
        m_msgLabel->setText(tr("The disk you selected appears to have sufficient space. However, there will be less than 1% storage space available after the installation. %1").arg(m_msgLabel->text()));
    } else if (installVolumeAvailableSize - required < 100 * 1024 * 1024LL) {
        // warn for less than 100MB being free
        m_msgLabel->setText(tr("The disk you selected appears to have sufficient space. However, there will be less than 100MB storage space available after the installation. %1")
            .arg(m_msgLabel->text()));
    }

    m_msgLabel->setText(QString::fromLatin1("%1 %2").arg(m_msgLabel->text(),
            tr("The installation will use %1 of disk space.")
            .arg(humanReadableSize(packageManagerCore()->requiredDiskSpace()))));
}

bool ReadyForInstallationPage::calculateComponents(QString *displayString)
{
    QString htmlOutput;
    QString lastInstallReason;
    if (!packageManagerCore()->calculateComponentsToUninstall() ||
            !packageManagerCore()->calculateComponentsToInstall()) {
        htmlOutput.append(QString::fromLatin1("<h2><font color=\"red\">%1</font></h2><ul>")
                          .arg(tr("Unable to resolve all dependencies.")));
        //if we have a missing dependency or a recursion we can display it
        if (!packageManagerCore()->componentsToInstallError().isEmpty()) {
            htmlOutput.append(QString::fromLatin1("<li> %1 </li>").arg(
                                  packageManagerCore()->componentsToInstallError()));
        }
        htmlOutput.append(QLatin1String("</ul>"));
        if (displayString)
            *displayString = htmlOutput;
        return false;
    }

    // In case of updater mode we don't uninstall components.
    if (!packageManagerCore()->isUpdater()) {
        QList<Component*> componentsToRemove = packageManagerCore()->componentsToUninstall();
        if (!componentsToRemove.isEmpty()) {
            htmlOutput.append(QString::fromLatin1("<h3>%1</h3><ul>").arg(tr("App components to remove.")));
            foreach (Component *component, componentsToRemove)
                htmlOutput.append(QString::fromLatin1("<li> %1 </li>").arg(component->name()));
            htmlOutput.append(QLatin1String("</ul>"));
        }
    }

    foreach (Component *component, packageManagerCore()->orderedComponentsToInstall()) {
        const QString installReason = packageManagerCore()->installReason(component);
        if (lastInstallReason != installReason) {
            if (!lastInstallReason.isEmpty()) // means we had to close the previous list
                htmlOutput.append(QLatin1String("</ul>"));
            htmlOutput.append(QString::fromLatin1("<h3>%1</h3><ul>").arg(installReason));
            lastInstallReason = installReason;
        }
        htmlOutput.append(QString::fromLatin1("<li> %1 </li>").arg(component->name()));
    }
    if (displayString)
        *displayString = htmlOutput;
    return true;
}

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

void ReadyForInstallationPage::onDeleteUserDataChanged() {
    packageManagerCore()->setValue(scRemoveUserSettings, m_deleteUserData->isChecked() ? scTrue : scFalse);
}

//#include "packagemanagerreadyforinstallationpage.moc"
//#include "moc_packagemanagerreadyforinstallationpage.cpp"
