# GUI Application automation and testing library
# Copyright (C) 2006-2018 Mark Mc Mahon and Contributors
# https://github.com/pywinauto/pywinauto/graphs/contributors
# http://pywinauto.readthedocs.io/en/latest/credits.html
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of pywinauto nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""Basic wrapping of UI Automation elements"""

from __future__ import unicode_literals
from __future__ import print_function

import six
import time
import warnings
import comtypes

from .. import backend
from ..timings import Timings
from ..base_wrapper import BaseWrapper
from ..base_wrapper import BaseMeta

from ..uia_defines import IUIA
from .. import uia_defines as uia_defs
from ..uia_element_info import UIAElementInfo, elements_from_uia_array

# region PATTERNS
AutomationElement = IUIA().ui_automation_client.IUIAutomationElement
DockPattern = IUIA().ui_automation_client.IUIAutomationDockPattern
ExpandCollapsePattern = IUIA().ui_automation_client.IUIAutomationExpandCollapsePattern
GridItemPattern = IUIA().ui_automation_client.IUIAutomationGridItemPattern
GridPattern = IUIA().ui_automation_client.IUIAutomationGridPattern
InvokePattern = IUIA().ui_automation_client.IUIAutomationInvokePattern
ItemContainerPattern = IUIA().ui_automation_client.IUIAutomationItemContainerPattern
LegacyIAccessiblePattern = IUIA().ui_automation_client.IUIAutomationLegacyIAccessiblePattern
MultipleViewPattern = IUIA().ui_automation_client.IUIAutomationMultipleViewPattern
RangeValuePattern = IUIA().ui_automation_client.IUIAutomationRangeValuePattern
ScrollItemPattern = IUIA().ui_automation_client.IUIAutomationScrollItemPattern
ScrollPattern = IUIA().ui_automation_client.IUIAutomationScrollPattern
SelectionItemPattern = IUIA().ui_automation_client.IUIAutomationSelectionItemPattern
SelectionPattern = IUIA().ui_automation_client.IUIAutomationSelectionPattern
SynchronizedInputPattern = IUIA().ui_automation_client.IUIAutomationSynchronizedInputPattern
TableItemPattern = IUIA().ui_automation_client.IUIAutomationTableItemPattern
TablePattern = IUIA().ui_automation_client.IUIAutomationTablePattern
TextPattern = IUIA().ui_automation_client.IUIAutomationTextPattern
TogglePattern = IUIA().ui_automation_client.IUIAutomationTogglePattern
TransformPattern = IUIA().ui_automation_client.IUIAutomationTransformPattern
ValuePattern = IUIA().ui_automation_client.IUIAutomationValuePattern
VirtualizedItemPattern = IUIA().ui_automation_client.IUIAutomationVirtualizedItemPattern
WindowPattern = IUIA().ui_automation_client.IUIAutomationWindowPattern
# endregion

# =========================================================================
_friendly_classes = {
    'Custom': None,
    'DataGrid': 'ListView',
    'DataItem': 'DataItem',
    'Document': None,  # TODO: this is RichTextBox
    'Group': 'GroupBox',
    'Header': None,
    'HeaderItem': None,
    'Hyperlink': None,
    'Image': None,
    'List': 'ListBox',
    'ListItem': 'ListItem',
    'MenuBar': 'Menu',
    'Menu': 'Menu',
    'MenuItem': 'MenuItem',
    'Pane': None,
    'ProgressBar': 'Progress',
    'ScrollBar': None,
    'Separator': None,
    'Slider': None,
    'Spinner': 'UpDown',
    'SplitButton': None,
    'Tab': 'TabControl',
    'Table': None,
    'Text': 'Static',
    'Thumb': None,
    'TitleBar': None,
    'ToolBar': 'Toolbar',
    'ToolTip': 'ToolTips',
    'Tree': 'TreeView',
    'TreeItem': 'TreeItem',
    'Window': 'Dialog',
}


# =========================================================================
class LazyProperty(object):

    """
    A lazy evaluation of an object attribute.

    The property should represent immutable data, as it replaces itself.
    Provided by: http://stackoverflow.com/a/6849299/1260742
    """

    def __init__(self, fget):
        """Init the property name and method to calculate the property"""
        self.fget = fget
        self.func_name = fget.__name__

    def __get__(self, obj, cls):
        """Replace the property itself on a first access"""
        if obj is None:
            return None
        value = self.fget(obj)
        setattr(obj, self.func_name, value)
        return value
lazy_property = LazyProperty


# =========================================================================
class UiaMeta(BaseMeta):

    """Metaclass for UiaWrapper objects"""
    control_type_to_cls = {}

    def __init__(cls, name, bases, attrs):
        """Register the control types"""

        BaseMeta.__init__(cls, name, bases, attrs)

        for t in cls._control_types:
            UiaMeta.control_type_to_cls[t] = cls

    @staticmethod
    def find_wrapper(element):
        """Find the correct wrapper for this UIA element"""
        # Set a general wrapper by default
        wrapper_match = UIAWrapper

        # Check for a more specific wrapper in the registry
        if element.control_type in UiaMeta.control_type_to_cls:
            wrapper_match = UiaMeta.control_type_to_cls[element.control_type]

        return wrapper_match


# =========================================================================
@six.add_metaclass(UiaMeta)
class UIAWrapper(BaseWrapper):

    """
    Default wrapper for User Interface Automation (UIA) controls.

    All other UIA wrappers are derived from this.

    This class wraps a lot of functionality of underlying UIA features
    for working with windows.

    Most of the methods apply to every single element type. For example
    you can click() on any element.
    """

    _control_types = []

    # ------------------------------------------------------------
    def __new__(cls, element_info):
        """Construct the control wrapper"""
        return super(UIAWrapper, cls)._create_wrapper(cls, element_info, UIAWrapper)

    # -----------------------------------------------------------
    def __init__(self, element_info):
        """
        Initialize the control

        * **element_info** is either a valid UIAElementInfo or it can be an
          instance or subclass of UIAWrapper.
        If the handle is not valid then an InvalidWindowHandle error
        is raised.
        """
        BaseWrapper.__init__(self, element_info, backend.registry.backends['uia'])

    # ------------------------------------------------------------
    def __hash__(self):
        """Return a unique hash value based on the element's Runtime ID"""
        return hash(self.element_info.runtime_id)

    # ------------------------------------------------------------
    @lazy_property
    def iface_expand_collapse(self):
        """Get the element's ExpandCollapse interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "ExpandCollapse")

    # ------------------------------------------------------------
    @lazy_property
    def iface_selection(self):
        """Get the element's Selection interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "Selection")

    # ------------------------------------------------------------
    @lazy_property
    def iface_selection_item(self):
        """Get the element's SelectionItem interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "SelectionItem")

    # ------------------------------------------------------------
    @lazy_property
    def iface_invoke(self):
        """Get the element's Invoke interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "Invoke")

    # ------------------------------------------------------------
    @lazy_property
    def iface_toggle(self):
        """Get the element's Toggle interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "Toggle")

    # ------------------------------------------------------------
    @lazy_property
    def iface_text(self):
        """Get the element's Text interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "Text")

    # ------------------------------------------------------------
    @lazy_property
    def iface_value(self):
        """Get the element's Value interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "Value")

    # ------------------------------------------------------------
    @lazy_property
    def iface_range_value(self):
        """Get the element's RangeValue interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "RangeValue")

    # ------------------------------------------------------------
    @lazy_property
    def iface_grid(self):
        """Get the element's Grid interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "Grid")

    # ------------------------------------------------------------
    @lazy_property
    def iface_grid_item(self):
        """Get the element's GridItem interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "GridItem")

    # ------------------------------------------------------------
    @lazy_property
    def iface_table(self):
        """Get the element's Table interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "Table")

    # ------------------------------------------------------------
    @lazy_property
    def iface_table_item(self):
        """Get the element's TableItem interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "TableItem")

    # ------------------------------------------------------------
    @lazy_property
    def iface_scroll_item(self):
        """Get the element's ScrollItem interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "ScrollItem")

    # ------------------------------------------------------------
    @lazy_property
    def iface_scroll(self):
        """Get the element's Scroll interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "Scroll")

    # ------------------------------------------------------------
    @lazy_property
    def iface_transform(self):
        """Get the element's Transform interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "Transform")

    # ------------------------------------------------------------
    @lazy_property
    def iface_transformV2(self):
        """Get the element's TransformV2 interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "TransformV2")

    # ------------------------------------------------------------
    @lazy_property
    def iface_window(self):
        """Get the element's Window interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "Window")

    # ------------------------------------------------------------
    @lazy_property
    def iface_item_container(self):
        """Get the element's ItemContainer interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "ItemContainer")

    # ------------------------------------------------------------
    @lazy_property
    def iface_virtualized_item(self):
        """Get the element's VirtualizedItem interface pattern"""
        elem = self.element_info.element
        return uia_defs.get_elem_interface(elem, "VirtualizedItem")

    # ------------------------------------------------------------
    @property
    def writable_props(self):
        """Extend default properties list."""
        props = super(UIAWrapper, self).writable_props
        props.extend(['is_keyboard_focusable',
                      'has_keyboard_focus',
                      'automation_id',
                      ])
        return props

    # ------------------------------------------------------------
    def legacy_properties(self):
        """Get the element's LegacyIAccessible control pattern interface properties"""
        elem = self.element_info.element
        impl = uia_defs.get_elem_interface(elem, "LegacyIAccessible")
        property_name_identifier = 'Current'

        interface_properties = [prop for prop in dir(LegacyIAccessiblePattern)
                                if (isinstance(getattr(LegacyIAccessiblePattern, prop), property)
                                and property_name_identifier in prop)]

        return {prop.replace(property_name_identifier, '') : getattr(impl, prop) for prop in interface_properties}

    # ------------------------------------------------------------
    def friendly_class_name(self):
        """
        Return the friendly class name for the control

        This differs from the class of the control in some cases.
        class_name() is the actual 'Registered' window class of the control
        while friendly_class_name() is hopefully something that will make
        more sense to the user.

        For example Checkboxes are implemented as Buttons - so the class
        of a CheckBox is "Button" - but the friendly class is "CheckBox"
        """
        if self.friendlyclassname is None:
            if self.element_info.control_type not in IUIA().known_control_types.keys():
                self.friendlyclassname = self.element_info.control_type
            else:
                ctrl_type = self.element_info.control_type
                if (ctrl_type not in _friendly_classes) or (_friendly_classes[ctrl_type] is None):
                    self.friendlyclassname = ctrl_type
                else:
                    self.friendlyclassname = _friendly_classes[ctrl_type]
        return self.friendlyclassname

    #------------------------------------------------------------
    def automation_id(self):
        """Return the Automation ID of the control"""
        return self.element_info.automation_id

    # -----------------------------------------------------------
    def is_keyboard_focusable(self):
        """Return True if the element can be focused with keyboard"""
        return self.element_info.element.CurrentIsKeyboardFocusable == 1

    # -----------------------------------------------------------
    def has_keyboard_focus(self):
        """Return True if the element is focused with keyboard"""
        return self.element_info.element.CurrentHasKeyboardFocus == 1

    # -----------------------------------------------------------
    def set_focus(self):
        """Set the focus to this element"""
        try:
            if self.is_minimized():
                if self.was_maximized():
                    self.maximize()
                else:
                    self.restore()
        except uia_defs.NoPatternInterfaceError:
            pass
        try:
            self.element_info.element.SetFocus()
        except comtypes.COMError as exc:
            warnings.warn('The window has not been focused due to ' \
                'COMError: {}'.format(exc), RuntimeWarning)

        return self

    # TODO: figure out how to implement .has_focus() method (if no handle available)

    # -----------------------------------------------------------
    def close(self):
        """
        Close the window

        Only a control supporting Window pattern should answer.
        If it doesn't (menu shadows, tooltips,...), try to send "Esc" key
        """
        try:
            name = self.element_info.name
            control_type = self.element_info.control_type

            iface = self.iface_window
            iface.Close()

            if name and control_type:
                self.actions.log("Closed " + control_type.lower() + ' "' +  name + '"')
        except(uia_defs.NoPatternInterfaceError):
            self.type_keys("{ESC}")

    # -----------------------------------------------------------
    def minimize(self):
        """
        Minimize the window

        Only controls supporting Window pattern should answer
        """
        iface = self.iface_window
        if iface.CurrentCanMinimize:
            iface.SetWindowVisualState(uia_defs.window_visual_state_minimized)
        return self

    # -----------------------------------------------------------
    def maximize(self):
        """
        Maximize the window

        Only controls supporting Window pattern should answer
        """
        iface = self.iface_window
        if iface.CurrentCanMaximize:
            iface.SetWindowVisualState(uia_defs.window_visual_state_maximized)
        return self

    # -----------------------------------------------------------
    def restore(self):
        """
        Restore the window to normal size

        Only controls supporting Window pattern should answer
        """
        iface = self.iface_window
        iface.SetWindowVisualState(uia_defs.window_visual_state_normal)
        return self

    # -----------------------------------------------------------
    def get_show_state(self):
        """Get the show state and Maximized/minimzed/restored state

        Returns values as following

        window_visual_state_normal = 0
        window_visual_state_maximized = 1
        window_visual_state_minimized = 2
        """
        iface = self.iface_window
        ret = iface.CurrentWindowVisualState

        return ret

    # -----------------------------------------------------------
    def is_minimized(self):
        """Indicate whether the window is minimized or not"""
        return self.get_show_state() == uia_defs.window_visual_state_minimized

    # -----------------------------------------------------------
    def is_maximized(self):
        """Indicate whether the window is maximized or not"""
        return self.get_show_state() == uia_defs.window_visual_state_maximized

    # -----------------------------------------------------------
    def is_normal(self):
        """Indicate whether the window is normal (i.e. not minimized and not maximized)"""
        return self.get_show_state() == uia_defs.window_visual_state_normal

    # -----------------------------------------------------------
    def invoke(self):
        """An interface to the Invoke method of the Invoke control pattern"""
        name = self.element_info.name
        control_type = self.element_info.control_type

        self.iface_invoke.Invoke()

        if name and control_type:
            self.actions.log("Invoked " + control_type.lower() + ' "' + name + '"')
        # Return itself to allow action chaining
        return self

    # -----------------------------------------------------------
    def expand(self):
        """
        Displays all child nodes, controls, or content of the control

        An interface to Expand method of the ExpandCollapse control pattern.
        """
        self.iface_expand_collapse.Expand()

        # Return itself to allow action chaining
        return self

    # -----------------------------------------------------------
    def collapse(self):
        """
        Displays all child nodes, controls, or content of the control

        An interface to Collapse method of the ExpandCollapse control pattern.
        """
        self.iface_expand_collapse.Collapse()

        # Return itself to allow action chaining
        return self

    # -----------------------------------------------------------
    def get_expand_state(self):
        """
        Indicates the state of the control: expanded or collapsed.

        An interface to CurrentExpandCollapseState property of the ExpandCollapse control pattern.
        Values for enumeration as defined in uia_defines module:
        expand_state_collapsed = 0
        expand_state_expanded = 1
        expand_state_partially = 2
        expand_state_leaf_node = 3
        """
        return self.iface_expand_collapse.CurrentExpandCollapseState

    # -----------------------------------------------------------
    def is_expanded(self):
        """Test if the control is expanded"""
        state = self.get_expand_state()
        return state == uia_defs.expand_state_expanded

    # -----------------------------------------------------------
    def is_collapsed(self):
        """Test if the control is collapsed"""
        state = self.get_expand_state()
        return state == uia_defs.expand_state_collapsed

    # -----------------------------------------------------------
    def get_selection(self):
        """
        An interface to GetSelection of the SelectionProvider pattern

        Retrieves a UI Automation provider for each child element
        that is selected. Builds a list of UIAElementInfo elements
        from all retrieved providers.
        """
        ptrs_array = self.iface_selection.GetCurrentSelection()
        return elements_from_uia_array(ptrs_array)

    # -----------------------------------------------------------
    def selected_item_index(self):
        """Return the index of a selected item"""
        # Go through all children and look for an index
        # of an item with the same text.
        # Maybe there is another and more efficient way to do it
        selection = self.get_selection()
        if selection:
            for i, c in enumerate(self.children()):
                if c.window_text() == selection[0].name:
                    return i
        return None

    # -----------------------------------------------------------
    def select(self):
        """Select the item

        Only items supporting SelectionItem pattern should answer.
        Raise NoPatternInterfaceError if the pattern is not supported

        Usually applied for controls like: a radio button, a tree view item
        or a list item.
        """
        self.iface_selection_item.Select()

        name = self.element_info.name
        control_type = self.element_info.control_type
        if name and control_type:
            self.actions.log("Selected " + control_type.lower() + ' "' + name + '"')

        # Return itself so that action can be chained
        return self

    # -----------------------------------------------------------
    def is_selected(self):
        """Indicate that the item is selected or not.

        Only items supporting SelectionItem pattern should answer.
        Raise NoPatternInterfaceError if the pattern is not supported

        Usually applied for controls like: a radio button, a tree view item,
        a list item.
        """
        return self.iface_selection_item.CurrentIsSelected

    # -----------------------------------------------------------
    def children_texts(self):
        """Get texts of the control's children"""
        return [c.window_text() for c in self.children()]

    # -----------------------------------------------------------
    def can_select_multiple(self):
        """
        An interface to CanSelectMultiple of the SelectionProvider pattern

        Indicates whether the UI Automation provider allows more than one
        child element to be selected concurrently.
        """
        return self.iface_selection.CurrentCanSelectMultiple

    # -----------------------------------------------------------
    def is_selection_required(self):
        """
        An interface to IsSelectionRequired property of the SelectionProvider pattern.

        This property can be dynamic. For example, the initial state of
        a control might not have any items selected by default,
        meaning that IsSelectionRequired is FALSE. However,
        after an item is selected the control must always have
        at least one item selected.
        """
        return self.iface_selection.CurrentIsSelectionRequired

    # -----------------------------------------------------------
    def _select(self, item=None):
        """
        Find a child item by the name or index and select

        The action can be applied for dirrent controls with items:
        ComboBox, TreeView, Tab control
        """
        if isinstance(item, six.integer_types):
            item_index = item
            title = None
        elif isinstance(item, six.string_types):
            item_index = 0
            title = item
        else:
            err_msg = u"unsupported {0} for item {1}".format(type(item), item)
            raise ValueError(err_msg)

        list_ = self.children(title=title)
        if item_index < len(list_):
            wrp = list_[item_index]
            wrp.iface_selection_item.Select()
        else:
            raise IndexError("item '{0}' not found".format(item))

    # -----------------------------------------------------------
    def is_active(self):
        """Whether the window is active or not"""
        ae = IUIA().get_focused_element()
        focused_wrap = UIAWrapper(UIAElementInfo(ae))
        return (focused_wrap.top_level_parent() == self.top_level_parent())

    # -----------------------------------------------------------
    def is_dialog(self):
        """Return true if the control is a dialog window (WindowPattern interface is available)"""
        try:
            return self.iface_window is not None
        except uia_defs.NoPatternInterfaceError:
            return False

    # -----------------------------------------------------------
    def menu_select(self, path, exact=False, ):
        """Select a menu item specified in the path

        The full path syntax is specified in:
        :py:meth:`pywinauto.menuwrapper.Menu.get_menu_path`

        There are usually at least two menu bars: "System" and "Application"
        System menu bar is a standard window menu with items like:
        'Restore', 'Move', 'Size', 'Minimize', e.t.c.
        This menu bar usually has a "Title Bar" control as a parent.
        Application menu bar is often what we look for. In most cases,
        its parent is the dialog itself so it should be found among the direct
        children of the dialog. Notice that we don't use "Application"
        string as a title criteria because it couldn't work on applications
        with a non-english localization.
        If there is no menu bar has been found we fall back to look up
        for Menu control. We try to find the control through all descendants
        of the dialog
        """
        self.verify_actionable()

        cc = self.children(control_type="MenuBar")
        if not cc:
            cc = self.descendants(control_type="Menu")
            if not cc:
                raise AttributeError
        menu = cc[0]
        menu.item_by_path(path, exact).select()

    # -----------------------------------------------------------
    _scroll_types = {
        "left": {
            "line": (uia_defs.scroll_small_decrement, uia_defs.scroll_no_amount),
            "page": (uia_defs.scroll_large_decrement, uia_defs.scroll_no_amount),
        },
        "right": {
            "line": (uia_defs.scroll_small_increment, uia_defs.scroll_no_amount),
            "page": (uia_defs.scroll_large_increment, uia_defs.scroll_no_amount),
        },
        "up": {
            "line": (uia_defs.scroll_no_amount, uia_defs.scroll_small_decrement),
            "page": (uia_defs.scroll_no_amount, uia_defs.scroll_large_decrement),
        },
        "down": {
            "line": (uia_defs.scroll_no_amount, uia_defs.scroll_small_increment),
            "page": (uia_defs.scroll_no_amount, uia_defs.scroll_large_increment),
        },
    }

    def scroll(self, direction, amount, count=1, retry_interval=Timings.scroll_step_wait):
        """Ask the control to scroll itself

        **direction** can be any of "up", "down", "left", "right"
        **amount** can be only "line" or "page"
        **count** (optional) the number of times to scroll
        **retry_interval** (optional) interval between scroll actions
        """
        def _raise_attrib_err(details):
            control_type = self.element_info.control_type
            name = self.element_info.name
            msg = "".join([control_type.lower(), ' "', name, '" ', details])
            raise AttributeError(msg)

        try:
            scroll_if = self.iface_scroll
            if direction.lower() in ("up", "down"):
                if not scroll_if.CurrentVerticallyScrollable:
                    _raise_attrib_err('is not vertically scrollable')
            elif direction.lower() in ("left", "right"):
                if not scroll_if.CurrentHorizontallyScrollable:
                    _raise_attrib_err('is not horizontally scrollable')

            h, v = self._scroll_types[direction.lower()][amount.lower()]

            # Scroll as often as we have been asked to
            for _ in range(count, 0, -1):
                scroll_if.Scroll(h, v)
                time.sleep(retry_interval)

        except uia_defs.NoPatternInterfaceError:
            _raise_attrib_err('is not scrollable')
        except KeyError:
            raise ValueError("""Wrong arguments:
                direction can be any of "up", "down", "left", "right"
                amount can be only "line" or "page"
                """)

        return self


backend.register('uia', UIAElementInfo, UIAWrapper)
