#include "systeminfo.h"

#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
#include <QDateTime>
#include <QDebug>
#include <QCoreApplication>

struct Fragment {
    QString value;
};

struct Software {
    uint8_t size;
    uint8_t authorized;
    time_t installTime;
    Fragment name;
    Fragment vsersion;
};

class SystemInfoPrivate
{
public:
    SystemInfoPrivate(SystemInfo *parent) : q_ptr(parent) {}

    QString         data_version = "1";
    QString         uuid;
    QString         macAddress;
    QJsonArray      softInfo;
    QString         areaCode;
    QList<DiskInfo> diskInfo;
    QList<AdapterInfo> adapterInfo;
    ExternInfo      externInfo;

    UserInfo userInfo;

    QJsonValue diskInfoJson() const;
    QJsonValue adapterInfoJson() const;
    QByteArray packDataV1() const;
    QByteArray packDataV2() const;

    SystemInfo *q_ptr;
    Q_DECLARE_PUBLIC(SystemInfo)
};

SystemInfo::SystemInfo(QObject *parent) :
    QObject(parent), dd_ptr(new SystemInfoPrivate(this))
{

}

SystemInfo::~SystemInfo()
{

}

QByteArray SystemInfo::briefV1() const
{
    Q_D(const SystemInfo);
    return d->packDataV1();
}

QByteArray SystemInfo::briefV2() const
{
    Q_D(const SystemInfo);
    return d->packDataV2();
}

QByteArray SystemInfo::full() const
{
    Q_D(const SystemInfo);

    QJsonObject sys;
    sys.insert("data_version", d->data_version);
    sys.insert("uuid", d->uuid);
    sys.insert("areacode", d->areaCode);
    sys.insert("orgcode", d->userInfo.orgcode);
    sys.insert("department", d->userInfo.department);
    sys.insert("room_number", d->userInfo.roomNumber);
    sys.insert("owner", d->userInfo.ownner);
    sys.insert("macaddress", d->macAddress);
    sys.insert("disk", d->diskInfoJson());
    sys.insert("adapter", d->adapterInfoJson());
    sys.insert("extern", d->externInfo.toObject());

//    sys.insert("soft", QString::fromUtf8(QJsonDocument(d->softInfo).toJson(QJsonDocument::Compact)));
    sys.insert("soft", d->softInfo);

    return QJsonDocument(sys).toJson(QJsonDocument::Compact);
}

/*
{
    "areacode": "420923",
    "orgcode": "招商局",
    "department": "主任办公室",
    "owner": "ZSJ002",
    "macaddress": "10:17:10:10:14:12",
    "data_version": "1",
    "soft": [
        {
            "name": "YZ Office",
            "version": "2017",
            "category": "office",
            "authorized": false,
            "installation_time": "1424806550038",
            "product_keys": [ ],
            "used_history": {
                "name": [
                    {
                        "startup": "1524806550038",
                        "shutdown": "1554806550038"
                    }
                ]
            }
        }
    ]
}
*/
QByteArray SystemInfo::toJson() const
{
    Q_D(const SystemInfo);

    QJsonObject sys;
    sys.insert("macaddress", d->macAddress);
    sys.insert("soft", d->softInfo);
    sys.insert("data_version", "1");

    return QJsonDocument(sys).toJson(QJsonDocument::Indented);
}

void SystemInfo::loadJson(const QByteArray &jsonData)
{
    Q_D(SystemInfo);

    auto doc = QJsonDocument::fromJson(jsonData);

    if (doc.isObject()) {
        auto sysInfo = doc.object();
        d->macAddress = sysInfo.value("macaddress").toString();
        d->softInfo = sysInfo.value("softInfo").toArray();
        qDebug() << sysInfo;
    }
}

QString SystemInfo::getMacAddress()
{
    Q_D(SystemInfo);
    return d->macAddress;
}

void SystemInfo::setUUID(const QString &uuid)
{
    Q_D(SystemInfo);
    d->uuid = uuid;
}

void SystemInfo::setMacAddress(const QString &addr)
{
    Q_D(SystemInfo);
    d->macAddress = addr;
}

void SystemInfo::setDiskInfo(const QList<DiskInfo> &disks)
{
    Q_D(SystemInfo);
    d->diskInfo = disks;
}

void SystemInfo::setExternInfo(const ExternInfo &info)
{
    Q_D(SystemInfo);
    d->externInfo = info;
}

void SystemInfo::setAdapterInfo(const QList<AdapterInfo> &adapterInfo)
{
    Q_D(SystemInfo);
    d->adapterInfo = adapterInfo;
}

//void SystemInfo::setOSInfo(const QJsonObject &osInfo)
//{
//    Q_D(SystemInfo);
//    d->osInfo = osInfo;
//}

//void SystemInfo::setOfficeInfo(const QJsonObject &officeInfo)
//{
//    Q_D(SystemInfo);
//    d->officeInfo = officeInfo;
//}

void SystemInfo::setSoftInfo(const QJsonArray &softInfo)
{
    Q_D(SystemInfo);
    d->softInfo = softInfo;
}

void SystemInfo::setUserInfo(const UserInfo &userInfo)
{
    Q_D(SystemInfo);
    d->userInfo = userInfo;
}

void SystemInfo::setAreaCode(const QString &areaCode)
{
    Q_D(SystemInfo);
    d->areaCode = areaCode;
}

QJsonValue SystemInfoPrivate::diskInfoJson() const
{
    QJsonArray array;
    for (auto &disk : diskInfo) {
        QJsonObject diskObj;
        diskObj["name"] = disk.name;
        diskObj["model"] = disk.model;
        diskObj["serial"] = disk.serial_no;
        array.push_back(diskObj);
    }
    return QJsonValue::fromVariant(array);
}

QJsonValue SystemInfoPrivate::adapterInfoJson() const
{
    QJsonArray array;
    for (auto &adapter : adapterInfo) {
        array.push_back(QJsonObject{
            {"name", adapter.name},
            {"human_readable_name", adapter.humanReadableName},
            {"flags", adapter.flags},
            {"macaddress", adapter.hardwareAddress},
            {"address", adapter.addressList.join(" ")},
        });
    }
    return QJsonValue::fromVariant(array);

}

/*!
 * \brief The SystemInfoPrivate class
 * brief format
 * byte 0: version
 * byte 1: magic number 0x1C
 * byte 2~3: size: 16 bit
 * byte 4~10: mac addr
 * byte 11~19: orgcode info
 * byte 20~22: keep
 * byte 23: authorized; 0 no / 1 yes
 * byte 24~31: install time
 * byte 32~39: last login time
 * byte 40~47: last boot time
 * byte 48~55: last shotdown time
 * fragment 0: os name
 * fragment 1: os version
 * fragment 2: owner
 * fragment 3: department
 * software 0: office info
 */
QByteArray SystemInfoPrivate::packDataV1() const
{
    QByteArray data;
    data.push_back(char(data_version.toInt()));
    data.push_back(char(0x1c));

    data.push_back(char(0x00));
    data.push_back(char(0x00));

    auto macHexList = macAddress.split(":");

    if (macHexList.length() != 6) {
        qCritical() << "parse macAddress failed" << macAddress;
    }

    for (int i = 0; i < 6; ++i) {
        bool ret = false;
        data.push_back(char(macHexList.value(i).toUInt(&ret, 16)));
    }

    Q_ASSERT(data.size() == 10);

    auto dumpLong = [](qint64 t, QByteArray & data) {
        for (int i = 0; i < 8; ++i) {
            data.push_back(char(t >> i * 8 & 0xFF));
        }
    };

    QJsonObject osInfo;
    QJsonObject officeInfo;
    for (auto v : softInfo) {
        auto obj = v.toObject();
        if (obj.value("category").toString() == "os") {
            osInfo = obj;
        }
        if (obj.value("category").toString() == "office") {
            officeInfo = obj;
        }
    }
    qDebug() << osInfo;
    qDebug() << officeInfo;
    bool authorized = osInfo.value("authorized").toBool();
    data.push_back(authorized ? 1 : 0);

    auto installationTimeStr = osInfo.value("installation_time").toString();
    auto installationTime = QDateTime::fromMSecsSinceEpoch(installationTimeStr.toLongLong());

    auto history = osInfo.value("used_history").toObject();
    auto firstHistory = history.value(history.keys().value(0)).toArray().at(0);
    auto lastStartupTimeStr = firstHistory.toObject().value("startup").toString();
    auto lastStartupTime = QDateTime::fromMSecsSinceEpoch(lastStartupTimeStr.toLongLong());
    qDebug() << installationTime << lastStartupTime;

    dumpLong(installationTime.toMSecsSinceEpoch(), data);
    dumpLong(lastStartupTime.toMSecsSinceEpoch(), data);
    dumpLong(installationTime.toMSecsSinceEpoch(), data);
    dumpLong(installationTime.toMSecsSinceEpoch(), data);

    QByteArray areacodeData = areaCode.toUtf8();
    data.push_back(char(areacodeData.size()));
    data.push_back(areacodeData);

    QByteArray orgcodeData = userInfo.orgcode.toUtf8();
    data.push_back(char(orgcodeData.size()));
    data.push_back(orgcodeData);

    QByteArray departmentData = userInfo.department.toUtf8();
    data.push_back(char(departmentData.size()));
    data.push_back(departmentData);

    QByteArray ownerData = userInfo.ownner.toUtf8();
    data.push_back(char(ownerData.size()));
    data.push_back(ownerData);

    QByteArray osName = osInfo.value("name").toString().toUtf8();
    data.push_back(char(osName.size()));
    data.push_back(osName);

    QByteArray osVersion = osInfo.value("version").toString().toUtf8();;
    data.push_back(char(osVersion.size()));
    data.push_back(osVersion);

    auto dumpString = [](QByteArray str, QByteArray & data) {
        data.push_back(char(str.size()));
        data.push_back(str);
    };

    {
        QByteArray softwareData;
        softwareData.push_back(char(0xff));

        bool authorized = officeInfo.value("authorized").toBool();
        softwareData.push_back(authorized ? 1 : 0);

        auto installationTimeStr = officeInfo.value("installation_time").toString();
        auto installationTime = QDateTime::fromMSecsSinceEpoch(installationTimeStr.toLongLong());

        qDebug() << "office time" << installationTime << installationTime.toMSecsSinceEpoch();
        dumpLong(installationTime.toMSecsSinceEpoch(), softwareData);
        dumpString(officeInfo.value("name").toString().toUtf8(), softwareData);
        dumpString(officeInfo.value("version").toString().toUtf8(), softwareData);
        softwareData[0] = char(softwareData.size());

        qDebug() << "office size" << softwareData << softwareData.size();

        dumpString(softwareData, data);
    }

    data[2] = char(data.size() & 0xFF);
    data[3] = char(data.size() >> 8 & 0xFF);
    qDebug() << data;
    return data;
}

QByteArray SystemInfoPrivate::packDataV2() const
{
    QByteArray data;
    data.push_back(char(2));
    data.push_back(char(0x1d));

    // size
    data.push_back(char(0x00));
    data.push_back(char(0x00));

    // save macaddress
    auto macHexList = macAddress.split(":");
    if (macHexList.length() != 6) {
        qCritical() << "parse macAddress failed" << macAddress;
    }
    for (int i = 0; i < 6; ++i) {
        bool ret = false;
        data.push_back(char(macHexList.value(i).toUInt(&ret, 16)));
    }

    Q_ASSERT(data.size() == 10);

    QByteArray versionData = qApp->applicationVersion().toUtf8();
    data.push_back(char(versionData.size()));
    data.push_back(versionData);

    QByteArray uuidData = uuid.toUtf8();
    data.push_back(char(uuidData.size()));
    data.push_back(uuidData);

    QByteArray areacodeData = areaCode.toUtf8();
    data.push_back(char(areacodeData.size()));
    data.push_back(areacodeData);

    QByteArray orgcodeData = userInfo.orgcode.toUtf8();
    data.push_back(char(orgcodeData.size()));
    data.push_back(orgcodeData);

    QByteArray ownerData = userInfo.ownner.toUtf8();
    data.push_back(char(ownerData.size()));
    data.push_back(ownerData);

    QByteArray roomNumberData = userInfo.roomNumber.toUtf8();
    data.push_back(char(roomNumberData.size()));
    data.push_back(roomNumberData);

    QByteArray departmentData = userInfo.department.toUtf8();
    data.push_back(char(departmentData.size()));
    data.push_back(departmentData);

    auto dumpLong = [](qint64 t, QByteArray & data) {
        for (int i = 0; i < 8; ++i) {
            data.push_back(char(t >> i * 8 & 0xFF));
        }
    };

    auto softwareSize = softInfo.size();
    data.push_back(char(softwareSize));

    QMap<QString, QString> categoryIDMap = {
        { "os", "1"},
        { "office", "2"},
        { "antivirus", "3"},
    };

    for (auto softwareJson : softInfo) {
        auto software = softwareJson.toObject();
        auto authorized = software.value("authorized").toBool();
        auto category = software.value("category").toString();
        auto name = software.value("name").toString();
        auto version = software.value("version").toString();
        auto installationTimeStr = software.value("installation_time").toString();
        auto installationTime = QDateTime::fromMSecsSinceEpoch(installationTimeStr.toLongLong());

        auto usedHistories = software.value("used_history").toObject();

        QList<qint64> startupList;
        for (auto execPath : usedHistories.keys()) {
            auto historyArray = usedHistories.value(execPath).toArray();
            for (auto var : historyArray) {
                auto history = var.toObject();
                auto startup = history.value("startup").toString().toLongLong();
                startupList.push_back(startup);
            }

        }
        std::sort(startupList.begin(), startupList.end());

        auto historySize = startupList.size();

        data.push_back(authorized ? 1 : 0);
        data.push_back(char(historySize));

        QByteArray nameData = name.toUtf8();
        data.push_back(char(nameData.size()));
        data.push_back(nameData);

        QByteArray versionData = version.toUtf8();
        data.push_back(char(versionData.size()));
        data.push_back(versionData);

        auto categoryID = category;
        if (categoryIDMap.contains(category)) {
            categoryID = categoryIDMap.value(category);
        }

        QByteArray categoryIDData = categoryID.toUtf8();
        data.push_back(char(categoryIDData.size()));
        data.push_back(categoryIDData);

        dumpLong(installationTime.toMSecsSinceEpoch(), data);

        if (historySize > 0) {
            // get startup list
            auto lastStartupTime = QDateTime::fromMSecsSinceEpoch(startupList.last());
            dumpLong(lastStartupTime.toMSecsSinceEpoch(), data);
        }
    }
    // push disk serial
    auto diskNumber = diskInfo.size();
    data.push_back(char(diskNumber));
    for (auto &disk : diskInfo) {
        data.push_back(char(disk.serial_no.size()));
        data.push_back(disk.serial_no.toUtf8());
    }
    data[2] = char(data.size() & 0xFF);
    data[3] = char(data.size() >> 8 & 0xFF);
    qDebug() << data << data.size();
    return data;
}

UserInfo UserInfo::fromObject(const QJsonObject &obj)
{
    UserInfo userInfo;
    userInfo.orgcode = obj.value("orgcode").toString();
    userInfo.orgname = obj.value("orgname").toString();
    userInfo.ownner = obj.value("username").toString();
    userInfo.department = obj.value("department").toString();
    userInfo.roomNumber = obj.value("room_number").toString();
    return  userInfo;
}

UserInfo UserInfo::fromJson(const QByteArray &json)
{
    return fromObject(QJsonDocument::fromJson(json).object());
}

QJsonObject UserInfo::toObject() const
{
    return QJsonObject {
        {"orgcode", orgcode},
        {"orgname", orgname},
        {"username", ownner},
        {"department", department},
        {"room_number", roomNumber}
    };

}

QByteArray UserInfo::toJson() const
{
    return QJsonDocument(toObject()).toJson();
}

QJsonObject ExternInfo::toObject() const
{
    return QJsonObject {
        {"machine_id", machineID},
        {"deepin_version", deepinVersion},
        {"deepin_copyright", deepinCopyright},
        {"deepin_edition", deepinEdition},
        {"dmi_board_serial", dmi_board_serial},
        {"dmi_product_uuid", dmi_product_uuid},
    };
}
