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

#define ISO_PATH "/tmp/kylin-burner/iso/"

ProcessCopyDiskDL::ProcessCopyDiskDL(void *sourceCDROM, void *destCDROM, QObject *parent) :
    QObject(parent),
    m_oIcon(QIcon::fromTheme("ukui-loading-0-symbolic")),
    m_oProcessGetIso(new QProcess(this)),
    m_oProcessBurnImage(new QProcess(this)),
    m_bCancel(false),
    m_bBurnSuccess(false),
    m_iProgress(0),
    m_oGetIsoTimer(new QTimer(this)),
    m_isSpecialMachine(false)
{
    m_oProcessName.clear();
    m_oArgumentsGetIso.clear();
    m_oArgumentsBurnImage.clear();
    m_oDestCDROM = (DataCDROM*)destCDROM;

    init(sourceCDROM, destCDROM);

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

    auditLogInit(); // 审计日志初始化
    connectInit();
    progressUIInit();


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

ProcessCopyDiskDL::~ProcessCopyDiskDL()
{
    if(m_auditBurner)
        delete m_auditBurner;
    if(m_oProcessGetIso)
        delete m_oProcessGetIso;
    if(m_oProcessBurnImage)
        delete m_oProcessBurnImage;
}

bool ProcessCopyDiskDL::binIsExist()
{
    QFileInfo f;
    f.setFile("/bin/mkisofs");
    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;
    }

    f.setFile("/bin/xorriso");
    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 ProcessCopyDiskDL::init(void *sourceCDROM, void* destCDROM)
{
    cloneSourceCDROM = ((DataCDROM *)sourceCDROM)->clone();
    cloneDestCDROM = ((DataCDROM *)destCDROM)->clone();

    m_oSrcDeviceBlock = cloneSourceCDROM->device();
    m_oDesDeviceBlock = cloneDestCDROM->device();

    m_oProcessName = _("Copy Disk");
    LOGGER->registerName(this, _("PROCESS_CD"));

    m_oGetIsoTimer->setInterval(1000);
}

void ProcessCopyDiskDL::progressUIInit()
{
    title(m_oProcessName);
    setRange(0, 100);
    setValue(0);
    PROGRESS;
    MODAL->show();
}

void ProcessCopyDiskDL::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(g_user_signal, &GlobalUserSignal::removeDisc, this, &ProcessCopyDiskDL::slotsRemoveDisc);

    connect(m_oGetIsoTimer, &QTimer::timeout, this, &ProcessCopyDiskDL::slotsProcessBarGetIso);

    connect(m_oProcessGetIso, SIGNAL(readyReadStandardOutput()), this, SLOT(slotsOutput()));
    connect(m_oProcessGetIso, SIGNAL(readyReadStandardError()), this, SLOT(slotsErrorGetIso()));
    connect(m_oProcessGetIso, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotsFinishedGetIso(int,QProcess::ExitStatus)));

    connect(m_oProcessBurnImage, SIGNAL(readyReadStandardOutput()), this, SLOT(slotsOutput()));
    connect(m_oProcessBurnImage, SIGNAL(readyReadStandardError()), this, SLOT(slotsErrorBurnImage()));
    connect(m_oProcessBurnImage, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotsFinishedBurnImage(int,QProcess::ExitStatus)));
}

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

void ProcessCopyDiskDL::auditLogWrite()
{
    if(m_isSpecialMachine){
        m_auditBurner->set("burner-os-auditlog-name", m_auditLogPath);
        m_copyDiskLog.outputCopyResult("", cloneDestCDROM->display(), cloneDestCDROM->mediumType(), m_oProcessName, true==m_bBurnSuccess);
    }else{
        m_auditBurner->set("burner-os-auditlog-name","");
    }
}

void ProcessCopyDiskDL::setCommandParameter()
{
    QString output;
    QFileInfo f;

    output.clear();
    output.append(cloneSourceCDROM->settingVolumnID());
    output.append(".iso");
    f.setFile(output);


    // get iso
    m_oIsoPath.append(cloneDestCDROM->settingVolumnID().trimmed() + ".iso");
    m_oProcessGetIso->setProgram("/usr/bin/mkisofs");

    m_oArgumentsGetIso << "-o" << m_oIsoPath;
    m_oArgumentsGetIso << "-J" << "-r" << "-R" << "-joliet-long" << "-no-iso-translate"
                        << "-gui" << "-v" << "-ldots" << "-no-split-symlink-fields"
                        << "-no-cache-inodes" << "-full-iso9660-filenames";

    m_oArgumentsGetIso << "-A" << _("KylinBurner");
    m_oArgumentsGetIso << "-V" << cloneSourceCDROM->settingVolumnID().trimmed();
    m_oArgumentsGetIso << "-p" << cloneSourceCDROM->settingPreparer().trimmed();
    m_oArgumentsGetIso << "-P" << cloneSourceCDROM->settingPublisher().trimmed();
    m_oArgumentsGetIso << "-copyright" << cloneSourceCDROM->settingCopyright().trimmed();
    m_oArgumentsGetIso << cloneSourceCDROM->mediumMountPoint();

    setValue(++m_iProgress, m_oIcon, _("Preparing get iso file."));

    LOGGER->info(QString("镜像名m_oIsoPath  : %1").arg(m_oIsoPath));
    LOGGER->info(_("Volumn ID : %1").arg(m_oArgumentsGetIso.last()));
    LOGGER->info(_("Preparer : %1").arg(m_oArgumentsGetIso.last()));
    LOGGER->info(_("Publisher : %1").arg(m_oArgumentsGetIso.last()));
    LOGGER->info(_("Copyright : %1").arg(m_oArgumentsGetIso.last()));


    // burn image
    m_oArgumentsBurnImage << "-as" << "cdrecord" << "-v" << QString("dev=%1").arg(cloneDestCDROM->device()) << "blank=as_needed" << "-multi" << "-eject" << m_oIsoPath;
}

bool ProcessCopyDiskDL::prepareIsoPath()
{
    QString isoPath;
    QDir isoDir;
    bool success;

    isoPath = ISO_PATH;
    isoDir.setPath(ISO_PATH);

    if (isoDir.exists())
    {
        success = isoDir.removeRecursively();
        if (!success)
        {
            LOGGER->warn(QString("(ProcessCopyDiskDL::prepareIsoPath) 删除目录isoPath：%1 失败").arg(isoPath));
            m_bBurnSuccess = false;
            goto end;
        } else {
            LOGGER->info(QString("(ProcessCopyDiskDL::prepareIsoPath) isoPath：%1 已存在，成功删除").arg(isoPath));
        }
    } else {
        LOGGER->info(QString("(ProcessCopyDiskDL::prepareIsoPath) isoPathn：%1 不存在").arg(isoPath));
    }
    success = isoDir.mkpath(isoPath);
    if (!success)
    {
        LOGGER->warn(QString("(ProcessCopyDiskDL::prepareIsoPath) 创建目录isoPath：%1 失败").arg(isoPath));
        m_bBurnSuccess = false;
        goto end;
    }
    else
    {
        LOGGER->info(QString("(ProcessCopyDiskDL::prepareIsoPath) 创建目录isoPath：%1 成功").arg(isoPath));
    }
    m_oIsoPath = isoPath;

end:
    return success;
}

void ProcessCopyDiskDL::execProcess()
{
    if(!prepareIsoPath()) {
        fail(m_oProcessName);
        return ;
    }

    g_user_signal->setDeviceBusyStatus(m_oSrcDeviceBlock,true);
    g_user_signal->setDeviceBusyStatus(m_oDesDeviceBlock,true);

    setCommandParameter();

    m_oProcessGetIso->setArguments(m_oArgumentsGetIso);
    readIso();
}

void ProcessCopyDiskDL::readIso()
{
    LOGGER->info(QString("/*************** Read DVD+RDL Disc ISO ***************/"));
    m_cmdStr.clear();
    m_cmdStr = "Argument setted for " + m_oProcessGetIso->program() + "\n" + "Arg: ";
    for (int i = 0; i < m_oArgumentsGetIso.size(); ++i)
    {
        m_cmdStr += " "; m_cmdStr +=  m_oArgumentsGetIso.at(i);
    }
    LOGGER->info(m_cmdStr);

    m_oGetIsoTimer->start(1000);

    m_oProcessGetIso->start();
    while (m_oProcessGetIso->state() != QProcess::NotRunning){
        QCoreApplication::processEvents();
    }
}

void ProcessCopyDiskDL::writeIso()
{
    LOGGER->info(QString("/*************** Write DVD+RDL Disc ISO ***************/"));

    m_oProcessBurnImage->setProgram("/usr/bin/xorriso");
    m_oProcessBurnImage->setArguments(m_oArgumentsBurnImage);
    m_cmdStr.clear();
    m_cmdStr = "Argument setted for " + m_oProcessBurnImage->program() + "\n" + "Arg: ";
    for (int i = 0; i < m_oArgumentsBurnImage.size(); ++i)
    {
        m_cmdStr += " "; m_cmdStr +=  m_oArgumentsBurnImage.at(i);
    }
    LOGGER->info(m_cmdStr);
    m_oProcessBurnImage->start();
    while (m_oProcessBurnImage->state() != QProcess::NotRunning){
        QCoreApplication::processEvents();
    }
}

void ProcessCopyDiskDL::slotsRemoveDisc(QString dev)
{
    if(dev == m_oSrcDeviceBlock || dev == m_oDesDeviceBlock) {
        if(m_oProcessGetIso->state() != QProcess::NotRunning) {
            qDebug() << __func__ << __LINE__ << dev << "被拔出，杀死getiso进程";
            LOGGER->info(QString("[%1] [%2] : %3 被拔出，杀死getiso进程").arg(__func__).arg(__LINE__).arg(dev));
            m_oProcessGetIso->kill();
        }
        if(m_oProcessBurnImage->state() != QProcess::NotRunning) {
            qDebug() << __func__ << __LINE__ << dev << "被拔出，杀死brunimage进程";
            LOGGER->info(QString("[%1] [%2] : %3 被拔出，杀死burnimage进程").arg(__func__).arg(__LINE__).arg(dev));
            m_oProcessBurnImage->kill();
        }
    }
}

void ProcessCopyDiskDL::slotsExit()
{
    m_oProcessBurnImage->kill();
    m_oProcessGetIso->kill();
}

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

void ProcessCopyDiskDL::slotsOutput()
{

}

void ProcessCopyDiskDL::slotsErrorGetIso()
{
    QString output;
    output = m_oProcessGetIso->readAllStandardError();

    if (!m_oGetIsoTimer->isActive()) {
        m_oGetIsoTimer->start();
    }
    if (output.contains("Input/output error")) {
        m_bBurnSuccess = false;
        m_lastErrorMsg = QString(_("Input/output error. Disc may be damaged, please clean or replace a disc and try again."));
    }

    LOGGER->error(output);
    cancel(false);
}


void ProcessCopyDiskDL::slotsErrorBurnImage()
{
    QString output;
    output = m_oProcessBurnImage->readAllStandardError();

    if (output.contains("xorriso : aborting")) {
        m_bBurnSuccess = false;
        m_lastErrorMsg = QString(_("Burning command aborted."));
    }

    if( output.startsWith("xorriso : UPDATE : Thank you for being patient."))
    {
        setValue(m_iProgress, m_oIcon, _("Preparing for burning image(wait a few minutes)")); //刻录镜像准备中(需要等待几分钟)
    }
    else if( output.startsWith("xorriso : UPDATE :") && output.contains("written"))
    {
        //例子：xorriso : UPDATE :    0 of  569 MB written (fifo 99%) [buf 100%]   0.4x.

        QRegExp rx3("(\\d+)\\s+of\\s+(\\d+)");
        int value1;
        int value2;
        int ratio;
        QString speed;
        int pos3 = rx3.indexIn(output);
        if (pos3 > -1)
        {
            value1 = rx3.cap(1).toInt();    //获取已刻录镜像的容量
            value2 = rx3.cap(2).toInt();    //获取镜像的总容量
            LOGGER->error(QString("value2 = %1").arg(value2));
        }
        QRegExp rx4("(\\d+\.\\d[x])");
        int pos4 = rx4.indexIn(output);
        if (pos4 > -1)
        {
            speed = rx4.cap(1);     //获取刻录的速度
        }

        if(value2 > 0) {
            ratio = 50 + ((value1 * 1.0) / (value2 * 1.0) * 45);
            m_iProgress = ratio;
        }

        if(m_iProgress > 95) m_iProgress = 95;
        setValue(m_iProgress, m_oIcon, _("Burning image by speed %1. %2 of %3 written.").arg(speed).arg(value1).arg(value2)); //速度 0.4x.已写入569M中的1M
    }
    else if( output.startsWith("xorriso : UPDATE :") && output.contains("Closing track/session"))
    {
        m_iProgress = 98;
        setValue(m_iProgress, m_oIcon, _("closing track"));
    }

    if (output.contains("libburn : FATAL : SCSI error on write")) {
        m_lastErrorMsg = QString(_("Input/output error. Disc may be damaged, please clean or replace a disc and try again."));
    }
    LOGGER->error(output);
}

void ProcessCopyDiskDL::slotsFinishedGetIso(int code, QProcess::ExitStatus status)
{
    m_oGetIsoTimer->stop();
    m_iProgress = 50;
    setValue(m_iProgress, m_oIcon, _("Getting ISO"));

    if (QProcess::NormalExit == status  && 0 == code) {
        LOGGER->info(QString("[%1 %2] : slotsFinishedGetIso() mkisofs success.").arg(__FILE__).arg(__LINE__));
        writeIso();
    }
    else
    {
        LOGGER->error(QString("[%1 %2] : slotsFinishedGetIso() mkisofs image. [%3]").arg(__FILE__).arg(__LINE__).arg(m_oProcessGetIso->errorString()));
        m_bBurnSuccess = false;

        // 刻录失败
        g_user_signal->setDeviceBusyStatus(m_oSrcDeviceBlock,false);
        g_user_signal->setDeviceBusyStatus(m_oDesDeviceBlock,false);

        LOGGER->info(QString("ProcessBurnDataDL::slotsFinishedGetIso() : copy disk dl fail. Line:%1").arg(__LINE__));
        auditLogWrite();
        LOGGER->info("ProcessBurnDataDL::slotsFinishedGetIso 弹出光盘");
        cloneSourceCDROM->updateCDROMBurnStart(false);
        emit g_user_signal->noMedium(m_oDestCDROM);
        cloneDestCDROM->eject();
        fail(m_oProcessName);
    }
    LOGGER->info(QString("ProcessBurnDataDL::slotsFinishedGetIso() : end"));
}


void ProcessCopyDiskDL::slotsFinishedBurnImage(int code, QProcess::ExitStatus status)
{

    if (QProcess::NormalExit != status  || code != 0)
    {
        m_bBurnSuccess = false;
        LOGGER->error(QString("[%1 %2] line:[%2] : slotsFinishedBurnImage() DVD Medium have error in burn image. [%3]").arg(__FILE__).arg(__LINE__).arg(m_oProcessBurnImage->errorString()));
    }
    else {
        m_bBurnSuccess = true;
        LOGGER->info(QString("[%1 %2]] : slotsFinishedBurnImage() DVD Medium burn image success.").arg(__FILE__).arg(__LINE__));
    }


    if (m_bBurnSuccess) {
        g_user_signal->setDeviceBusyStatus(m_oSrcDeviceBlock,false);
        g_user_signal->setDeviceBusyStatus(m_oDesDeviceBlock,false);
        LOGGER->info(QString("ProcessCopyDiskDL::slotsFinishedBurnImage() : copy disk dl success. Line:%1").arg(__LINE__));
        auditLogWrite();
        LOGGER->info("ProcessCopyDiskDL::slotsFinishedBurnImage 弹出光盘");
        cloneSourceCDROM->updateCDROMBurnStart(false);
        emit g_user_signal->noMedium(m_oDestCDROM);

        success(m_oProcessName);
    } else {
        // 刻录失败
        g_user_signal->setDeviceBusyStatus(m_oSrcDeviceBlock,false);
        g_user_signal->setDeviceBusyStatus(m_oDesDeviceBlock,false);

        LOGGER->info(QString("ProcessBurnDataDL::slotsFinishedBurnImage() : copy disk dl fail. Line:%1").arg(__LINE__));
        auditLogWrite();
        LOGGER->info("ProcessBurnDataDL::slotsFinishedBurnImage 弹出光盘");
        cloneSourceCDROM->updateCDROMBurnStart(false);
        emit g_user_signal->noMedium(m_oDestCDROM);
        cloneDestCDROM->eject();
        fail(m_oProcessName);
    }

    LOGGER->info(QString("ProcessBurnDataDL::slotsFinishedBurnImage() : end"));
}

void ProcessCopyDiskDL::slotsProcessBarGetIso()
{
    m_iProgress += 1;
    if (m_iProgress > 50) m_iProgress = 50;
    setValue(m_iProgress, m_oIcon, _("Getting ISO"));
}
