/*
 * Copyright 2022 Connor McAdams for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "uia_private.h"
#include "ocidl.h"

#include "wine/debug.h"
#include "initguid.h"
#include "wine/iaccessible2.h"

WINE_DEFAULT_DEBUG_CHANNEL(uiautomation);

DEFINE_GUID(SID_AccFromDAWrapper, 0x33f139ee, 0xe509, 0x47f7, 0xbf,0x39, 0x83,0x76,0x44,0xf7,0x45,0x76);

static BOOL msaa_check_acc_state(IAccessible *acc, VARIANT cid, ULONG flag)
{
    HRESULT hr;
    VARIANT v;

    VariantInit(&v);
    hr = IAccessible_get_accState(acc, cid, &v);
    if (SUCCEEDED(hr) && V_VT(&v) == VT_I4 && (V_I4(&v) & flag))
        return TRUE;

    return FALSE;
}

static HRESULT msaa_acc_get_service(IAccessible *acc, REFGUID sid, REFIID riid, void **service)
{
    IServiceProvider *sp;
    HRESULT hr;

    *service = NULL;
    hr = IAccessible_QueryInterface(acc, &IID_IServiceProvider, (void **)&sp);
    if (FAILED(hr))
        return hr;

    hr = IServiceProvider_QueryService(sp, sid, riid, (void **)service);
    IServiceProvider_Release(sp);
    return hr;
}

static IAccessible2 *msaa_acc_get_ia2(IAccessible *acc)
{
    IAccessible2 *ia2 = NULL;
    HRESULT hr;

    hr = msaa_acc_get_service(acc, &IID_IAccessible2, &IID_IAccessible2, (void **)&ia2);
    if (SUCCEEDED(hr) && ia2)
        return ia2;

    hr = IAccessible_QueryInterface(acc, &IID_IAccessible2, (void **)&ia2);
    if (SUCCEEDED(hr) && ia2)
        return ia2;

    return NULL;
}

static IAccessible *msaa_acc_da_unwrap(IAccessible *acc)
{
    IAccessible *acc2;
    HRESULT hr;

    hr = msaa_acc_get_service(acc, &SID_AccFromDAWrapper, &IID_IAccessible, (void **)&acc2);
    if (SUCCEEDED(hr) && acc2)
        return acc2;

    IAccessible_AddRef(acc);
    return acc;
}

static BOOL msaa_acc_is_oleacc_proxy(IAccessible *acc)
{
    IUnknown *unk;
    HRESULT hr;

    hr = msaa_acc_get_service(acc, &IIS_IsOleaccProxy, &IID_IUnknown, (void **)&unk);
    if (SUCCEEDED(hr) && unk)
    {
        IUnknown_Release(unk);
        return TRUE;
    }

    return FALSE;
}

/*
 * Compare role, state, child count, and location properties of the two
 * IAccessibles. If all four are successfully retrieved and are equal, this is
 * considered a match.
 */
static HRESULT msaa_acc_prop_match(IAccessible *acc, IAccessible *acc2)
{
    BOOL role_match, state_match, child_count_match, location_match;
    LONG child_count[2], left[2], top[2], width[2], height[2];
    VARIANT cid, v, v2;
    HRESULT hr, hr2;

    role_match = state_match = child_count_match = location_match = FALSE;
    variant_init_i4(&cid, CHILDID_SELF);
    hr = IAccessible_get_accRole(acc, cid, &v);
    if (SUCCEEDED(hr) && (V_VT(&v) == VT_I4))
    {
        VariantInit(&v2);
        hr = IAccessible_get_accRole(acc2, cid, &v2);
        if (SUCCEEDED(hr) && (V_VT(&v2) == VT_I4))
        {
            if (V_I4(&v) != V_I4(&v2))
                return E_FAIL;

            role_match = TRUE;
        }
    }

    VariantInit(&v);
    hr = IAccessible_get_accState(acc, cid, &v);
    if (SUCCEEDED(hr) && (V_VT(&v) == VT_I4))
    {
        VariantInit(&v2);
        hr = IAccessible_get_accState(acc2, cid, &v2);
        if (SUCCEEDED(hr) && (V_VT(&v2) == VT_I4))
        {
            if (V_I4(&v) != V_I4(&v2))
                return E_FAIL;

            state_match = TRUE;
        }
    }

    hr = IAccessible_get_accChildCount(acc, &child_count[0]);
    hr2 = IAccessible_get_accChildCount(acc2, &child_count[1]);
    if (SUCCEEDED(hr) && SUCCEEDED(hr2))
    {
        if (child_count[0] != child_count[1])
            return E_FAIL;

        child_count_match = TRUE;
    }

    hr = IAccessible_accLocation(acc, &left[0], &top[0], &width[0], &height[0], cid);
    if (SUCCEEDED(hr))
    {
        hr = IAccessible_accLocation(acc2, &left[1], &top[1], &width[1], &height[1], cid);
        if (SUCCEEDED(hr))
        {
            if ((left[0] != left[1]) || (top[0] != top[1]) || (width[0] != width[1]) ||
                    (height[0] != height[1]))
                return E_FAIL;

            location_match = TRUE;
        }
    }

    if (role_match && state_match && child_count_match && location_match)
        return S_OK;

    return S_FALSE;
}

static BOOL msaa_acc_compare(IAccessible *acc, IAccessible *acc2)
{
    IAccessible2 *ia2[2] = { NULL, NULL };
    IUnknown *unk, *unk2;
    BOOL matched = FALSE;
    LONG unique_id[2];
    BSTR name[2];
    VARIANT cid;
    HRESULT hr;

    acc = msaa_acc_da_unwrap(acc);
    acc2 = msaa_acc_da_unwrap(acc2);
    IAccessible_QueryInterface(acc, &IID_IUnknown, (void**)&unk);
    IAccessible_QueryInterface(acc2, &IID_IUnknown, (void**)&unk2);
    if (unk == unk2)
    {
        matched = TRUE;
        goto exit;
    }

    ia2[0] = msaa_acc_get_ia2(acc);
    ia2[1] = msaa_acc_get_ia2(acc2);
    if (!ia2[0] != !ia2[1])
        goto exit;
    if (ia2[0])
    {
        hr = IAccessible2_get_uniqueID(ia2[0], &unique_id[0]);
        if (SUCCEEDED(hr))
        {
            hr = IAccessible2_get_uniqueID(ia2[1], &unique_id[1]);
            if (SUCCEEDED(hr))
            {
                if (unique_id[0] == unique_id[1])
                    matched = TRUE;

                goto exit;
            }
        }
    }

    hr = msaa_acc_prop_match(acc, acc2);
    if (FAILED(hr))
        goto exit;
    if (hr == S_OK)
        matched = TRUE;

    variant_init_i4(&cid, CHILDID_SELF);
    hr = IAccessible_get_accName(acc, cid, &name[0]);
    if (SUCCEEDED(hr))
    {
        hr = IAccessible_get_accName(acc2, cid, &name[1]);
        if (SUCCEEDED(hr))
        {
            if (!name[0] && !name[1])
                matched = TRUE;
            else if (!name[0] || !name[1])
                matched = FALSE;
            else
            {
                if (!wcscmp(name[0], name[1]))
                    matched = TRUE;
                else
                    matched = FALSE;
            }

            SysFreeString(name[1]);
        }

        SysFreeString(name[0]);
    }

exit:
    IUnknown_Release(unk);
    IUnknown_Release(unk2);
    IAccessible_Release(acc);
    IAccessible_Release(acc2);
    if (ia2[0])
        IAccessible2_Release(ia2[0]);
    if (ia2[1])
        IAccessible2_Release(ia2[1]);

    return matched;
}

static HRESULT msaa_acc_get_parent(IAccessible *acc, IAccessible **parent)
{
    IDispatch *disp = NULL;
    HRESULT hr;

    *parent = NULL;
    hr = IAccessible_get_accParent(acc, &disp);
    if (FAILED(hr) || !disp)
        return hr;

    hr = IDispatch_QueryInterface(disp, &IID_IAccessible, (void**)parent);
    IDispatch_Release(disp);
    return hr;
}

#define DIR_FORWARD 0
#define DIR_REVERSE 1
static HRESULT msaa_acc_get_next_child(IAccessible *acc, LONG start_pos, LONG direction,
        IAccessible **child, LONG *child_id, LONG *child_pos, BOOL check_visible)
{
    LONG child_count, cur_pos;
    IDispatch *disp;
    VARIANT cid;
    HRESULT hr;

    *child = NULL;
    *child_id = 0;
    cur_pos = start_pos;
    while (1)
    {
        hr = IAccessible_get_accChildCount(acc, &child_count);
        if (FAILED(hr) || (cur_pos > child_count))
            break;

        variant_init_i4(&cid, cur_pos);
        hr = IAccessible_get_accChild(acc, cid, &disp);
        if (FAILED(hr))
            break;

        if (hr == S_FALSE)
        {
            if (!check_visible || !msaa_check_acc_state(acc, cid, STATE_SYSTEM_INVISIBLE))
            {
                *child = acc;
                *child_id = *child_pos = cur_pos;
                IAccessible_AddRef(acc);
                return S_OK;
            }
        }
        else
        {
            IAccessible *acc_child = NULL;

            hr = IDispatch_QueryInterface(disp, &IID_IAccessible, (void **)&acc_child);
            IDispatch_Release(disp);
            if (FAILED(hr))
                break;

            variant_init_i4(&cid, CHILDID_SELF);
            if (!check_visible || !msaa_check_acc_state(acc_child, cid, STATE_SYSTEM_INVISIBLE))
            {
                *child = acc_child;
                *child_id = CHILDID_SELF;
                *child_pos = cur_pos;
                return S_OK;
            }

            IAccessible_Release(acc_child);
        }

        if (direction == DIR_FORWARD)
            cur_pos++;
        else
            cur_pos--;

        if ((cur_pos > child_count) || (cur_pos <= 0))
            break;
    }

    return hr;
}

static HRESULT msaa_acc_get_child_pos(IAccessible *acc, IAccessible **out_parent,
        LONG *out_child_pos)
{
    LONG child_count, child_id, child_pos, match_pos;
    IAccessible *child, *parent, *match, **children;
    HRESULT hr;
    int i;

    *out_parent = NULL;
    *out_child_pos = 0;
    hr = msaa_acc_get_parent(acc, &parent);
    if (FAILED(hr) || !parent)
        return hr;

    hr = IAccessible_get_accChildCount(parent, &child_count);
    if (FAILED(hr) || !child_count)
    {
        IAccessible_Release(parent);
        return hr;
    }

    children = calloc(child_count, sizeof(*children));
    if (!children)
        return E_OUTOFMEMORY;

    match = NULL;
    for (i = 0; i < child_count; i++)
    {
        hr = msaa_acc_get_next_child(parent, i + 1, DIR_FORWARD, &child, &child_id, &child_pos, FALSE);
        if (FAILED(hr) || !child)
            goto exit;

        if (child != parent)
            children[i] = child;
        else
            IAccessible_Release(child);
    }

    for (i = 0; i < child_count; i++)
    {
        if (!children[i])
            continue;

        if (msaa_acc_compare(acc, children[i]))
        {
            if (!match)
            {
                match = children[i];
                match_pos = i + 1;
            }
            /* Can't have more than one IAccessible match. */
            else
            {
                match = NULL;
                match_pos = 0;
                break;
            }
        }
    }

exit:
    if (match)
    {
        *out_parent = parent;
        *out_child_pos = match_pos;
    }
    else
        IAccessible_Release(parent);

    for (i = 0; i < child_count; i++)
    {
        if (children[i])
            IAccessible_Release(children[i]);
    }

    free(children);

    return hr;
}

static LONG msaa_role_to_uia_control_type(LONG role)
{
    switch (role)
    {
    case ROLE_SYSTEM_TITLEBAR:           return UIA_TitleBarControlTypeId;
    case ROLE_SYSTEM_MENUBAR:            return UIA_MenuBarControlTypeId;
    case ROLE_SYSTEM_SCROLLBAR:          return UIA_ScrollBarControlTypeId;
    case ROLE_SYSTEM_INDICATOR:
    case ROLE_SYSTEM_GRIP:               return UIA_ThumbControlTypeId;
    case ROLE_SYSTEM_APPLICATION:
    case ROLE_SYSTEM_WINDOW:             return UIA_WindowControlTypeId;
    case ROLE_SYSTEM_MENUPOPUP:          return UIA_MenuControlTypeId;
    case ROLE_SYSTEM_TOOLTIP:            return UIA_ToolTipControlTypeId;
    case ROLE_SYSTEM_DOCUMENT:           return UIA_DocumentControlTypeId;
    case ROLE_SYSTEM_PANE:               return UIA_PaneControlTypeId;
    case ROLE_SYSTEM_GROUPING:           return UIA_GroupControlTypeId;
    case ROLE_SYSTEM_SEPARATOR:          return UIA_SeparatorControlTypeId;
    case ROLE_SYSTEM_TOOLBAR:            return UIA_ToolBarControlTypeId;
    case ROLE_SYSTEM_STATUSBAR:          return UIA_StatusBarControlTypeId;
    case ROLE_SYSTEM_TABLE:              return UIA_TableControlTypeId;
    case ROLE_SYSTEM_COLUMNHEADER:
    case ROLE_SYSTEM_ROWHEADER:          return UIA_HeaderControlTypeId;
    case ROLE_SYSTEM_CELL:               return UIA_DataItemControlTypeId;
    case ROLE_SYSTEM_LINK:               return UIA_HyperlinkControlTypeId;
    case ROLE_SYSTEM_LIST:               return UIA_ListControlTypeId;
    case ROLE_SYSTEM_LISTITEM:           return UIA_ListItemControlTypeId;
    case ROLE_SYSTEM_OUTLINE:            return UIA_TreeControlTypeId;
    case ROLE_SYSTEM_OUTLINEITEM:        return UIA_TreeItemControlTypeId;
    case ROLE_SYSTEM_PAGETAB:            return UIA_TabItemControlTypeId;
    case ROLE_SYSTEM_GRAPHIC:            return UIA_ImageControlTypeId;
    case ROLE_SYSTEM_STATICTEXT:         return UIA_TextControlTypeId;
    case ROLE_SYSTEM_TEXT:               return UIA_EditControlTypeId;
    case ROLE_SYSTEM_CLOCK:
    case ROLE_SYSTEM_BUTTONDROPDOWNGRID:
    case ROLE_SYSTEM_PUSHBUTTON:         return UIA_ButtonControlTypeId;
    case ROLE_SYSTEM_CHECKBUTTON:        return UIA_CheckBoxControlTypeId;
    case ROLE_SYSTEM_RADIOBUTTON:        return UIA_RadioButtonControlTypeId;
    case ROLE_SYSTEM_COMBOBOX:           return UIA_ComboBoxControlTypeId;
    case ROLE_SYSTEM_PROGRESSBAR:        return UIA_ProgressBarControlTypeId;
    case ROLE_SYSTEM_SLIDER:             return UIA_SliderControlTypeId;
    case ROLE_SYSTEM_SPINBUTTON:         return UIA_SpinnerControlTypeId;
    case ROLE_SYSTEM_BUTTONMENU:
    case ROLE_SYSTEM_MENUITEM:           return UIA_MenuItemControlTypeId;
    case ROLE_SYSTEM_PAGETABLIST:        return UIA_TabControlTypeId;
    case ROLE_SYSTEM_BUTTONDROPDOWN:
    case ROLE_SYSTEM_SPLITBUTTON:        return UIA_SplitButtonControlTypeId;
    case ROLE_SYSTEM_SOUND:
    case ROLE_SYSTEM_CURSOR:
    case ROLE_SYSTEM_CARET:
    case ROLE_SYSTEM_ALERT:
    case ROLE_SYSTEM_CLIENT:
    case ROLE_SYSTEM_CHART:
    case ROLE_SYSTEM_DIALOG:
    case ROLE_SYSTEM_BORDER:
    case ROLE_SYSTEM_COLUMN:
    case ROLE_SYSTEM_ROW:
    case ROLE_SYSTEM_HELPBALLOON:
    case ROLE_SYSTEM_CHARACTER:
    case ROLE_SYSTEM_PROPERTYPAGE:
    case ROLE_SYSTEM_DROPLIST:
    case ROLE_SYSTEM_DIAL:
    case ROLE_SYSTEM_HOTKEYFIELD:
    case ROLE_SYSTEM_DIAGRAM:
    case ROLE_SYSTEM_ANIMATION:
    case ROLE_SYSTEM_EQUATION:
    case ROLE_SYSTEM_WHITESPACE:
    case ROLE_SYSTEM_IPADDRESS:
    case ROLE_SYSTEM_OUTLINEBUTTON:
        WARN("No UIA control type mapping for MSAA role %ld\n", role);
        break;

    default:
        FIXME("UIA control type mapping unimplemented for MSAA role %ld\n", role);
        break;
    }

    return 0;
}

/*
 * UiaProviderFromIAccessible IRawElementProviderSimple interface.
 */
struct msaa_provider {
    IRawElementProviderSimple IRawElementProviderSimple_iface;
    IRawElementProviderFragment IRawElementProviderFragment_iface;
    IRawElementProviderFragmentRoot IRawElementProviderFragmentRoot_iface;
    ILegacyIAccessibleProvider ILegacyIAccessibleProvider_iface;
    LONG refcount;

    IAccessible *acc;
    IAccessible2 *ia2;
    VARIANT cid;
    HWND hwnd;
    LONG control_type;

    BOOL root_acc_check_ran;
    BOOL is_root_acc;

    IAccessible *parent;
    INT child_pos;
};

static BOOL msaa_check_root_acc(struct msaa_provider *msaa_prov)
{
    IAccessible *acc;
    HRESULT hr;

    if (msaa_prov->root_acc_check_ran)
        return msaa_prov->is_root_acc;

    msaa_prov->root_acc_check_ran = TRUE;
    if (V_I4(&msaa_prov->cid) != CHILDID_SELF || msaa_prov->parent)
        return FALSE;

    hr = AccessibleObjectFromWindow(msaa_prov->hwnd, OBJID_CLIENT, &IID_IAccessible, (void **)&acc);
    if (FAILED(hr))
        return FALSE;

    if (msaa_acc_compare(msaa_prov->acc, acc))
        msaa_prov->is_root_acc = TRUE;

    IAccessible_Release(acc);
    return msaa_prov->is_root_acc;
}

static inline struct msaa_provider *impl_from_msaa_provider(IRawElementProviderSimple *iface)
{
    return CONTAINING_RECORD(iface, struct msaa_provider, IRawElementProviderSimple_iface);
}

HRESULT WINAPI msaa_provider_QueryInterface(IRawElementProviderSimple *iface, REFIID riid, void **ppv)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_provider(iface);

    *ppv = NULL;
    if (IsEqualIID(riid, &IID_IRawElementProviderSimple) || IsEqualIID(riid, &IID_IUnknown))
        *ppv = iface;
    else if (IsEqualIID(riid, &IID_IRawElementProviderFragment))
        *ppv = &msaa_prov->IRawElementProviderFragment_iface;
    else if (IsEqualIID(riid, &IID_IRawElementProviderFragmentRoot))
        *ppv = &msaa_prov->IRawElementProviderFragmentRoot_iface;
    else if (IsEqualIID(riid, &IID_ILegacyIAccessibleProvider))
        *ppv = &msaa_prov->ILegacyIAccessibleProvider_iface;
    else
        return E_NOINTERFACE;

    IRawElementProviderSimple_AddRef(iface);
    return S_OK;
}

ULONG WINAPI msaa_provider_AddRef(IRawElementProviderSimple *iface)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_provider(iface);
    ULONG refcount = InterlockedIncrement(&msaa_prov->refcount);

    TRACE("%p, refcount %ld\n", iface, refcount);

    return refcount;
}

ULONG WINAPI msaa_provider_Release(IRawElementProviderSimple *iface)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_provider(iface);
    ULONG refcount = InterlockedDecrement(&msaa_prov->refcount);

    TRACE("%p, refcount %ld\n", iface, refcount);

    if (!refcount)
    {
        IAccessible_Release(msaa_prov->acc);
        if (msaa_prov->parent)
            IAccessible_Release(msaa_prov->parent);
        if (msaa_prov->ia2)
            IAccessible2_Release(msaa_prov->ia2);
        free(msaa_prov);
    }

    return refcount;
}

HRESULT WINAPI msaa_provider_get_ProviderOptions(IRawElementProviderSimple *iface,
        enum ProviderOptions *ret_val)
{
    TRACE("%p, %p\n", iface, ret_val);
    *ret_val = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading;
    return S_OK;
}

HRESULT WINAPI msaa_provider_GetPatternProvider(IRawElementProviderSimple *iface,
        PATTERNID pattern_id, IUnknown **ret_val)
{
    TRACE("%p, %d, %p\n", iface, pattern_id, ret_val);

    *ret_val = NULL;
    switch (pattern_id)
    {
    case UIA_LegacyIAccessiblePatternId:
        return IRawElementProviderSimple_QueryInterface(iface, &IID_IUnknown, (void **)ret_val);

    default:
        FIXME("Unimplemented patternId %d\n", pattern_id);
        break;
    }

    return S_OK;
}

HRESULT WINAPI msaa_provider_GetPropertyValue(IRawElementProviderSimple *iface,
        PROPERTYID prop_id, VARIANT *ret_val)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_provider(iface);
    HRESULT hr;
    VARIANT v;

    TRACE("%p, %d, %p\n", iface, prop_id, ret_val);

    VariantInit(ret_val);
    VariantInit(&v);
    switch (prop_id)
    {
    case UIA_ProviderDescriptionPropertyId:
        V_VT(ret_val) = VT_BSTR;
        V_BSTR(ret_val) = SysAllocString(L"Wine: MSAA Proxy");
        break;

    case UIA_ControlTypePropertyId:
        if (!msaa_prov->control_type)
        {
            hr = IAccessible_get_accRole(msaa_prov->acc, msaa_prov->cid, &v);
            if (SUCCEEDED(hr) && (V_VT(&v) == VT_I4))
                msaa_prov->control_type = msaa_role_to_uia_control_type(V_I4(&v));
        }

        if (msaa_prov->control_type)
            variant_init_i4(ret_val, msaa_prov->control_type);

        break;

    case UIA_HasKeyboardFocusPropertyId:
        variant_init_bool(ret_val, msaa_check_acc_state(msaa_prov->acc, msaa_prov->cid,
                    STATE_SYSTEM_FOCUSED));
        break;

    case UIA_IsKeyboardFocusablePropertyId:
        variant_init_bool(ret_val, msaa_check_acc_state(msaa_prov->acc, msaa_prov->cid,
                    STATE_SYSTEM_FOCUSABLE));
        break;

    case UIA_IsEnabledPropertyId:
        variant_init_bool(ret_val, !msaa_check_acc_state(msaa_prov->acc, msaa_prov->cid,
                    STATE_SYSTEM_UNAVAILABLE));
        break;

    case UIA_IsPasswordPropertyId:
        variant_init_bool(ret_val, msaa_check_acc_state(msaa_prov->acc, msaa_prov->cid,
                    STATE_SYSTEM_PROTECTED));
        break;

    case UIA_NamePropertyId:
    {
        BSTR name;

        hr = IAccessible_get_accName(msaa_prov->acc, msaa_prov->cid, &name);
        if (SUCCEEDED(hr) && name)
        {
            V_VT(ret_val) = VT_BSTR;
            V_BSTR(ret_val) = name;
        }
        break;
    }

    case UIA_IsOffscreenPropertyId:
    {
        RECT rect[2] = { 0 };
        RECT intersect_rect;
        LONG width, height;

        variant_init_bool(ret_val, FALSE);
        if (msaa_check_acc_state(msaa_prov->acc, msaa_prov->cid, STATE_SYSTEM_OFFSCREEN))
        {
            variant_init_bool(ret_val, TRUE);
            break;
        }

        hr = IAccessible_accLocation(msaa_prov->acc, &rect[0].left, &rect[0].top, &width, &height, msaa_prov->cid);
        if (FAILED(hr))
            break;

        rect[0].right = rect[0].left + width;
        rect[0].bottom = rect[0].top + height;
        SetLastError(NOERROR);
        if (!GetClientRect(msaa_prov->hwnd, &rect[1]))
        {
            if (GetLastError() == ERROR_INVALID_WINDOW_HANDLE)
                variant_init_bool(ret_val, TRUE);
            break;
        }

        SetLastError(NOERROR);
        if (!MapWindowPoints(msaa_prov->hwnd, NULL, (POINT *)&rect[1], 2) && GetLastError())
        {
            if (GetLastError() == ERROR_INVALID_WINDOW_HANDLE)
                variant_init_bool(ret_val, TRUE);
            break;
        }

        variant_init_bool(ret_val, !IntersectRect(&intersect_rect, &rect[0], &rect[1]));
        break;
    }

    default:
        FIXME("Unimplemented propertyId %d\n", prop_id);
        break;
    }

    return S_OK;
}

HRESULT WINAPI msaa_provider_get_HostRawElementProvider(IRawElementProviderSimple *iface,
        IRawElementProviderSimple **ret_val)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_provider(iface);

    TRACE("%p, %p\n", iface, ret_val);

    *ret_val = NULL;
    if (msaa_check_root_acc(msaa_prov))
        return UiaHostProviderFromHwnd(msaa_prov->hwnd, ret_val);

    return S_OK;
}

static const IRawElementProviderSimpleVtbl msaa_provider_vtbl = {
    msaa_provider_QueryInterface,
    msaa_provider_AddRef,
    msaa_provider_Release,
    msaa_provider_get_ProviderOptions,
    msaa_provider_GetPatternProvider,
    msaa_provider_GetPropertyValue,
    msaa_provider_get_HostRawElementProvider,
};

/*
 * IRawElementProviderFragment interface for UiaProviderFromIAccessible
 * providers.
 */
static inline struct msaa_provider *impl_from_msaa_fragment(IRawElementProviderFragment *iface)
{
    return CONTAINING_RECORD(iface, struct msaa_provider, IRawElementProviderFragment_iface);
}

static HRESULT WINAPI msaa_fragment_QueryInterface(IRawElementProviderFragment *iface, REFIID riid,
        void **ppv)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_fragment(iface);
    return IRawElementProviderSimple_QueryInterface(&msaa_prov->IRawElementProviderSimple_iface, riid, ppv);
}

static ULONG WINAPI msaa_fragment_AddRef(IRawElementProviderFragment *iface)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_fragment(iface);
    return IRawElementProviderSimple_AddRef(&msaa_prov->IRawElementProviderSimple_iface);
}

static ULONG WINAPI msaa_fragment_Release(IRawElementProviderFragment *iface)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_fragment(iface);
    return IRawElementProviderSimple_Release(&msaa_prov->IRawElementProviderSimple_iface);
}

static HRESULT WINAPI msaa_fragment_Navigate(IRawElementProviderFragment *iface,
        enum NavigateDirection direction, IRawElementProviderFragment **ret_val)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_fragment(iface);
    LONG child_count, child_id, child_pos;
    IRawElementProviderSimple *elprov;
    IAccessible *acc;
    HRESULT hr;

    TRACE("%p, %d, %p\n", iface, direction, ret_val);

    *ret_val = NULL;
    switch (direction)
    {
    case NavigateDirection_Parent:
        if (msaa_check_root_acc(msaa_prov))
            break;

        if (V_I4(&msaa_prov->cid) == CHILDID_SELF)
        {
            hr = msaa_acc_get_parent(msaa_prov->acc, &acc);
            if (FAILED(hr) || !acc)
                break;
        }
        else
            acc = msaa_prov->acc;

        hr = create_msaa_provider(acc, CHILDID_SELF, NULL, FALSE, &elprov);
        if (SUCCEEDED(hr))
        {
            struct msaa_provider *prov = impl_from_msaa_provider(elprov);
            *ret_val = &prov->IRawElementProviderFragment_iface;
        }

        if (acc != msaa_prov->acc)
            IAccessible_Release(acc);

        break;

    case NavigateDirection_FirstChild:
    case NavigateDirection_LastChild:
        if (V_I4(&msaa_prov->cid) != CHILDID_SELF)
            break;

        hr = IAccessible_get_accChildCount(msaa_prov->acc, &child_count);
        if (FAILED(hr) || !child_count)
            break;

        if (direction == NavigateDirection_FirstChild)
            hr = msaa_acc_get_next_child(msaa_prov->acc, 1, DIR_FORWARD, &acc, &child_id,
                    &child_pos, TRUE);
        else
            hr = msaa_acc_get_next_child(msaa_prov->acc, child_count, DIR_REVERSE, &acc, &child_id,
                    &child_pos, TRUE);

        if (FAILED(hr) || !acc)
            break;

        hr = create_msaa_provider(acc, child_id, NULL, FALSE, &elprov);
        if (SUCCEEDED(hr))
        {
            struct msaa_provider *prov = impl_from_msaa_provider(elprov);

            *ret_val = &prov->IRawElementProviderFragment_iface;
            prov->parent = msaa_prov->acc;
            IAccessible_AddRef(msaa_prov->acc);
            if (acc != msaa_prov->acc)
                prov->child_pos = child_pos;
            else
                prov->child_pos = child_id;
        }
        IAccessible_Release(acc);

        break;

    case NavigateDirection_NextSibling:
    case NavigateDirection_PreviousSibling:
        if (msaa_check_root_acc(msaa_prov))
            break;

        if (!msaa_prov->parent)
        {
            if (V_I4(&msaa_prov->cid) != CHILDID_SELF)
            {
                msaa_prov->parent = msaa_prov->acc;
                IAccessible_AddRef(msaa_prov->acc);
                msaa_prov->child_pos = V_I4(&msaa_prov->cid);
            }
            else
            {
                hr = msaa_acc_get_child_pos(msaa_prov->acc, &acc, &child_pos);
                if (FAILED(hr) || !acc)
                    break;
                msaa_prov->parent = acc;
                msaa_prov->child_pos = child_pos;
            }
        }

        if (direction == NavigateDirection_NextSibling)
            hr = msaa_acc_get_next_child(msaa_prov->parent, msaa_prov->child_pos + 1, DIR_FORWARD,
                    &acc, &child_id, &child_pos, TRUE);
        else
            hr = msaa_acc_get_next_child(msaa_prov->parent, msaa_prov->child_pos - 1, DIR_REVERSE,
                    &acc, &child_id, &child_pos, TRUE);

        if (FAILED(hr) || !acc)
            break;

        hr = create_msaa_provider(acc, child_id, NULL, FALSE, &elprov);
        if (SUCCEEDED(hr))
        {
            struct msaa_provider *prov = impl_from_msaa_provider(elprov);

            *ret_val = &prov->IRawElementProviderFragment_iface;
            prov->parent = msaa_prov->parent;
            IAccessible_AddRef(msaa_prov->parent);
            if (acc != msaa_prov->acc)
                prov->child_pos = child_pos;
            else
                prov->child_pos = child_id;
        }
        IAccessible_Release(acc);

        break;

    default:
        FIXME("Invalid NavigateDirection %d\n", direction);
        return E_INVALIDARG;
    }

    return S_OK;
}

static HRESULT WINAPI msaa_fragment_GetRuntimeId(IRawElementProviderFragment *iface,
        SAFEARRAY **ret_val)
{
    FIXME("%p, %p: stub!\n", iface, ret_val);
    *ret_val = NULL;
    return E_NOTIMPL;
}

static HRESULT WINAPI msaa_fragment_get_BoundingRectangle(IRawElementProviderFragment *iface,
        struct UiaRect *ret_val)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_fragment(iface);
    LONG left, top, width, height;
    HRESULT hr;

    TRACE("%p, %p\n", iface, ret_val);

    memset(ret_val, 0, sizeof(*ret_val));

    /*
     * If this IAccessible is at the root of its HWND, the BaseHwnd provider
     * will supply the bounding rectangle.
     */
    if (msaa_check_root_acc(msaa_prov))
        return S_OK;

    if (msaa_check_acc_state(msaa_prov->acc, msaa_prov->cid, STATE_SYSTEM_OFFSCREEN))
        return S_OK;

    hr = IAccessible_accLocation(msaa_prov->acc, &left, &top, &width, &height, msaa_prov->cid);
    if (FAILED(hr))
        return hr;

    ret_val->left = left;
    ret_val->top = top;
    ret_val->width = width;
    ret_val->height = height;

    return S_OK;
}

static HRESULT WINAPI msaa_fragment_GetEmbeddedFragmentRoots(IRawElementProviderFragment *iface,
        SAFEARRAY **ret_val)
{
    FIXME("%p, %p: stub!\n", iface, ret_val);
    *ret_val = NULL;
    return E_NOTIMPL;
}

static HRESULT WINAPI msaa_fragment_SetFocus(IRawElementProviderFragment *iface)
{
    FIXME("%p: stub!\n", iface);
    return E_NOTIMPL;
}

static HRESULT WINAPI msaa_fragment_get_FragmentRoot(IRawElementProviderFragment *iface,
        IRawElementProviderFragmentRoot **ret_val)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_fragment(iface);
    IRawElementProviderSimple *elprov;
    IAccessible *acc;
    HRESULT hr;

    TRACE("%p, %p\n", iface, ret_val);

    *ret_val = NULL;
    hr = AccessibleObjectFromWindow(msaa_prov->hwnd, OBJID_CLIENT, &IID_IAccessible, (void **)&acc);
    if (FAILED(hr) || !acc)
        return hr;

    hr = create_msaa_provider(acc, CHILDID_SELF, msaa_prov->hwnd, TRUE, &elprov);
    IAccessible_Release(acc);
    if (FAILED(hr))
        return hr;

    hr = IRawElementProviderSimple_QueryInterface(elprov, &IID_IRawElementProviderFragmentRoot, (void **)ret_val);
    IRawElementProviderSimple_Release(elprov);

    return hr;
}

static const IRawElementProviderFragmentVtbl msaa_fragment_vtbl = {
    msaa_fragment_QueryInterface,
    msaa_fragment_AddRef,
    msaa_fragment_Release,
    msaa_fragment_Navigate,
    msaa_fragment_GetRuntimeId,
    msaa_fragment_get_BoundingRectangle,
    msaa_fragment_GetEmbeddedFragmentRoots,
    msaa_fragment_SetFocus,
    msaa_fragment_get_FragmentRoot,
};

/*
 * IRawElementProviderFragmentRoot interface for UiaProviderFromIAccessible
 * providers.
 */
static inline struct msaa_provider *impl_from_msaa_fragment_root(IRawElementProviderFragmentRoot *iface)
{
    return CONTAINING_RECORD(iface, struct msaa_provider, IRawElementProviderFragmentRoot_iface);
}

static HRESULT WINAPI msaa_fragment_root_QueryInterface(IRawElementProviderFragmentRoot *iface, REFIID riid,
        void **ppv)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_fragment_root(iface);
    return IRawElementProviderSimple_QueryInterface(&msaa_prov->IRawElementProviderSimple_iface, riid, ppv);
}

static ULONG WINAPI msaa_fragment_root_AddRef(IRawElementProviderFragmentRoot *iface)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_fragment_root(iface);
    return IRawElementProviderSimple_AddRef(&msaa_prov->IRawElementProviderSimple_iface);
}

static ULONG WINAPI msaa_fragment_root_Release(IRawElementProviderFragmentRoot *iface)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_fragment_root(iface);
    return IRawElementProviderSimple_Release(&msaa_prov->IRawElementProviderSimple_iface);
}

static HRESULT WINAPI msaa_fragment_root_ElementProviderFromPoint(IRawElementProviderFragmentRoot *iface,
        double x, double y, IRawElementProviderFragment **ret_val)
{
    FIXME("%p, %f, %f, %p: stub!\n", iface, x, y, ret_val);
    *ret_val = NULL;
    return E_NOTIMPL;
}

static HRESULT WINAPI msaa_fragment_root_GetFocus(IRawElementProviderFragmentRoot *iface,
        IRawElementProviderFragment **ret_val)
{
    FIXME("%p, %p: stub!\n", iface, ret_val);
    *ret_val = NULL;
    return E_NOTIMPL;
}

static const IRawElementProviderFragmentRootVtbl msaa_fragment_root_vtbl = {
    msaa_fragment_root_QueryInterface,
    msaa_fragment_root_AddRef,
    msaa_fragment_root_Release,
    msaa_fragment_root_ElementProviderFromPoint,
    msaa_fragment_root_GetFocus,
};

/*
 * ILegacyIAccessibleProvider interface for UiaProviderFromIAccessible
 * providers.
 */
static inline struct msaa_provider *impl_from_msaa_acc_provider(ILegacyIAccessibleProvider *iface)
{
    return CONTAINING_RECORD(iface, struct msaa_provider, ILegacyIAccessibleProvider_iface);
}

static HRESULT WINAPI msaa_acc_provider_QueryInterface(ILegacyIAccessibleProvider *iface, REFIID riid, void **ppv)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_acc_provider(iface);
    return IRawElementProviderSimple_QueryInterface(&msaa_prov->IRawElementProviderSimple_iface, riid, ppv);
}

static ULONG WINAPI msaa_acc_provider_AddRef(ILegacyIAccessibleProvider *iface)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_acc_provider(iface);
    return IRawElementProviderSimple_AddRef(&msaa_prov->IRawElementProviderSimple_iface);
}

static ULONG WINAPI msaa_acc_provider_Release(ILegacyIAccessibleProvider *iface)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_acc_provider(iface);
    return IRawElementProviderSimple_Release(&msaa_prov->IRawElementProviderSimple_iface);
}

static HRESULT WINAPI msaa_acc_provider_Select(ILegacyIAccessibleProvider *iface, LONG select_flags)
{
    FIXME("%p, %#lx: stub!\n", iface, select_flags);
    return E_NOTIMPL;
}

static HRESULT WINAPI msaa_acc_provider_DoDefaultAction(ILegacyIAccessibleProvider *iface)
{
    FIXME("%p: stub!\n", iface);
    return E_NOTIMPL;
}

static HRESULT WINAPI msaa_acc_provider_SetValue(ILegacyIAccessibleProvider *iface, LPCWSTR val)
{
    FIXME("%p, %p<%s>: stub!\n", iface, val, debugstr_w(val));
    return E_NOTIMPL;
}

static HRESULT WINAPI msaa_acc_provider_GetIAccessible(ILegacyIAccessibleProvider *iface,
        IAccessible **out_acc)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_acc_provider(iface);

    TRACE("%p, %p\n", iface, out_acc);

    *out_acc = NULL;
    if (msaa_acc_is_oleacc_proxy(msaa_prov->acc))
        return S_OK;

    return IAccessible_QueryInterface(msaa_prov->acc, &IID_IAccessible, (void **)out_acc);
}

static HRESULT WINAPI msaa_acc_provider_get_ChildId(ILegacyIAccessibleProvider *iface, int *out_cid)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_acc_provider(iface);

    TRACE("%p, %p\n", iface, out_cid);
    *out_cid = V_I4(&msaa_prov->cid);

    return S_OK;
}

static HRESULT WINAPI msaa_acc_provider_get_Name(ILegacyIAccessibleProvider *iface, BSTR *out_name)
{
    FIXME("%p, %p: stub!\n", iface, out_name);
    return E_NOTIMPL;
}

static HRESULT WINAPI msaa_acc_provider_get_Value(ILegacyIAccessibleProvider *iface, BSTR *out_value)
{
    FIXME("%p, %p: stub!\n", iface, out_value);
    return E_NOTIMPL;
}

static HRESULT WINAPI msaa_acc_provider_get_Description(ILegacyIAccessibleProvider *iface,
        BSTR *out_description)
{
    FIXME("%p, %p: stub!\n", iface, out_description);
    return E_NOTIMPL;
}

static HRESULT WINAPI msaa_acc_provider_get_Role(ILegacyIAccessibleProvider *iface, DWORD *out_role)
{
    struct msaa_provider *msaa_prov = impl_from_msaa_acc_provider(iface);
    HRESULT hr;
    VARIANT v;

    TRACE("%p, %p\n", iface, out_role);

    *out_role = 0;
    VariantInit(&v);
    hr = IAccessible_get_accRole(msaa_prov->acc, msaa_prov->cid, &v);
    if (SUCCEEDED(hr) && V_VT(&v) == VT_I4)
        *out_role = V_I4(&v);

    return S_OK;
}

static HRESULT WINAPI msaa_acc_provider_get_State(ILegacyIAccessibleProvider *iface, DWORD *out_state)
{
    FIXME("%p, %p: stub!\n", iface, out_state);
    return E_NOTIMPL;
}

static HRESULT WINAPI msaa_acc_provider_get_Help(ILegacyIAccessibleProvider *iface, BSTR *out_help)
{
    FIXME("%p, %p: stub!\n", iface, out_help);
    return E_NOTIMPL;
}

static HRESULT WINAPI msaa_acc_provider_get_KeyboardShortcut(ILegacyIAccessibleProvider *iface,
        BSTR *out_kbd_shortcut)
{
    FIXME("%p, %p: stub!\n", iface, out_kbd_shortcut);
    return E_NOTIMPL;
}

static HRESULT WINAPI msaa_acc_provider_GetSelection(ILegacyIAccessibleProvider *iface,
        SAFEARRAY **out_selected)
{
    FIXME("%p, %p: stub!\n", iface, out_selected);
    return E_NOTIMPL;
}

static HRESULT WINAPI msaa_acc_provider_get_DefaultAction(ILegacyIAccessibleProvider *iface,
        BSTR *out_default_action)
{
    FIXME("%p, %p: stub!\n", iface, out_default_action);
    return E_NOTIMPL;
}

static const ILegacyIAccessibleProviderVtbl msaa_acc_provider_vtbl = {
    msaa_acc_provider_QueryInterface,
    msaa_acc_provider_AddRef,
    msaa_acc_provider_Release,
    msaa_acc_provider_Select,
    msaa_acc_provider_DoDefaultAction,
    msaa_acc_provider_SetValue,
    msaa_acc_provider_GetIAccessible,
    msaa_acc_provider_get_ChildId,
    msaa_acc_provider_get_Name,
    msaa_acc_provider_get_Value,
    msaa_acc_provider_get_Description,
    msaa_acc_provider_get_Role,
    msaa_acc_provider_get_State,
    msaa_acc_provider_get_Help,
    msaa_acc_provider_get_KeyboardShortcut,
    msaa_acc_provider_GetSelection,
    msaa_acc_provider_get_DefaultAction,
};

HRESULT create_msaa_provider(IAccessible *acc, LONG child_id, HWND hwnd, BOOL known_root_acc,
        IRawElementProviderSimple **elprov)
{
    struct msaa_provider *msaa_prov = calloc(1, sizeof(*msaa_prov));

    if (!msaa_prov)
        return E_OUTOFMEMORY;

    msaa_prov->IRawElementProviderSimple_iface.lpVtbl = &msaa_provider_vtbl;
    msaa_prov->IRawElementProviderFragment_iface.lpVtbl = &msaa_fragment_vtbl;
    msaa_prov->IRawElementProviderFragmentRoot_iface.lpVtbl = &msaa_fragment_root_vtbl;
    msaa_prov->ILegacyIAccessibleProvider_iface.lpVtbl = &msaa_acc_provider_vtbl;
    msaa_prov->refcount = 1;
    variant_init_i4(&msaa_prov->cid, child_id);
    msaa_prov->acc = acc;
    IAccessible_AddRef(acc);
    msaa_prov->ia2 = msaa_acc_get_ia2(acc);

    if (!hwnd)
    {
        HRESULT hr;

        hr = WindowFromAccessibleObject(acc, &msaa_prov->hwnd);
        if (FAILED(hr))
            WARN("WindowFromAccessibleObject failed with hr %#lx\n", hr);
    }
    else
        msaa_prov->hwnd = hwnd;

    if (known_root_acc)
        msaa_prov->root_acc_check_ran = msaa_prov->is_root_acc = TRUE;

    *elprov = &msaa_prov->IRawElementProviderSimple_iface;

    return S_OK;
}

/***********************************************************************
 *          UiaProviderFromIAccessible (uiautomationcore.@)
 */
HRESULT WINAPI UiaProviderFromIAccessible(IAccessible *acc, LONG child_id, DWORD flags,
        IRawElementProviderSimple **elprov)
{
    HWND hwnd = NULL;
    HRESULT hr;

    TRACE("(%p, %ld, %#lx, %p)\n", acc, child_id, flags, elprov);

    if (elprov)
        *elprov = NULL;

    if (!elprov)
        return E_POINTER;
    if (!acc)
        return E_INVALIDARG;

    if (flags != UIA_PFIA_DEFAULT)
    {
        FIXME("unsupported flags %#lx\n", flags);
        return E_NOTIMPL;
    }

    if (msaa_acc_is_oleacc_proxy(acc))
    {
        WARN("Cannot wrap an oleacc proxy IAccessible!\n");
        return E_INVALIDARG;
    }

    hr = WindowFromAccessibleObject(acc, &hwnd);
    if (FAILED(hr))
       return hr;
    if (!hwnd)
        return E_FAIL;

    return create_msaa_provider(acc, child_id, hwnd, FALSE, elprov);
}

static HRESULT uia_get_hr_for_last_error(void)
{
    DWORD last_err = GetLastError();

    switch (last_err)
    {
    case ERROR_INVALID_WINDOW_HANDLE:
        return UIA_E_ELEMENTNOTAVAILABLE;

    case ERROR_TIMEOUT:
        return UIA_E_TIMEOUT;

    default:
        return E_FAIL;
    }
}

#define UIA_DEFAULT_MSG_TIMEOUT 10000
static HRESULT uia_send_message_timeout(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, UINT timeout, LRESULT *lres)
{
    *lres = 0;
    if (!SendMessageTimeoutW(hwnd, msg, wparam, lparam, SMTO_NORMAL, timeout, (PDWORD_PTR)lres))
        return uia_get_hr_for_last_error();

    return S_OK;
}

static BOOL is_top_level_hwnd(HWND hwnd)
{
    return GetAncestor(hwnd, GA_PARENT) == GetDesktopWindow();
}

static HRESULT get_uia_control_type_for_hwnd(HWND hwnd, int *control_type)
{
    LONG_PTR style, ex_style;

    *control_type = 0;
    if ((ex_style = GetWindowLongPtrW(hwnd, GWL_EXSTYLE)) & WS_EX_APPWINDOW)
    {
        *control_type = UIA_WindowControlTypeId;
        return S_OK;
    }

    SetLastError(NO_ERROR);
    if (!(style = GetWindowLongPtrW(hwnd, GWL_STYLE)) && (GetLastError() != NO_ERROR))
        return uia_get_hr_for_last_error();

    /*
     * Non-caption HWNDs that are popups or tool windows aren't considered full
     * windows, only panes.
     */
    if (((style & WS_CAPTION) != WS_CAPTION) && ((ex_style & WS_EX_TOOLWINDOW) || (style & WS_POPUP)))
    {
        *control_type = UIA_PaneControlTypeId;
        return S_OK;
    }

    /* Non top-level HWNDs are considered panes as well. */
    if (!is_top_level_hwnd(hwnd))
        *control_type = UIA_PaneControlTypeId;
    else
        *control_type = UIA_WindowControlTypeId;

    return S_OK;
}

/*
 * Default ProviderType_BaseHwnd IRawElementProviderSimple interface.
 */
struct base_hwnd_provider {
    IRawElementProviderSimple IRawElementProviderSimple_iface;
    IRawElementProviderFragment IRawElementProviderFragment_iface;
    LONG refcount;

    HWND hwnd;
};

static inline struct base_hwnd_provider *impl_from_base_hwnd_provider(IRawElementProviderSimple *iface)
{
    return CONTAINING_RECORD(iface, struct base_hwnd_provider, IRawElementProviderSimple_iface);
}

static HRESULT WINAPI base_hwnd_provider_QueryInterface(IRawElementProviderSimple *iface, REFIID riid, void **ppv)
{
    struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_provider(iface);

    *ppv = NULL;
    if (IsEqualIID(riid, &IID_IRawElementProviderSimple) || IsEqualIID(riid, &IID_IUnknown))
        *ppv = iface;
    else if (IsEqualIID(riid, &IID_IRawElementProviderFragment))
        *ppv = &base_hwnd_prov->IRawElementProviderFragment_iface;
    else
        return E_NOINTERFACE;

    IRawElementProviderSimple_AddRef(iface);
    return S_OK;
}

static ULONG WINAPI base_hwnd_provider_AddRef(IRawElementProviderSimple *iface)
{
    struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_provider(iface);
    ULONG refcount = InterlockedIncrement(&base_hwnd_prov->refcount);

    TRACE("%p, refcount %ld\n", iface, refcount);

    return refcount;
}

static ULONG WINAPI base_hwnd_provider_Release(IRawElementProviderSimple *iface)
{
    struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_provider(iface);
    ULONG refcount = InterlockedDecrement(&base_hwnd_prov->refcount);

    TRACE("%p, refcount %ld\n", iface, refcount);

    if (!refcount)
        free(base_hwnd_prov);

    return refcount;
}

static HRESULT WINAPI base_hwnd_provider_get_ProviderOptions(IRawElementProviderSimple *iface,
        enum ProviderOptions *ret_val)
{
    TRACE("%p, %p\n", iface, ret_val);
    *ret_val = ProviderOptions_ClientSideProvider;
    return S_OK;
}

static HRESULT WINAPI base_hwnd_provider_GetPatternProvider(IRawElementProviderSimple *iface,
        PATTERNID pattern_id, IUnknown **ret_val)
{
    FIXME("%p, %d, %p: stub\n", iface, pattern_id, ret_val);
    *ret_val = NULL;
    return E_NOTIMPL;
}

static HRESULT WINAPI base_hwnd_provider_GetPropertyValue(IRawElementProviderSimple *iface,
        PROPERTYID prop_id, VARIANT *ret_val)
{
    struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_provider(iface);
    HRESULT hr = S_OK;

    TRACE("%p, %d, %p\n", iface, prop_id, ret_val);

    VariantInit(ret_val);
    if (!IsWindow(base_hwnd_prov->hwnd))
        return UIA_E_ELEMENTNOTAVAILABLE;

    switch (prop_id)
    {
    case UIA_ProviderDescriptionPropertyId:
        V_VT(ret_val) = VT_BSTR;
        V_BSTR(ret_val) = SysAllocString(L"Wine: HWND Proxy");
        break;

    case UIA_NativeWindowHandlePropertyId:
        V_VT(ret_val) = VT_I4;
        V_I4(ret_val) = HandleToUlong(base_hwnd_prov->hwnd);
        break;

    case UIA_ProcessIdPropertyId:
    {
        DWORD pid;

        if (!GetWindowThreadProcessId(base_hwnd_prov->hwnd, &pid))
            return UIA_E_ELEMENTNOTAVAILABLE;

        V_VT(ret_val) = VT_I4;
        V_I4(ret_val) = pid;
        break;
    }

    case UIA_ClassNamePropertyId:
    {
        WCHAR buf[256] = { 0 };

        if (!GetClassNameW(base_hwnd_prov->hwnd, buf, ARRAY_SIZE(buf)))
            hr = uia_get_hr_for_last_error();
        else
        {
            V_VT(ret_val) = VT_BSTR;
            V_BSTR(ret_val) = SysAllocString(buf);
        }
        break;
    }

    case UIA_NamePropertyId:
    {
        LRESULT lres;

        V_VT(ret_val) = VT_BSTR;
        V_BSTR(ret_val) = SysAllocString(L"");
        hr = uia_send_message_timeout(base_hwnd_prov->hwnd, WM_GETTEXTLENGTH, 0, 0, UIA_DEFAULT_MSG_TIMEOUT, &lres);
        if (FAILED(hr) || !lres)
            break;

        if (!SysReAllocStringLen(&V_BSTR(ret_val), NULL, lres))
        {
            hr = E_OUTOFMEMORY;
            break;
        }

        hr = uia_send_message_timeout(base_hwnd_prov->hwnd, WM_GETTEXT, SysStringLen(V_BSTR(ret_val)) + 1,
                (LPARAM)V_BSTR(ret_val), UIA_DEFAULT_MSG_TIMEOUT, &lres);
        break;
    }

    case UIA_ControlTypePropertyId:
    {
        int control_type;

        hr = get_uia_control_type_for_hwnd(base_hwnd_prov->hwnd, &control_type);
        if (SUCCEEDED(hr))
        {
            V_VT(ret_val) = VT_I4;
            V_I4(ret_val) = control_type;
        }
        break;
    }

    default:
        break;
    }

    if (FAILED(hr))
        VariantClear(ret_val);

    return hr;
}

static HRESULT WINAPI base_hwnd_provider_get_HostRawElementProvider(IRawElementProviderSimple *iface,
        IRawElementProviderSimple **ret_val)
{
    TRACE("%p, %p\n", iface, ret_val);
    *ret_val = NULL;
    return S_OK;
}

static const IRawElementProviderSimpleVtbl base_hwnd_provider_vtbl = {
    base_hwnd_provider_QueryInterface,
    base_hwnd_provider_AddRef,
    base_hwnd_provider_Release,
    base_hwnd_provider_get_ProviderOptions,
    base_hwnd_provider_GetPatternProvider,
    base_hwnd_provider_GetPropertyValue,
    base_hwnd_provider_get_HostRawElementProvider,
};

/*
 * IRawElementProviderFragment interface for default ProviderType_BaseHwnd
 * providers.
 */
static inline struct base_hwnd_provider *impl_from_base_hwnd_fragment(IRawElementProviderFragment *iface)
{
    return CONTAINING_RECORD(iface, struct base_hwnd_provider, IRawElementProviderFragment_iface);
}

static HRESULT WINAPI base_hwnd_fragment_QueryInterface(IRawElementProviderFragment *iface, REFIID riid,
        void **ppv)
{
    struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_fragment(iface);
    return IRawElementProviderSimple_QueryInterface(&base_hwnd_prov->IRawElementProviderSimple_iface, riid, ppv);
}

static ULONG WINAPI base_hwnd_fragment_AddRef(IRawElementProviderFragment *iface)
{
    struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_fragment(iface);
    return IRawElementProviderSimple_AddRef(&base_hwnd_prov->IRawElementProviderSimple_iface);
}

static ULONG WINAPI base_hwnd_fragment_Release(IRawElementProviderFragment *iface)
{
    struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_fragment(iface);
    return IRawElementProviderSimple_Release(&base_hwnd_prov->IRawElementProviderSimple_iface);
}

static HRESULT WINAPI base_hwnd_fragment_Navigate(IRawElementProviderFragment *iface,
        enum NavigateDirection direction, IRawElementProviderFragment **ret_val)
{
    struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_fragment(iface);
    IRawElementProviderSimple *elprov = NULL;
    HRESULT hr = S_OK;

    TRACE("%p, %d, %p\n", iface, direction, ret_val);

    *ret_val = NULL;

    switch (direction)
    {
    case NavigateDirection_Parent:
    {
        HWND parent, owner;

        /*
         * Top level owned windows have their owner window as a parent instead
         * of the desktop window.
         */
        if (is_top_level_hwnd(base_hwnd_prov->hwnd) && (owner = GetWindow(base_hwnd_prov->hwnd, GW_OWNER)))
            parent = owner;
        else
            parent = GetAncestor(base_hwnd_prov->hwnd, GA_PARENT);

        if (parent)
            hr = create_base_hwnd_provider(parent, &elprov);
        break;
    }

    case NavigateDirection_FirstChild:
    case NavigateDirection_LastChild:
    case NavigateDirection_PreviousSibling:
    case NavigateDirection_NextSibling:
        FIXME("Unimplemented NavigateDirection %d\n", direction);
        return E_NOTIMPL;

    default:
        FIXME("Invalid NavigateDirection %d\n", direction);
        return E_INVALIDARG;
    }

    if (elprov)
    {
        hr = IRawElementProviderSimple_QueryInterface(elprov, &IID_IRawElementProviderFragment, (void **)ret_val);
        IRawElementProviderSimple_Release(elprov);
    }

    return hr;
}

static HRESULT WINAPI base_hwnd_fragment_GetRuntimeId(IRawElementProviderFragment *iface,
        SAFEARRAY **ret_val)
{
    FIXME("%p, %p: stub!\n", iface, ret_val);
    *ret_val = NULL;
    return E_NOTIMPL;
}

static HRESULT WINAPI base_hwnd_fragment_get_BoundingRectangle(IRawElementProviderFragment *iface,
        struct UiaRect *ret_val)
{
    struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_fragment(iface);
    RECT rect = { 0 };

    TRACE("%p, %p\n", iface, ret_val);

    memset(ret_val, 0, sizeof(*ret_val));

    /* Top level minimized window - Return empty rect. */
    if (is_top_level_hwnd(base_hwnd_prov->hwnd) && IsIconic(base_hwnd_prov->hwnd))
        return S_OK;

    if (!GetWindowRect(base_hwnd_prov->hwnd, &rect))
        return uia_get_hr_for_last_error();

    ret_val->left = rect.left;
    ret_val->top = rect.top;
    ret_val->width = (rect.right - rect.left);
    ret_val->height = (rect.bottom - rect.top);

    return S_OK;
}

static HRESULT WINAPI base_hwnd_fragment_GetEmbeddedFragmentRoots(IRawElementProviderFragment *iface,
        SAFEARRAY **ret_val)
{
    FIXME("%p, %p: stub!\n", iface, ret_val);
    *ret_val = NULL;
    return E_NOTIMPL;
}

static HRESULT WINAPI base_hwnd_fragment_SetFocus(IRawElementProviderFragment *iface)
{
    FIXME("%p: stub!\n", iface);
    return E_NOTIMPL;
}

static HRESULT WINAPI base_hwnd_fragment_get_FragmentRoot(IRawElementProviderFragment *iface,
        IRawElementProviderFragmentRoot **ret_val)
{
    FIXME("%p, %p: stub!\n", iface, ret_val);
    *ret_val = NULL;
    return E_NOTIMPL;
}

static const IRawElementProviderFragmentVtbl base_hwnd_fragment_vtbl = {
    base_hwnd_fragment_QueryInterface,
    base_hwnd_fragment_AddRef,
    base_hwnd_fragment_Release,
    base_hwnd_fragment_Navigate,
    base_hwnd_fragment_GetRuntimeId,
    base_hwnd_fragment_get_BoundingRectangle,
    base_hwnd_fragment_GetEmbeddedFragmentRoots,
    base_hwnd_fragment_SetFocus,
    base_hwnd_fragment_get_FragmentRoot,
};

HRESULT create_base_hwnd_provider(HWND hwnd, IRawElementProviderSimple **elprov)
{
    struct base_hwnd_provider *base_hwnd_prov;

    *elprov = NULL;

    if (!hwnd)
        return E_INVALIDARG;

    if (!IsWindow(hwnd))
        return UIA_E_ELEMENTNOTAVAILABLE;

    if (!(base_hwnd_prov = calloc(1, sizeof(*base_hwnd_prov))))
        return E_OUTOFMEMORY;

    base_hwnd_prov->IRawElementProviderSimple_iface.lpVtbl = &base_hwnd_provider_vtbl;
    base_hwnd_prov->IRawElementProviderFragment_iface.lpVtbl = &base_hwnd_fragment_vtbl;
    base_hwnd_prov->refcount = 1;
    base_hwnd_prov->hwnd = hwnd;
    *elprov = &base_hwnd_prov->IRawElementProviderSimple_iface;

    return S_OK;
}

/*
 * UI Automation provider thread functions.
 */
struct uia_provider_thread
{
    struct rb_tree node_map;
    struct list nodes_list;
    HANDLE hthread;
    HWND hwnd;
    LONG ref;
};

static struct uia_provider_thread provider_thread;
static CRITICAL_SECTION provider_thread_cs;
static CRITICAL_SECTION_DEBUG provider_thread_cs_debug =
{
    0, 0, &provider_thread_cs,
    { &provider_thread_cs_debug.ProcessLocksList, &provider_thread_cs_debug.ProcessLocksList },
      0, 0, { (DWORD_PTR)(__FILE__ ": provider_thread_cs") }
};
static CRITICAL_SECTION provider_thread_cs = { &provider_thread_cs_debug, -1, 0, 0, 0, 0 };

struct uia_provider_thread_map_entry
{
    struct rb_entry entry;

    SAFEARRAY *runtime_id;
    struct list nodes_list;
};

static int uia_runtime_id_compare(const void *key, const struct rb_entry *entry)
{
    struct uia_provider_thread_map_entry *prov_entry = RB_ENTRY_VALUE(entry, struct uia_provider_thread_map_entry, entry);
    return uia_compare_safearrays(prov_entry->runtime_id, (SAFEARRAY *)key, UIAutomationType_IntArray);
}

void uia_provider_thread_remove_node(HUIANODE node)
{
    struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node);

    TRACE("Removing node %p\n", node);

    EnterCriticalSection(&provider_thread_cs);

    list_remove(&node_data->prov_thread_list_entry);
    list_init(&node_data->prov_thread_list_entry);
    if (!list_empty(&node_data->node_map_list_entry))
    {
        list_remove(&node_data->node_map_list_entry);
        list_init(&node_data->node_map_list_entry);
        if (list_empty(&node_data->map->nodes_list))
        {
            rb_remove(&provider_thread.node_map, &node_data->map->entry);
            SafeArrayDestroy(node_data->map->runtime_id);
            free(node_data->map);
        }
        node_data->map = NULL;
    }

    LeaveCriticalSection(&provider_thread_cs);
}

static void uia_provider_thread_disconnect_node(SAFEARRAY *sa)
{
    struct rb_entry *rb_entry;

    EnterCriticalSection(&provider_thread_cs);

    /* Provider thread hasn't been started, no nodes to disconnect. */
    if (!provider_thread.ref)
        goto exit;

    rb_entry = rb_get(&provider_thread.node_map, sa);
    if (rb_entry)
    {
        struct uia_provider_thread_map_entry *prov_map;
        struct list *cursor, *cursor2;
        struct uia_node *node_data;

        prov_map = RB_ENTRY_VALUE(rb_entry, struct uia_provider_thread_map_entry, entry);
        LIST_FOR_EACH_SAFE(cursor, cursor2, &prov_map->nodes_list)
        {
            node_data = LIST_ENTRY(cursor, struct uia_node, node_map_list_entry);

            list_remove(cursor);
            list_remove(&node_data->prov_thread_list_entry);
            list_init(&node_data->prov_thread_list_entry);
            list_init(&node_data->node_map_list_entry);
            node_data->map = NULL;

            IWineUiaNode_disconnect(&node_data->IWineUiaNode_iface);
        }

        rb_remove(&provider_thread.node_map, &prov_map->entry);
        SafeArrayDestroy(prov_map->runtime_id);
        free(prov_map);
    }

exit:
    LeaveCriticalSection(&provider_thread_cs);
}

static HRESULT uia_provider_thread_add_node(HUIANODE node, SAFEARRAY *rt_id)
{
    struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node);
    int prov_type = get_node_provider_type_at_idx(node_data, 0);
    struct uia_provider *prov_data;
    HRESULT hr = S_OK;

    prov_data = impl_from_IWineUiaProvider(node_data->prov[prov_type]);
    node_data->nested_node = prov_data->return_nested_node = prov_data->refuse_hwnd_node_providers = TRUE;

    TRACE("Adding node %p\n", node);

    EnterCriticalSection(&provider_thread_cs);
    list_add_tail(&provider_thread.nodes_list, &node_data->prov_thread_list_entry);

    /* If we have a runtime ID, create an entry in the rb tree. */
    if (rt_id)
    {
        struct uia_provider_thread_map_entry *prov_map;
        struct rb_entry *rb_entry;

        if ((rb_entry = rb_get(&provider_thread.node_map, rt_id)))
            prov_map = RB_ENTRY_VALUE(rb_entry, struct uia_provider_thread_map_entry, entry);
        else
        {
            prov_map = calloc(1, sizeof(*prov_map));
            if (!prov_map)
            {
                hr = E_OUTOFMEMORY;
                goto exit;
            }

            hr = SafeArrayCopy(rt_id, &prov_map->runtime_id);
            if (FAILED(hr))
            {
                free(prov_map);
                goto exit;
            }
            list_init(&prov_map->nodes_list);
            rb_put(&provider_thread.node_map, prov_map->runtime_id, &prov_map->entry);
        }

        list_add_tail(&prov_map->nodes_list, &node_data->node_map_list_entry);
        node_data->map = prov_map;
    }

exit:
    LeaveCriticalSection(&provider_thread_cs);

    return hr;
}

#define WM_GET_OBJECT_UIA_NODE (WM_USER + 1)
#define WM_UIA_PROVIDER_THREAD_STOP (WM_USER + 2)
static LRESULT CALLBACK uia_provider_thread_msg_proc(HWND hwnd, UINT msg, WPARAM wparam,
        LPARAM lparam)
{
    switch (msg)
    {
    case WM_GET_OBJECT_UIA_NODE:
    {
        SAFEARRAY *rt_id = (SAFEARRAY *)wparam;
        HUIANODE node = (HUIANODE)lparam;
        LRESULT lr;

        if (FAILED(uia_provider_thread_add_node(node, rt_id)))
        {
            WARN("Failed to add node %p to provider thread list.\n", node);
            return 0;
        }

        /*
         * LresultFromObject returns an index into the global atom string table,
         * which has a valid range of 0xc000-0xffff.
         */
        lr = LresultFromObject(&IID_IWineUiaNode, 0, (IUnknown *)node);
        if ((lr > 0xffff) || (lr < 0xc000))
        {
            WARN("Got invalid lresult %Ix\n", lr);
            lr = 0;
        }

        return lr;
    }

    default:
        break;
    }

    return DefWindowProcW(hwnd, msg, wparam, lparam);
}

static DWORD WINAPI uia_provider_thread_proc(void *arg)
{
    HANDLE initialized_event = arg;
    HWND hwnd;
    MSG msg;

    CoInitializeEx(NULL, COINIT_MULTITHREADED);
    hwnd = CreateWindowW(L"Message", NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
    if (!hwnd)
    {
        WARN("CreateWindow failed: %ld\n", GetLastError());
        CoUninitialize();
        FreeLibraryAndExitThread(huia_module, 1);
    }

    SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)uia_provider_thread_msg_proc);
    provider_thread.hwnd = hwnd;

    /* Initialization complete, thread can now process window messages. */
    SetEvent(initialized_event);
    TRACE("Provider thread started.\n");
    while (GetMessageW(&msg, NULL, 0, 0))
    {
        if (msg.message == WM_UIA_PROVIDER_THREAD_STOP)
            break;
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    TRACE("Shutting down UI Automation provider thread.\n");

    DestroyWindow(hwnd);
    CoUninitialize();
    FreeLibraryAndExitThread(huia_module, 0);
}

static BOOL uia_start_provider_thread(void)
{
    BOOL started = TRUE;

    EnterCriticalSection(&provider_thread_cs);
    if (++provider_thread.ref == 1)
    {
        HANDLE ready_event;
        HANDLE events[2];
        HMODULE hmodule;
        DWORD wait_obj;

        /* Increment DLL reference count. */
        GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
                (const WCHAR *)uia_start_provider_thread, &hmodule);

        list_init(&provider_thread.nodes_list);
        rb_init(&provider_thread.node_map, uia_runtime_id_compare);
        events[0] = ready_event = CreateEventW(NULL, FALSE, FALSE, NULL);
        if (!(provider_thread.hthread = CreateThread(NULL, 0, uia_provider_thread_proc,
                ready_event, 0, NULL)))
        {
            FreeLibrary(hmodule);
            started = FALSE;
            goto exit;
        }

        events[1] = provider_thread.hthread;
        wait_obj = WaitForMultipleObjects(2, events, FALSE, INFINITE);
        if (wait_obj != WAIT_OBJECT_0)
        {
            CloseHandle(provider_thread.hthread);
            started = FALSE;
        }

exit:
        CloseHandle(ready_event);
        if (!started)
        {
            WARN("Failed to start provider thread\n");
            memset(&provider_thread, 0, sizeof(provider_thread));
        }
    }

    LeaveCriticalSection(&provider_thread_cs);
    return started;
}

void uia_stop_provider_thread(void)
{
    EnterCriticalSection(&provider_thread_cs);
    if (!--provider_thread.ref)
    {
        PostMessageW(provider_thread.hwnd, WM_UIA_PROVIDER_THREAD_STOP, 0, 0);
        CloseHandle(provider_thread.hthread);
        if (!list_empty(&provider_thread.nodes_list))
            ERR("Provider thread shutdown with nodes still in the list\n");
        memset(&provider_thread, 0, sizeof(provider_thread));
    }
    LeaveCriticalSection(&provider_thread_cs);
}

/*
 * Pass our IWineUiaNode interface to the provider thread for marshaling. UI
 * Automation has to work regardless of whether or not COM is initialized on
 * the thread calling UiaReturnRawElementProvider.
 */
LRESULT uia_lresult_from_node(HUIANODE huianode)
{
    SAFEARRAY *rt_id;
    LRESULT lr = 0;
    HRESULT hr;

    hr = UiaGetRuntimeId(huianode, &rt_id);
    if (SUCCEEDED(hr) && uia_start_provider_thread())
        lr = SendMessageW(provider_thread.hwnd, WM_GET_OBJECT_UIA_NODE, (WPARAM)rt_id, (LPARAM)huianode);

    if (FAILED(hr))
        WARN("UiaGetRuntimeId failed with hr %#lx\n", hr);

    /*
     * LresultFromObject increases refcnt by 1. If LresultFromObject
     * failed or wasn't called, this is expected to release the node.
     */
    UiaNodeRelease(huianode);
    SafeArrayDestroy(rt_id);
    return lr;
}

/***********************************************************************
 *          UiaReturnRawElementProvider (uiautomationcore.@)
 */
LRESULT WINAPI UiaReturnRawElementProvider(HWND hwnd, WPARAM wparam,
        LPARAM lparam, IRawElementProviderSimple *elprov)
{
    HUIANODE node;
    HRESULT hr;

    TRACE("(%p, %Ix, %#Ix, %p)\n", hwnd, wparam, lparam, elprov);

    if (!wparam && !lparam && !elprov)
    {
        FIXME("UIA-to-MSAA bridge not implemented, no provider map to free.\n");
        return 0;
    }

    if (lparam != UiaRootObjectId)
    {
        FIXME("Unsupported object id %Id, ignoring.\n", lparam);
        return 0;
    }

    hr = create_uia_node_from_elprov(elprov, &node, FALSE);
    if (FAILED(hr))
    {
        WARN("Failed to create HUIANODE with hr %#lx\n", hr);
        return 0;
    }

    return uia_lresult_from_node(node);
}

/***********************************************************************
 *          UiaDisconnectProvider (uiautomationcore.@)
 */
HRESULT WINAPI UiaDisconnectProvider(IRawElementProviderSimple *elprov)
{
    SAFEARRAY *sa;
    HUIANODE node;
    HRESULT hr;

    TRACE("(%p)\n", elprov);

    hr = create_uia_node_from_elprov(elprov, &node, FALSE);
    if (FAILED(hr))
        return hr;

    hr = UiaGetRuntimeId(node, &sa);
    UiaNodeRelease(node);
    if (FAILED(hr))
        return hr;

    if (!sa)
        return E_INVALIDARG;

    uia_provider_thread_disconnect_node(sa);

    SafeArrayDestroy(sa);

    return S_OK;
}