#include "processburndatadl.h"
#include "src/frame/logger.h"
#include "src/po/translation.h"
#include "src/data/datacdrom.h"
#include "src/data/datadirectory.h"
#include "src/view/modaldialog.h"
#include "src/view/execdialog.h"
#include "src/frame/signals.h"
#include "src/model/diroptutils.h"
#include "src/view/tip_burnresult.h"
#include "src/model/logicmain.h"


#include "kysdk/kysdk-system/libkysysinfo.h"

#include <QTimer>
#include <QFileInfo>
#include <QDir>
#include <QCoreApplication>
#include <QStandardPaths>
#include <fstream>
#include <QDebug>
#include <QGSettings/QGSettings>

ProcessBurnDataDL::ProcessBurnDataDL(void *root, void *cdrom, QObject *parent) :
    QObject(parent),
    m_oProcess(new QProcess(this)),
    m_bCancel(false),
    m_u32Icon(0),
    m_oIcon(QIcon::fromTheme("ukui-loading-0-symbolic")),
    m_oFlag(false),
    m_bBurnSuccess(false),
    m_bBurnLogError(false),
    m_bVerficationData(false),
    m_bGreateThan2GB(false),
    m_icountBurn(0),
    m_oProgressBarTimer_DVD(new QTimer(this))
{
    m_oCDROM = cdrom;
    m_oRoot = root;
    m_oProcessName.clear();
    m_oArguments.clear();


    init();

    // 检测二进制growisofs是否存在
    if (!binIsExist()) {
        fail(m_oProcessName);
        this->deleteLater();
        return;
    }

    verficationDataInit(m_bVerficationData); // 数据校验初始化
    auditLogInit(); // 审计日志初始化
    connectInit();
    progressUIInit();
    setCommandParameter();

    prepareFile();
    if(!DataFile::isCopySuccess) {
        DataFile::isCopySuccess = true;
        LOGGER->info("(ProcessBurnDataDL 拷贝文件失败");
        fail(m_oProcessName);
        this->deleteLater();
        return;
    }

    // 在此之前，用户可取消，之后'取消'按钮置灰
    emit cancel(false);
    if (m_bCancel) {
        MODAL->hide();
        fail(m_oProcessName);
        this->deleteLater();
        return;
    }

    execProcess();
}

ProcessBurnDataDL::~ProcessBurnDataDL()
{
    if(m_oProgressBarTimer_DVD)
        delete m_oProgressBarTimer_DVD;
    m_oProgressBarTimer_DVD = nullptr;

    if(cloneCDROM)
        delete cloneCDROM;
    cloneCDROM = nullptr;
    LOGGER->info("ProcessBurnDataDL::~ProcessBurnDataDL() 析构函数");
    g_user_signal->setDeviceBusyStatus(m_oDeviceBlock,false);
}

bool ProcessBurnDataDL::binIsExist()
{
    QFileInfo f;
    f.setFile("/bin/growisofs");
    if (!f.exists() || !f.isExecutable()) {
        LOGGER->error(_("There is no %1 program or %1 cannot execute."
                "Please check the cdrecord, growisofs and xorriso is "
                "exists or not, and give the right permissions"
                ".%2 exiting...")
              .arg(f.absoluteFilePath())
              .arg("KylinBurner"));
        return false;
    }
    return true;
}

void ProcessBurnDataDL::init()
{
    cloneCDROM = ((DataCDROM *)m_oCDROM)->clone();
    m_root = (DataDirectory *)m_oRoot;
    m_odestination = m_root->destination();
    m_bGreateThan2GB = m_root->isGreateThan2GByNewAddSingleFile();
    m_ototal = m_root->total(); //新旧文件总数量
    m_oSize = m_root->size(); // 新旧文件总大小
    m_oNewSize = m_root->nSize()/1024.0/1024.0; // 单位：MB, 所有新文件容量
    m_oDeviceBlock = cloneCDROM->device();
    m_bVerficationData = cloneCDROM->settingVerficationData();

    if (cloneCDROM->mediumStatus() == DataMedium::MEDIUM_EMPTY)
    {
        m_oProcessName = _("Burn Data");
        m_bAppendBurn = false;
    }
    else if (cloneCDROM->mediumStatus() == DataMedium::MEDIUM_INCOMPLETE)
    {
        m_oProcessName = _("Append Burn");
        m_bAppendBurn = true;
    }

    g_user_signal->setDeviceBusyStatus(m_oDeviceBlock,true);
}

void ProcessBurnDataDL::progressUIInit()
{
    if (!m_bAppendBurn) {
        LOGGER->registerName(this, _("PROCESS_BD"));
        title(_("Burning Data"));
    }
    else {
        LOGGER->registerName(this, _("PROCESS_AB"));
        title(_("Append Burning"));
    }

    m_fSpeed = 0;
    m_iProgress = 0;
    emit setRange(0, 100);
    emit setValue(0);
    PROGRESS;
    MODAL->show();

}

void ProcessBurnDataDL::connectInit()
{

    connect(this, SIGNAL(title(QString)), ModalDialog::instance()->getPageByIndex(ModalDialog::Progress), SIGNAL(title(QString)));
    connect(this, SIGNAL(setRange(int,int)), ModalDialog::instance()->getPageByIndex(ModalDialog::Progress), SIGNAL(setRange(int, int)));
    connect(this, SIGNAL(setValue(int,QIcon,QString)), ModalDialog::instance()->getPageByIndex(ModalDialog::Progress), SIGNAL(setValue(int,QIcon,QString)));
    connect(this, SIGNAL(addValue(int,QIcon,QString)), ModalDialog::instance()->getPageByIndex(ModalDialog::Progress), SIGNAL(addValue(int,QIcon,QString)));


    connect(this, SIGNAL(success(QString)), TIP_BURNRESULT, SLOT(slotsSuccess(QString)));
    connect(this, SIGNAL(fail(QString)), TIP_BURNRESULT, SLOT(slotsFailed(QString)));
    connect(this, SIGNAL(title(QString)), TIP_BURNRESULT, SLOT(slotsTitle(QString)));

    connect(this, SIGNAL(cancel(bool)), MODAL, SLOT(cancelEnable(bool)));

    connect(MAIN, SIGNAL(exit()), this, SLOT(slotsExit()));
    connect(MODAL, SIGNAL(rejected()), this, SLOT(slotsCancel()));
    connect(m_root, SIGNAL(copied(QString,QString)), this, SLOT(slotsCopy(QString,QString)));
    connect(m_oProgressBarTimer_DVD, SIGNAL(timeout()), this, SLOT(slotsProcessBarDVD()));
    connect(g_user_signal, &GlobalUserSignal::removeDisc, this, &ProcessBurnDataDL::slotsRemoveDisc);

    connect(m_oProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(slotsOutput()));
    connect(m_oProcess, SIGNAL(readyReadStandardError()), this, SLOT(slotsError()));
    connect(m_oProcess, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotsFinished(int,QProcess::ExitStatus)));
}

void ProcessBurnDataDL::auditLogInit()
{
    auditburner = new QGSettings("org.ukui.kylinburner");
    auditlogpath = "/var/log/kylin-burner/os_audit.log";
    m_isSpecialMachine = auditburner->get("burneraudit-isrunning").toBool();
}

void ProcessBurnDataDL::auditLogWrite()
{
    if(m_isSpecialMachine==true){
        auditburner->set("burner-os-auditlog-name",auditlogpath);
        m_oBurnDataDLLog.outputResult(cloneCDROM->display(), cloneCDROM->mediumType(), m_oProcessName, false==m_bBurnLogError);
    }else{
        auditburner->set("burner-os-auditlog-name","");
    }
}


void ProcessBurnDataDL::verficationDataInit(bool enable)
{
    if (enable) {
        //拼接路径
        m_oTempPath = cloneCDROM->settingTempPath();
        if (m_oTempPath.endsWith("/")) {
            m_oTempPath = m_oTempPath + cloneCDROM->settingVolumnID();
        } else{
            m_oTempPath = m_oTempPath + '/' + cloneCDROM->settingVolumnID();
        }
    }
}

void ProcessBurnDataDL::setCommandParameter()
{
    ifstream fin;
    QString sysid;
    std::string s;

    fin.open("/etc/kylin-build");
    if (fin.is_open()) {
        std::getline(fin, s);
        sysid = QString::fromStdString(s);
    }

    if (sysid.isEmpty())
        sysid = _("KylinOS");

    setValue(++m_iProgress, m_oIcon, _("Prepare to burn data to DVD."));
    m_oGrowisofs = QString("growisofs -J -r -R -joliet-long -v -gui ");
    m_oGrowisofs.append(QString("-A \"%1\" ").arg(_("KylinBurner")));
    if (!sysid.isEmpty()) {
        m_oGrowisofs.append(QString("--sysid \"%1\" ").arg(sysid));
    }
    if (cloneCDROM->mediumStatus() == DataMedium::MEDIUM_EMPTY) {
        m_oGrowisofs.append(QString("-p \"%1\" ").arg(cloneCDROM->settingPreparer()));
        m_oGrowisofs.append(QString("-P \"%1\" ").arg(cloneCDROM->settingPublisher()));
        m_oGrowisofs.append(QString("-copyright \"%1\" ").arg(cloneCDROM->settingCopyright()));
    }
    m_oGrowisofs.append(QString("-V \"%1\" ").arg(cloneCDROM->settingVolumnID()));
    if (cloneCDROM->settingSpeed()) {
        m_oGrowisofs.append(QString("-speed=%1 ").arg(cloneCDROM->settingSpeed()));
    }

    if (!cloneCDROM->settingCloseSession()) {

    } else {
        m_oGrowisofs.append("-dvd-compat ");
    }

    if (m_bGreateThan2GB) {
        m_oGrowisofs.append("-allow-limited-size ");
    }

    if (m_oSize > 4UL * 1024UL * 1024UL * 1024UL) {
        m_oGrowisofs.append("-udf ");
    }

    m_oArguments << "-c";
    setValue(++m_iProgress, m_oIcon, _("Prepare to start burn data."));
}

void ProcessBurnDataDL::prepareFile()
{
    m_iCopied = 0;
    m_root->copy();

    if (m_bVerficationData) {
        DirOptUtils *diropt  = new DirOptUtils;
        diropt->getFileListUnderDir1(m_oTempPath);
        diropt->testPrintMap();
        diropt->generateCheckFile();
        delete diropt;
    }
}

void ProcessBurnDataDL::execProcess()
{
    m_oProcess->setProgram("/bin/bash");
    m_cmdStr = "Argument setted for " + m_oProcess->program() + "\n" + "Arg: ";
    if (cloneCDROM->mediumStatus() == DataMedium::MEDIUM_EMPTY)
    {
        m_oGrowisofs.append(QString("-Z %1 \"%2\"").arg(cloneCDROM->device()).arg(m_odestination));
        m_oArguments << m_oGrowisofs;
        for (int i = 0; i < m_oArguments.size(); ++i)
        {
            m_cmdStr += " "; m_cmdStr +=  m_oArguments.at(i);
        }
        m_oProcess->setArguments(m_oArguments);
        emptyDiscBurn();

    }
    else
    {
        m_oGrowisofs.append(QString("-M %1 \"%2\"").arg(cloneCDROM->device()).arg(m_odestination));
        m_oArguments << m_oGrowisofs;
        for (int i = 0; i < m_oArguments.size(); ++i)
        {
            m_cmdStr += " "; m_cmdStr +=  m_oArguments.at(i);
        }
        m_oProcess->setArguments(m_oArguments);
        appendBurn();
    }
}

void ProcessBurnDataDL::emptyDiscBurn()
{
    LOGGER->info(QString("/*************** Empty DVD+RDL Disc Burn Data, count = %1 ***************/").arg(m_icountBurn + 1));

    LOGGER->info(m_cmdStr);
    m_bBurnLogError = false;
    m_oProcess->start();
    while (m_oProcess->state() != QProcess::NotRunning){
        QCoreApplication::processEvents();
    }
}

void ProcessBurnDataDL::appendBurn()
{
    LOGGER->info(QString("/*************** NonEmpty DVD+RDL Disc Append Burn Data ***************/"));
    LOGGER->info(m_cmdStr);
    m_oProcess->start();
    while (m_oProcess->state() != QProcess::NotRunning){
        QCoreApplication::processEvents();
    }
}

void ProcessBurnDataDL::slotsRemoveDisc(QString dev)
{
    if(dev == m_oDeviceBlock) {
        if(m_oProcess->state() != QProcess::NotRunning) {
            qDebug() << __func__ << __LINE__ << dev << "被拔出，杀死刻录数据进程";
            LOGGER->info(QString("[%1] [%2] : %3 被拔出，杀死刻录数据进程").arg(__func__).arg(__LINE__).arg(dev));
            m_oProcess->kill();
        }
    }
}

void ProcessBurnDataDL::slotsCopy(QString src, QString des)
{
    qreal v;
    int basic;

    if (m_isSpecialMachine) {
        auditburner->set("burner-os-auditlog-name",auditlogpath);
        m_oBurnDataDLLog.appendFile(src);                         //待刻录的源文件
    } else {
        auditburner->set("burner-os-auditlog-name","");
    }

    basic = 50 - m_iProgress;
    ++m_iCopied;
    v = ((m_iCopied * 1.0) / (m_ototal * 1.0));
    v = v * basic;
    m_iProgress += v;
    setValue(m_iProgress, m_oIcon, _("Copied %1 to %2").arg(src).arg(des));
    LOGGER->debug(_("Copied %1 to %2").arg(src).arg(des));
}

void ProcessBurnDataDL::slotsProcessBarDVD()
{
    m_iProgress += 1;

    if (m_iProgress > 99) {
        m_iProgress = 99;
    }

    emit setValue(m_iProgress, m_oIcon, _("closing track"));
}

void ProcessBurnDataDL::slotsExit()
{
    m_oProcess->kill();
}

void ProcessBurnDataDL::slotsCancel()
{
    if (m_bCancel) return;
    m_bCancel = true;
    if (m_oProcess->state() != QProcess::NotRunning)
    {
        LOGGER->info(_("Stop process when user canceled."));
        m_oProcess->kill();
    }
}

void ProcessBurnDataDL::slotsOutput()
{
    m_oProcess->readAllStandardOutput();
    if (m_bCancel)
        m_oProcess->kill();
}

void ProcessBurnDataDL::slotsError()
{
    qreal v;
    qreal tmp;
    int basic;
    QString output;
    QStringList ss;
    DataCDROM *cdrom;
    cdrom = (DataCDROM *)m_oCDROM;
    output = m_oProcess->readAllStandardError();

    QList<QString> errorInfoList;
    errorInfoList << "Input/output error" << "OPC failed." << "FATAL:" << ":-[";

    bool isErrorInfo = false;
    foreach (QString index, errorInfoList) {
        if(output.contains(index)){
            m_bBurnLogError = true;
            isErrorInfo = true;
            break;
        }
    }

    if (output.contains("Input/output error")) {
        m_lastErrorMsg = QString(_("Input/output error. Disc may be damaged, please clean or replace a disc and try again."));
    }

    if (output.contains(":-(")) {
        LOGGER->info(QString("[%1] [%2] 发现错误： [%3]").arg(__func__).arg(__LINE__).arg(output));
        if (m_oFlag && output.contains(":-( unable to reload tray")){
            m_bBurnLogError = false;
            LOGGER->info(QString("[%1] [%2] 出现 :-( unable to reload tray, 不认为是错误输出 设置m_bBurnLogError = [%3]").arg(__func__).arg(__LINE__).arg(m_bBurnLogError));
        } else {
            m_bBurnLogError = true;
        }
    }

    basic = 99 - m_iProgress;
    if (output.contains("done, estimate"))
    {
        //例子：  8.96% done, estimate finish Wed Mar  2 15:10:17 2022\n
        output = output.left(output.indexOf('%'));
        v = output.toDouble();
        v = (v / 100.00) * basic;
        tmp = v + m_iProgress;
        if (tmp > 99) tmp = 99;
        if (m_fSpeed > 0){
            setValue(tmp, m_oIcon, _("Burning by speed %1x").arg(m_fSpeed));
        }
    }

    if (output.contains("Current Write Speed")) {
        ss = output.split(" ");
        ss = ss.last().split("x");
        m_fSpeed = ss.first().toDouble();
    }

    if (output.contains("flushing cache")) {
        emit setValue(99, m_oIcon, _("flushing cache"));
    }

    if (output.contains("closing track")) {
        emit setValue(99, m_oIcon, _("closing track"));
    }

    if (output.contains("closing session")) {
        emit setValue(99, m_oIcon, _("closing session"));
    }

    if(output.contains("reloading tray")) {
        m_oFlag = true;
        LOGGER->info(QString("[%1] [%2]  run return value: reloading tray 设置m_oFlag = [%3]").arg(__func__).arg(__LINE__).arg(m_oFlag));
    }

    if(isErrorInfo){
        LOGGER->error(output);
    }else{
        LOGGER->info(output);
    }
}

void ProcessBurnDataDL::slotsFinished(int code, QProcess::ExitStatus status)
{
    DataCDROM *cdrom;
    cdrom = (DataCDROM *)m_oCDROM;
    m_icountBurn++;
    LOGGER->info(QString("Exit code is %1, status : %2, m_bBurnLogError : %3, m_icountBurn : %4")
         .arg(code).arg(status).arg(m_bBurnLogError).arg(m_icountBurn));
    if (QProcess::NormalExit == status && 0 == code) {
        if (m_bBurnLogError) {
            m_bBurnSuccess = false;
        } else {
            m_bBurnSuccess = true;
        }
    } else {
        m_bBurnSuccess = false;
        if (m_oFlag && !m_bBurnLogError && (1 == code)){
            LOGGER->info(QString("%1 %2 出现 :-( unable to reload tray, 不认为是错误输出 m_oFlag = %3, code = %4 m_bBurnLogError = %5")
                 .arg(__LINE__).arg(__func__).arg(m_oFlag).arg(code).arg(m_bBurnLogError));
            m_bBurnSuccess = true;
        }
    }

    if (m_bBurnSuccess) {
        g_user_signal->setDeviceBusyStatus(m_oDeviceBlock,false);
        LOGGER->info(QString("ProcessBurnDataDL::slotsFinished() : burn DVD+R DL data success. Line:%1").arg(__LINE__));
        auditLogWrite();
        LOGGER->info("ProcessBurnDataDL::slotsFinished 弹出光盘");
        cloneCDROM->updateCDROMBurnStart(false);
        emit g_user_signal->noMedium(cdrom);
        cloneCDROM->eject();
        success(m_oProcessName);
    } else {
        if(m_icountBurn < 3 && (cloneCDROM->mediumStatus() == DataMedium::MEDIUM_EMPTY)) {
            // command `growisofs` maybe fail, such as DVD+R DL disk, we need burn three times again to make burn success.
            emptyDiscBurn();
            return;
        } else {
            // 刻录失败
            g_user_signal->setDeviceBusyStatus(m_oDeviceBlock,false);
            LOGGER->info(QString("ProcessBurnDataDL::slotsFinished() : burn DVD+R DL data fail. Line:%1").arg(__LINE__));
            auditLogWrite();
            LOGGER->info("ProcessBurnDataDL::slotsFinished 弹出光盘");
            cloneCDROM->updateCDROMBurnStart(false);
            emit g_user_signal->noMedium(cdrom);
            cloneCDROM->eject();
            fail(m_oProcessName);
        }
    }

    this->deleteLater();

}
