#include "pch.h"
#include <variant>
#include "object_visualizer.h"
#include "property_visualizer.h"

using namespace Microsoft::VisualStudio::Debugger;
using namespace Microsoft::VisualStudio::Debugger::Evaluation;
using namespace std::literals;
using namespace winrt;
using namespace winmd::reader;

#define IID_IInspectable L"AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90"
#define IID_IStringable  L"96369F54-8EB6-48F0-ABCE-C1B211E627C3"

constexpr struct
{
    PCWSTR propField;
    PCWSTR displayType;
}
g_categoryData[] = 
{
    { L"b", L"bool" },
    { L"c", L"wchar_t" },
    { L"i1", L"int8_t" },
    { L"u1", L"uint8_t" },
    { L"i2", L"int16_t" },
    { L"u2", L"uint16_t" },
    { L"i4", L"int32_t" },
    { L"u4", L"uint32_t" },
    { L"i8", L"int64_t" },
    { L"u8", L"uint64_t" },
    { L"r4", L"float" },
    { L"r8", L"double" },
    { L"s,sh", L"winrt::hstring" },
    { L"g", L"winrt::guid" },
};

NatvisDiagnosticLevel GetNatvisDiagnosticLevel()
{
    static NatvisDiagnosticLevel level = NatvisDiagnosticLevel::Unknown;
    if (level != NatvisDiagnosticLevel::Unknown)
    {
        return level;
    }
    level = NatvisDiagnosticLevel::Error;

    // If < VS16, just output errors
    if (!DkmComponentManager::IsApiVersionSupported(DkmApiVersion::VS16RTM))
    {
        return level;
    }

    // Else, use VS natvis diagnostics level directly
    HKEY userSettingsKey;
    if (FAILED(DkmGlobalSettings::OpenVSUserSettingsKey(L"Debugger\\NatvisDiagnostics", &userSettingsKey)))
    {
        return level;
    }

    char data[MAX_PATH];
    DWORD dataSize = _countof(data);
    if (RegGetValue(userSettingsKey, "", "Level", RRF_RT_REG_SZ, nullptr, data, &dataSize) == ERROR_SUCCESS)
    {
        switch (data[0])
        {
        case 'O':
            level = NatvisDiagnosticLevel::Off;
            break;
        case 'E':
            level = NatvisDiagnosticLevel::Error;
            break;
        case 'W':
            level = NatvisDiagnosticLevel::Warning;
            break;
        case 'V':
            level = NatvisDiagnosticLevel::Verbose;
            break;
        }
    }
    RegCloseKey(userSettingsKey);
    return level;
}

HRESULT NatvisDiagnostic(DkmProcess* process, std::wstring_view const& messageText, NatvisDiagnosticLevel level, HRESULT errorCode)
{
    if (GetNatvisDiagnosticLevel() < level)
    {
        return S_OK;
    }
    auto userMessage = std::wstring(L"Natvis C++/WinRT: ") + std::wstring(messageText) + L"\n";
    com_ptr<DkmString> pUserMessageText;
    IF_FAIL_RET(DkmString::Create(DkmSourceString(userMessage.c_str()), pUserMessageText.put()));
    com_ptr<DkmUserMessage> pUserMessage;
    IF_FAIL_RET(DkmUserMessage::Create(process->Connection(), process, DkmUserMessageOutputKind::UnfilteredOutputWindowMessage, pUserMessageText.get(), 0, errorCode, pUserMessage.put()));
    return pUserMessage->Post();
}

static HRESULT EvaluatePropertyExpression(
    _In_ PropertyData const& prop,
    _In_ DkmVisualizedExpression* pExpression,
    _In_ DkmPointerValueHome* pObject,
    ObjectType objectType,
    _Out_ com_ptr<DkmEvaluationResult>& pEvaluationResult
)
{
    wchar_t abiAddress[40];
    auto process = pExpression->RuntimeInstance()->Process();
    bool is64Bit = ((process->SystemInformation()->Flags() & DefaultPort::DkmSystemInformationFlags::Is64Bit) != 0);
    swprintf_s(abiAddress, is64Bit ? L"%s0x%I64x" : L"%s0x%08x", objectType == ObjectType::Abi ? L"(::IUnknown*)" : L"*(::IUnknown**)", pObject->Address());
    wchar_t wszEvalText[500];
    std::wstring propCast;
    PCWSTR propField;
    if (prop.category < PropertyCategory::Value)
    {
        propField = g_categoryData[(int)prop.category].propField;
    }
    else
    {
        propField = L"v";
        propCast = L"*(" + prop.abiType + L"*)";
    }
    swprintf_s(wszEvalText, L"%sWINRT_abi_val(%s, L\"{%s}\", %i).%s", propCast.c_str(), abiAddress, prop.iid.c_str(), prop.index, propField);

    com_ptr<DkmString> pEvalText;
    IF_FAIL_RET(DkmString::Create(DkmSourceString(wszEvalText), pEvalText.put()));
    NatvisDiagnostic(process, wszEvalText, NatvisDiagnosticLevel::Verbose);

    auto evalFlags = DkmEvaluationFlags::TreatAsExpression | DkmEvaluationFlags::ForceEvaluationNow | DkmEvaluationFlags::ForceRealFuncEval;
    auto inspectionContext = pExpression->InspectionContext();
    com_ptr<DkmLanguageExpression> pLanguageExpression;
    IF_FAIL_RET(DkmLanguageExpression::Create(
        inspectionContext->Language(),
        evalFlags,
        pEvalText.get(),
        DkmDataItem::Null(),
        pLanguageExpression.put()
    ));

    com_ptr<DkmInspectionContext> pInspectionContext;
    if ( (pExpression->InspectionContext()->EvaluationFlags() & evalFlags) != evalFlags)
    {
        DkmInspectionContext::Create(
            inspectionContext->InspectionSession(),
            inspectionContext->RuntimeInstance(),
            inspectionContext->Thread(),
            inspectionContext->Timeout(),
            evalFlags,
            inspectionContext->FuncEvalFlags(),
            inspectionContext->Radix(),
            inspectionContext->Language(),
            inspectionContext->ReturnValue(),
            pInspectionContext.put()
        );
    }
    else
    {
        pInspectionContext.copy_from(inspectionContext);
    }

    auto hr = pExpression->EvaluateExpressionCallback(
        pInspectionContext.get(),
        pLanguageExpression.get(),
        pExpression->StackFrame(),
        pEvaluationResult.put()
    );
    if (hr != S_OK)
    {
        NatvisDiagnostic(process, L"EvaluateExpressionCallback failed", NatvisDiagnosticLevel::Warning, hr);
    }
    return hr;
}

static HRESULT EvaluatePropertyString(
    _In_ PropertyData const& prop,
    _In_ DkmVisualizedExpression* pExpression,
    _In_ DkmPointerValueHome* pObject,
    ObjectType objectType,
    _Out_ com_ptr<DkmString>& pValue
)
{
    com_ptr<DkmEvaluationResult> pEvaluationResult;
    IF_FAIL_RET(EvaluatePropertyExpression(prop, pExpression, pObject, objectType, pEvaluationResult));
    if (pEvaluationResult->TagValue() != DkmEvaluationResult::Tag::SuccessResult)
    {
        return E_FAIL;
    }
    com_ptr<DkmSuccessEvaluationResult> pSuccessEvaluationResult = pEvaluationResult.as<DkmSuccessEvaluationResult>();
    if (pSuccessEvaluationResult->Address()->Value() != 0)
    {
        pValue.copy_from(pSuccessEvaluationResult->Value());
    }
    return S_OK;
}

static std::string GetRuntimeClass(
    _In_ DkmVisualizedExpression* pExpression,
    _In_ DkmPointerValueHome* pObject,
    ObjectType objectType
)
{
    com_ptr<DkmString> pValue;
    EvaluatePropertyString({ IID_IInspectable, -2, PropertyCategory::String }, pExpression, pObject, objectType, pValue);
    if (!pValue || pValue->Length() == 0)
    {
        return "";
    }
    return to_string(pValue->Value());
}

static HRESULT ObjectToString(
    _In_ DkmVisualizedExpression* pExpression,
    _In_ DkmPointerValueHome* pObject,
    ObjectType objectType,
    _Out_ com_ptr<DkmString>& pValue
)
{
    if (SUCCEEDED(EvaluatePropertyString({ IID_IStringable, 0, PropertyCategory::String }, pExpression, pObject, objectType, pValue)))
    {
        if (pValue && pValue->Length() > 0)
        {
            return S_OK;
        }
        pValue = nullptr;
        
        // WINRT_abi_val returned 0, which may be success or failure (due to VirtualQuery validation)
        // Call back for the runtime class name to determine which it was
        if (!GetRuntimeClass(pExpression, pObject, objectType).empty())
        {
            return DkmString::Create(L"<Expand object to view properties>", pValue.put());
        }
    }

    // VirtualQuery validation failed (as determined by no runtime class name) or an
    // exception escaped WINRT_abi_val (e.g, bad pointer, which we try to avoid via VirtualQuery)
    return DkmString::Create(L"<Object uninitialized or information unavailable>", pValue.put());
}

static HRESULT CreateChildVisualizedExpression(
    _In_ PropertyData const& prop,
    _In_ DkmVisualizedExpression* pParent,
    ObjectType objectType,
    _Deref_out_ DkmChildVisualizedExpression** ppResult
)
{
    *ppResult = nullptr;

    com_ptr<DkmEvaluationResult> pEvaluationResult;
    auto valueHome = make_com_ptr(pParent->ValueHome());
    com_ptr<DkmPointerValueHome> pParentPointer = valueHome.as<DkmPointerValueHome>();
    IF_FAIL_RET(EvaluatePropertyExpression(prop, pParent, pParentPointer.get(), objectType, pEvaluationResult));
    if (pEvaluationResult->TagValue() != DkmEvaluationResult::Tag::SuccessResult)
    {
        return E_FAIL;
    }

    com_ptr<DkmSuccessEvaluationResult> pSuccessEvaluationResult = pEvaluationResult.as<DkmSuccessEvaluationResult>();
    com_ptr<DkmString> pValue;
    com_ptr<DkmPointerValueHome> pChildPointer;
    bool isNonNullObject = false;
    if (prop.category == PropertyCategory::Class)
    {
        auto childObjectAddress = pSuccessEvaluationResult->Address()->Value();
        if (childObjectAddress)
        {
            isNonNullObject = true;
            IF_FAIL_RET(DkmPointerValueHome::Create(childObjectAddress, pChildPointer.put()));
            IF_FAIL_RET(ObjectToString(pParent, pChildPointer.get(), ObjectType::Abi, pValue));
        }
    }
    if(!isNonNullObject)
    {
        com_ptr<DkmExpressionValueHome> expressionValueHome = make_com_ptr(pParent->ValueHome());
        pChildPointer = expressionValueHome.as<DkmPointerValueHome>();
        pValue.copy_from(pSuccessEvaluationResult->Value());
    }

    com_ptr<DkmString> pDisplayName;
    IF_FAIL_RET(DkmString::Create(prop.displayName.c_str(), pDisplayName.put()));

    PCWSTR displayType;
    if (prop.category < PropertyCategory::Value)
    {
        displayType = g_categoryData[(int)prop.category].displayType;
    }
    else
    {
        displayType = prop.displayType.c_str();
    }
    com_ptr<DkmString> pDisplayType;
    IF_FAIL_RET(DkmString::Create(displayType, pDisplayType.put()));

    com_ptr<DkmSuccessEvaluationResult> pVisualizedResult;
    IF_FAIL_RET(DkmSuccessEvaluationResult::Create(
        pParent->InspectionContext(),
        pParent->StackFrame(),
        pDisplayName.get(),
        pSuccessEvaluationResult->FullName(),
        pSuccessEvaluationResult->Flags(),
        pValue.get(),
        pSuccessEvaluationResult->EditableValue(),
        pDisplayType.get(),
        pSuccessEvaluationResult->Category(),
        pSuccessEvaluationResult->Access(),
        pSuccessEvaluationResult->StorageType(),
        pSuccessEvaluationResult->TypeModifierFlags(),
        pSuccessEvaluationResult->Address(),
        pSuccessEvaluationResult->CustomUIVisualizers(),
        pSuccessEvaluationResult->ExternalModules(),
        DkmDataItem::Null(),
        pVisualizedResult.put()
    ));

    com_ptr<DkmChildVisualizedExpression> pChildVisualizedExpression;
    IF_FAIL_RET(DkmChildVisualizedExpression::Create(
        pParent->InspectionContext(),
        pParent->VisualizerId(),
        pParent->SourceId(),
        pParent->StackFrame(),
        pChildPointer.get(),
        pVisualizedResult.get(),
        pParent,
        2,
        DkmDataItem::Null(),
        pChildVisualizedExpression.put()
    ));

    if (isNonNullObject)
    {
        com_ptr<object_visualizer> pObjectVisualizer = make_self<object_visualizer>(pChildVisualizedExpression.get(), ObjectType::Abi);
        IF_FAIL_RET(pChildVisualizedExpression->SetDataItem(DkmDataCreationDisposition::CreateNew, pObjectVisualizer.get()));
    }
    else
    {
        com_ptr<property_visualizer> pPropertyVisualizer = make_self<property_visualizer>(pChildVisualizedExpression.get(), pSuccessEvaluationResult.get());
        IF_FAIL_RET(pChildVisualizedExpression->SetDataItem(DkmDataCreationDisposition::CreateNew, pPropertyVisualizer.get()));
    }

    *ppResult = pChildVisualizedExpression.detach();

    return S_OK;
}

struct property_type
{
    MethodDef get;
    MethodDef set;
};

void GetInterfaceData(
    Microsoft::VisualStudio::Debugger::DkmProcess* process,
    coded_index<TypeDefOrRef> index,
    _Inout_ std::vector<PropertyData>& propertyData,
    _Out_ bool& isStringable
){
    auto [type, propIid] = ResolveTypeInterface(process, index);
    if (!type)
    {
        return;
    }

    if (propIid == IID_IStringable)
    {
        isStringable = true;
        return;
    }

    int32_t propIndex = -1;
    for (auto&& method : type.MethodList())
    {
        propIndex++;

        auto isGetter = method.Flags().SpecialName() && starts_with(method.Name(), "get_");
        if (!isGetter)
        {
            continue;
        }

        std::optional<PropertyCategory> propCategory;
        std::wstring propAbiType;
        std::wstring propDisplayType;

        auto retType = method.Signature().ReturnType();
        std::visit(overloaded{
            [&](ElementType type)
            {
                if ((ElementType::Boolean <= type) && (type <= ElementType::String))
                {
                    propCategory = (PropertyCategory)(static_cast<std::underlying_type<ElementType>::type>(type) -
                        static_cast<std::underlying_type<ElementType>::type>(ElementType::Boolean));
                }
                else if (type == ElementType::Object)
                {
                    //propDisplayType = L"winrt::Windows::Foundation::IInspectable";
                    //propCategory = PropertyCategory::Class;
                    //propAbiType = L"winrt::impl::inspectable_abi*";
                }
            },
            [&](coded_index<TypeDefOrRef> const& index)
            {
                auto type = ResolveType(process, index);
                if (!type)
                {
                    return;
                }

                auto typeName = type.TypeName();
                if (typeName == "GUID"sv)
                {
                    propCategory = PropertyCategory::Guid;
                }
                else
                {
                    auto ns = std::string(type.TypeNamespace());
                    auto name = std::string(type.TypeName());

                    // Map numeric type names
                    if (ns == "Windows.Foundation.Numerics")
                    {
                        if (name == "Matrix3x2") { name = "float3x2"; }
                        else if (name == "Matrix4x4") { name = "float4x4"; }
                        else if (name == "Plane") { name = "plane"; }
                        else if (name == "Quaternion") { name = "quaternion"; }
                        else if (name == "Vector2") { name = "float2"; }
                        else if (name == "Vector3") { name = "float3"; }
                        else if (name == "Vector4") { name = "float4"; }
                    }

                    // Types come back from winmd files with '.', need to be '::'
                    // Ex. Windows.Foundation.Uri needs to be Windows::Foundation::Uri
                    auto fullTypeName = ns + "::" + name;
                    wchar_t cppTypename[500];
                    size_t i, j;
                    for (i = 0, j = 0; i < (fullTypeName.length() + 1); i++, j++)
                    {
                        if (fullTypeName[i] == L'.')
                        {
                            cppTypename[j++] = L':';
                            cppTypename[j] = L':';
                        }
                        else
                        {
                            cppTypename[j] = fullTypeName[i];
                        }
                    }

                    propDisplayType = std::wstring(L"winrt::") + cppTypename;
                    if(get_category(type) == category::class_type)
                    {
                        propCategory = PropertyCategory::Class;
                        propAbiType = L"winrt::impl::inspectable_abi*";
                    }
                    else
                    {
                        propCategory = PropertyCategory::Value;
                        propAbiType = propDisplayType;
                    }
                }
            },
            [&](GenericTypeIndex /*var*/)
            {
                    NatvisDiagnostic(process, L"Generics not yet supported", NatvisDiagnosticLevel::Warning);
            },
            [&](GenericMethodTypeIndex /*var*/)
            {
                    NatvisDiagnostic(process, L"Generics not yet supported", NatvisDiagnosticLevel::Warning);
            },
            [&](GenericTypeInstSig const& /*type*/)
            {
                    NatvisDiagnostic(process, L"Generics not yet supported", NatvisDiagnosticLevel::Warning);
            }
        }, retType.Type().Type());

        if (propCategory)
        {
            auto propName = method.Name().substr(4);
            std::wstring propDisplayName(propName.cbegin(), propName.cend());
            propertyData.push_back({ propIid, propIndex, *propCategory, propAbiType, propDisplayType, propDisplayName });
        }
    }
}

void object_visualizer::GetPropertyData()
{
    auto valueHome = make_com_ptr(m_pVisualizedExpression->ValueHome());
    com_ptr<DkmPointerValueHome> pObject = valueHome.as<DkmPointerValueHome>();
    auto rc = GetRuntimeClass(m_pVisualizedExpression.get(), pObject.get(), m_objectType);
    if (rc.empty())
    {
        return;
    }
    auto process = m_pVisualizedExpression->RuntimeInstance()->Process();
    // runtime class name is delimited by L"..."
    GetTypeProperties(process, std::string_view{ rc.data() + 2, rc.length() - 3 });
}

void object_visualizer::GetTypeProperties(Microsoft::VisualStudio::Debugger::DkmProcess* process, std::string_view const& type_name)
{
    // TODO: add support for direct generic interface implementations (e.g., key_value_pair)
    auto type = FindType(process, type_name);
    if (!type)
    {
        return;
    }

    if (get_category(type) == category::class_type)
    {
        auto const& [extends_namespace, extends_name] = get_base_class_namespace_and_name(type);
        if(!extends_namespace.empty() && !extends_name.empty())
        {
            auto base_type = std::string(extends_namespace) + "." + std::string(extends_name);
            if (base_type != "System.Object")
            {
                GetTypeProperties(process, base_type);
            }
        }
        auto impls = type.InterfaceImpl();
        for (auto&& impl : impls)
        {
            GetInterfaceData(process, impl.Interface(), m_propertyData, m_isStringable);
        }
    }
    else if (get_category(type) == category::interface_type)
    {
        auto impls = type.InterfaceImpl();
        for (auto&& impl : impls)
        {
            GetInterfaceData(process, impl.Interface(), m_propertyData, m_isStringable);
        }
        GetInterfaceData(process, type.coded_index<TypeDefOrRef>(), m_propertyData, m_isStringable);
    }
}

HRESULT object_visualizer::CreateEvaluationResult(_In_ DkmVisualizedExpression* pVisualizedExpression, _In_ ObjectType objectType, _Deref_out_ DkmEvaluationResult** ppResultObject)
{
    com_ptr<object_visualizer> pObjectVisualizer = make_self<object_visualizer>(pVisualizedExpression, objectType);

    IF_FAIL_RET(pVisualizedExpression->SetDataItem(DkmDataCreationDisposition::CreateNew, pObjectVisualizer.get()));

    IF_FAIL_RET(pObjectVisualizer->CreateEvaluationResult(ppResultObject));

    IF_FAIL_RET(pVisualizedExpression->SetDataItem(DkmDataCreationDisposition::CreateNew, *ppResultObject));

    return S_OK;
}

#ifdef COMPONENT_DEPLOYMENT
static std::set<UINT64> g_refresh_cache;
bool requires_refresh(UINT64 address, DkmEvaluationFlags_t evalFlags)
{
    auto refreshed = g_refresh_cache.find(address) != g_refresh_cache.end();
    return !refreshed && ((evalFlags & DkmEvaluationFlags::EnableExtendedSideEffects) != DkmEvaluationFlags::EnableExtendedSideEffects);
}
void cache_refresh(UINT64 address)
{
    g_refresh_cache.insert(address);
}
#else
bool requires_refresh(UINT64, DkmEvaluationFlags_t)
{
    return false;
}
void cache_refresh(UINT64)
{
}
#endif

HRESULT object_visualizer::CreateEvaluationResult(_Deref_out_ DkmEvaluationResult** ppResultObject)
{
    com_ptr<DkmRootVisualizedExpression> pRootVisualizedExpression = m_pVisualizedExpression.as<DkmRootVisualizedExpression>();

    auto valueHome = make_com_ptr(m_pVisualizedExpression->ValueHome());
    com_ptr<DkmPointerValueHome> pPointerValueHome = valueHome.as<DkmPointerValueHome>();
    auto address = pPointerValueHome->Address();
    
    com_ptr<DkmString> pValue;
    DkmEvaluationResultFlags_t evalResultFlags = DkmEvaluationResultFlags::ReadOnly | DkmEvaluationResultFlags::Expandable;
    if (requires_refresh(address, m_pVisualizedExpression->InspectionContext()->EvaluationFlags()))
    {
        IF_FAIL_RET(DkmString::Create(L"<Refresh to view properties>", pValue.put()));
        evalResultFlags |= DkmEvaluationResultFlags::EnableExtendedSideEffectsUponRefresh | DkmEvaluationResultFlags::CanEvaluateNow;
    }
    else
    {
        cache_refresh(address);
        IF_FAIL_RET(ObjectToString(m_pVisualizedExpression.get(), pPointerValueHome.get(), m_objectType, pValue));
    }

    com_ptr<DkmDataAddress> pAddress;
    IF_FAIL_RET(DkmDataAddress::Create(m_pVisualizedExpression->StackFrame()->RuntimeInstance(), address, nullptr, pAddress.put()));

    com_ptr<DkmSuccessEvaluationResult> pSuccessEvaluationResult;
    IF_FAIL_RET(DkmSuccessEvaluationResult::Create(
        m_pVisualizedExpression->InspectionContext(),
        m_pVisualizedExpression->StackFrame(),
        pRootVisualizedExpression->Name(),
        pRootVisualizedExpression->FullName(),
        evalResultFlags,
        pValue.get(),
        pValue.get(),
        pRootVisualizedExpression->Type(),
        DkmEvaluationResultCategory::Class,
        DkmEvaluationResultAccessType::None,
        DkmEvaluationResultStorageType::None,
        DkmEvaluationResultTypeModifierFlags::None,
        pAddress.get(),
        (DkmReadOnlyCollection<DkmCustomUIVisualizerInfo*>*)nullptr,
        (DkmReadOnlyCollection<DkmModuleInstance*>*)nullptr,
        DkmDataItem::Null(),
        pSuccessEvaluationResult.put()
    ));

    pSuccessEvaluationResult->SetDataItem(DkmDataCreationDisposition::CreateNew, this);

    *ppResultObject = (DkmEvaluationResult*)pSuccessEvaluationResult.detach();

    return S_OK;
}

HRESULT object_visualizer::GetChildren(
    _In_ UINT32 InitialRequestSize,
    _In_ DkmInspectionContext* pInspectionContext,
    _Out_ DkmArray<DkmChildVisualizedExpression*>* pInitialChildren,
    _Deref_out_ DkmEvaluationResultEnumContext** ppEnumContext)
{
    // Ignore metadata errors to ensure that Raw Data is always available
    if (m_propertyData.empty())
    {
        try
        {
            GetPropertyData();
        }
        catch (std::invalid_argument const& e)
        {
            std::string_view message(e.what());
            NatvisDiagnostic(m_pVisualizedExpression.get(),
                std::wstring(L"Exception in object_visualizer::GetPropertyData: ") +
                std::wstring(message.begin(), message.end()),
                NatvisDiagnosticLevel::Error, to_hresult());
        }
        catch (...)
        {
            NatvisDiagnostic(m_pVisualizedExpression.get(),
                L"Exception in object_visualizer::GetPropertyData", NatvisDiagnosticLevel::Error, to_hresult());
        }
    }

    com_ptr<DkmEvaluationResultEnumContext> pEnumContext;
    IF_FAIL_RET(DkmEvaluationResultEnumContext::Create(
        static_cast<uint32_t>(m_propertyData.size()),
        m_pVisualizedExpression->StackFrame(),
        pInspectionContext,
        this,
        pEnumContext.put()));

    IF_FAIL_RET(GetItems(m_pVisualizedExpression.get(), pEnumContext.get(), 0, InitialRequestSize, pInitialChildren));

    *ppEnumContext = pEnumContext.detach();

    return S_OK;
}

HRESULT object_visualizer::GetItems(
    _In_ DkmVisualizedExpression* pVisualizedExpression,
    _In_ DkmEvaluationResultEnumContext* /*pEnumContext*/,
    _In_ UINT32 StartIndex,
    _In_ UINT32 Count,
    _Out_ DkmArray<DkmChildVisualizedExpression*>* pItems)
{
    CAutoDkmArray<DkmChildVisualizedExpression*> resultValues;
    IF_FAIL_RET(DkmAllocArray(std::min(m_propertyData.size(), size_t(Count)), &resultValues));

    auto pParent = pVisualizedExpression;
    auto childCount = std::min(m_propertyData.size() - StartIndex, (size_t)Count);
    for(auto i = 0; i < childCount; ++i)
    {
        auto& prop = m_propertyData[i + (size_t)StartIndex];
        com_ptr<DkmChildVisualizedExpression> pPropertyVisualized;
        if(FAILED(CreateChildVisualizedExpression(prop, pParent, m_objectType, pPropertyVisualized.put())))
        {
            com_ptr<DkmString> pErrorMessage;
            IF_FAIL_RET(DkmString::Create(L"<Property evaluation failed>", pErrorMessage.put()));

            com_ptr<DkmString> pDisplayName;
            IF_FAIL_RET(DkmString::Create(prop.displayName.c_str(), pDisplayName.put()));

            com_ptr<DkmFailedEvaluationResult> pVisualizedResult;
            IF_FAIL_RET(DkmFailedEvaluationResult::Create(
                pParent->InspectionContext(),
                pParent->StackFrame(),
                pDisplayName.get(),
                nullptr,
                pErrorMessage.get(),
                DkmEvaluationResultFlags::ExceptionThrown,
                DkmDataItem::Null(),
                pVisualizedResult.put()
            ));

            IF_FAIL_RET(DkmChildVisualizedExpression::Create(
                pParent->InspectionContext(),
                pParent->VisualizerId(),
                pParent->SourceId(),
                pParent->StackFrame(),
                nullptr,
                pVisualizedResult.get(),
                pParent,
                2,
                DkmDataItem::Null(),
                pPropertyVisualized.put()
            ));
        }
        resultValues.Members[i] = pPropertyVisualized.detach();
    }

    *pItems = resultValues.Detach();
    return S_OK;
}
