#include "updaterdialog.h"
#include "ui_updaterdialog.h"

#include <QDesktopServices>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMessageBox>
#include <QMetaEnum>
#include <cmath>

#include <private/qzipreader_p.h>

QString CHECK_URL = "https://file.tianzd.cn/TianZD/deploy/raw/branch/master/";
QString ORIGIN_URL = "https://file.tianzd.cn/TianZD/deploy/raw/branch/master/";

QString PLATFORM = "windows";

QString APPVERSION = "";
QString APPNAME = "";

QString APPDATE = "0";
QString MODIFYCNT = "0";

UpdaterDialog::UpdaterDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::UpdaterDialog)
{
    ui->setupUi(this);
    this->setWindowTitle("Updater");
    ui->btn_update->setEnabled(0);
    ui->btn_update_patch->setEnabled(0);

    m_startTime = 0;

    m_version = APPVERSION;
    ui->versionInput->setText(m_version);
    ui->appNameInput->setText(APPNAME);

    ui->show->setEnabled(0);
    ui->label_2->setText("");
    ui->label_3->setText("");
//    ui->label_4->setText(APPNAME);
//    ui->versionInput->setEnabled(false);
//    ui->show->appendPlainText(APPNAME);
//    ui->show->appendPlainText(APPVERSION);
//    ui->show->appendPlainText(APPDATE);
//    ui->show->appendPlainText(MODIFYCNT);

    m_pDownloader = new Downloader(this);

    connect(m_pDownloader, &Downloader::doShowInfo, this, [&](const QString & s)
    {
        showStatus(s);
    });

    connect(m_pDownloader, &Downloader::onError, this, [&](QNetworkReply::NetworkError e)
    {
        QMetaEnum metaEnum = QMetaEnum::fromType<QNetworkReply::NetworkError>();
        QString str = QString(metaEnum.valueToKey(e));
        ui->show->appendPlainText(str);
    });

    connect(m_pDownloader, &Downloader::doFinished, this, [&]()
    {
        uint _end_time = QDateTime::currentSecsSinceEpoch();
        uint _total_time = _end_time - m_startTime;
        ui->label_3->setText("total time: " + QString::number(_total_time, 'g', 3) +
                             "s    average speed: " + QString::number((m_totalSize / 1024.0 / _total_time), 'g', 3) + "kb/s");
        ui->show->appendPlainText("download finished");
        onDownloadFinished();
    });

    connect(m_pDownloader, &Downloader::doProgress, this, [&](quint64 received, quint64 total)
    {
        m_totalSize = total;
        if (total > 0)
        {
            ui->progressBar->setMinimum(0);
            ui->progressBar->setMaximum(100);
            ui->progressBar->setValue((received * 100) / total);
            calculateSizes(received, total);
            calculateTimeRemaining(received, total);
        }
        else
        {
            ui->progressBar->setMinimum(0);
            ui->progressBar->setMaximum(0);
            ui->progressBar->setValue(-1);
            ui->show->appendPlainText(tr("Downloading Updates") + "...");
        }
    });
}

UpdaterDialog::~UpdaterDialog()
{
    delete ui;
}

void UpdaterDialog::showStatus(const QString &s)
{
//    ui->show->clear();
    ui->show->appendPlainText(s);
}

void UpdaterDialog::on_btn_check_clicked()
{
    APPNAME = ui->appNameInput->text();
    if(QString(APPNAME) == "")
    {
        ui->show->appendPlainText("Please input app name");
        return;
    }
    ui->show->clear();
    ui->btn_update->setEnabled(0);
    ui->btn_update_patch->setEnabled(0);
    //version
    m_version = ui->versionInput->text();

    if(!m_pManager)
    {
        m_pManager = new QNetworkAccessManager(this);
        connect(m_pManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(onCheckReply(QNetworkReply *)));
    }
    CHECK_URL = ORIGIN_URL + APPNAME + "/updates.json";
    qDebug() << APPNAME << CHECK_URL;
    QNetworkRequest request(CHECK_URL);

    m_pManager->get(request);
}

bool UpdaterDialog::checkVersion(const QString &_localVersion, const QString &_remoteVersion)
{
    //版本号分割，1.1.2分为1 1 2
    QStringList _list_curr = _localVersion.split(".");
    QStringList _list_lastest = _remoteVersion.split(".");

    //检查是否需要更新
    bool _isNotLatest = false;
    //版本号为空
    if(_list_curr.count() == 0)
    {
        m_bDownloadFullExe = true;
        return true;
    }
    if(_list_curr.count() != _list_lastest.count())
        _isNotLatest = true;

    for(int i = 0; i < _list_curr.count(); ++i)
    {
        if(_list_curr[i].toUInt() <  _list_lastest[i].toUInt())
        {
            _isNotLatest = true;
        }
    }

    //版本号一致，检查日期
    if(!_isNotLatest)
    {
        if(m_lastChangeTime.toUInt() > APPDATE.toUInt())
        {
            _isNotLatest = true;
        }

        //检查修改次数
        if(m_modifyCnt != "" && m_modifyCnt != " " && MODIFYCNT != "" && MODIFYCNT != " ")
        {
            if(m_modifyCnt.toUInt() > MODIFYCNT.toUInt())
            {
                _isNotLatest = true;
            }
        }
    }

    return _isNotLatest;
}

void UpdaterDialog::onCheckReply(QNetworkReply *reply)
{
    /* There was a network error */
    if (reply->error() != QNetworkReply::NoError)
    {
        showStatus("check error");
        return;
    }

    /* Try to create a JSON document from downloaded data */
    QJsonDocument document = QJsonDocument::fromJson(reply->readAll());

    /* JSON is invalid */
    if (document.isNull())
    {
        showStatus("reply doc is null");
        return;
    }

    /* Get the platform information */
    QJsonObject updates = document.object().value("updates").toObject();
    QJsonObject platform = updates.value(PLATFORM).toObject();

    /* Get update information */
    m_changelog = platform.value("changelog").toString();
    m_patchVersion = platform.value("patch-version").toString();
    m_fullVersion = platform.value("full-version").toString();
    m_lastChangeTime = platform.value("modify-time").toString();
    m_modifyCnt = platform.value("modifyCnt").toString();
    m_bDownloadFullExe = platform.value("download-full").toBool();
    if(m_version == "" || m_version == "0")  //当前版本号为空
    {
        m_bDownloadFullExe = true;
    }
    m_fileName = APPNAME + "-patch-v" + m_patchVersion + ".zip";
    m_patchUrl = platform.value("patch-url").toString() + APPNAME + "/" + APPNAME + "-patch-v" + m_patchVersion + ".zip";
    m_fullUrl = platform.value("full-url").toString() + APPNAME + "/" + APPNAME + "-full-v" + m_fullVersion + ".exe";

    QString _remoteVersion = m_bDownloadFullExe ? m_fullVersion : m_patchVersion;
    bool _isNotLatest = checkVersion(m_version, _remoteVersion);

    if(_isNotLatest)
    {
        showStatus("======================================"
                   + tr("\ncurrent version: v") + m_version
                   + tr("\nmodify     time: ") + APPDATE
                   + tr("\nmodify      cnt: ") + MODIFYCNT
                   + "\n    ------------------------------"
                   + tr("\nlatest  version: v") + _remoteVersion
                   + tr("\nmodify     time: ") + m_lastChangeTime
                   + tr("\nmodify      cnt: ") + m_modifyCnt
                   + tr("\nchange      log: ") + m_changelog
                   + "\n======================================"
                  );
        if(m_bDownloadFullExe)
        {
            showStatus("need download full exe");
            m_fileName = APPNAME + "-full-v" + m_patchVersion + ".exe";
        }
        showStatus(tr("click update button to update"));
    }
    else
    {
        showStatus("======================================"
                   + tr("\ncurrent software is up-to-date")
                   + tr("\nversion        : v") + _remoteVersion
                   + tr("\nmodify     time: ") + m_lastChangeTime
                   + tr("\nmodify      cnt: ") + m_modifyCnt
                   + "\n======================================"
                   + tr("\nno need to update")
                   + tr("\nclick cancel button to quit"));
    }
    ui->btn_update->setEnabled(1);
    ui->btn_update_patch->setEnabled(1);
}

void UpdaterDialog::calculateSizes(qint64 received, qint64 total)
{
    QString totalSize;
    QString receivedSize;

    if (total < 1024)
        totalSize = tr("%1 bytes").arg(total);

    else if (total < 1048576)
        totalSize = tr("%1 KB").arg(round(total / 1024));

    else
        totalSize = tr("%1 MB").arg((total / 1048576.0));

    if (received < 1024)
        receivedSize = tr("%1 bytes").arg(received);

    else if (received < 1048576)
        receivedSize = tr("%1 KB").arg(received / 1024);

    else
        receivedSize = tr("%1 MB").arg(received / 1048576.0);

    ui->label_2->setText(tr("Downloading updates") + " (" + receivedSize + " " + tr("of") + " " + totalSize
                         + ")");
}

/**
 * Uses two time samples (from the current time and a previous sample) to
 * calculate how many bytes have been downloaded.
 *
 * Then, this function proceeds to calculate the appropiate units of time
 * (hours, minutes or seconds) and constructs a user-friendly string, which
 * is displayed in the dialog.
 */
void UpdaterDialog::calculateTimeRemaining(qint64 received, qint64 total)
{
    static quint64 lastRec = 0;
    static uint lastTime = 0;
    uint _time = QDateTime::currentDateTime().toMSecsSinceEpoch();
    double _speed = (received - lastRec) / 1024.0 / (_time - lastTime) * 1000.0;
    lastTime = _time;
    lastRec = received;
    uint difference = QDateTime::currentDateTime().toSecsSinceEpoch() - m_startTime;

    if (difference > 0)
    {
        QString timeString;
        qreal timeRemaining = (total - received) / (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 = tr("1 second");
        }

        ui->label_3->setText(tr("speed: %1 kb/s  ").arg(_speed) + tr("Time remaining") + ": " + timeString);
    }
}

void UpdaterDialog::on_btn_update_clicked()
{
    m_pDownloader->setFileName(m_fileName);
    m_startTime = QDateTime::currentDateTime().toSecsSinceEpoch();

    QString _url = m_bDownloadFullExe ? m_fullUrl : m_patchUrl;
    m_pDownloader->startDownload(_url);
}

void UpdaterDialog::on_btn_cancel_clicked()
{
    if(!m_UpdateProc)
    {
        m_UpdateProc = new QProcess;
    }
#if defined Q_OS_WIN
    m_UpdateProc->setProgram(APPNAME + ".exe");
#elif defined Q_OS_LINUX
    m_UpdateProc->setProgram("./" + APPNAME);
#endif
    m_UpdateProc->start();
    m_UpdateProc->waitForStarted();
    QApplication::quit();
}

void UpdaterDialog::onDownloadFinished()
{
//    ui->show->appendPlainText(m_pDownloader->fileName());
//    ui->show->appendPlainText("m_bDownloadFullExe");
    auto localInformation = QMessageBox::question(this, "update", "download finished\nstart update?");
    if(localInformation == QMessageBox::Yes)
    {
        QString _appName;
        if(!m_bDownloadFullExe)
        {
            //解压文件
            QFileInfo f(m_pDownloader->getFile());
            QString _path = f.fileName();
            QZipReader * zipReader = new QZipReader(_path);
            zipReader->extractAll(f.path());
            _appName = APPNAME + ".exe";
        }
        else
        {
            _appName = m_pDownloader->fileName();
        }

        QFileInfo f2(QCoreApplication::applicationDirPath() + "/" + _appName);
        if(f2.exists())
        {
            QDesktopServices::openUrl(f2.absoluteFilePath());
        }
        QApplication::quit();
    }
}

void UpdaterDialog::on_btn_update_patch_clicked()
{
    m_pDownloader->setFileName(m_fileName);
    m_startTime = QDateTime::currentDateTime().toSecsSinceEpoch();
    m_bDownloadFullExe = false;
    QString _url = m_patchUrl;
    m_pDownloader->startDownload(_url);
}

