// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// Description: HWND-based ListBox Proxy
//


using System;
using System.Collections;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Windows.Automation;
using System.Windows.Automation.Provider;
using System.Windows;
using Accessibility;
using MS.Win32;

namespace MS.Internal.AutomationProxies
{
    // This class represents ListBox and ListBox with check buttons.
    class WindowsListBox: ProxyHwnd, ISelectionProvider
    {
        // ------------------------------------------------------
        //
        // Constructors
        //
        // ------------------------------------------------------

        #region Constructors

        internal WindowsListBox (IntPtr hwnd, ProxyFragment parent, int item, bool parentedByCombo)
           : base(hwnd, parent, item)
        {
            // Set the strings to return properly the properties.
            _parentedByCombo = parentedByCombo;
            _fIsKeyboardFocusable = true;
            _cControlType = ControlType.List;
            _fIsContent = !_parentedByCombo;

            if (parentedByCombo)
            {
                _sAutomationId = "ListBox"; // This string is a non-localizable string
            }

            // support for events
            _createOnEvent = new WinEventTracker.ProxyRaiseEvents (RaiseEvents);
        }

        #endregion

        #region Proxy Create

        // Static Create method called by UIAutomation to create this proxy.
        // returns null if unsuccessful
        internal static IRawElementProviderSimple Create(IntPtr hwnd, int idChild, int idObject)
        {
            return Create(hwnd, idChild);
        }

        internal static IRawElementProviderSimple Create(IntPtr hwnd, int idChild)
        {
            bool parentedByCombo = false;
            ProxyFragment parent = null;
            int item = 0;

            try
            {
                int style = Misc.GetWindowStyle(hwnd);
                // If can not get windows style the hwnd is bad so do not create a proxy for it.
                if (style == 0)
                {
                    return null;
                }

                if (Misc.IsBitSet(style, NativeMethods.LBS_COMBOBOX))
                {
                    // List portion of combo box
                    NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize);

                    if (WindowsComboBox.GetComboInfo(hwnd, ref cbInfo) && (cbInfo.hwndCombo != IntPtr.Zero))
                    {
                        parent = (ProxyFragment)WindowsComboBox.Create(cbInfo.hwndCombo, 0);
                        parentedByCombo = true;
                        item = (int)WindowsComboBox.ComboChildren.List;
                    }
                }
            }
            catch (ElementNotAvailableException)
            {
                return null;
            }

            WindowsListBox listbox = new WindowsListBox (hwnd, parent, item, parentedByCombo);

            if (idChild == 0)
            {
                return listbox;
            }
            else
            {
                return listbox.CreateListboxItem(idChild - 1);
            }
        }

        // Static Create method called by the event tracker system
        // WinEvents are one throwns because items exist. so it makes sense to create the item and
        // check for details afterward.
        internal static void RaiseEvents (IntPtr hwnd, int eventId, object idProp, int idObject, int idChild)
        {
            // NOTE: List may be a portion of the Combobox
            // Use WindowsListBox.Create in order to set-up a correct parenthood chain

            switch (idObject)
            {
                case NativeMethods.OBJID_WINDOW:
                    RaiseEventsOnWindow(hwnd, eventId, idProp, idObject, idChild);
                    break;

                case NativeMethods.OBJID_CLIENT:
                    RaiseEventsOnClient(hwnd, eventId, idProp, idObject, idChild);
                    break;

                case NativeMethods.OBJID_VSCROLL :
                case NativeMethods.OBJID_HSCROLL :
                    break;

                default :
                    ProxySimple el = (ProxyHwnd)WindowsListBox.Create(hwnd, 0);
                    if (el != null)
                    {
                        el.DispatchEvents(eventId, idProp, idObject, idChild);
                    }
                    break;
            }
        }

        #endregion

        //------------------------------------------------------
        //
        //  Patterns Implementation
        //
        //------------------------------------------------------

        #region ProxySimple Interface

        // Returns a pattern interface if supported.
        internal override object GetPatternProvider (AutomationPattern iid)
        {
            // selection is always supported
            if (iid == SelectionPattern.Pattern)
            {
                return this;
            }
            // the scroll pattern is only supported when the list is scrollable.
            else if (iid == ScrollPattern.Pattern && WindowScroll.HasScrollableStyle(_hwnd))
            {
                // delegate work to the NonClientArea implementation of IScrollProvider
                IScrollProvider scroll = NonClientArea.Create(_hwnd, 0) as IScrollProvider;

                if (scroll != null)
                {
                    return scroll;
                }
            }


            return null;
        }

        // Process all the Element Properties
        internal override object GetElementProperty(AutomationProperty idProp)
        {
            if (idProp == AutomationElement.IsControlElementProperty)
            {
                return IsParentedByCombo() || SafeNativeMethods.IsWindowVisible(_hwnd);
            }
            else if (idProp == AutomationElement.IsOffscreenProperty)
            {
                if (IsParentedByCombo())
                {
                    // Since the bounding rectangle of a collapsed listbox protion of a combo-box is still 
                    // on the virtal desktop needs to check to make sure the listbox protion is
                    // expanded, i.e. visible.
                    if (!SafeNativeMethods.IsWindowVisible(_hwnd))
                    {
                        return true;
                    }
                }
            }

            return base.GetElementProperty(idProp);
        }

        #endregion

        #region ProxyFragment Interface

        // ------------------------------------------------------
        //
        // RawElementProvider interface implementation
        //
        // ------------------------------------------------------

        // Returns the next sibling element in the raw hierarchy.
        // Peripheral controls have always negative values.
        internal override ProxySimple GetNextSibling (ProxySimple child)
        {
            int item = child._item;
            int count = Length;

            // Next for an item that does not exist in the list
            if (item >= count)
            {
                throw new ElementNotAvailableException ();
            }

            if (item >= 0 && (item + 1) < count)
            {
                return CreateListboxItem (item + 1);
            }
            else
            {
                return base.GetNextSibling (child);
            }
        }

        // Returns the previous sibling element in the raw hierarchy.
        // Peripheral controls have always negative values.
        internal override ProxySimple GetPreviousSibling (ProxySimple child)
        {
            // start with the scrollbars
            ProxySimple ret = base.GetPreviousSibling (child);

            if (ret != null)
            {
                return ret;
            }

            // then try out the items
            int item = child._item;
            int count = Length;

            // Next for an item that does not exist in the list
            if (item >= count)
            {
                throw new ElementNotAvailableException ();
            }

            if (item > 0 && (item) < count)
            {
                return CreateListboxItem (item - 1);
            }
            else
            {
                return item != 0 && count > 0 ? CreateListboxItem (count - 1) : null;
            }
        }

        // Returns the first child element in the raw hierarchy.
        internal override ProxySimple GetFirstChild ()
        {
            if (Length > 0)
            {
                return CreateListboxItem (0);
            }

            // no content go for the scrollbars
            return base.GetFirstChild ();
        }

        // Returns the last child element in the raw hierarchy.
        internal override ProxySimple GetLastChild ()
        {
            // start with the scrollbars
            ProxySimple ret = base.GetFirstChild ();

            if (ret != null)
            {
                return ret;
            }

            int count = Length;

            return count > 0 ? CreateListboxItem (count - 1) : null;
        }

        // Returns a Proxy element corresponding to the specified screen coordinates.
        internal override ProxySimple ElementProviderFromPoint (int x, int y)
        {
            NativeMethods.Win32Rect listboxrect = new NativeMethods.Win32Rect ();

            Misc.GetClientRectInScreenCoordinates(_hwnd, ref listboxrect);
            if (Misc.PtInRect(ref listboxrect, x, y))
            {
                int ret = Misc.ProxySendMessageInt(_hwnd, NativeMethods.LB_ITEMFROMPOINT, IntPtr.Zero, NativeMethods.Util.MAKELPARAM(x - listboxrect.left, y - listboxrect.top));
                if (NativeMethods.Util.HIWORD(ret) == 0)
                {
                    int index = NativeMethods.Util.LOWORD(ret);
                    return CreateListboxItem(index);
                }
            }

            return base.ElementProviderFromPoint (x, y);
        }

        // Returns an item corresponding to the focused element (if there is one), 
        // or null otherwise.
        internal override ProxySimple GetFocus ()
        {
            int index = Misc.ProxySendMessageInt(_hwnd, NativeMethods.LB_GETCARETINDEX, IntPtr.Zero, IntPtr.Zero);

            if (index != NativeMethods.LB_ERR)
            {
                return CreateListboxItem(index);
            }

            return this;
        }

        #endregion

        #region Selection Pattern

        // ------------------------------------------------------
        //
        // ISelectionProvider interface implementation
        //
        // ------------------------------------------------------

        // Returns an enumerator over the current selection.
        IRawElementProviderSimple[] ISelectionProvider.GetSelection()
        {
            int count = Length;
            int countSelection = IsMultipleSelection() ? GetSelectionCount() : 1;

            if (count <= 0 || countSelection <= 0)
            {
                return null;
            }

            IRawElementProviderSimple[] selection = new IRawElementProviderSimple[countSelection];

            int index = 0;
            for (int itemPos = 0; itemPos < count; itemPos++)
            {
                if (ListboxItem.IsSelected(_hwnd, itemPos))
                {
                    selection[index] = CreateListboxItem(itemPos);
                    index++;
                }
            }

            if (index == 0)
            {
                return null;
            }

            return selection;
        }

        // Returns whether the control supports multiple selection.
        bool ISelectionProvider.CanSelectMultiple
        {
            get
            {
                return IsMultipleSelection ();
            }
        }

        // Returns whether the control requires a minimum of one
        // selected element at all times.
        bool ISelectionProvider.IsSelectionRequired
        {
            // If ListBox supports multipleselection - this property always returns false
            // since user can unselect everything using the Ctrl + Click
            // This property is dynamic in the case of single-selected listbox.
            // This should be documented, user should not cached this value (single-selection lb)
            get
            {
                if (IsMultipleSelection ())
                {
                    return false;
                }

                return Misc.ProxySendMessageInt(_hwnd, NativeMethods.LB_GETCURSEL, IntPtr.Zero, IntPtr.Zero) >= 0;
            }
        }

        #endregion

        // ------------------------------------------------------
        //
        // Protected Methods
        //
        // ------------------------------------------------------

        #region Protected Methods

        // Picks a WinEvent to track for a UIA property
        protected override int[] PropertyToWinEvent(AutomationProperty idProp)
        {
            // Upon creation, a single selection Listbox can have no selection to start with
            // however once an item has been selection, the selection cannot be removed.
            // The notification handler is set based on the type of Listbox (single selection) 
            // and if nothing is selected.
            if (idProp == SelectionPattern.IsSelectionRequiredProperty && !IsMultipleSelection() && !HasSelection())
            {
                return new int[] { NativeMethods.EventObjectSelection };
            }

            return base.PropertyToWinEvent(idProp);
        }

        #endregion

        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------

        #region Private Method

        // Creates a list item RawElementBase Item
        private ProxySimple CreateListboxItem (int index)
        {
            return new ListboxItem (_hwnd, this, index);
        }

        // Return the number of items (non peripheral) in the listbox.
        private int Length
        {
            get
            {
                return Misc.ProxySendMessageInt(_hwnd, NativeMethods.LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero);
            }
        }

        private bool IsParentedByCombo ()
        {
            return _parentedByCombo;
        }

        private static void RaiseEventsOnClient(IntPtr hwnd, int eventId, object idProp, int idObject, int idChild)
        {
            ProxySimple el = null;

            WindowsListBox wlb = (WindowsListBox)WindowsListBox.Create(hwnd, 0);

            // Upon creation, a single selection Listbox can have no selection to start with
            // however once an item has been selection, the selection cannot be removed.
            // This WinEvent can only be received once, on the first selection.
            // Once the notification is received the notification handler is removed to not get it a 
            // second time.
            if ((eventId == NativeMethods.EventObjectSelection || eventId == NativeMethods.EventObjectSelectionAdd) && (idProp as AutomationProperty) == SelectionPattern.IsSelectionRequiredProperty)
            {
                // This array must be kept in sync with the array in PropertyToWinEvent
                WinEventTracker.EvtIdProperty[] aEvtIdProperties = new WinEventTracker.EvtIdProperty[] { new WinEventTracker.EvtIdProperty(NativeMethods.EventObjectSelection, SelectionPattern.IsSelectionRequiredProperty) };

                WinEventTracker.RemoveToNotificationList(hwnd, aEvtIdProperties, null, aEvtIdProperties.Length);
                el = wlb;
            }
            else if (eventId == NativeMethods.EventObjectSelection || eventId == NativeMethods.EventObjectSelectionRemove || eventId == NativeMethods.EventObjectSelectionAdd)
            {
                bool isMultipleSelection = wlb.IsMultipleSelection();

                // User should send SelectionAdd for a Multiselect listbox but it sends instead
                // Selection. The code below fixes the bug in User 
                if (eventId == NativeMethods.EventObjectSelection && isMultipleSelection && wlb.HasOtherSelections(idChild - 1))
                {
                    eventId = NativeMethods.EventObjectSelectionAdd;
                }

                // The spec says a ElementSelectionEvent should be fired when action causes only one 
                // selection.
                if ((eventId == NativeMethods.EventObjectSelectionRemove || eventId == NativeMethods.EventObjectSelectionAdd) &&
                    isMultipleSelection && wlb.GetSelectionCount() == 1)
                {
                    // The net result of the user action is that there is only one item selected in the
                    // listbox, so change the event to an EventObjectSelected.
                    idProp = SelectionItemPattern.ElementSelectedEvent;
                    eventId = NativeMethods.EventObjectSelection;

                    // Now need to find what item is selected.
                    int selection = wlb.GetOtherSelection(idChild - 1);
                    if (selection != NativeMethods.LB_ERR)
                    {
                        idChild = selection;
                    }
                }

                el = wlb.CreateListboxItem(idChild - 1);
            }
            else
            {
                el = wlb;
            }

            // Special case for logical element change for listbox item
            if ((idProp as AutomationEvent) == AutomationElement.StructureChangedEvent &&
                (eventId == NativeMethods.EventObjectDestroy || eventId == NativeMethods.EventObjectCreate))
            {
                // Since children are referenced by position in the tree, addition and removal
                // of items leads to different results when asking properties for the same element
                // On removal, item + 1 is now item!
                // Use Children Invalidated to let the client knows that all the cached 
                AutomationInteropProvider.RaiseStructureChangedEvent( wlb, new StructureChangedEventArgs( StructureChangeType.ChildrenInvalidated, wlb.MakeRuntimeId() ) );
                return;
            }

            if (el != null)
            {
                el.DispatchEvents(eventId, idProp, idObject, idChild);
            }
        }

        private static void RaiseEventsOnWindow(IntPtr hwnd, int eventId, object idProp, int idObject, int idChild)
        {
            AutomationProperty automationProperty = idProp as AutomationProperty;
            ProxySimple el = null;

            if ((eventId == NativeMethods.EventObjectShow || eventId == NativeMethods.EventObjectHide) &&
                (automationProperty != null && automationProperty == ExpandCollapsePattern.ExpandCollapseStateProperty))
            {
                if (Misc.IsBitSet(Misc.GetWindowStyle(hwnd), NativeMethods.LBS_COMBOBOX))
                {
                    // List portion of combo: We'll hit it in the case when user hovers over it
                    NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize);

                    if (WindowsComboBox.GetComboInfo(hwnd, ref cbInfo) && (cbInfo.hwndCombo != IntPtr.Zero))
                    {
                        WindowsComboBox cb = (WindowsComboBox)WindowsComboBox.Create(cbInfo.hwndCombo, 0);

                        if (!cb.IsSimpleCombo())
                        {
                            el = cb;
                        }
                    }
                }
            }

            if (el != null)
            {
                el.DispatchEvents(eventId, idProp, idObject, idChild);
            }
        }

        #region Selection Pattern Helpers

        private int GetOtherSelection(int skipItem)
        {
            for (int i = 0, count = Length; i < count; i++)
            {
                if (i != skipItem && ListboxItem.IsSelected(_hwnd, i))
                {
                    // Win32 listbox items are 0 based, UIAutomation listbox items are 1 based.
                    return i + 1;
                }
            }

            return NativeMethods.LB_ERR;
        }

        private int GetSelectionCount()
        {
            int result = Misc.ProxySendMessageInt(_hwnd, NativeMethods.LB_GETSELCOUNT, IntPtr.Zero, IntPtr.Zero);
            return result != NativeMethods.LB_ERR ? result : 0;
        }

        // Detect if there any selections in
        // This is used by the WindowsListBoxItem class
        // returns true if any items are selected
        private bool HasSelection ()
        {
            int i, count;

            for (i = 0, count = Length; i < count && !ListboxItem.IsSelected (_hwnd, i); i++)
                ;

            return (i < count);
        }

        // Detect if listbox has any element except skipItem selected
        // This is used by the WindowsListBoxItem class
        // returns true if any items
        private bool HasOtherSelections (int skipItem)
        {
            for (int i = 0, count = Length; i < count; i++)
            {
                if (i != skipItem && ListboxItem.IsSelected (_hwnd, i))
                {
                    return true;
                }
            }

            return false;
        }

        // Clears all elements in the multiple-selection  listbox
        // This is used by the WindowsListBoxItem class
        // returns true if operation succeeded
        private bool ClearAll ()
        {
            // clear all possible only in the multi-select case
            System.Diagnostics.Debug.Assert (IsMultipleSelection (), "Calling ClearAll on single-selected listbox");

            return Misc.ProxySendMessageInt(_hwnd, NativeMethods.LB_SETSEL, IntPtr.Zero, new IntPtr(-1)) != NativeMethods.LB_ERR;
        }

        // Check the LBS_MULTIPLESEL or/and LBS_EXTENDEDSEL for multiple selection
        // This is used by WindowListBoxItem class
        // returns true if multiple selection is supported
        private bool IsMultipleSelection ()
        {
            return (0 != (WindowStyle & (NativeMethods.LBS_MULTIPLESEL | NativeMethods.LBS_EXTENDEDSEL)));
        }

        private bool IsWinFormCheckedListBox()
        {
            if (WindowsFormsHelper.IsWindowsFormsControl(_hwnd))
            {
                return ((WindowStyle & NativeMethods.LBS_OWNERDRAWFIXED) == NativeMethods.LBS_OWNERDRAWFIXED) &&
                    ((WindowStyle & NativeMethods.LBS_WANTKEYBOARDINPUT) == NativeMethods.LBS_WANTKEYBOARDINPUT);
            }
            return false;
        }


        #endregion

        #endregion

        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------

        #region Private Fields

        private bool _parentedByCombo;

        #endregion

        // ------------------------------------------------------
        //
        //  ListboxItem Private Class
        //
        //------------------------------------------------------

        #region ListBoxItem

        // Summary description for WindowsListboxItem.
        class ListboxItem : ProxySimple, ISelectionItemProvider, IScrollItemProvider, IToggleProvider
        {
            // ------------------------------------------------------
            //
            // Constructors
            //
            // ------------------------------------------------------

            #region Constructors

            // Constructor.
            internal ListboxItem (IntPtr hwnd, ProxyFragment parent, int item)
            : base (hwnd, parent, item)
            {
                // Set the strings to return properly the properties.
                _cControlType = ControlType.ListItem;
                _fHasPersistentID = false;
                _fIsKeyboardFocusable = true;
                _listBox = (WindowsListBox) parent;
            }

            #endregion

            //------------------------------------------------------
            //
            //  Patterns Implementation
            //
            //------------------------------------------------------

            #region ProxySimple Interface

            // Returns a pattern interface if supported.
            internal override object GetPatternProvider (AutomationPattern iid)
            {
                if (iid == SelectionItemPattern.Pattern)
                {
                    return this;
                }
                else if (iid == ScrollItemPattern.Pattern && WindowScroll.IsScrollable(_hwnd))
                {
                    return this;
                }
                else if (_listBox.IsWinFormCheckedListBox() && iid == TogglePattern.Pattern)
                {
                    return this;
                }

                return null;
            }

            // Gets the bounding rectangle for this element
            internal override Rect BoundingRectangle
            {
                get
                {
                    // Don't need to normalize, LB_GETITEMRECT returns absolute coordinates.
                    return BoundingRect().ToRect(false);                    
                }
            }

            //Gets the localized name
            // Returns the text of ListBox item. Check value is not included.
            internal override string LocalizedName
            {
                get
                {
                    // NOTE: List can be a part of the combobox. Many times
                    // sending LB_ message will work, however many apps have ownerdraw combos,
                    // in this case only CB_ type message will work
                    if (((WindowsListBox)_parent).IsParentedByCombo())
                    {
                        WindowsComboBox cb = (WindowsComboBox)_parent._parent;
                        return cb.GetListItemText(_item);
                    }


                    int iTextLen = Misc.ProxySendMessageInt(_hwnd, NativeMethods.LB_GETTEXTLEN, new IntPtr(_item), IntPtr.Zero);

                    if (iTextLen != 0)
                    {
                        if (Misc.GetClassName(_hwnd).Equals("Internet Explorer_TridentLstBox"))
                        {
                            // The Trident listbox is a superclassed standard listbox.
                            // Trident listboxes are owner draw that does not have the hasstring style set.
                            // All the control contains is the owner draw data and not text.  Private
                            // messages were added to retrieve the owner draw data as text.  The new messages
                            // are used just like the normally LB_GETTEXT and CB_GETTEXT messages.
                            return XSendMessage.GetItemText(_hwnd, NativeMethods.WM_USER + NativeMethods.LB_GETTEXT, _item, iTextLen);
                        }
                        else
                        {
                            string text = Misc.GetUnsafeText(_hwnd, NativeMethods.LB_GETTEXT, new IntPtr(_item), iTextLen);
                            // The application engineer has most likely hidden associated information in the 
                            // listbox item's text with a tab, '\t'.  If this is the case remove that hidden 
                            // information.
                            int iPos = text.IndexOf('\t');
                            if (iPos > 0)
                            {
                                text = text.Substring(0, iPos);
                            }

                            return text;
                        }
                    }

                    return "";
                }
            }

            #endregion

            #region SelectionItem Pattern

            // Selects this element
            void ISelectionItemProvider.Select ()
            {
                // Check that control can be interacted with.
                // This state could change anytime
                if (!SafeNativeMethods.IsWindowEnabled(_hwnd))
                {
                    throw new ElementNotEnabledException();
                }

                // get needed info about lbItem
                bool multipleSelected = _listBox.IsMultipleSelection ();
                bool itemSelected = IsSelected (_hwnd, _item);
                bool parentedByCombo = _listBox.IsParentedByCombo();

                if (multipleSelected)
                {
                    if (_listBox.HasOtherSelections(_item))
                    {   // some other elements are selected
                        // unselect all. It is just simplier to unselect all
                        // and re-select us, than unselect each element one by one
                        _listBox.ClearAll ();
                    }
                    else if (itemSelected)
                    {
                        // multi-selected lbItem with
                        // only 1 selected element - us
                        return;
                    }
                }
                else if (itemSelected)
                {
                    // if it is a combo, then always perform the selection otherwise the listbox won't disappear
                    if (!parentedByCombo)
                    {
                        // single-selection and we selected already
                        return;
                    }
                }

                if (parentedByCombo)
                {
                    // if this is a combo and the listbox is not displayed the selection will not stick, so 
                    // display the listbox before doing the select.
                    if (((IExpandCollapseProvider)_listBox._parent).ExpandCollapseState == ExpandCollapseState.Collapsed)
                    {
                        ((IExpandCollapseProvider)_listBox._parent).Expand();
                    }
                }

                // do the selection
                if (!Select(multipleSelected))
                {
                    throw new InvalidOperationException(SR.OperationCannotBePerformed);
                }
            }

            // Adds this element to the selection
            void ISelectionItemProvider.AddToSelection ()
            {
                // Check that control can be interacted with.
                // This state could change anytime
                if (!SafeNativeMethods.IsWindowEnabled(_hwnd))
                {
                    throw new ElementNotEnabledException();
                }

                // check if item already selected
                if (ListboxItem.IsSelected (_hwnd, _item) && !_listBox.IsParentedByCombo())
                {
                    // if it is a combo, then always perform the selection otherwise the listbox won't disappear
                    return;
                }

                bool multipleSelection = _listBox.IsMultipleSelection();

                // object does not support multi-selection
                if (!multipleSelection)
                {
                    IRawElementProviderSimple container = ((ISelectionItemProvider)this).SelectionContainer;
                    bool selectionRequired = container != null ? ((ISelectionProvider)container).IsSelectionRequired : true;

                    // For single selection containers that IsSelectionRequired == false and nothing is selected
                    // an AddToSelection is valid.
                    if (selectionRequired || _listBox.HasSelection())
                    {
                        throw new InvalidOperationException(SR.DoesNotSupportMultipleSelection);
                    }
                }

                if (_listBox.IsParentedByCombo())
                {
                    // if this is a combo and the listbox is not displayed the selection will not stick, so 
                    // display the listbox before doing the select.
                    if (((IExpandCollapseProvider)_listBox._parent).ExpandCollapseState == ExpandCollapseState.Collapsed)
                    {
                        ((IExpandCollapseProvider)_listBox._parent).Expand();
                    }
                }

                // At this point we know: Item either supports multiple selection or nothing
                // is selected in the list
                // Try to select an item
                if (!Select(multipleSelection))
                {
                    throw new InvalidOperationException(SR.OperationCannotBePerformed);
                }
            }

            // Removes this element from the selection
            void ISelectionItemProvider.RemoveFromSelection ()
            {
                // Check that control can be interacted with.
                // This state could change anytime
                if (!SafeNativeMethods.IsWindowEnabled(_hwnd))
                {
                    throw new ElementNotEnabledException();
                }

                if (!IsSelected(_hwnd, _item))
                {
                    // simple case, item is not selected
                    return;
                }

                // object does not support multi-selection
                if (!_listBox.IsMultipleSelection())
                {
                    // single-selected lb - user cannot remove the selection using keyboard and mouse
                    // At this point we know that item is selected, lb is single-selected hence
                    // RemoveFromSelection is not possible
                    throw new InvalidOperationException(SR.SelectionRequired);
                }

                if (!UnSelect(_hwnd, _item))
                {
                    throw new InvalidOperationException(SR.OperationCannotBePerformed);
                }
            }

            // True if this element is part of the the selection
            bool ISelectionItemProvider.IsSelected
            {
                get
                {
                    return ListboxItem.IsSelected (_hwnd, _item);
                }
            }

            // Returns the container for this element
            IRawElementProviderSimple ISelectionItemProvider.SelectionContainer
            {
                get
                {
                    System.Diagnostics.Debug.Assert (_parent is WindowsListBox, "Invalid Parent for a Listbox Item");
                    return _parent;
                }
            }

            #endregion SelectionItem Pattern

            #region ScrollItem Pattern

            void IScrollItemProvider.ScrollIntoView ()
            {
                if (_listBox._parentedByCombo && !SafeNativeMethods.IsWindowVisible(_hwnd))
                {
                    throw new InvalidOperationException(SR.OperationCannotBePerformed);
                }

                if (!WindowScroll.IsScrollable(_hwnd))
                {
                    throw new InvalidOperationException(SR.OperationCannotBePerformed);
                }

                // It is assumed that in a listbox an item will always be smaller than the scrolling area
                Misc.ProxySendMessage(_hwnd, NativeMethods.LB_SETTOPINDEX, new IntPtr(_item), IntPtr.Zero);
            }

            #endregion ScrollItem Pattern

            #region IToggleProvider

            void IToggleProvider.Toggle()
            {
                // Check that button can be clicked
                // This state could change anytime
                if (!SafeNativeMethods.IsWindowEnabled(_hwnd))
                {
                    throw new ElementNotEnabledException();
                }
                if (!SafeNativeMethods.IsWindowVisible(_hwnd))
                {
                    throw new InvalidOperationException(SR.OperationCannotBePerformed);
                }

                Toggle();
            }

            ToggleState IToggleProvider.ToggleState
            {
                get
                {
                    return ToggleState;
                }
            }

            #endregion IToggleProvider

            //------------------------------------------------------
            //
            //  Internal Methods
            //
            //------------------------------------------------------

            #region Internal Methods

            #region Selection Pattern Helpers

            // Detects if an item is selected
            internal static bool IsSelected (IntPtr hwnd, int item)
            {
                return Misc.ProxySendMessageInt(hwnd, NativeMethods.LB_GETSEL, new IntPtr(item), IntPtr.Zero) > 0;
            }

            internal static bool Select (IntPtr hwnd, int item, bool fMultipleSelection)
            {
                int SendMessageResult = 0;

                if (fMultipleSelection)
                {
                    SendMessageResult = Misc.ProxySendMessageInt(hwnd, NativeMethods.LB_SETSEL, new IntPtr(1), new IntPtr(item));
                }
                else
                {
                    SendMessageResult = Misc.ProxySendMessageInt(hwnd, NativeMethods.LB_SETCURSEL, new IntPtr(item), IntPtr.Zero);
                }

                return NativeMethods.LB_ERR != SendMessageResult;
            }

            #endregion

            #region Focus Helper

            // Returns an item corresponding to the focused element (if there is one), or null otherwise.
            internal override bool SetFocus ()
            {
                if (_listBox.IsMultipleSelection ())
                {
                    return Misc.ProxySendMessageInt(_hwnd, NativeMethods.LB_SETCARETINDEX, new IntPtr(_item), new IntPtr(0)) != NativeMethods.LB_ERR;
                }
                else
                {
                    return Select (_hwnd, _item, false);
                }
            }

            #endregion

            #endregion

            //------------------------------------------------------
            //
            //  Protected Methods
            //
            //------------------------------------------------------

            #region Protected Methods

            // This routine is only called on elements belonging to an hwnd that has the focus.
            protected override bool IsFocused ()
            {
                if (_listBox.IsMultipleSelection ())
                {
                    return Misc.ProxySendMessageInt(_hwnd, NativeMethods.LB_GETCARETINDEX, IntPtr.Zero, IntPtr.Zero) == _item;
                }
                else
                {
                    return Misc.ProxySendMessageInt(_hwnd, NativeMethods.LB_GETSEL, new IntPtr(_item), IntPtr.Zero) >= 0;
                }
            }

            #endregion

            //------------------------------------------------------
            //
            //  Private Methods
            //
            //------------------------------------------------------

            #region Private Methods

            private NativeMethods.Win32Rect BoundingRect()
            {
                NativeMethods.Win32Rect itemRect = new NativeMethods.Win32Rect();
                Misc.ProxySendMessage(_hwnd, NativeMethods.LB_GETITEMRECT, new IntPtr(_item), ref itemRect);
                return Misc.MapWindowPoints(_hwnd, IntPtr.Zero, ref itemRect, 2) ? itemRect : NativeMethods.Win32Rect.Empty;
            }

            // Process all the Toggle Properties
            private ToggleState ToggleState
            {
                get
                {
                    ToggleState icsState = ToggleState.Indeterminate;

                    // Special handling for forms
                    if (_windowsForms == WindowsFormsHelper.FormControlState.Undeterminate)
                    {
                        _windowsForms = WindowsFormsHelper.GetControlState(_hwnd);
                    }

                    if (_windowsForms == WindowsFormsHelper.FormControlState.True)
                    {
                        int childrenReturned;
                        object[] accChildren = Accessible.GetAccessibleChildren(this.AccessibleObject, out childrenReturned);
                        IAccessible accChild = (IAccessible)accChildren[_item];

                        if (((int)accChild.get_accState(NativeMethods.CHILD_SELF) & NativeMethods.STATE_SYSTEM_CHECKED) == NativeMethods.STATE_SYSTEM_CHECKED)
                        {
                            icsState = ToggleState.On;
                        }
                        else if (((int)accChild.get_accState(NativeMethods.CHILD_SELF) & NativeMethods.STATE_SYSTEM_MIXED) == NativeMethods.STATE_SYSTEM_MIXED)
                        {
                            icsState = ToggleState.Indeterminate;
                        }
                        else
                        {
                            icsState = ToggleState.Off;
                        }
                    }
                    return icsState;
                }
            }

            #region Selection Pattern Helpers

            private void Toggle()
            {
                // Convoluted way fake a mouse action
                NativeMethods.Win32Point pt = new NativeMethods.Win32Point();
                if (GetClickablePoint(out pt, false))
                {
                    // Mouse method is used here because following methods fail:
                    //     -BM_CLICK message doesn't work with all buttons (e.g. Start)
                    //     -WM_MOUSEACTIVATE + WM_LBUTTONDOWN + WM_LBUTTONUP messages don't work with all buttons
                    //     -WM_KEYDOWN + WM_KEYUP messages for space bar
                    //     -SendKeyboardInput for space bar
                    // See prior versions of this file for alternative code.
                    //
                    Misc.MouseClick(pt.x, pt.y);
                }
            }

            private bool Select(bool fMultipleSelection)
            {
                int sendMessageResult = 0;
                bool success = true;

                if (!((WindowsListBox)_parent).IsParentedByCombo())
                {
                    if (fMultipleSelection)
                    {
                        sendMessageResult =
                            Misc.ProxySendMessageInt(_hwnd, NativeMethods.LB_SETSEL, new IntPtr(1), new IntPtr(_item));
                    }
                    else
                    {
                        sendMessageResult =
                            Misc.ProxySendMessageInt(_hwnd, NativeMethods.LB_SETCURSEL, new IntPtr(_item), IntPtr.Zero);
                    }

                    success = (NativeMethods.LB_ERR != sendMessageResult);
                    if (success)
                    {
                        // Whether multiple selection or not, send LBN_SELCHANGE.
                        // This is normally sent when a user presses an arrow key, but
                        // NOT when LB_SETCURSEL is sent programmatically.  We need to
                        // mimic the action of a user, including all side effects.
                        int listBoxStyle = Misc.GetWindowStyle(_hwnd);
                        if (Misc.IsBitSet(listBoxStyle, NativeMethods.LBS_NOTIFY))
                        {
                            // Get the child ID of the listbox in its parent hwnd.
                            int idListBox = Misc.GetWindowId(_hwnd);
                            IntPtr wParam =
                                new IntPtr(NativeMethods.Util.MAKELONG(
                                                idListBox, NativeMethods.LBN_SELCHANGE));
                            IntPtr hwndListBoxParent = Misc.GetParent(_hwnd);
                            // The return value indicates whether the WM_COMMAND was processed,
                            // which is irrelevant, so ignore the return value here.
                            Misc.ProxySendMessageInt(
                                hwndListBoxParent, NativeMethods.WM_COMMAND, wParam, _hwnd);
                        }
                    }
                }
                else
                {
                    ProxyFragment combo = (WindowsComboBox)_parent._parent;
                    sendMessageResult =
                        Misc.ProxySendMessageInt(_hwnd, NativeMethods.LB_SETCURSEL, new IntPtr(_item), IntPtr.Zero);
                    success = (NativeMethods.LB_ERR != sendMessageResult);
                    if (success)
                    {
                        int id = Misc.GetWindowId(_hwnd);
                        IntPtr wParam = new IntPtr(NativeMethods.Util.MAKELONG(id, NativeMethods.LBN_SELCHANGE));
                        // The return value indicates whether the WM_COMMAND was processed,
                        // which is irrelevant, so ignore the return value here.
                        Misc.ProxySendMessageInt(combo._hwnd, NativeMethods.WM_COMMAND, wParam, _hwnd);
                    }
                }

                return success;
            }

            // This method should be called only on multi-selected listbox
            private bool UnSelect (IntPtr hwnd, int item)
            {
                // Even though listbox item can be unselected in the single-selected lb programmaticaly
                // user cannot do it using the keyboard and mouse hence we will not permit the "removal" of selection in the
                // single-selected listbox
                //  ProxySendMessage(hwnd, NativeMethods.LB_SETCURSEL, new IntPtr (-1), IntPtr.Zero, ref SendMessageResult );
                System.Diagnostics.Debug.Assert (_listBox.IsMultipleSelection (), "Calling UnSelect on single-selected listbox");

                return Misc.ProxySendMessageInt(hwnd, NativeMethods.LB_SETSEL, IntPtr.Zero, new IntPtr(item)) != NativeMethods.LB_ERR;
            }

            #endregion

            #endregion

            //------------------------------------------------------
            //
            //  Private Fields
            //
            //------------------------------------------------------

            #region Private Fields

            private WindowsListBox _listBox;

            #endregion

        }

        #endregion

    }
}
