/*
 * Copyright (C) 2002  KylinSoft Co., Ltd.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
#include "systemdbusregister.h"
#include <polkitqt1-authority.h>
#include <QDBusConnection>
#include <QDBusError>
#include <QDBusMessage>
#include <QDebug>
#include <QThread>
#include <QRegularExpression>
#include <QStandardPaths>

using namespace PolkitQt1;

SystemDbusRegister::SystemDbusRegister()
{

}

//excute dd command
void SystemDbusRegister::MakeStart(QString sourcePath,QString targetPath,QString userName){
    //root authorization
    //   防命令注入
    //  1、形如：mkdir '`id&>id_bak_test.txt`'中的文件夹名称
    if (sourcePath.contains(QRegularExpression(".*`.*`.*"))||targetPath.contains(QRegularExpression(".*`.*`.*")))
        return ;
    // 2、形如：$()的文件夹名称
    if (sourcePath.contains(QRegularExpression(".*\\$\\(.*\\).*"))||targetPath.contains(QRegularExpression(".*\\$\\(.*\\).*")))
        return ;
    // 3、形如：${}的文件夹名称
    if (sourcePath.contains(QRegularExpression(".*\\$\\{.*\\}.*"))||targetPath.contains(QRegularExpression(".*\\$\\{.*\\}.*")))
        return ;
    // 4、包含[;、&、|]等可以包含并执行系统命令或用于连续执行系统命令的符号
    if (sourcePath.contains(QRegularExpression("[;&|]+"))||targetPath.contains(QRegularExpression("[;&|]+")))
        return ;
    m_userName = userName;
    sourceFile = sourcePath;
    Authority::Result result;
    SystemBusNameSubject subject(message().service());
    result = Authority::instance()->checkAuthorizationSync("com.kylinusbcreator.systemdbus.authoritycheck",
                                                           subject , Authority::AllowUserInteraction);
    if (result == Authority::Yes){
        //TODO: send authorization failed dbus message
        emit authorityStatus("success");
    }else{
        //there's two cases NO and Challenge
        emit authorityStatus("failed");
        return ;
    }
    uDiskPath = targetPath;
    QFileInfo info(sourcePath);
    sourceFileSize = info.size()/1000000;
    command_dd = new QProcess();
    //指定输出语言
    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
    env.insert("LANGUAGE","en_US:en");
    command_dd->setProcessEnvironment(env);
    connect(command_dd,&QProcess::readyReadStandardOutput,this,&SystemDbusRegister::readBashStandardErrorInfo);
    connect(command_dd,&QProcess::readyReadStandardError,this,&SystemDbusRegister::readBashStandardErrorInfo);
    connect(command_dd,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(dd_finished(int,QProcess::ExitStatus)));
    m_isFinished = false;
    QTimer::singleShot(1*1000, this, &SystemDbusRegister::handletimeout);
    checkMd5FinishTimer = new QTimer;
    connect(checkMd5FinishTimer, &QTimer::timeout, this, &SystemDbusRegister::checkmd5finish);
    if(!unmountDevice(targetPath)){
        //TODO:Deal sbus message unmount_error
        QProcess *format=new QProcess;
        QStringList args512;
        args512<<"if=/dev/zero"<<"of="+targetPath.toLocal8Bit()<<"bs=512"<<"count=1";
        format->start("dd", args512);
        connect(format,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(clear_finished(int,QProcess::ExitStatus)));
        format->waitForStarted();
        if(format->waitForStarted()){
            qDebug()<<"58ag0"<<format->arguments();
            qInfo()<<"58ag0"<<format->arguments();

        }
        emit makeFinish("unmount_error");
    }

    if(!unmountDevice(targetPath + "1")){
        // TODO:Deal sbus message unmount_error
        QProcess *format=new QProcess;
        QStringList args512;
        args512<<"if=/dev/zero"<<"of="+targetPath.toLocal8Bit()<<"bs=512"<<"count=1";
        format->start("dd", args512);
        connect(format,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(clear_finished(int,QProcess::ExitStatus)));
        format->waitForStarted();
        if(format->waitForStarted()){
            qDebug()<<"70ag0"<<format->arguments();
            qInfo()<<"70ag0"<<format->arguments();

        }
        emit makeFinish("unmount_error");
    }
}
void SystemDbusRegister::clear_finished(int exitCode,QProcess::ExitStatus exitStatus){
    if (exitStatus==QProcess::NormalExit) {
        QStringList args;
        args<<"if="+sourceFile.toLocal8Bit()<<"of="+uDiskPath.toLocal8Bit()<<"status=progress"<<"bs=32M";
        command_dd->start("dd", args);
        if(command_dd->waitForStarted()){
            qInfo()<<"97command_dd->arguments()"<<command_dd->arguments();

        }
        m_isStarted = true;
        qInfo()<<"shuchu91"<<command_dd->arguments();
    } else {
        m_isFinished = true;
        emit makeFinish("make_error");
    }
}
void SystemDbusRegister::dd_finished(int exitCode, QProcess::ExitStatus exitStatus){

    if(exitStatus!=QProcess::NormalExit){
        m_isStarted = false;
        m_isFinished = true;
        qDebug("make_error");
        qInfo("make_error");
        emit makeFinish("make_error");
    } else if (m_isStarted) {
        m_isStarted = false;
        finishEvent();
    } else {
        m_isStarted = false;
        m_isFinished = true;
    }
}

/**
 * @brief 判断程序是否已开启
 * @param processName
 * @return bool
 */
bool SystemDbusRegister::isRunning(const QString &processName)
{
    bool bRet = false;

    QProcess ps;
    QProcess grep;
    ps.setStandardOutputProcess(&grep);

    ps.start("ps", QStringList() << "-ef");
    grep.start("grep", QStringList() << processName);

    if (grep.waitForFinished()) {
        QString result(grep.readAll());
        qDebug() << result;
        QStringList lines = result.split("\n");
        for (const QString& line : lines) {
            if (line.contains("grep"))
                continue;
            if (line.contains(processName)){
                bRet = true;
                break;
            }
        }
    }

    grep.close();
    ps.close();

    return bRet;
}

void SystemDbusRegister::handletimeout(){
    if (m_isFinished)
        return;

    QFile file(sourceFile);
    if (file.exists()) {
        if (m_isStarted) {
            qInfo("SystemDbusRegister::handletimeout : dd exists?");
            // 判断dd是否还在，不在就报错
            if (!isRunning("/usr/bin/dd "))
            {
                qInfo()<<__LINE__<<__func__;
                command_dd->kill();
                emit makeFinish("make_error");
            }
        }
        QTimer::singleShot(1*1000, this, &SystemDbusRegister::handletimeout);
    } else {
        command_dd->kill();
        emit makeFinish("make_error");
    }
}

bool SystemDbusRegister::unmountDevice(QString target)
{
    QProcess unmount;
    unmount.start("udisksctl",QStringList{"unmount","-b",target});
    qDebug()<<"Start unmount disk:"<<target;
    if(!unmount.waitForStarted()){
        qWarning()<<"unmount process start failed.device path:"<<target;
        return false;
    }
    if(!unmount.waitForFinished()){
        qWarning()<<"unmount process finish failed,device path:"<<target;
        return false;
    }
    if(QProcess::NormalExit == unmount.exitCode()){
        qDebug()<<"unmount device success.device path:"<<target;
        return true;
    }else{
        QString output = unmount.readAll();
    }
    qWarning()<<"An unknown error occurred! unmount process exit code:"<<unmount.exitCode();
    return false;
}
void SystemDbusRegister::udiskmountpoint(){
    QProcess lsblk;
    lsblk.start("lsblk -J");
    lsblk.waitForFinished();

    QProcess lsblk2;
    lsblk2.start("lsblk -JS");
    lsblk2.waitForFinished();
    QJsonArray arr1 = QStringToJsonArray(QString::fromLocal8Bit(lsblk.readAllStandardOutput()));  //获取json类型的shell执行结果
    QJsonArray arr2 = QStringToJsonArray(QString::fromLocal8Bit(lsblk2.readAllStandardOutput()));
    QJsonArray arr3 ;
    foreach (const QJsonValue& value, arr1) {
        QJsonObject jsonObj1 = value.toObject();
        foreach (const QJsonValue& value, arr2) {
            QJsonObject jsonObj2 = value.toObject();
            if(jsonObj1["name"] == jsonObj2["name"] && jsonObj2["tran"] == "usb" && jsonObj2["type"] == "disk"){
                if(jsonObj1["mountpoint"].toString()==NULL){
                    arr3=jsonObj1["children"].toArray();
                    qDebug()<<__LINE__<<__func__<<"Page1"<<arr3;
                    foreach (const QJsonValue& Value, arr3) {
                        QJsonObject jsonObj3 = Value.toObject();
                        if(jsonObj3["name"].toString()==jsonObj1["name"].toString()+"1"){
                            mountpoint=jsonObj3["mountpoint"].toString();
                            qDebug()<<__LINE__<<__func__<<"SystemDbusRegister"<<jsonObj3["mountpoint"];
                        }
                    }
                }else if(jsonObj1["mountpoint"].toString()!=NULL){
                    mountpoint=jsonObj1["mountpoint"].toString();
                    qDebug()<<__LINE__<<__func__<<"SystemDbusRegister"<<mountpoint;
                }
            }
        }
    }
}
//kill task process
void SystemDbusRegister::MakeExit(){
    qDebug()<<"exit dd process";
    if(!command_dd) return ;
    if(command_dd->Running){
        command_dd->kill();
    }
    return ;
}
void SystemDbusRegister::readBashStandardErrorInfo()
{
    QByteArray cmdout = command_dd->readAllStandardError();
    cmdout.trimmed();
    if(!cmdout.isEmpty() && cmdout != "\r" && cmdout != "\n"){
        QString str = cmdout;
        qInfo()<<__LINE__<<__func__<<str;
        str = str.replace(" ","");
        //        if(str =="" || str.contains("[sudo]")) {return;}
        str = str.replace("\r","");
        QStringList bytes2 = str.split("bytes");
        QString size_progress = bytes2.first();
        bool ok = false;
        qulonglong progress_num = size_progress.toDouble(&ok)/1048576;
        int mission_percent = progress_num*100/sourceFileSize;
        qInfo()<<"mission_percent"<<mission_percent;

        if(!mission_percent){
            return ;
        }
        QString ii=QString::number(mission_percent);
        if(ii.isEmpty()){
            return;
        }
        //send mission percent debus message every output
        qInfo()<<__func__<<__LINE__<<mission_percent;
        emit workingProgress(mission_percent);
    }else{
        return;
    }
}

void SystemDbusRegister::finishEvent(){
    QTimer *diskRefreshDelay = new QTimer;
    connect(diskRefreshDelay,&QTimer::timeout,[=]{
        diskRefreshDelay->stop();
        if(isMakingSucess()){
            //send production success dbus message
            qInfo()<<"make success";
            QProcess sync;
            sync.start("sync");
            sync.waitForStarted();
            sync.waitForFinished();
            m_isFinished = true;
            if(mountDevice(uDiskPath)){
                QThread::sleep(1);
                udiskmountpoint();
                checkmd5();
                // emit makeFinish("success");
                //removefile();
                qInfo()<<"uDiskPath"<<uDiskPath;
            }
        }else{
            //send production failure dbus message
            qInfo()<<"make failed";
            m_isFinished = true;
            emit makeFinish("fail");
        }
    });
    diskRefreshDelay->start(2000);
}

bool SystemDbusRegister::mountDevice(QString target){

    QProcess mount;
    qInfo()<<"start mounting disk:"<<target;
    QDir dir;
    mountpath = "/mnt/kylinusbcreator/";
    QDir::setCurrent("/mnt/");
    dir.mkdir("kylinusbcreator");
    QDir::setCurrent(QDir::rootPath());
    QStringList stringlist;
    stringlist<<target.toLocal8Bit()<<mountpath.toLocal8Bit();
    mount.startDetached("mount",stringlist);
    qDebug()<<__LINE__<<__func__<<mount.arguments();
    if(QProcess::NormalExit == mount.exitCode()){
        qInfo()<<"Mount success!";
        return true;
    }
    qWarning()<<"An unknown error occurred! Mount process exit code:"<<mount.exitCode();
    return false;
}
bool SystemDbusRegister::isMakingSucess()
{
    QProcess lsblk;
    lsblk.start("lsblk -JS");
    //    lsblk.waitForStarted();
    lsblk.waitForFinished();
    //    qDebug()<<"lsblk output"<<lsblk.readAllStandardError();
    QJsonArray output = QStringToJsonArray(QString::fromLocal8Bit(lsblk.readAllStandardOutput()));
    foreach (const QJsonValue& value, output) {
        QString diskpath = value.toObject()["name"].toString();
        if("/dev/"+diskpath == uDiskPath) return true;
    }
    return false;
}
QJsonArray SystemDbusRegister::QStringToJsonArray(const QString jsonString){
    QJsonParseError err;
    QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonString.toLocal8Bit().data(),&err);
    if(jsonDocument.isNull())
    {
        qDebug()<< "JsonDocument is NULL.Origin data is:"<< jsonString.toLocal8Bit().data();
    }
    if(err.error != QJsonParseError::NoError){
        qDebug()<<"Parase json"<<jsonString<<" error:"<<err.error;
        //TODO：这里的错误处理后期还可以优化,目前处理错误了就会调用exit()退出程序
        //        exit(-1);
    }
    QJsonObject obj = jsonDocument.object();
    return obj["blockdevices"].toArray();
}
void SystemDbusRegister::checkmd5(){
    m_isMd5Finish=false;
    ismakesuccess=false;
    md5=new QProcess();
    connect(md5,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(readStandardOutput(int,QProcess::ExitStatus)));

    QDir::setCurrent(mountpath);
    qDebug()<<QDir::currentPath();
    checkMd5FinishTimer->start(2000);
    md5->start("md5sum",QStringList{"-c","md5sum.txt"});
    connect(md5, &QProcess::readyReadStandardOutput, this, [=]() {
        MD5str=this->md5->readAllStandardOutput();
        qDebug()<<__LINE__<<__func__<<MD5str;
    });
}

void SystemDbusRegister::readStandardOutput(int exitCode,QProcess::ExitStatus exitStatus){
    qDebug()<<"md5退出"<<exitCode;
    if(exitStatus==QProcess::NormalExit && exitCode==0){
        if(MD5str.isEmpty()){
            qDebug()<<__LINE__<<__func__<<"输出为空"<<MD5str;
            QDir::setCurrent(QDir::rootPath());
            qDebug()<<QDir::currentPath();
            ismakesuccess=false;
        }else{
            if (MD5str.contains(tr("Fail")) || MD5str.contains(tr("FAILED")))
            {
                qDebug()<<__LINE__<<__func__<<"有错误字符"<<MD5str;
                qDebug()<<"md5 fail!";
                QDir::setCurrent(QDir::rootPath());
                qDebug()<<QDir::currentPath();
                ismakesuccess=false;
            }
            else{
                qDebug()<<__LINE__<<__func__<<"完全正确"<<MD5str;
                QDir::setCurrent(QDir::rootPath());
                qDebug()<<QDir::currentPath();
                ismakesuccess=true;
            }
        }

        qDebug()<<"md5 success!";
    }else{
        QDir::setCurrent(QDir::rootPath());
        qDebug()<<QDir::currentPath();
    }
    qDebug()<<"md5退出"<<exitCode;
    m_isMd5Finish=true;

}
void SystemDbusRegister::checkmd5finish(){
    if(m_isMd5Finish==true){
        checkMd5FinishTimer->stop();
        if(ismakesuccess==true){
            if(umountPath(mountpath)){
                emit makeFinish("success");
            }
            qDebug()<<"status"<<m_isFinishstatus;
        }else if(ismakesuccess==false){
            if(umountPath(mountpath)){
                emit makeFinish("fail");
            }
            qDebug()<<"ismakesuccess"<<ismakesuccess;
            qDebug()<<"status"<<m_isFinishstatus;
        }
    }
}
bool SystemDbusRegister::umountPath(QString Path){
    QProcess umount;
    qInfo()<<"start umounting disk path:"<<Path;
    umount.startDetached("umount",QStringList()<<Path);
    if(QProcess::NormalExit == umount.exitCode()){
        qInfo()<<"umount success!";
        return true;
    }
    qWarning()<<"An unknown error occurred! Mount process exit code:"<<umount.exitCode();
    return false;
}
