#include "sendprogthread.h"
#include "gutil/qnetwork.h"
#include <QHostAddress>
#include <QDir>
#include <QJsonArray>
#include <QDirIterator>
#include <QMessageBox>
#include <QMetaEnum>

SendProgThread::SendProgThread(const QString &progDir, const QString &ip, int port) : prog_dir(progDir), ip(ip), port(port) {
    connect(this, &QThread::finished, this, &QThread::deleteLater);
}

void SendProgThread::run() {
    emit emProgress(0); // 进度条归零
    auto fileInfos = QDir(prog_dir).entryInfoList(QDir::Files);
    if(fileInfos.isEmpty()) {
        emit emErr(tr("Program is empty"));
        return;
    }
    if(stoped) return;
    TcpSocket tcp;
    tcp.connectToHost(ip, port);
    if(! tcp.waitForConnected()) {
        emit emErr(QString(socketErrKey(tcp.error()))+" ("+QString::number(tcp.error())+") when waitForConnected");
        tcp.close();
        return;
    }
    if(stoped) {
        tcp.close();
        return;
    };
    //发送节目列表协商
    QJsonArray ids;
    foreach(auto fileInfo, fileInfos) {
        auto baseName = fileInfo.baseName();
        if(baseName!="program") ids.append(baseName);
    }
    if(! ids.isEmpty()) {
        QJsonObject req;
        req.insert("_type", "consult");
        req.insert("proName", "program");
        req.insert("idList", ids);
        req.insert("zVer", "xixun1");
        auto requ = QJsonDocument(req).toJson(QJsonDocument::Compact);
        auto resNum = tcp.write(requ);
        if(resNum == -1 || ! tcp.waitForBytesWritten()) {
            emit emErr(QString(socketErrKey(tcp.error()))+" ("+QString::number(tcp.error())+") when write 'consult'. size"+QString::number(requ.size()));
            tcp.close();
            return;
        }
        if(! tcp.waitForReadyRead()) {
            emit emErr(QString(socketErrKey(tcp.error()))+" ("+QString::number(tcp.error())+") when waitForRead 'consult'. size"+QString::number(requ.size()));
            tcp.close();
            return;
        }
        auto resp = tcp.readAll();
        if(resp.isEmpty()) {
            emit emErr(QString(socketErrKey(tcp.error()))+" ("+QString::number(tcp.error())+") when read 'consult'. size"+QString::number(requ.size()));
            tcp.close();
            return;
        }
        if(stoped) {
            tcp.close();
            return;
        };
        QJsonParseError parseErr;
        QJsonDocument res = QJsonDocument::fromJson(resp, &parseErr);
        for(int i=2; parseErr.error == QJsonParseError::UnterminatedString && i < 10; i++) {
            if(! tcp.waitForReadyRead()) {
                emit emErr(QString(socketErrKey(tcp.error()))+" ("+QString::number(tcp.error())+") when waitForRead 'consult' "+QString::number(i));
                tcp.close();
                return;
            }
            auto resp2 = tcp.readAll();
            if(resp2.isEmpty()) {
                emit emErr(QString(socketErrKey(tcp.error()))+" ("+QString::number(tcp.error())+") when read 'consult' "+QString::number(i));
                tcp.close();
                return;
            }
            resp += resp2;
            res = QJsonDocument::fromJson(resp, &parseErr);
        }
        if(parseErr.error != QJsonParseError::NoError) {
            emit emErr(parseErr.errorString()+" when parse consult. size:"+QString::number(resp.size()));
            tcp.close();
            return;
        }
        if(res["_type"].toString()=="consult") {
            fileInfos.clear();
            fileInfos.append(QFileInfo(prog_dir+"/program"));
            QJsonArray ids = res["idList"].toArray();
            foreach(auto id, ids) fileInfos.append(QFileInfo(prog_dir+"/"+id.toString()));
        }
    }
    if(stoped) {
        tcp.close();
        return;
    }
    qint64 progSize = 0;
    foreach(auto fileInfo, fileInfos) progSize += fileInfo.size();
    if(progSize == 0) {
        emit emErr(tr("Program is empty"));
        tcp.close();
        return;
    }
    auto req = QJsonObject();
    req.insert("_type", "proStart");
    req.insert("proName", "program");
    req.insert("proSize", progSize);
    req.insert("zVer","xixun1");
    auto resNum = tcp.write(QJsonDocument(req).toJson(QJsonDocument::Compact));
    if(resNum == -1 || ! tcp.waitForBytesWritten()) {
        emit emErr(QString(socketErrKey(tcp.error()))+" ("+QString::number(tcp.error())+") when write 'proStart'");
        tcp.close();
        return;
    }
    if(stoped) {
        tcp.close();
        return;
    }
    //4.发送协商列表应答里的文件
    long long sentBytes = 0;
    foreach(auto info, fileInfos) if(info.isFile()) {
        auto baseName = info.baseName();
        auto remain = info.size();
        req = QJsonObject();
        req.insert("_type", "fileStart");
        req.insert("id", baseName);
        req.insert("size", remain);
        req.insert("relative_path", "");
        req.insert("zVer","xixun1");
        auto resNum = tcp.write(QJsonDocument(req).toJson(QJsonDocument::Compact));
        if(resNum == -1 || ! tcp.waitForBytesWritten()) {
            emit emErr(QString(socketErrKey(tcp.error()))+" ("+QString::number(tcp.error())+") when write 'fileStart'");
            tcp.close();
            return;
        }
        auto file = new QFile(info.filePath());
        if(! file->open(QFile::ReadOnly)) {
            emit emErr(tr("Open file failed")+" "+file->errorString());
            tcp.close();
            return;
        }
        while(remain > 0) {
            auto readed = file->read(qMin(4096LL, remain));
            if(readed.isEmpty()) {
                emit emErr(tr("Read file failed")+" "+file->errorString());
                tcp.close();
                file->close();
                return;
            }
            if(stoped) {
                tcp.close();
                file->close();
                return;
            };
            resNum = tcp.write(readed);
            if(resNum == -1) {
                emit emErr(QString(socketErrKey(tcp.error()))+" ("+QString::number(tcp.error())+") when write file: "+file->fileName());
                tcp.close();
                file->close();
                return;
            }
            if(! tcp.waitForBytesWritten(60000)) {
                emit emErr(QString(socketErrKey(tcp.error()))+" ("+QString::number(tcp.error())+") when waitForWritten file: "+file->fileName());
                tcp.close();
                file->close();
                return;
            }
            if(stoped) {
                tcp.close();
                file->close();
                return;
            };
            remain -= resNum;
            sentBytes += resNum;
            if(sentBytes != 0) emit emProgress(sentBytes * 99 / progSize);
        }
        file->close();
        if(stoped) {
            tcp.close();
            return;
        };
        req = QJsonObject();
        req.insert("_type", "fileEnd");
        req.insert("id", baseName);
        req.insert("zVer", "xixun1");
        resNum = tcp.write(QJsonDocument(req).toJson(QJsonDocument::Compact));
        if(resNum == -1 || ! tcp.waitForBytesWritten()) {
            emit emErr(QString(socketErrKey(tcp.error()))+" ("+QString::number(tcp.error())+") when write 'fileEnd'");
            tcp.close();
            return;
        }
    }
    if(stoped) {
        tcp.close();
        return;
    };
    //5.发送结束
    req = QJsonObject();
    req.insert("_type", "proEnd");
    req.insert("proName", "program");
    req.insert("zVer","xixun1");
    resNum = tcp.write(QJsonDocument(req).toJson(QJsonDocument::Compact));
    if(resNum == -1 || ! tcp.waitForBytesWritten()) {
        emit emErr(QString(socketErrKey(tcp.error()))+" ("+QString::number(tcp.error())+") when write 'proEnd'");
        tcp.close();
        return;
    };
    if(! tcp.waitForReadyRead()) {
        emit emErr(QString(socketErrKey(tcp.error()))+" ("+QString::number(tcp.error())+") when waitForRead 'proEnd'");
        tcp.close();
        return;
    }
    auto resp = tcp.readAll();
    if(resp.isEmpty()) {
        emit emErr(QString(socketErrKey(tcp.error()))+" ("+QString::number(tcp.error())+") when read 'proEnd'");
        tcp.close();
        return;
    }
    tcp.close();
    emit emProgress(100);
    emit emErr("OK");
}
