#include "stdinc.h"
#include "st.h"
#include "install.h"

CDeque<INSTALL_THREAD_PROC_DATA, offsetof(INSTALL_THREAD_PROC_DATA, Linkage)> g_Installs;

#if 0
#define DATA_DIRECTORY_NAME L"install"
#define SLASH_INI_FILE      L"\\install.ini"
#define       INI_FILE      L"install.ini"
#else
#define DATA_DIRECTORY_NAME L"assemblies"
#define SLASH_INI_FILE      L"\\assembly.ini"
#define       INI_FILE      L"assembly.ini"
#endif

BOOL InitializeInstall()
{
    FN_PROLOG_WIN32

    CFindFile hFind;
    WIN32_FIND_DATAW wfd;
    CStringBuffer TempDirectory;
    CDequeIterator<INSTALL_THREAD_PROC_DATA, offsetof(INSTALL_THREAD_PROC_DATA, Linkage)> iter(&g_Installs);
    CStringBuffer IniFileName;
    CStringBuffer BaseDirectory2;

    if (!BaseDirectory2.Win32Assign(BaseDirectory))
        goto Exit;

    if (!BaseDirectory2.Win32AppendPathElement(DATA_DIRECTORY_NAME, NUMBER_OF(DATA_DIRECTORY_NAME) - 1))
        goto Exit;

    if (!TempDirectory.Win32Assign(BaseDirectory2))

    if ((wfd.dwFileAttributes = ::GetFileAttributesW(TempDirectory)) == 0xffffffff
        && (wfd.dwFileAttributes = ::FusionpGetLastWin32Error()) == ERROR_FILE_NOT_FOUND)
    {
        printf("no %ls tests, skipping\n", DATA_DIRECTORY_NAME);
        FN_SUCCESSFUL_EXIT();
    }

    if (!TempDirectory.Win32AppendPathElement(L"*", 1))
        goto Exit;

    hFind = ::FindFirstFileW(TempDirectory, &wfd);
    if (hFind == INVALID_HANDLE_VALUE)
    {
        ::ReportFailure("Failed to find any files matching \"%ls\"\n", static_cast<PCWSTR>(TempDirectory));
        goto Exit;
    }

    for (;;)
    {
        DWORD dwTemp;
        WCHAR rgwchGuid[64];
        WCHAR rgwchKey[MAX_PATH];
        WCHAR rgwchData[MAX_PATH];
        INSTALL_THREAD_PROC_DATA *pData = NULL;
        struct
        {
            SXS_MANIFEST_INFORMATION_BASIC mib;
            WCHAR rgwchBuffer[65535]; // big frame but what the heck
        } ManifestInformation;

        if (FusionpIsDotOrDotDot(wfd.cFileName))
            goto Skip;

        if (!IniFileName.Win32Assign(BaseDirectory2))
            goto Exit;

        if (!TempDirectory.Win32AppendPathElement(DATA_DIRECTORY_NAME, NUMBER_OF(DATA_DIRECTORY_NAME) - 1))
            goto Exit;

        if (!IniFileName.Win32AppendPathElement(wfd.cFileName, wcslen(wfd.cFileName)))
            goto Exit;

        if ((pData = new INSTALL_THREAD_PROC_DATA) == NULL)
        {
            ::FusionpSetLastWin32Error(ERROR_OUTOFMEMORY);
            ::ReportFailure("Failed to allocate INSTALL_THREAD_PROC_DATA\n");
            goto Exit;
        }

        if (!pData->ManifestPath.Win32Append(IniFileName))
            goto Exit;
        if (!pData->ManifestPath.Win32Append(L"\\", 1))
            goto Exit;

        if (!IniFileName.Win32AppendPathElement(INI_FILE, NUMBER_OF(INI_FILE) - 1))
            goto Exit;

        if (!SxStressToolGetStringSetting(0, IniFileName, L"assembly", L"manifest", L"assembly.manifest", pData->ManifestPath, NULL))
            goto Exit;

        if (!::SxsQueryManifestInformation(
                0,
                pData->ManifestPath,
                SXS_QUERY_MANIFEST_INFORMATION_INFOCLASS_BASIC,
                SXS_QUERY_MANIFEST_INFORMATION_INFOCLASS_BASIC_FLAG_OMIT_SHORTNAME,
                sizeof(ManifestInformation),
                &ManifestInformation,
                NULL))
        {
            ::ReportFailure("Unable to query manifest information for manifest \"%ls\"\n", static_cast<PCWSTR>(pData->ManifestPath));
            goto Exit;
        }

        dwTemp = ::GetPrivateProfileStringW(L"reference", L"guid", L"", rgwchGuid, NUMBER_OF(rgwchGuid), IniFileName);
        if (dwTemp == (NUMBER_OF(rgwchGuid) - 1))
        {
            ::FusionpSetLastWin32Error(ERROR_INVALID_PARAMETER);
            ::ReportFailure("Enormous guid in \"%ls\"; section \"reference\", key \"guid\" (does not fit in %Iu characters).\n",
                static_cast<PCWSTR>(IniFileName), NUMBER_OF(rgwchGuid));
            goto Exit;
        }

        dwTemp = ::GetPrivateProfileStringW(L"reference", L"key", L"", rgwchKey, NUMBER_OF(rgwchKey), IniFileName);
        if (dwTemp == (NUMBER_OF(rgwchKey) - 1))
        {
            ::FusionpSetLastWin32Error(ERROR_INVALID_PARAMETER);
            ::ReportFailure("Enormous value in \"%ls\"; section \"reference\", key \"key\" (does not fit in %Iu characters).\n",
                static_cast<PCWSTR>(IniFileName), NUMBER_OF(rgwchKey));
            goto Exit;
        }

        dwTemp = ::GetPrivateProfileStringW(L"reference", L"data", L"", rgwchData, NUMBER_OF(rgwchData), IniFileName);
        if (dwTemp == (NUMBER_OF(rgwchData) - 1))
        {
            ::FusionpSetLastWin32Error(ERROR_INVALID_PARAMETER);
            ::ReportFailure("Enourmous value in \"%ls\"; section \"reference\", key \"data\" (does not fit in %Iu characters).\n",
                static_cast<PCWSTR>(IniFileName), NUMBER_OF(rgwchData));
            goto Exit;
        }

        pData->AfterInstallSleep = static_cast<DWORD>(::GetPrivateProfileIntW(L"assembly", L"AfterInstallSleepMS", 500, IniFileName));
        pData->AfterUninstallSleep = static_cast<DWORD>(::GetPrivateProfileIntW(L"assembly", L"AfterUninstallSleepMS", 500, IniFileName));
        pData->Install = (::GetPrivateProfileIntW(L"assembly", L"install", 1, IniFileName) != 0);
        pData->Uninstall = (::GetPrivateProfileIntW(L"assembly", L"uninstall", 1, IniFileName) != 0);

        if (!pData->Identity.Win32Assign(ManifestInformation.mib.lpIdentity, wcslen(ManifestInformation.mib.lpIdentity)))
        {
            ::FusionpSetLastWin32Error(ERROR_OUTOFMEMORY);
            ::ReportFailure("Error allocating installation identify string\n");
            goto Exit;
        }

        if (rgwchGuid[0] != L'\0')
        {
            HRESULT hr;

            if (FAILED(hr = ::CLSIDFromString(rgwchGuid, &pData->InstallationReference.guidScheme)))
            {
                ::FusionpSetLastWin32Error(ERROR_INVALID_PARAMETER);
                ::ReportFailure("CLSIDFromString() on [reference]/guid value in \"%ls\" failed with HRESULT 0x%08lx\n", static_cast<PCWSTR>(IniFileName), hr);
                goto Exit;
            }
            pData->InstallationReferencePtr = &pData->InstallationReference;
        }

        if (rgwchKey[0] != L'\0')
        {
            if (!pData->InstallationReference_Identifier.Win32Assign(rgwchKey, wcslen(rgwchKey)))
            {
                ::FusionpSetLastWin32Error(ERROR_OUTOFMEMORY);
                ::ReportFailure("Unable to allocate installation reference non-canonical data buffer\n");
                goto Exit;
            }
            pData->InstallationReference.lpIdentifier = pData->InstallationReference_Identifier;
        }

        if (rgwchData[0] != L'\0')
        {
            if (!pData->InstallationReference_NonCanonicalData.Win32Assign(rgwchData, wcslen(rgwchData)))
            {
                ::FusionpSetLastWin32Error(ERROR_OUTOFMEMORY);
                ::ReportFailure("Unable to allocate installation reference non-canonical data buffer\n");
                goto Exit;
            }
            pData->InstallationReference.lpNonCanonicalData = pData->InstallationReference_NonCanonicalData;
        }

        g_Installs.AddToTail(pData);

Skip:
        if (!::FindNextFileW(hFind, &wfd))
        {
            if (::FusionpGetLastWin32Error() != ERROR_NO_MORE_FILES)
            {
                ::ReportFailure("Error iterating over assemblies\n");
                goto Exit;
            }
            break;
        }
    }
    for (iter.Reset(); iter.More(); iter.Next())
    {
        if (!iter->Thread.Win32CreateThread(&InstallThreadProc, iter.Current()))
        {
            ::ReportFailure("Error launching install thread\n");
            goto Exit;
        }
        TotalThreads += 1;
    }

    FN_EPILOG
}

void RequestShutdownInstallThreads()
{
    CDequeIterator<INSTALL_THREAD_PROC_DATA, offsetof(INSTALL_THREAD_PROC_DATA, Linkage)> iter(&g_Installs);

    for (iter.Reset(); iter.More(); iter.Next())
    {
        iter->Stop = true;
    }
}

void WaitForInstallThreads()
{
    CDequeIterator<INSTALL_THREAD_PROC_DATA, offsetof(INSTALL_THREAD_PROC_DATA, Linkage)> iter(&g_Installs);

    for (iter.Reset(); iter.More(); iter.Next())
    {
        DWORD WaitResult = ::WaitForSingleObject(iter->Thread, INFINITE);
        switch (WaitResult)
        {
        case WAIT_OBJECT_0:
            break;
        case WAIT_FAILED:
            ::ReportFailure("Failed to WaitForSingleObject.\n");
            break;
        default:
            ::FusionpSetLastWin32Error(WaitResult);
            ::ReportFailure("Failed to WaitForSingleObject.\n");
            break;
        }
        iter->Thread.Win32Close();
    }
}

void CleanupInstall()
{
    g_Installs.ClearAndDeleteAll();
}

DWORD
WINAPI
InstallThreadProc(
    LPVOID pvData
    )
{
    INSTALL_THREAD_PROC_DATA *pData = reinterpret_cast<INSTALL_THREAD_PROC_DATA *>(pvData);
    DWORD dwReturnValue = ERROR_INTERNAL_ERROR;
    SXS_INSTALLW Install = { sizeof(SXS_INSTALLW) };
    SXS_UNINSTALLW Uninstall = { sizeof(SXS_UNINSTALLW) };
    DWORD WaitResult = 0;

    if ((Install.lpReference = pData->InstallationReferencePtr) != NULL)
        Install.dwFlags |= SXS_INSTALL_FLAG_REFERENCE_VALID;

    Install.lpManifestPath = pData->ManifestPath;

    Uninstall.lpAssemblyIdentity = pData->Identity;
    if ((Uninstall.lpInstallReference = pData->InstallationReferencePtr) != NULL)
        Uninstall.dwFlags |= SXS_UNINSTALL_FLAG_REFERENCE_VALID;

    InterlockedIncrement(&ThreadsWaiting);
    WaitResult = WaitForSingleObject(ResumeThreadsEvent, INFINITE);
    switch (WaitResult)
    {
    case WAIT_OBJECT_0:
        break;
    case WAIT_FAILED:
        dwReturnValue = ::FusionpGetLastWin32Error();
        ::ReportFailure("Failed to WaitForSingleObject.\n");
        goto Exit;
    default:
        dwReturnValue = WaitResult;
        ::FusionpSetLastWin32Error(WaitResult);
        ::ReportFailure("Failed to WaitForSingleObject.\n");
        goto Exit;
    }

    for (;;)
    {
        DWORD dwUninstallDisposition;

        if (pData->Install)
        {
            if (!::SxsInstallW(&Install))
            {
                dwReturnValue = ::FusionpGetLastWin32Error();
                ::ReportFailure("Failed to install \"%ls\"\n", static_cast<PCWSTR>(pData->ManifestPath));
                goto Exit;
            }

            printf("[%lx.%lx] Manifest \"%ls\" installed\n",
                SxStressToolGetCurrentProcessId(),
                SxStressToolGetCurrentThreadId(),
                static_cast<PCWSTR>(pData->ManifestPath)
                );

            ::WaitForSingleObject(StopEvent, pData->AfterInstallSleep);
        }

        if (pData->Stop)
            break;

        if (pData->Uninstall)
        {
            if (!::SxsUninstallW(&Uninstall, &dwUninstallDisposition))
            {
                dwReturnValue = ::FusionpGetLastWin32Error();
                ::ReportFailure("Failed to uninstall \"%ls\"\n", static_cast<PCWSTR>(pData->ManifestPath));
                goto Exit;
            }

            printf("[%lx.%lx] Manifest \"%ls\" uninstalled; disposition = %lu\n",
                SxStressToolGetCurrentProcessId(),
                SxStressToolGetCurrentThreadId(),
                static_cast<PCWSTR>(pData->ManifestPath),
                dwUninstallDisposition
                );

            ::WaitForSingleObject(StopEvent, pData->AfterUninstallSleep);
        }
        if (pData->Stop)
            break;
    }

    dwReturnValue = ERROR_SUCCESS;
Exit:
    printf("[%lx.%lx] install(%ls) thread exiting %lu\n",
        SxStressToolGetCurrentProcessId(), SxStressToolGetCurrentThreadId(), static_cast<PCWSTR>(pData->ManifestPath), dwReturnValue);
    return dwReturnValue;
}