#include "program.h"

#include <regex>
#include <string>

#include <Windows.h>
#include <Msi.h>
#include <QDebug>
#include <QSettings>
#include <QFileInfo>
#include <QDateTime>
#include <QByteArray>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>

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

#pragma comment(lib, "msi.lib")

namespace Win
{

void updateProgramList(QMap<QString, Software> &existingProgramList,  const Software &program)
{
    // merge data;
    Software old;
    if (!existingProgramList.contains(program.key())) {
        old = program;
    } else {
        old = existingProgramList.value(program.key());
        if (!old.InstallDate.isValid()) {
            old.InstallDate = program.InstallDate;
        }
        if (old.InstallLocation.isEmpty()) {
            old.InstallLocation = program.InstallLocation;
        }
    }

//    qDebug() << program.key() << program.DisplayName << program.InstallDate << program.UninstallString;
//    qDebug() << old.key() << old.DisplayName << old.InstallDate << old.UninstallString;
    existingProgramList.insert(old.key(), Software(old));
//    auto newd = existingProgramList.value(program.key());
//    qDebug() <<"new"<< newd.InstallDate;
}

void GetUninstallKeyProgramList(
    Registry *uninstallRegKey,
    QMap<QString, Software> &existingProgramList)
{
    Q_ASSERT(uninstallRegKey);
    auto subKeyList = uninstallRegKey->childGroups();
//    qDebug() << "subKeyList:" << subKeyList;
    for (auto groupKey : subKeyList) {
//        qDebug() << "search" << groupKey;
        auto subRegKey = uninstallRegKey->subKey(groupKey);
        auto Name = subRegKey->value(QLatin1String("DisplayName")).toString();
        if (Name.isEmpty()) {
            continue;
        }
//        qDebug() << Name;
//        if (Name.toLower().contains("wps")) {
//            qDebug() << uninstallRegKey->allKeys();
//            for (auto k : uninstallRegKey->allKeys()) {
//                qDebug() << k << uninstallRegKey->value(k);
//            }
//        }
        Software program(subRegKey);

        auto isSystemComponent = subRegKey->value("SystemComponent");
        if (isSystemComponent.isValid() && isSystemComponent.toInt() == 1) {
            continue;
        }

        auto isWindowsInstaller = subRegKey->value("WindowsInstaller");
        if (!isWindowsInstaller.isValid() || isWindowsInstaller.toInt() != 1) {
            std::wregex windowsUpdateRegEx(TEXT("KB[0-9]{6}$"));
            // Release Type
            auto ProgramReleaseType = subRegKey->value(QLatin1String("ReleaseType")).toString();
            auto ParentKeyName = subRegKey->value(QLatin1String("ParentKeyName")).toString();

            //Check to see if this program is classed as an update
            if (std::regex_match(groupKey.toStdWString(), windowsUpdateRegEx) == true || \
                    !ParentKeyName.isEmpty() || \
                    ProgramReleaseType == ("Security Update")  || \
                    ProgramReleaseType == ("Update Rollup")  || \
                    ProgramReleaseType == ("Hotfix")) {
//                qDebug()  << "skip update" << Name;
            } else {
                auto UninstallString = subRegKey->value(QLatin1String("UninstallString")).toString();
                if (!UninstallString.isEmpty() && !Name.isEmpty()) {
                    updateProgramList(existingProgramList, program);
                }
            }
        } else {
            updateProgramList(existingProgramList, program);
        }
    }
}

/*
 * https://msdn.microsoft.com/en-us/library/aa394378(v=vs.85).aspx
class Win32_Product : CIM_Product
{
uint16   AssignmentType;
string   Caption;
string   Description;
string   IdentifyingNumber;
string   InstallDate;
datetime InstallDate2;
string   InstallLocation;
sint16   InstallState;
string   HelpLink;
string   HelpTelephone;
string   InstallSource;
string   Language;
string   LocalPackage;
string   Name;
string   PackageCache;
string   PackageCode;
string   PackageName;
string   ProductID;
string   RegOwner;
string   RegCompany;
string   SKUNumber;
string   Transforms;
string   URLInfoAbout;
string   URLUpdateInfo;
string   Vendor;
uint32   WordCount;
string   Version;
};
*/
void AppendProductProgramList(QMap<QString, Software> &existingProgramList)
{
    QStringList props;
    props << "PackageName" << "Caption"
          << "InstallDate" << "InstallLocation"
          << "Version" << "ProductID";

    auto obj = Win::GetWmiObject();
    auto result = obj->Query("SELECT * FROM Win32_Product WHERE Caption Like 'Microsoft Office Professional%'",
                             props);
    for (auto product : result) {
        Software program;
        program.DisplayName = product.value("Caption").toString();
        program.InstallLocation = product.value("InstallLocation").toString();
        program.Version = product.value("Version").toString();
        // "20180210061925.000000+480"
        auto timeStr =  product.value("InstallDate").toString();
        auto installDate = QDateTime::fromString(timeStr.split(".").value(0), "yyyyMMdd");
        program.InstallDate = installDate;
        updateProgramList(existingProgramList, program);
    }
}


QString MsiQueryProperty(LPCTSTR szProductCode,
                         LPCTSTR szUserSid,
                         MSIINSTALLCONTEXT dwContext,
                         LPCTSTR szProperty)
{
    DWORD cchValue = 0;
    UINT ret2 = ::MsiGetProductInfoEx(
                    szProductCode,
                    szUserSid,
                    dwContext,
                    szProperty,
                    NULL,
                    &cchValue);

    cchValue++;

    std::wstring buffer;
    buffer.resize(cchValue);

    if (ret2 == ERROR_SUCCESS) {

        ret2 = ::MsiGetProductInfoEx(
                   szProductCode,
                   szUserSid,
                   dwContext,
                   szProperty,
                   (LPTSTR)&buffer[0],
                   &cchValue);
    }

    return QString::fromStdWString(buffer.substr(0, buffer.size() - 1));
}


void AppendMSIProgramList(QMap<QString, Software> &existingProgramList)
{
    UINT ret = 0;
    DWORD dwIndex = 0;
    TCHAR szInstalledProductCode[39] = {0};
    TCHAR szSid[128] = {0};
    DWORD cchSid;
    MSIINSTALLCONTEXT dwInstalledContext;

    do {
        memset(szInstalledProductCode, 0, sizeof(szInstalledProductCode));
        cchSid = sizeof(szSid) / sizeof(szSid[0]);

        ret = ::MsiEnumProductsEx(
                  NULL,             // all the products in the context
                  TEXT("s-1-1-0"),  // i.e.Everyone, all users in the system
                  MSIINSTALLCONTEXT_ALL,
                  dwIndex,
                  szInstalledProductCode,
                  &dwInstalledContext,
                  szSid,
                  &cchSid);
        if (ret == ERROR_SUCCESS) {
            auto name = MsiQueryProperty(
                            szInstalledProductCode,
                            cchSid == 0 ? NULL : szSid,
                            dwInstalledContext,
                            INSTALLPROPERTY_INSTALLEDPRODUCTNAME);

            auto installdate = MsiQueryProperty(
                                   szInstalledProductCode,
                                   cchSid == 0 ? NULL : szSid,
                                   dwInstalledContext,
                                   INSTALLPROPERTY_INSTALLDATE);

            auto version = MsiQueryProperty(
                               szInstalledProductCode,
                               cchSid == 0 ? NULL : szSid,
                               dwInstalledContext,
                               INSTALLPROPERTY_VERSIONSTRING);

            auto location = MsiQueryProperty(
                                szInstalledProductCode,
                                cchSid == 0 ? NULL : szSid,
                                dwInstalledContext,
                                INSTALLPROPERTY_INSTALLLOCATION);
            Software program;
            program.DisplayName = name;
            if (!location.isEmpty() && location.at(location.length() - 1) == "\\") {
                location = location.left(location.length() - 1);
            }
            program.InstallLocation = location;
            program.Version = version;
            // 2018021
            auto installDateTime = QDateTime::fromString(installdate, "yyyyMMdd");
            program.InstallDate = installDateTime;

//            qDebug() << program.InstallDate << program.InstallLocation;
            if (!program.DisplayName.isEmpty()) {
//                qDebug() << program.DisplayName << installdate << installDateTime;
                updateProgramList(existingProgramList, program);
            }
            dwIndex++;
        }

    } while (ret == ERROR_SUCCESS);
}

QList<Software> SoftManager::List()
{
    QMap<QString, Software> programList;

    WinRegKey *wow64UninstallRegKey = new WinRegKey(HKEY_LOCAL_MACHINE,
            "SOFTWARE\\MICROSOFT\\Windows\\CurrentVersion\\Uninstall",
            false,
            true);
    GetUninstallKeyProgramList(wow64UninstallRegKey,  programList);


    WinRegKey *uninstallRegKey = new WinRegKey(HKEY_LOCAL_MACHINE,
            "SOFTWARE\\MICROSOFT\\Windows\\CurrentVersion\\Uninstall",
            false,
            false);
    GetUninstallKeyProgramList(uninstallRegKey, programList);


    auto usersRegKey = new WinRegKey(HKEY_USERS, "");

    for (auto groupKey : usersRegKey->childGroups()) {
        auto userRegKey = usersRegKey->subKey(groupKey);
        auto useruninstallKey = userRegKey->subKey("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall");

        GetUninstallKeyProgramList(useruninstallKey, programList);
        auto product = userRegKey->subKey("Software\\Microsoft\\Installer\\Products");
        GetUninstallKeyProgramList(product, programList);
    }

    AppendMSIProgramList(programList);
//    qDebug() << programList.keys();
    for (auto v : programList.values()) {
        if (v.DisplayName.toLower().contains("office")) {
            qDebug() << v.DisplayName << v.InstallDate << v.UninstallString;
        }
    }
    return programList.values();
}

}
