#include "genuine.h"

#include <regex>

#include <Windows.h>

#include <QDebug>
#include <QFile>
#include <QDateTime>
#include <QJsonDocument>

#include "wmiobject.h"
#include "util.h"
#include "variant.h"
#include "program.h"

typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL);


BOOL IsWow64()
{
    BOOL bIsWow64 = FALSE;
    LPFN_ISWOW64PROCESS fnIsWow64Process;

    //IsWow64Process is not available on all supported versions of Windows.
    //Use GetModuleHandle to get a handle to the DLL that contains the function
    //and GetProcAddress to get a pointer to the function if available.

    fnIsWow64Process = reinterpret_cast<LPFN_ISWOW64PROCESS>(GetProcAddress(
                           GetModuleHandle(TEXT("kernel32")), "IsWow64Process"));

    if (nullptr != fnIsWow64Process) {
        if (!fnIsWow64Process(GetCurrentProcess(), &bIsWow64)) {
            //handle error
        }
    }
    return bIsWow64;
}

namespace Genuine
{

bool isGrub4Dos(const QString &dir, const QString &filename, DWORD fileAttributes)
{
    QString filePath = QString("%1\\%2").arg(dir).arg(filename);

    if (filename.length() == 5 && !(fileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
        std::regex grub4dos("^([A-Z]+)+$");
        if (std::regex_match(filename.toStdString(), grub4dos)) {
            Win::TakeOwnership(filePath);
            QFile f(filePath);
            if (f.open(QIODevice::ReadOnly)) {
                unsigned char buffer[] = {0xEB, 0x3E, 0x80, 0x05, 0x20, 0x39, 0xFF, 0xFF};
                auto fileMagicNumber = f.read(8);
                if (fileMagicNumber == QByteArray::fromRawData(reinterpret_cast<char *>(buffer), 8)) {
                    qDebug() << "MagicNumber match";
                    return true;
                }
            }
        }
    }
    return false;
}

bool isBootLoaderHack()
{
    auto result = Win::GetWmiObject()->Query("SELECT * FROM Win32_BootConfiguration", {"BootDirectory"});

    if (result.size() <= 0) {
        return false;
    }

    auto bootDirectory = (result.value(0).value("BootDirectory").toString());
    auto letter = bootDirectory.split(":").value(0);
    auto rootDir = QString("%1:").arg(letter);

    WIN32_FIND_DATA data;
    HANDLE hFind = FindFirstFile(QString(rootDir + "\\*").toStdWString().c_str(), &data);    // DIRECTORY

    bool isHack = false;
    if (hFind != INVALID_HANDLE_VALUE) {
        do {
            if (isGrub4Dos(rootDir, QString::fromStdWString(data.cFileName), data.dwFileAttributes)) {
                isHack = true;
                break;
            }
        } while (FindNextFile(hFind, &data));
        FindClose(hFind);
    }
    return isHack;
}

QString GetKeyWithMask(const QString &regPath, DWORD mask)
{
    HKEY  hRegistryKey;                     //Registry Handler
    BYTE *DigitalProductID = Q_NULLPTR;     //Digital Product Key Value
    DWORD DataLength = 0;         //Digital Product Key Length
    BYTE  ProductKeyExtract[15]; //Extract Key
    char  sCDKey[30];            //Temp, adding a Window Product Key
    int   ByteCounter;           //Counter
    int   k;                     //Convert
    int   nCur;                  //XOR calculate
    bool  bOk = false;

    const char *KeyChars[] = {
        "B", "C", "D", "F", "G", "H", "J", "K", "M",
        "P", "Q", "R", "T", "V", "W", "X", "Y",
        "2", "3", "4", "6", "7", "8", "9", nullptr
    };

    qDebug() << regPath;
    LONG ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                            regPath.toStdWString().c_str(),
                            REG_OPTION_NON_VOLATILE,
                            KEY_READ | mask,
                            &hRegistryKey);
    if (ERROR_SUCCESS != ret) {
        qDebug() << "RegOpenKeyEx Failed" << ret;
        return "";
    }

    ret = RegQueryValueEx(hRegistryKey, TEXT("DigitalProductId"),
                          nullptr, nullptr, nullptr, &DataLength);

    if (DataLength <= 0) {
        qDebug() << "RegQueryValueEx Failed" << ret << "DataLength" << DataLength;
        RegCloseKey(hRegistryKey);
        return "";
    }

    //Allocate Memory
    DigitalProductID = new byte[DataLength];
    //Memory Initialization
    memset(DigitalProductID, 0, DataLength);

    //Digital Product
    ret = RegQueryValueEx(hRegistryKey, TEXT("DigitalProductId"),
                          nullptr, nullptr, DigitalProductID, &DataLength);
    if (ret == ERROR_SUCCESS) {
        //reading a value start position 52, by 66
        memcpy(ProductKeyExtract, DigitalProductID + 52, 15);
        bOk = true;
    } else {
        qDebug() << "RegQueryValueEx Failed" << ret << "DataLength" << DataLength;
    }

    //Release Memory
    if (DigitalProductID) { delete []DigitalProductID; }

    //Close Registry
    RegCloseKey(hRegistryKey);


    if (!bOk) {
        qWarning() << "Get Key Failed" << ret;
        return "";
    }

    //Start Converting job, Next Step
    memset(sCDKey, 0, sizeof(sCDKey));

    for (ByteCounter = 24; ByteCounter >= 0; --ByteCounter) {
        nCur = 0;

        for (k = 14; k >= 0; --k) {
            nCur = (nCur * 256) ^ ProductKeyExtract[k];

            ProductKeyExtract[k] = nCur / 24;
            nCur %= 24;
        }

        strcat(sCDKey, KeyChars[nCur]);

        //Insert "-"
        if (!(ByteCounter % 5) && ByteCounter) {
            strcat(sCDKey, "-");
        }
    }

    _strrev(sCDKey);

    return QString::fromStdString(sCDKey);
}

QString GetKey(const QString &regPath)
{
    qDebug() << "get key" << regPath;
    auto value = GetKeyWithMask(regPath, KEY_WOW64_32KEY);
    if (!value.isEmpty()) {
        return value;
    }
    return GetKeyWithMask(regPath, KEY_WOW64_64KEY);
}

QString GetWindowsKey()
{
    auto regPath = QStringLiteral("SOFTWARE\\MICROSOFT\\Windows NT\\CurrentVersion");
    return GetKey(regPath);
}

bool IsWindowsVistaOrGreater()
{
    OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 };
    DWORDLONG const dwlConditionMask = VerSetConditionMask(
                                           VerSetConditionMask(
                                                   VerSetConditionMask(
                                                           0, VER_MAJORVERSION, VER_GREATER_EQUAL),
                                                   VER_MINORVERSION, VER_GREATER_EQUAL),
                                           VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
    osvi.dwMajorVersion = HIBYTE(_WIN32_WINNT_VISTA);
    osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA);
    osvi.wServicePackMajor = 0;

    return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
}

void GetGenuineForWindowsXP(LicenseInfo &info)
{
    auto bIsGenuine = false;
    QStringList props;
    props << "ActivationRequired" << "Caption"
          << "Description" << "ProductID"
          << "RemainingEvaluationPeriod" << "RemainingGracePeriod";

    auto obj = Win::GetWmiObject();
    auto result = obj->Query("SELECT * FROM Win32_WindowsProductActivation", props);
    qDebug() << "XP result" << result;
    auto productActivation = result.value(0);
    auto activationRequired = productActivation.value("ActivationRequired");
    if (activationRequired.isValid() && 0 == activationRequired.toInt()) {
        bIsGenuine = true;
    }

    info.IsGenuine = bIsGenuine;
    info.ActivationRequired = activationRequired.toInt();
    info.Description = productActivation.value("Description").toString();
}

bool IsKmsClient(const BSTR bstrDescription)
{
    if (wcsstr(bstrDescription, TEXT("VOLUME_KMSCLIENT"))) {
        return true;
    }
    return false;
}

bool IsKmsServer(const BSTR bstrDescription)
{
    if (IsKmsClient(bstrDescription)) {
        return false;
    } else {
        if (wcsstr(bstrDescription, TEXT("VOLUME_KMS"))) {
            return true;
        }
        return false;
    }
}

bool GetIsPrimaryWindowsSKU(const QVariantMap obj)
{
    INT iPrimarySku = 0;

    VARIANT_BOOL bLicenseIsAddon = obj.value("LicenseIsAddon").toBool();
    auto wsDescription = obj.value("Description").toString().toStdWString();
    BSTR bsDescription = BSTR(wsDescription.c_str());

    if (!wsDescription.empty()) {
        if (bLicenseIsAddon) {
            iPrimarySku = 0;
        } else {
            iPrimarySku = 1;
        }
    } else {
        if (IsKmsClient(bsDescription) || IsKmsServer(bsDescription)) {
            iPrimarySku = 1;
        } else {
            iPrimarySku = 2;
        }
    }

    qDebug() << "iPrimarySku:" << iPrimarySku;

    return (1 == iPrimarySku) || (2 == iPrimarySku);
}

void GetGenuineForVistaOrGreater(LicenseInfo &info)
{
    QStringList props;
    props << "ID" << "Name"
          << "ApplicationId" << "PartialProductKey"
          << "LicenseStatusReason" << "PartialProductKey"
          << "LicenseIsAddon" << "Description"
          << "LicenseStatus" << "GracePeriodRemaining"
          << "RemainingEvaluationPeriod" << "RemainingGracePeriod"
          << "ProductKeyChannel" << "KeyManagementServiceMachine";

    auto obj = Win::GetWmiObject();
    auto result = obj->Query("SELECT * FROM SoftwareLicensingProduct WHERE "
                             "(PartialProductKey <> NULL) AND (ApplicationId = '55c92734-d682-4d71-983e-d6ec3f16059f')",
                             props);
    qDebug() << "SoftwareLicensingProduct size" << result.size();
    // no product key
    if (result.size() <= 0) {
        info.IsGenuine = false;
        info.PartialProductKey = "";
    }

    for (auto product : result) {
        if (!GetIsPrimaryWindowsSKU(product)) {
            continue;
        }

        info.Description = product.value("Description").toString();
        info.LicenseStatus = product.value("LicenseStatus").toInt();
        info.LicenseStatusReason = product.value("LicenseStatusReason").toInt();
        info.ProductKeyChannel = product.value("ProductKeyChannel").toString();
        info.PartialProductKey = product.value("PartialProductKey").toString();
        info.KeyManagementServiceMachine = product.value("KeyManagementServiceMachine").toString();

        switch (info.LicenseStatus) {
        case 1:
            info.IsGenuine = true;
            break;
        default:
            break;
        }

        // check oem spl
        if (info.Description.contains("OEM_SLP")) {
            qWarning() << "License by OEM_SLP";
            info.BootLoaderHack = isBootLoaderHack();
            info.IsGenuine = false;
        }

        // check kms
        if (info.Description.contains("VOLUME_KMSCLIENT")) {
            qWarning() << "License by KMS";
            if (info.KeyManagementServiceMachine.isEmpty()) {
                info.IsGenuine = false;
            }
        }
    }
}

QVariantMap GetWin32OperatingSystem()
{
    QVariantMap data;
    QStringList props;
    props << "Caption" << "Description" << "ProductID" << "InstallDate" << "LastBootUpTime";

    auto obj = Win::GetWmiObject();
    auto result = obj->Query("SELECT * FROM Win32_OperatingSystem",
                             props);
    auto os = result.value(0);
    auto timeStr = os.value("InstallDate").toString();
    auto installDate = QDateTime::fromString(timeStr.split(".").value(0), "yyyyMMddhhmmss");
    data.insert("InstallDate", installDate);

    timeStr = os.value("LastBootUpTime").toString();
    auto lastBootUpTime = QDateTime::fromString(timeStr.split(".").value(0), "yyyyMMddhhmmss");
    data.insert("LastBootUpTime", lastBootUpTime);

    return data;
}

LicenseInfo QueryGenuineInfo()
{
    LicenseInfo info;
    info.ProductKey = GetWindowsKey();
    if (IsWindowsVistaOrGreater()) {
        GetGenuineForVistaOrGreater(info);
    } else {
        GetGenuineForWindowsXP(info);
    }
    return info;
}

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

//bool    IsGenuine = false;
//// For XP
//int     ActivationRequired;
//// For Vista or later
//bool    BootLoaderHack = false;
//int     LicenseStatus = -1;
//int     LicenseStatusReason = -1;

//QString ProductKey;
//QString PartialProductKey;
//QString ProductKeyChannel;
//QString KeyManagementServiceMachine;
//QString Description;
QJsonObject LicenseInfo::toObject() const
{
    return QJsonObject {
        {"IsGenuine", IsGenuine},
        {"Description", Description},
        {"ActivationRequired", ActivationRequired},
        {"LicenseStatus", LicenseStatus},
        {"LicenseStatusReason", LicenseStatusReason},
        {"BootLoaderHack", BootLoaderHack},
        {"ProductKey", ProductKey},
        {"PartialProductKey", PartialProductKey},
        {"ProductKeyChannel", ProductKeyChannel},
        {"KeyManagementServiceMachine", KeyManagementServiceMachine},
    };
}

}

