// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.

#include "precomp.h"

// internal function declarations

static HRESULT GetVersionInternal(
    __in BURN_VARIANT* pVariant,
    __in BOOL fHidden,
    __in BOOL fSilent,
    __out VERUTIL_VERSION** ppValue
    );

// function definitions

extern "C" void BVariantUninitialize(
    __in BURN_VARIANT* pVariant
    )
{
    if (BURN_VARIANT_TYPE_FORMATTED == pVariant->Type ||
        BURN_VARIANT_TYPE_STRING == pVariant->Type)
    {
        StrSecureZeroFreeString(pVariant->sczValue);
    }
    SecureZeroMemory(pVariant, sizeof(BURN_VARIANT));
}

extern "C" HRESULT BVariantGetNumeric(
    __in BURN_VARIANT* pVariant,
    __out LONGLONG* pllValue
    )
{
    HRESULT hr = S_OK;

    switch (pVariant->Type)
    {
    case BURN_VARIANT_TYPE_NUMERIC:
        *pllValue = pVariant->llValue;
        break;
    case BURN_VARIANT_TYPE_FORMATTED: __fallthrough;
    case BURN_VARIANT_TYPE_STRING:
        hr = StrStringToInt64(pVariant->sczValue, 0, pllValue);
        if (FAILED(hr))
        {
            hr = DISP_E_TYPEMISMATCH;
        }
        break;
    case BURN_VARIANT_TYPE_VERSION:
        hr = StrStringToInt64(pVariant->pValue ? pVariant->pValue->sczVersion : NULL, 0, pllValue);
        if (FAILED(hr))
        {
            hr = DISP_E_TYPEMISMATCH;
        }
        break;
    default:
        hr = E_INVALIDARG;
        break;
    }

    return hr;
}

extern "C" HRESULT BVariantGetString(
    __in BURN_VARIANT* pVariant,
    __out_z LPWSTR* psczValue
    )
{
    HRESULT hr = S_OK;

    switch (pVariant->Type)
    {
    case BURN_VARIANT_TYPE_NUMERIC:
        hr = StrAllocFormattedSecure(psczValue, L"%I64d", pVariant->llValue);
        ExitOnFailure(hr, "Failed to convert int64 to string.");
        break;
    case BURN_VARIANT_TYPE_FORMATTED: __fallthrough;
    case BURN_VARIANT_TYPE_STRING:
        hr = StrAllocStringSecure(psczValue, pVariant->sczValue, 0);
        ExitOnFailure(hr, "Failed to copy string value.");
        break;
    case BURN_VARIANT_TYPE_VERSION:
        hr = StrAllocStringSecure(psczValue, pVariant->pValue ? pVariant->pValue->sczVersion : NULL, 0);
        ExitOnFailure(hr, "Failed to copy version value.");
        break;
    default:
        hr = E_INVALIDARG;
        break;
    }

LExit:
    return hr;
}

extern "C" HRESULT BVariantGetVersion(
    __in BURN_VARIANT* pVariant,
    __out VERUTIL_VERSION** ppValue
    )
{
    return GetVersionInternal(pVariant, FALSE, FALSE, ppValue);
}

extern "C" HRESULT BVariantGetVersionHidden(
    __in BURN_VARIANT* pVariant,
    __in BOOL fHidden,
    __out VERUTIL_VERSION** ppValue
    )
{
    return GetVersionInternal(pVariant, fHidden, FALSE, ppValue);
}

extern "C" HRESULT BVariantGetVersionSilent(
    __in BURN_VARIANT* pVariant,
    __in BOOL fSilent,
    __out VERUTIL_VERSION** ppValue
    )
{
    return GetVersionInternal(pVariant, FALSE, fSilent, ppValue);
}

static HRESULT GetVersionInternal(
    __in BURN_VARIANT* pVariant,
    __in BOOL fHidden,
    __in BOOL fSilent,
    __out VERUTIL_VERSION** ppValue
    )
{
    HRESULT hr = S_OK;

    switch (pVariant->Type)
    {
    case BURN_VARIANT_TYPE_NUMERIC:
        hr = VerVersionFromQword(pVariant->llValue, ppValue);
        break;
    case BURN_VARIANT_TYPE_FORMATTED: __fallthrough;
    case BURN_VARIANT_TYPE_STRING:
        hr = VerParseVersion(pVariant->sczValue, 0, FALSE, ppValue);
        if (SUCCEEDED(hr) && !fSilent && (*ppValue)->fInvalid)
        {
            LogId(REPORT_WARNING, MSG_INVALID_VERSION_COERSION, fHidden ? L"*****" : pVariant->sczValue);
        }
        break;
    case BURN_VARIANT_TYPE_VERSION:
        if (!pVariant->pValue)
        {
            *ppValue = NULL;
        }
        else
        {
            hr = VerCopyVersion(pVariant->pValue, ppValue);
        }
        break;
    default:
        hr = E_INVALIDARG;
        break;
    }

    return hr;
}

extern "C" HRESULT BVariantSetNumeric(
    __in BURN_VARIANT* pVariant,
    __in LONGLONG llValue
    )
{
    HRESULT hr = S_OK;

    if (BURN_VARIANT_TYPE_FORMATTED == pVariant->Type ||
        BURN_VARIANT_TYPE_STRING == pVariant->Type)
    {
        StrSecureZeroFreeString(pVariant->sczValue);
    }
    memset(pVariant, 0, sizeof(BURN_VARIANT));
    pVariant->llValue = llValue;
    pVariant->Type = BURN_VARIANT_TYPE_NUMERIC;

    return hr;
}

extern "C" HRESULT BVariantSetString(
    __in BURN_VARIANT* pVariant,
    __in_z_opt LPCWSTR wzValue,
    __in DWORD_PTR cchValue,
    __in BOOL fFormatted
    )
{
    HRESULT hr = S_OK;

    if (!wzValue) // if we're nulling out the string, make the variable NONE.
    {
        BVariantUninitialize(pVariant);
    }
    else // assign the value.
    {
        if (BURN_VARIANT_TYPE_FORMATTED != pVariant->Type &&
            BURN_VARIANT_TYPE_STRING != pVariant->Type)
        {
            memset(pVariant, 0, sizeof(BURN_VARIANT));
        }

        hr = StrAllocStringSecure(&pVariant->sczValue, wzValue, cchValue);
        ExitOnFailure(hr, "Failed to copy string.");

        pVariant->Type = fFormatted ? BURN_VARIANT_TYPE_FORMATTED : BURN_VARIANT_TYPE_STRING;
    }

LExit:
    return hr;
}

extern "C" HRESULT BVariantSetVersion(
    __in BURN_VARIANT* pVariant,
    __in VERUTIL_VERSION* pValue
    )
{
    HRESULT hr = S_OK;

    if (!pValue) // if we're nulling out the version, make the variable NONE.
    {
        BVariantUninitialize(pVariant);
    }
    else // assign the value.
    {
        if (BURN_VARIANT_TYPE_FORMATTED == pVariant->Type ||
            BURN_VARIANT_TYPE_STRING == pVariant->Type)
        {
            StrSecureZeroFreeString(pVariant->sczValue);
        }
        memset(pVariant, 0, sizeof(BURN_VARIANT));
        hr = VerCopyVersion(pValue, &pVariant->pValue);
        pVariant->Type = BURN_VARIANT_TYPE_VERSION;
    }

    return hr;
}

extern "C" HRESULT BVariantSetValue(
    __in BURN_VARIANT* pVariant,
    __in BURN_VARIANT* pValue
    )
{
    HRESULT hr = S_OK;

    switch (pValue->Type)
    {
    case BURN_VARIANT_TYPE_NONE:
        BVariantUninitialize(pVariant);
        break;
    case BURN_VARIANT_TYPE_NUMERIC:
        hr = BVariantSetNumeric(pVariant, pValue->llValue);
        break;
    case BURN_VARIANT_TYPE_FORMATTED: __fallthrough;
    case BURN_VARIANT_TYPE_STRING:
        hr = BVariantSetString(pVariant, pValue->sczValue, 0, BURN_VARIANT_TYPE_FORMATTED == pValue->Type);
        break;
    case BURN_VARIANT_TYPE_VERSION:
        hr = BVariantSetVersion(pVariant, pValue->pValue);
        break;
    default:
        hr = E_INVALIDARG;
    }
    ExitOnFailure(hr, "Failed to copy variant value.");

LExit:
    return hr;
}

extern "C" HRESULT BVariantCopy(
    __in BURN_VARIANT* pSource,
    __out BURN_VARIANT* pTarget
    )
{
    return BVariantSetValue(pTarget, pSource);
}

extern "C" HRESULT BVariantChangeType(
    __in BURN_VARIANT* pVariant,
    __in BURN_VARIANT_TYPE type
    )
{
    HRESULT hr = S_OK;
    BURN_VARIANT variant = { };

    if (pVariant->Type == type)
    {
        ExitFunction(); // variant already is of the requested type
    }
    else if (BURN_VARIANT_TYPE_FORMATTED == pVariant->Type && BURN_VARIANT_TYPE_STRING == type ||
             BURN_VARIANT_TYPE_STRING == pVariant->Type && BURN_VARIANT_TYPE_FORMATTED == type)
    {
        pVariant->Type = type;
        ExitFunction();
    }

    switch (type)
    {
    case BURN_VARIANT_TYPE_NONE:
        hr = S_OK;
        break;
    case BURN_VARIANT_TYPE_NUMERIC:
        hr = BVariantGetNumeric(pVariant, &variant.llValue);
        break;
    case BURN_VARIANT_TYPE_FORMATTED: __fallthrough;
    case BURN_VARIANT_TYPE_STRING:
        hr = BVariantGetString(pVariant, &variant.sczValue);
        break;
    case BURN_VARIANT_TYPE_VERSION:
        hr = BVariantGetVersionSilent(pVariant, TRUE, &variant.pValue);
        break;
    default:
        ExitFunction1(hr = E_INVALIDARG);
    }
    variant.Type = type;
    ExitOnFailure(hr, "Failed to copy variant value.");

    BVariantUninitialize(pVariant);
    memcpy_s(pVariant, sizeof(BURN_VARIANT), &variant, sizeof(BURN_VARIANT));
    SecureZeroMemory(&variant, sizeof(BURN_VARIANT));

LExit:
    return hr;
}