/*********************************************************************************************
 * Mozilla License
 * Just a meantime project to see the ability of qt, the framework that my OS might be based on
 * And for those linux users that believe in the power of notes
 *********************************************************************************************/

#include "updaterwindow.h"
#include "ui_updaterwindow.h"

#include <math.h>

#include <QTimer>
#include <QMessageBox>
#include <QMouseEvent>
#include <QNetworkReply>
#include <QDesktopServices>
#include <QNetworkAccessManager>
#include <QWindow>

#include <QSimpleUpdater.h>

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
#  define UseXdgOpen
#else
#  ifdef UseXdgOpen
#    undef UseXdgOpen
#  endif
#endif

#ifdef UseXdgOpen
#  include <QProcess>
static QProcess XDGOPEN_PROCESS;
#endif

/**
 * 表示从何处下载更新定义文件
 */
static const QString
        UPDATES_URL("https://raw.githubusercontent.com/nuttyartist/notes/master/UPDATES.json");

/**
 * 初始化窗口组件并配置 QSimpleUpdater
 */
UpdaterWindow::UpdaterWindow(QWidget *parent)
    : QDialog(parent),
      m_downloadDir(QDir::homePath() + "/Downloads/"),
      m_ui(new Ui::UpdaterWindow),
      m_canMoveWindow(false),
      m_checkingForUpdates(false),
      m_dontShowUpdateWindow(false),
      m_forced(false),
      m_reply(nullptr),
      m_updater(QSimpleUpdater::getInstance()),
      m_manager(new QNetworkAccessManager(this))
{
    /* 加载样式表 */
    QFile file(":/styles/updater-window.css");
    if (file.open(QIODevice::ReadOnly)) {
        setStyleSheet(QString::fromUtf8(file.readAll()).toLatin1());
        file.close();
    }

    /* 初始化 UI */
    m_ui->setupUi(this);
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
    setWindowFlag(Qt::WindowContextHelpButtonHint, false);
#endif
    setWindowTitle(qApp->applicationName() + " " + tr("Updater"));

    /* 更改字体 */
#ifdef __APPLE__
    QFont fontToUse = QFont(QStringLiteral("SF Pro Text")).exactMatch()
            ? QStringLiteral("SF Pro Text")
            : QStringLiteral("Roboto");
#elif _WIN32
    QFont fontToUse = QFont(QStringLiteral("Segoe UI")).exactMatch() ? QStringLiteral("Segoe UI")
                                                                     : QStringLiteral("Roboto");
#else
    QFont fontToUse = QFont(QStringLiteral("Roboto"));
#endif

    setFont(fontToUse);
    m_ui->changelog->setFont(fontToUse);
    m_ui->changelog->setTextColor(QColor(26, 26, 26));
    foreach (QWidget *widgetChild, findChildren<QWidget *>()) {
        widgetChild->setFont(fontToUse);
    }

    /* 连接 UI 信号/槽 */
    connect(m_ui->closeButton, &QPushButton::clicked, this, &UpdaterWindow::close);
    connect(m_ui->updateButton, &QPushButton::clicked, this,
            &UpdaterWindow::onDownloadButtonClicked);
    connect(m_updater, &QSimpleUpdater::checkingFinished, this, &UpdaterWindow::onCheckFinished);
    connect(m_ui->checkBox, &QCheckBox::toggled, this, &UpdaterWindow::dontShowUpdateWindowChanged);

    /* 启动 UI 循环 */
    updateTitleLabel();

    /* 移除原生窗口装饰 */
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    setWindowFlags(windowFlags() | Qt::CustomizeWindowHint | Qt::FramelessWindowHint);
#else
    setWindowFlags(windowFlags() | Qt::CustomizeWindowHint);
#endif

    /* 当 xdg-open 完成时响应（仅限 Linux） */
#ifdef UseXdgOpen
    connect(&XDGOPEN_PROCESS, SIGNAL(finished(int)), this, SLOT(onXdgOpenFinished(int)));
#endif

    /* 设置窗口标志 */
    setWindowModality(Qt::ApplicationModal);
}

/**
 * 删除用户界面
 */
UpdaterWindow::~UpdaterWindow()
{
    /* 确保 xdg-open 进程已关闭 */
#ifdef UseXdgOpen
    if (XDGOPEN_PROCESS.isOpen())
        XDGOPEN_PROCESS.close();
#endif

    /* 删除 UI 控件 */
    delete m_ui;
}

/**
 * 设置是否显示更新窗口
 */
void UpdaterWindow::setShowWindowDisable(const bool dontShowWindow)
{
    m_dontShowUpdateWindow = dontShowWindow;
    m_ui->checkBox->setChecked(m_dontShowUpdateWindow);
}

/**
 * 指示 QSimpleUpdater 下载并解析更新定义文件
 */
void UpdaterWindow::checkForUpdates(bool force)
{
    /* 更改静默标志 */
    if (!m_updater->getUpdateAvailable(UPDATES_URL)) {
        m_checkingForUpdates = true;

        m_forced = force;

        /* 设置模块属性 */
        m_updater->setNotifyOnFinish(UPDATES_URL, false);
        m_updater->setNotifyOnUpdate(UPDATES_URL, false);
        m_updater->setDownloaderEnabled(UPDATES_URL, false);
        m_updater->setUseCustomInstallProcedures(UPDATES_URL, true);
        m_updater->setModuleVersion(UPDATES_URL, qApp->applicationVersion());

        /* 检查更新 */
        m_updater->checkForUpdates(UPDATES_URL);
    }

    /* 如果 force 标志被设置，则显示窗口 */
    if (force) {
        if (!m_updater->getUpdateAvailable(UPDATES_URL)) {
            m_ui->updateButton->setEnabled(false);
            m_ui->title->setText(tr("Checking for updates...."));
        }

        resetControls();
        showNormal();
    }
}

/**
 * 重置 UI 控件的状态、文本和信息，以指示 QSimpleUpdater 当前的操作
 */
void UpdaterWindow::resetControls()
{
    /* 重置按钮状态 */
    m_ui->updateButton->setEnabled(false);

    /* 设置已安装版本标签 */
    m_ui->installedVersion->setText(qApp->applicationVersion());

    /* 设置可用版本标签 */
    m_ui->availableVersion->setText(m_updater->getLatestVersion(UPDATES_URL));

    /* 设置标题标签 */
    if (m_updater->getUpdateAvailable(UPDATES_URL)) {
        m_ui->title->setText(tr("A Newer Version is Available!"));
    } else {
        m_ui->title->setText(tr("You're up-to-date!"));
    }

    /* 重置进度控件 */
    m_ui->progressControls->hide();
    m_ui->progressBar->setValue(0);
    m_ui->downloadLabel->setText(tr("Downloading Updates") + "...");
    m_ui->timeLabel->setText(tr("Time remaining") + ": " + tr("unknown"));

    /* 设置更新日志文本 */
    QString changelogText = m_updater->getChangelog(UPDATES_URL);
    m_ui->changelog->setText(changelogText);
    if (m_ui->changelog->toPlainText().isEmpty()) {
        m_ui->changelog->setText("<p>No changelog found...</p>");
    } else {
        m_ui->changelog->setText(changelogText.append(
                "\n")); // 不知道为什么当前更新日志框在底部消失了，所以添加一个新行以查看文本。
    }

    /* 启用/禁用更新按钮 */
    bool available = m_updater->getUpdateAvailable(UPDATES_URL);
    bool validOpenUrl = !m_updater->getOpenUrl(UPDATES_URL).isEmpty();
    bool validDownUrl = !m_updater->getDownloadUrl(UPDATES_URL).isEmpty();
    m_ui->updateButton->setEnabled(available && (validOpenUrl || validDownUrl));

    /* 调整窗口大小 */
    bool showAgain = isVisible();
    int height = minimumSizeHint().height();
    int width = qMax(minimumSizeHint().width(), int(height * 1.2));
    resize(QSize(width, height));

    /* 重新显示窗口（如果需要） */
    if (showAgain) {
        showNormal();
    }
}

/**
 * 在 QSimpleUpdater 下载并解析更新定义文件时更改标题标签的点数
 */
void UpdaterWindow::updateTitleLabel()
{
    if (m_checkingForUpdates) {
        static int num = 0;
        QString base = tr("Checking for updates");
        QString dot = "";
        num++;
        dot.fill('.', num);
        m_ui->title->setText(base + dot);
        if (num == 4)
            num = 0;
    }
    QTimer::singleShot(500, this, SLOT(updateTitleLabel()));
}

/**
 * 更新 UI 控件显示的文本以反映 QSimpleUpdater 获取的信息，并显示对话框
 */
void UpdaterWindow::onUpdateAvailable()
{
    if (!m_dontShowUpdateWindow) {
        resetControls();
        showNormal();
    }
}

/**
 * 初始化更新下载并禁用“更新”按钮
 */
void UpdaterWindow::onDownloadButtonClicked()
{
    if (m_updater->getUpdateAvailable(UPDATES_URL)) {
        m_ui->updateButton->setEnabled(false);
        startDownload(m_updater->getDownloadUrl(UPDATES_URL));
    }
}

/**
 * 初始化更新下载并配置连接以在从下载/更新服务器接收到新数据时自动更新用户界面
 */
void UpdaterWindow::startDownload(const QUrl &url)
{
    /* URL 无效，尝试打开网页浏览器 */
    if (url.isEmpty()) {
        QDesktopServices::openUrl(QUrl(m_updater->getOpenUrl(UPDATES_URL)));
        return;
    }

    /* 取消之前的下载（如果有） */
    if (m_reply) {
        m_reply->abort();
        m_reply->deleteLater();
    }

    /* 开始下载 */
    m_startTime = QDateTime::currentDateTime().toSecsSinceEpoch();
    QNetworkRequest netReq(url);

    netReq.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
                        QNetworkRequest::NoLessSafeRedirectPolicy);
    m_reply = m_manager->get(netReq);

    /* 设置文件名 */
    m_fileName = m_updater->getDownloadUrl(UPDATES_URL).split("/").last();
    if (m_fileName.isEmpty()) {
        m_fileName = QString("%1_Update_%2.bin")
                             .arg(QCoreApplication::applicationName(),
                                  m_updater->getLatestVersion(UPDATES_URL));
    }

    /* 准备下载目录 */
    if (!m_downloadDir.exists())
        m_downloadDir.mkpath(".");

    /* 删除之前的下载（如果有） */
    QFile::remove(m_downloadDir.filePath(m_fileName));

    /* 显示 UI 控件 */
    m_ui->progressControls->show();
    showNormal();

    /* 当下载进度变化或下载完成时更新 UI */
    connect(m_reply, &QNetworkReply::downloadProgress, this, &UpdaterWindow::updateProgress);
    connect(m_reply, &QNetworkReply::finished, this, &UpdaterWindow::onDownloadFinished);
}

/**
 * 打开下载的文件
 */
void UpdaterWindow::openDownload(const QString &file)
{
    /* 文件为空，中止 */
    if (file.isEmpty()) {
        return;
    }

    /* 更改标签 */
    m_ui->downloadLabel->setText(tr("Download finished!"));
    m_ui->timeLabel->setText(tr("Opening downloaded file") + "...");

    /* 尝试打开下载的文件（Windows 和 Mac） */
#ifndef UseXdgOpen
    bool openUrl = QDesktopServices::openUrl(QUrl::fromLocalFile(file));
    if (!openUrl) {
        openDownloadFolder(file);
        m_ui->progressControls->hide();
        qApp->processEvents();
        showNormal();
        close();
    } else {
        qApp->quit();
    }
#endif

    /* 在 Linux 上，使用 xdg-open 来确认文件是否被正确处理 */
#ifdef UseXdgOpen
    XDGOPEN_PROCESS.start("xdg-open", QStringList() << file);
#endif
}

/**
 * 当 QSimpleUpdater 通知我们它已下载并解析更新定义文件时调用
 *
 * 此函数决定是否显示对话框或仅通知用户他/她正在运行最新版本的 notes
 */
void UpdaterWindow::onCheckFinished(const QString &url)
{
    Q_UNUSED(url)

    /* 不允许标题标签自动更改 */
    m_checkingForUpdates = false;

    /* 如果有可用更新，显示窗口 */
    if (m_updater->getUpdateAvailable(url) && (UPDATES_URL == url)) {
        onUpdateAvailable();
    } else if (m_forced) {
        m_forced = false;
        resetControls();
        showNormal();
    }
}

/**
 * 当 xdg-open 以给定的 exitCode 完成时调用
 * 如果 exitCode 不为 0，则尝试打开保存更新文件的文件夹
 */
void UpdaterWindow::onXdgOpenFinished(const int exitCode)
{
#ifdef UseXdgOpen
    const auto arguments = XDGOPEN_PROCESS.arguments();
    if (exitCode != 0 && !arguments.isEmpty()) {
        openDownloadFolder(arguments.first());
    } else {
        QCoreApplication::quit();
    }
#else
    Q_UNUSED(exitCode);
#endif
}

/**
 * 通知用户打开下载的文件时出错，并指示操作系统打开文件所在的文件夹
 */
void UpdaterWindow::openDownloadFolder(const QString &file)
{
    /* 通知用户问题 */
    QString extension = file.split(".").last();
    QMessageBox::information(this, tr("Open Error"),
                             tr("It seems that your OS does not have an "
                                "application that can handle *.%1 files. "
                                "We'll open the downloads folder for you.")
                                     .arg(extension),
                             QMessageBox::Ok);

    /* 获取下载文件的完整路径列表 */
    QString native_path = QDir::toNativeSeparators(QDir(file).absolutePath());
    QStringList directories = native_path.split(QDir::separator());

    /* 从列表中删除文件名以获取更新文件的文件夹 */
    directories.removeLast();
    QString path = directories.join(QDir::separator());

    /* 获取有效的 URL 并打开它 */
    QUrl url = QUrl::fromLocalFile(QDir(path).absolutePath());
    QDesktopServices::openUrl(url);
}

/**
 * 计算接收到的数据和总下载大小的适当单位（字节、KB 或 MB）
 * 然后，此函数继续更新对话框控件/UI
 */
void UpdaterWindow::calculateSizes(qint64 received, qint64 total)
{
    QString totalSize;
    QString receivedSize;

    /* 获取总大小字符串 */
    if (total < 1024) {
        totalSize = tr("%1 bytes").arg(total);
    } else if (total < (1024 * 1024)) {
        totalSize = tr("%1 KB").arg(round(total / 1024));
    } else {
        totalSize = tr("%1 MB").arg(round(total / (1024 * 1024)));
    }

    /* 获取接收大小字符串 */
    if (received < 1024) {
        receivedSize = tr("%1 bytes").arg(received);
    } else if (received < (1024 * 1024)) {
        receivedSize = tr("%1 KB").arg(received / 1024);
    } else {
        receivedSize = tr("%1 MB").arg(received / (1024 * 1024));
    }

    /* 更新标签文本 */
    m_ui->downloadLabel->setText(tr("Downloading updates") + " (" + receivedSize + " " + tr("of")
                                 + " " + totalSize + ")");
}

/**
 * 使用 received 和 total 参数获取下载进度并更新对话框上的进度条值
 */
void UpdaterWindow::updateProgress(qint64 received, qint64 total)
{
    if (total > 0) {
        m_ui->progressBar->setMinimum(0);
        m_ui->progressBar->setMaximum(100);
        m_ui->progressBar->setValue(int((received * 100) / total));

        calculateSizes(received, total);
        calculateTimeRemaining(received, total);
    } else {
        m_ui->progressBar->setMinimum(0);
        m_ui->progressBar->setMaximum(0);
        m_ui->progressBar->setValue(-1);
        m_ui->downloadLabel->setText(tr("Downloading Updates") + "...");
        m_ui->timeLabel->setText(QStringLiteral("%1: %2").arg(tr("Time Remaining"), tr("unknown")));
    }
}

/**
 * 使用两个时间样本（从当前时间和之前的样本）来计算已下载的字节数
 *
 * 然后，此函数继续计算适当的时间单位（小时、分钟或秒）并构建用户友好的字符串，显示在对话框中
 */
void UpdaterWindow::calculateTimeRemaining(qint64 received, qint64 total)
{
    uint difference = QDateTime::currentDateTime().toSecsSinceEpoch() - m_startTime;

    if (difference > 0) {
        QString timeString;
        qreal timeRemaining = total / (received / difference);

        if (timeRemaining > 7200) {
            timeRemaining /= 3600;
            int hours = int(timeRemaining + 0.5);

            if (hours > 1) {
                timeString = tr("about %1 hours").arg(hours);
            } else {
                timeString = tr("about one hour");
            }
        } else if (timeRemaining > 60) {
            timeRemaining /= 60;
            int minutes = int(timeRemaining + 0.5);

            if (minutes > 1) {
                timeString = tr("%1 minutes").arg(minutes);
            } else {
                timeString = tr("1 minute");
            }
        } else if (timeRemaining <= 60) {
            int seconds = int(timeRemaining + 0.5);

            if (seconds > 1) {
                timeString = tr("%1 seconds").arg(seconds);
            } else {
                timeString