text: # -*- coding: utf-8 -*-
  import functools
  import inspect
  import types
  from copy import copy
  
  from selenium.common.exceptions import NoSuchElementException
  from selenium.webdriver.remote.webelement import WebElement
  from smartloc import Locator
  from wait_for import wait_for
  
  from widgetastic.browser import Browser, BrowserParentWrapper
  from widgetastic.exceptions import DoNotReadThisWidget, LocatorNotImplemented
  from widgetastic.log import (create_child_logger, call_sig, logged, PrependParentsAdapter,
                               create_widget_logger)
  from widgetastic.utils import (Fillable, ConstructorResolvable, ParametrizedString, Widgetable,
                                 ParametrizedLocator, nested_getattr, deflatten_dict,
                                 DefaultFillViewStrategy, FillContext)
  
  
  def do_not_read_this_widget():
      """Call inside widget's read method in case you don't want it to appear in the data."""
      raise DoNotReadThisWidget('Do not read this widget.')
  
  
  def wrap_fill_method(method):
      """Generates a method that automatically coerces the first argument as Fillable."""
      @functools.wraps(method)
      def wrapped(self, value, *args, **kwargs):
          return method(self, Fillable.coerce(value), *args, **kwargs)
  
      return wrapped
  
  
  def resolve_verpicks_in_method(method):
      """Generates a method that automatically resolves VersionPick attributes"""
      @functools.wraps(method)
      def wrapped(self, *args, **kwargs):
          def resolve_arg(parent, arg):
              if (isinstance(arg, ConstructorResolvable) and not (
                      method.__name__ == '__new__' or
                      isinstance(arg, ParametrizedString) or
                      hasattr(method, 'skip_resolve'))):
                  # 1. ParametrizedString is also ConstructorResolvable but
                  # it should be resolved later in other place
                  # 2. if method has skip_resolve attr, its arguments won't be automatically resolved
                  # 3. __new__ doesn't require resolve
                  return arg.resolve(parent)
              else:
                  return arg
  
          if method.__name__ == '__init__':
              # __init__ doesn't have initialized self.parent.
              # So, we need to look for parent/browser in arguments
              if args and isinstance(args[0], (Widget, Browser)):
                  parent = args[0]
              elif 'parent' in kwargs and isinstance(kwargs['parent'], (Widget, Browser)):
                  parent = kwargs['parent']
              else:
                  raise ValueError("parent isn't passed to init")
          else:
              parent = self
  
          new_args = [resolve_arg(parent, arg) for arg in args]
          new_kwargs = {key: resolve_arg(parent, value) for key, value in kwargs.items()}
  
          return method(self, *new_args, **new_kwargs)
      return wrapped
  
  
  def process_parameters(parent_obj, args, kwargs):
      """Processes the widget input parameters - checks if args or kwarg values are parametrized."""
      new_args = []
      for arg in args:
          if isinstance(arg, ConstructorResolvable):
              new_args.append(arg.resolve(parent_obj))
          else:
              new_args.append(arg)
  
      new_kwargs = {}
      for k, v in kwargs.items():
          if isinstance(v, ConstructorResolvable):
              new_kwargs[k] = v.resolve(parent_obj)
          else:
              new_kwargs[k] = v
  
      return new_args, new_kwargs
  
  
  class WidgetDescriptor(Widgetable):
      """This class handles instantiating and caching of the widgets on view.
  
      It stores the class and the parameters it should be instantiated with. Once it is accessed from
      the instance of the class where it was defined on, it passes the instance to the widget class
      followed by args and then kwargs.
  
      It also acts as a counter, so you can then order the widgets by their "creation" stamp.
      """
      def __init__(self, klass, *args, **kwargs):
          self.klass = klass
          self.args = args
          self.kwargs = kwargs
          # TODO: WE can bring it back, popping out just for compatibility sake
          self.kwargs.pop('log_on_fill_unspecified', None)
  
      def __get__(self, obj, type=None):
          if obj is None:  # class access
              return self
  
          # Cache on WidgetDescriptor
          if self not in obj._widget_cache:
              kwargs = copy(self.kwargs)
              try:
                  kwargs['logger'] = create_child_logger(obj.logger, obj._desc_name_mapping[self])
              except KeyError:
                  kwargs['logger'] = obj.logger
              except AttributeError:
                  pass
  
              args, kwargs = process_parameters(obj, self.args, kwargs)
              if issubclass(self.klass, ParametrizedView):
                  # Shortcut, don't cache as the ParametrizedViewRequest is not the widget yet
                  return ParametrizedViewRequest(obj, self.klass, *args, **kwargs)
              else:
                  o = self.klass(obj, *args, **kwargs)
                  o.parent_descriptor = self
                  obj._widget_cache[self] = o
          widget = obj._widget_cache[self]
          obj.child_widget_accessed(widget)
          return widget
  
      def __repr__(self):
          return '{}{}'.format(self.klass.__name__, call_sig(self.args, self.kwargs))
  
  
  class ExtraData(object):
      """This class implements a simple access to the extra data passed through
      :py:class:`widgetastic.browser.Browser` object.
  
      .. code-block:: python
  
          widget.extra.foo
          # is equivalent to
          widget.browser.extra_objects['foo']
      """
      # TODO: Possibly replace it with a descriptor of some sort?
      def __init__(self, widget):
          self._widget = widget
  
      @property
      def _extra_objects_list(self):
          return list(self._widget.browser.extra_objects)
  
      def __dir__(self):
          return self._extra_objects_list
  
      def __getattr__(self, attr):
          try:
              return self._widget.browser.extra_objects[attr]
          except KeyError:
              raise AttributeError('Extra object {!r} was not found ({} are available)'.format(
                  attr, ', '.join(self._extra_objects_list)))
  
  
  class WidgetIncluder(Widgetable):
      """Includes widgets from another widget. Useful for sharing pieces of code."""
      def __init__(self, widget_class, use_parent=False):
          self.widget_class = widget_class
          self.use_parent = use_parent
  
      def __repr__(self):
          return '{}({})'.format(type(self).__name__, self.widget_class.__name__)
  
  
  class IncludedWidget(object):
      def __init__(self, included_id, widget_name, use_parent):
          self.included_id = included_id
          self.widget_name = widget_name
          self.use_parent = use_parent
  
      def __get__(self, o, t=None):
          if o is None:
              return self
  
          return o._get_included_widget(self.included_id, self.widget_name, self.use_parent)
  
      def __repr__(self):
          return '{}({}, {!r})'.format(type(self).__name__, self.included_id, self.widget_name)
  
  
  class WidgetMetaclass(type):
      """Metaclass that ensures that ``fill`` and ``read`` methods are logged and coerce Fillable
      properly.
  
      For ``fill`` methods placed in :py:class:`Widget` descendants it first wraps them using
      :py:func:`wrap_fill_method` that ensures that :py:class:`widgetastic.utils.Fillable` can be
      passed and then it wraps them in the :py:func:`widgetastic.log.logged`.
  
      The same happens for ``read`` except the ``wrap_fill_method`` which is only useful for ``fill``.
  
      Therefore, you shall not wrap any ``read`` or ``fill`` methods in
      :py:func:`widgetastic.log.logged`.
      """
      def __new__(cls, name, bases, attrs):
          new_attrs = {}
          desc_name_mapping = {}
          included_widgets = []
          for base in bases:
              for key, value in getattr(base, '_desc_name_mapping', {}).items():
                  desc_name_mapping[key] = value
              for widget_includer in getattr(base, '_included_widgets', ()):
                  included_widgets.append(widget_includer)
                  for widget_name in widget_includer.widget_class.cls_widget_names():
                      new_attrs[widget_name] = IncludedWidget(widget_includer._seq_id, widget_name,
                                                              widget_includer.use_parent)
  
          for key, value in attrs.items():
              if inspect.isclass(value) and issubclass(value, View):
                  new_attrs[key] = WidgetDescriptor(value)
                  desc_name_mapping[new_attrs[key]] = key
              elif isinstance(value, WidgetIncluder):
                  included_widgets.append(value)
                  # Now generate accessors for each included widget
                  for widget_name in value.widget_class.cls_widget_names():
                      new_attrs[widget_name] = IncludedWidget(value._seq_id, widget_name,
                                                              value.use_parent)
              elif isinstance(value, Widgetable):
                  new_attrs[key] = value
                  desc_name_mapping[value] = key
                  for widget in value.child_items:
                      if not isinstance(widget, (Widgetable, Widget)):
                          continue
                      desc_name_mapping[widget] = key
              elif key == 'fill':
                  # handle fill() specifics
                  new_attrs[key] = logged(log_args=True, log_result=True)(wrap_fill_method(value))
              elif key == 'read':
                  # handle read() specifics
                  new_attrs[key] = logged(log_result=True)(value)
              elif isinstance(value, types.FunctionType):
                  # VP resolution wrapper, allows to resolve VersionPicks in all widget methods
                  new_attrs[key] = resolve_verpicks_in_method(value)
              else:
                  # Do nothing
                  new_attrs[key] = value
          if 'ROOT' in new_attrs and '__locator__' not in new_attrs:
              # For handling the root locator of the View
              root = new_attrs['ROOT']
              if isinstance(root, ParametrizedLocator):
                  new_attrs['__locator__'] = _gen_locator_root()
              else:
                  new_attrs['__locator__'] = _gen_locator_meth(Locator(root))
          new_attrs['_included_widgets'] = tuple(sorted(included_widgets, key=lambda w: w._seq_id))
          new_attrs['_desc_name_mapping'] = desc_name_mapping
          return super(WidgetMetaclass, cls).__new__(cls, name, bases, new_attrs)
  
  
  class Widget(object, metaclass=WidgetMetaclass):
      """Base class for all UI objects.
  
      Does couple of things:
  
          * Ensures it gets instantiated with a browser or another widget as parent. If you create an
            instance in a class, it then creates a :py:class:`WidgetDescriptor` which is then invoked
            on the instance and instantiates the widget with underlying browser.
          * Implements some basic interface for all widgets.
  
      If you are inheriting from this class, you **MUST ALWAYS** ensure that the inherited class
      has an init that always takes the ``parent`` as the first argument. You can do that on your
      own, setting the parent as ``self.parent`` or you can do something like this:
  
      .. code-block:: python
  
          def __init__(self, parent, arg1, arg2, logger=None):
              super(MyClass, self).__init__(parent, logger=logger)
              # or if you have somehow complex inheritance ...
              Widget.__init__(self, parent, logger=logger)
      """
  
      #: Default value for parent_descriptor
      parent_descriptor = None
  
      # Helper methods
      @staticmethod
      def include(*args, **kwargs):
          """Include another widget with exposing the given widget's widgets in this widget."""
          return WidgetIncluder(*args, **kwargs)
  
      def __new__(cls, *args, **kwargs):
          """Implement some typing saving magic.
  
          Unless you are passing a :py:class:`Widget` or :py:class:`widgetastic.browser.Browser`
          as a first argument which implies the instantiation of an actual widget, it will return
          :py:class:`WidgetDescriptor` instead which will resolve automatically inside of
          :py:class:`View` instance.
  
          This allows you a sort of Django-ish access to the defined widgets then.
          """
          if (args and isinstance(args[0], (Widget, Browser))) \
                  or ('parent' in kwargs and isinstance(kwargs['parent'], (Widget, Browser))):
              return super(Widget, cls).__new__(cls)
          else:
              return WidgetDescriptor(cls, *args, **kwargs)
  
      def __init__(self, parent, logger=None):
          self.parent = parent
          if logger is None:
              self.logger = create_child_logger(parent.logger, type(self).__name__)
          elif isinstance(logger, PrependParentsAdapter):
              # The logger is already prepared
              self.logger = logger
          else:
              # We need a PrependParentsAdapter here.
              self.logger = create_widget_logger(type(self).__name__, logger)
          self.extra = ExtraData(self)
          self._widget_cache = {}
          self._initialized_included_widgets = {}
  
      def __element__(self):
          """Implement the logic of querying
          :py:class:`selenium.webdriver.remote.webelement.WebElement` from Selenium.
  
          It uses :py:meth:`__locator__` to retrieve the locator and then it looks up the WebElement
          on the parent's browser.
  
          If the ``__locator__`` isbadly implemented and returns a ``WebElement`` instance, it returns
          it directly.
  
          You usually want this method to be intact.
          """
          try:
              locator = self.__locator__()
          except AttributeError:
              raise AttributeError(
                  '__locator__() is not defined on {} class'.format(type(self).__name__))
          else:
              if isinstance(locator, WebElement):
                  self.logger.warning(
                      '__locator__ of %s class returns a WebElement!', type(self).__name__)
                  return locator
              else:
                  return self.parent_browser.element(locator)
  
      def _get_included_widget(self, includer_id, widget_name, use_parent):
          if includer_id not in self._initialized_included_widgets:
              for widget_includer in self._included_widgets:
                  if widget_includer._seq_id == includer_id:
                      parent = self if use_parent else self.parent
                      self._initialized_included_widgets[widget_includer._seq_id] =\
                          widget_includer.widget_class(parent, self.logger)
                      break
              else:
                  raise ValueError('Could not find includer #{}'.format(includer_id))
          return getattr(self._initialized_included_widgets[includer_id], widget_name)
  
      def flush_widget_cache(self):
          """Flush the widget cache recursively for the whole :py:class:`Widget` tree structure.
  
          Do not use this unless you see glitches and ``StaleElementReferenceException``. Well written
          widgets should not need flushing.
          """
          for widget in self._widget_cache.values():
              try:
                  widget.flush_widget_cache()
              except AttributeError:
                  # ParametrizedViewRequest does this, we can safely ignore that
                  pass
          self._widget_cache.clear()
          for widget in self._initialized_included_widgets.values():
              try:
                  widget.flush_widget_cache()
              except AttributeError:
                  # ParametrizedViewRequest does this, we can safely ignore that
                  pass
          self._initialized_included_widgets.clear()
  
      @classmethod
      def cls_widget_names(cls):
          """Returns a list of widget names in the order they were defined on the class.
  
          Returns:
              A :py:class:`list` of :py:class:`Widget` instances.
          """
          result = []
          for key in dir(cls):
              value = getattr(cls, key)
              if isinstance(value, Widgetable):
                  # check the values of a VersionPick object are widgetable themselves
                  if all([isinstance(item, Widgetable) for item in value.child_items]):
                      result.append((key, value))
          for includer in cls._included_widgets:
              result.append((None, includer))
          presorted_widgets = sorted(result, key=lambda pair: pair[1]._seq_id)
          result = []
          for name, widget in presorted_widgets:
              if isinstance(widget, WidgetIncluder):
                  result.extend(widget.widget_class.cls_widget_names())
              else:
                  result.append(name)
          return tuple(result)
  
      @property
      def widget_names(self):
          """Returns a list of widget names in the order they were defined on the class.
  
          Returns:
              A :py:class:`list` of :py:class:`Widget` instances.
          """
          return self.cls_widget_names()
  
      @property
      def hierarchy(self):
          """Returns a list of widgets from the top level to this one."""
          if not isinstance(self.parent, Widget):
              return [self]
          else:
              return self.parent.hierarchy + [self]
  
      @property
      def locatable_parent(self):
          """If the widget has a parent that is locatable, returns it. Otherwise returns None"""
          for locatable in list(reversed(self.hierarchy))[1:]:
              if hasattr(locatable, '__locator__') and not getattr(locatable, 'INDIRECT', False):
                  return locatable
          else:
              return None
  
      @property
      def root_browser(self):
          return self.parent.root_browser
  
      @property
      def parent_browser(self):
          try:
              return self.locatable_parent.browser
          except AttributeError:
              # locatable_parent == None
              return self.root_browser
  
      @property
      def browser(self):
          """Returns the instance of parent browser.
  
          If the view defines ``__locator__`` or ``ROOT`` then a new wrapper is created that injects
          the ``parent=``
  
          Returns:
              :py:class:`widgetastic.browser.Browser` instance
  
          Raises:
              :py:class:`ValueError` when the browser is not defined, which is an error.
          """
          try:
              if hasattr(self, '__locator__'):
                  # Wrap it so we have automatic parent injection
                  return BrowserParentWrapper(self, self.root_browser)
              else:
                  # This view has no locator, therefore just use the parent browser
                  return self.root_browser
          except AttributeError:
              raise ValueError('Unknown value {!r} specified as parent.'.format(self.parent))
  
      @property
      def parent_view(self):
          """Returns a parent view, if the widget lives inside one.
  
          Returns:
              :py:class:`View` instance if the widget is defined in one, otherwise ``None``.
          """
          if isinstance(self.parent, View):
              return self.parent
          else:
              return None
  
      @property
      def is_displayed(self):
          """Shortcut allowing you to detect if the widget is displayed.
  
          If the logic behind is_displayed is more complex, you can always override this.
  
          Returns:
              :py:class:`bool`
          """
          return self.browser.is_displayed(self)
  
      @property
      def is_enabled(self):
          """Check widget is enabled or not.
  
          The logic behind `is_enabled` is WebElement property. If `__locator__` not pointing to the
          expected `WebElement`, you can always override this.
  
          Returns:
              :py:class:`bool`
          """
          return self.browser.element(self).is_enabled()
  
      @logged()
      def wait_displayed(self, timeout='10s', delay=0.2):
          """Wait for the element to be displayed. Uses the :py:meth:`is_displayed`
  
          Args:
              timeout: If you want, you can override the default timeout here
              delay: override default delay for wait_for iterations
          """
          ret, _ = wait_for(lambda: self.is_displayed, timeout=timeout, delay=delay)
          return ret
  
      @logged()
      def move_to(self):
          """Moves the mouse to the Selenium WebElement that is resolved by this widget.
  
          Returns:
              :py:class:`selenium.webdriver.remote.webelement.WebElement` instance
          """
          return self.browser.move_to_element(self)
  
      def child_widget_accessed(self, widget):
          """Called when a child widget of this widget gets accessed.
  
          Useful when eg. the containing widget needs to open for the child widget to become visible.
  
          Args:
              widget: The widget being accessed.
          """
          pass
  
      def fill(self, *args, **kwargs):
          """Interactive objects like inputs, selects, checkboxes, et cetera should implement fill.
  
          When you implement this method, it *MUST ALWAYS* return a boolean whether the value
          *was changed*. Otherwise it can break.
  
          For actual filling, please use :py:meth:`fill_with`. It offers richer interface for filling.
  
          Returns:
              A boolean whether it changed the value or not.
          """
          raise NotImplementedError(
              'Widget {} does not implement fill()!'.format(type(self).__name__))
  
      def read(self, *args, **kwargs):
          """Each object should implement read so it is easy to get the value of such object.
  
          When you implement this method, the exact return value is up to you but it *MUST* be
          consistent with what :py:meth:`fill` takes.
          """
          raise NotImplementedError(
              'Widget {} does not implement read()!'.format(type(self).__name__))
  
      def _process_fill_handler(self, handler):
          """Processes a given handler in the way that it is usable as a callable + its representation
  
          Handlers can come in variety of ways. Simplest thing is to pass a callable, it will get
          executed. The handler can also work with classes that mix in :py:class:`ClickableMixin`
          where they use the :py:meth:`CallableMixin.click` as the handler action. If you pass a
          string, it will first get resolved by getting it as an attribute of the instance. Then all
          abovementioned steps are tried.
  
          Args:
              handler: The handler. More explanation in the description of this method.
  
          Returns:
              A 2-tuple consisting of ``(action_callable, obj_for_repr)``. The ``obj_for_repr`` is an
              object that can be passed to a logger that uses ``%r``.
          """
          if isinstance(handler, str):
              try:
                  handler = getattr(self, handler)
              except AttributeError:
                  raise TypeError('{} does not exist on {!r}'.format(handler, self))
  
          if isinstance(handler, ClickableMixin):
              return (handler.click, handler)
          elif callable(handler):
              return (handler, handler)
          else:
              raise TypeError('Fill handler must be callable or clickable.')
  
      def fill_with(self, value, on_change=None, no_change=None):
          """Method to fill the widget, especially usable when filling in forms.
  
          Args:
              value: Value to fill - gets passed to :py:meth:`fill`
              on_change: Optional handler to be executed when there was a change. See
                  :py:meth`_process_fill_handler` for details
              no_change: Optional handler to be executed when there was no change. See
                  :py:meth`_process_fill_handler` for details
  
          Returns:
              Whether there was any change. Same as :py:meth:`fill`.
          """
          changed = self.fill(value)
          if changed:
              if on_change is not None:
                  action, rep = self._process_fill_handler(on_change)
                  self.logger.info('invoking after fill on_change=%r', rep)
                  action()
          else:
              if no_change is not None:
                  action, rep = self._process_fill_handler(no_change)
                  self.logger.info('invoking after fill no_change=%r', rep)
                  action()
          return changed
  
      @property
      def sub_widgets(self):
          """Returns all sub-widgets of this widget.
  
          Returns:
              A :py:class:`list` of :py:class:`Widget`
          """
          return [getattr(self, widget_name) for widget_name in self.widget_names]
  
      @property
      def cached_sub_widgets(self):
          """Returns all cached sub-widgets of this widgets.
  
          Returns:
              A :py:class:`list` of :py:class:`Widget`
          """
          return [
              getattr(self, widget_name)
              for widget_name in self.widget_names
              # Grab the descriptor
              if getattr(type(self), widget_name) in self._widget_cache]
  
      @property
      def width(self):
          return self.browser.size_of(self, parent=self.parent)[0]
  
      @property
      def height(self):
          return self.browser.size_of(self, parent=self.parent)[1]
  
      def __iter__(self):
          """Allows iterating over the widgets on the view."""
          for widget_attr in self.widget_names:
              yield getattr(self, widget_attr)
  
  
  def _gen_locator_meth(loc):
      def __locator__(self):  # noqa
          return loc
      return __locator__
  
  
  def _gen_locator_root():
      def __locator__(self):  # noqa
          return self.ROOT
      return __locator__
  
  
  class ClickableMixin(object):
  
      @logged()
      def click(self, handle_alert=None):
          """Click this widget
  
          Args:
              handle_alert: Special alert handling. None - no handling, True - accept, False - dismiss
          """
          self.browser.click(self, ignore_ajax=(handle_alert is not None))
          if handle_alert is not None:
              self.browser.handle_alert(cancel=not handle_alert, wait=2.0, squash=True)
              # ignore_ajax will not execute the ensure_page_safe plugin with True
              self.browser.plugin.ensure_page_safe()
  
  
  class GenericLocatorWidget(Widget, ClickableMixin):
      """A base class for any widgets with a locator.
  
      Clickable.
  
      Args:
          locator: Locator of the object ob the page.
      """
      ROOT = ParametrizedLocator('{@locator}')
  
      def __init__(self, parent, locator, logger=None):
          Widget.__init__(self, parent, logger=logger)
          self.locator = locator
  
      def __repr__(self):
          return '{}({!r})'.format(type(self).__name__, self.locator)
  
  
  class WTMixin(object, metaclass=WidgetMetaclass):
      """Base class for mixins for views.
  
      Lightweight class that only has the bare minimum of what is required for widgetastic operation.
  
      Use this if you want to create mixins for views.
      """
  
  
  class ConditionalSwitchableView(Widgetable):
      """Conditional switchable view implementation.
  
      This widget proxy is useful when you have a form whose parts displayed depend on certain
      conditions. Eg. when you select certain value from a dropdown, one form is displayed next,
      when other value is selected, a different form is displayed next. This widget proxy is designed
      to register those multiple views and then upon accessing decide which view to use based on the
      registration conditions.
  
      The resulting widget proxy acts similarly like a nested view (if you use view of course).
  
      Example:
  
          .. code-block:: python
  
              class SomeForm(View):
                  foo = Input('...')
                  action_type = Select(name='action_type')
  
                  action_form = ConditionalSwitchableView(reference='action_type')
  
                  # Simple value matching. If Action type 1 is selected in the select, use this view.
                  # And if the action_type value does not get matched, use this view as default
                  @action_form.register('Action type 1', default=True)
                  class ActionType1Form(View):
                      widget = Widget()
  
                  # You can use a callable to declare the widget values to compare
                  @action_form.register(lambda action_type: action_type == 'Action type 2')
                  class ActionType2Form(View):
                      widget = Widget()
  
                  # With callable, you can use values from multiple widgets
                  @action_form.register(
                      lambda action_type, foo: action_type == 'Action type 2' and foo == 2)
                  class ActionType2Form(View):
                      widget = Widget()
  
          You can see it gives you the flexibility of decision based on the values in the view.
  
      Args:
          reference: For using non-callable conditions, this must be specified. Specifies the name of
              the widget whose value will be used for comparing non-callable conditions. Supports
              going across objects using ``.``.
          ignore_bad_reference: If this is enabled, then when the widget representing the reference
              is not displayed or otherwise broken, it will then use the default view.
      """
      def __init__(self, reference=None, ignore_bad_reference=False):
          self.reference = reference
          self.registered_views = []
          self.default_view = None
          self.ignore_bad_reference = ignore_bad_reference
  
      @property
      def child_items(self):
          return [
              descriptor
              for _, descriptor
              in self.registered_views
              if isinstance(descriptor, WidgetDescriptor)]
  
      def register(self, condition, default=False, widget=None):
          """Register a view class against given condition.
  
          Args:
              condition: Condition check for switching to appropriate view. Can be callable or
                  non-callable. If callable, then callable parameters are resolved as values from
                  widgets resolved by the argument name, then the callable is invoked with the params.
                  If the invocation result is truthy, that view class is used. If it is a non-callable
                  then it is compared with the value read from the widget specified as ``reference``.
              default: If no other condition matches any registered view, use this one. Can only be
                  specified for one registration.
              widget: In case you do not want to use this as a decorator, you can pass the widget
                  class or instantiated widget as this parameter.
          """
          def view_process(cls_or_descriptor):
              if not (
                      isinstance(cls_or_descriptor, WidgetDescriptor) or
                      (inspect.isclass(cls_or_descriptor) and issubclass(cls_or_descriptor, Widget))):
                  raise TypeError(
                      'Unsupported object registered into the selector (!r})'.format(
                          cls_or_descriptor))
              self.registered_views.append((condition, cls_or_descriptor))
              if default:
                  if self.default_view is not None:
                      raise TypeError('Multiple default views specified')
                  self.default_view = cls_or_descriptor
              # We explicitly return None
              return None
          if widget is None:
              return view_process
          else:
              return view_process(widget)
  
      def __get__(self, o, t):
          if o is None:
              return self
  
          condition_arg_cache = {}
          for condition, cls_or_descriptor in self.registered_views:
              if not callable(condition):
                  # Compare it to a known value (if present)
                  if self.reference is None:
                      # No reference to check against
                      raise TypeError(
                          'reference= not set so you cannot use non-callables as conditions')
                  else:
                      if self.reference not in condition_arg_cache:
                          try:
                              ref_o = nested_getattr(o, self.reference)
                              if isinstance(ref_o, Widget):
                                  ref_value = ref_o.read()
                              else:
                                  ref_value = ref_o
                              condition_arg_cache[self.reference] = ref_value
                          except AttributeError:
                              raise TypeError(
                                  'Wrong widget name specified as reference=: {}'.format(
                                      self.reference))
                          except NoSuchElementException:
                              if self.ignore_bad_reference:
                                  # reference is not displayed? We are probably aware of this so skip.
                                  continue
                              else:
                                  raise
                      if condition == condition_arg_cache[self.reference]:
                          view_object = cls_or_descriptor
                          break
              else:
                  # Parse the callable's args and inject the correct args
                  c_args, c_varargs, c_keywords, c_defaults = inspect.getargspec(condition)
                  if c_varargs or c_keywords or c_defaults:
                      raise TypeError('You can only use simple arguments in lambda conditions')
                  arg_values = []
                  for arg in c_args:
                      if arg not in condition_arg_cache:
                          try:
                              condition_arg_cache[arg] = getattr(o, arg).read()
                          except AttributeError:
                              raise TypeError(
                                  'Wrong widget name specified as parameter {}'.format(arg))
                      arg_values.append(condition_arg_cache[arg])
  
                  if condition(*arg_values):
                      view_object = cls_or_descriptor
                      break
          else:
              if self.default_view is not None:
                  view_object = self.default_view
              else:
                  raise ValueError('Could not find a corresponding registered view.')
          if inspect.isclass(view_object):
              view_class = view_object
          else:
              view_class = type(view_object)
          o.logger.info('Picked %s', view_class.__name__)
          if isinstance(view_object, Widgetable):
              # We init the widget descriptor here
              return view_object.__get__(o, t)
          else:
              return view_object(o, additional_context=o.context)
  
  
  class View(Widget):
      """View is a kind of abstract widget that can hold another widgets. Remembers the order,
      so therefore it can function like a form with defined filling order.
  
      It looks like this:
  
      .. code-block:: python
  
          class Login(View):
              user = SomeInputWidget('user')
              password = SomeInputWidget('pass')
              login = SomeButtonWidget('Log In')
  
              def a_method(self):
                  do_something()
  
      The view is usually instantiated with an instance of
      :py:class:`widgetastic.browser.Browser`, which will then enable resolving of all of the
      widgets defined.
  
      Args:
          parent: A parent :py:class:`View` or :py:class:`widgetastic.browser.Browser`
          additional_context: If the view needs some context, for example - you want to check that
              you are on the page of user XYZ but you can also be on the page for user FOO, then
              you shall use the ``additional_context`` to pass in required variables that will allow
              you to detect this.
      """
      #: Skip this view in the element lookup hierarchy
      INDIRECT = False
      FRAME = None
      fill_strategy = None
  
      def __init__(self, parent, logger=None, **kwargs):
          Widget.__init__(self, parent, logger=logger)
          self.context = kwargs.pop('additional_context', {})
          self.last_fill_data = None
  
          if not self.fill_strategy:
              if getattr(getattr(self.parent, 'fill_strategy', None), 'respect_parent', False):
                  self.fill_strategy = self.parent.fill_strategy
              else:
                  self.fill_strategy = DefaultFillViewStrategy()
  
      @staticmethod
      def nested(view_class):
          """Shortcut for :py:class:`WidgetDescriptor`
  
          Usage:
  
          .. code-block:: python
  
              class SomeView(View):
                  some_widget = Widget()
  
                  @View.nested
                  class another_view(View):
                      pass
  
          Why? The problem is counting things. When you are placing widgets themselves on a view, they
          handle counting themselves and just work. But when you are creating a nested view, that is a
          bit of a problem. The widgets are instantiated, whereas the views are placed in a class and
          wait for the :py:class:`ViewMetaclass` to pick them up, but that happens after all other
          widgets have been instantiated into the :py:class:`WidgetDescriptor`s, which has the
          consequence of things being out of order. By wrapping the class into the descriptor we do
          the job of :py:meth:`Widget.__new__` which creates the :py:class:`WidgetDescriptor` if not
          called with a :py:class:`widgetastic.browser.Browser` or :py:class:`Widget` instance as the
          first argument.
  
          Args:
              view_class: A subclass of :py:class:`View`
          """
          return WidgetDescriptor(view_class)
  
      @property
      def is_displayed(self):
          """Overrides the :py:meth:`Widget.is_displayed`. The difference is that if the view does
          not have the root locator, it assumes it is displayed.
  
          Returns:
              :py:class:`bool`
          """
          try:
              return super(View, self).is_displayed
          except (LocatorNotImplemented, AttributeError):
              return True
  
      def move_to(self):
          """Overrides the :py:meth:`Widget.move_to`. The difference is that if the view does
          not have the root locator, it returns None.
  
          Returns:
              :py:class:`selenium.webdriver.remote.webelement.WebElement` instance or ``None``.
          """
          try:
              return super(View, self).move_to()
          except LocatorNotImplemented:
              return None
  
      def fill(self, values):
          """Implementation of form filling.
  
          This method goes through all widgets defined on this view one by one and calls their
          ``fill`` methods appropriately.
  
          ``None`` values will be ignored.
  
          It will log any skipped fill items.
          It will log a warning if you pass any extra values for filling.
  
          It will store the fill value in :py:attr:`last_fill_data`. The data will be "deflattened" to
          ensure uniformity.
  
          Args:
              values: A dictionary of ``widget_name: value_to_fill``.
  
          Returns:
              :py:class:`bool` if the fill changed any value.
          """
          changed = []
          values = deflatten_dict(values)
          self.last_fill_data = values
          changed.append(self.before_fill(values))
          # there are some views like ConditionalView which are dynamically updated
          # it is necessary to pass current view at least for
          # name -> widget resolution right before fill and for logging
          self.fill_strategy.context = FillContext(parent=self)
          changed.append(self.fill_strategy.do_fill(values))
          a_fill = self.after_fill(any(changed))
          return a_fill if isinstance(a_fill, bool) else any(changed)
  
      def read(self):
          """Reads the contents of the view and presents them as a dictionary.
  
          Returns:
              A :py:class:`dict` of ``widget_name: widget_read_value`` where the values are retrieved
              using the :py:meth:`Widget.read`.
          """
          result = {}
          for widget_name in self.widget_names:
              widget = getattr(self, widget_name)
              try:
                  value = widget.read()
              except (NotImplementedError, NoSuchElementException, DoNotReadThisWidget):
                  continue
  
              result[widget_name] = value
  
          return result
  
      def before_fill(self, values):
          """A hook invoked before the loop of filling is invoked.
  
          If it returns None, the ``was_changed`` in :py:meth:`fill` does not change. If it returns a
          boolean, then on ``True`` it modifies the ``was_changed`` to True as well.
  
          Args:
              values: The same values that are passed to :py:meth:`fill`
          """
          pass
  
      def after_fill(self, was_change):
          """A hook invoked after all the widgets were filled.
  
          If it returns None, the ``was_changed`` in :py:meth:`fill` does not change. If it returns a
          boolean, that boolean will be returned as ``was_changed``.
  
          Args:
              was_change: :py:class:`bool` signalizing whether the :py:meth:`fill` changed anything,
          """
          pass
  
      def child_widget_accessed(self, widget):
          """This hook is called when a child widget of current view is accessed.
  
          One of useful examples is below. it allows us to switch between frames.
  
          Args:
              widget: The widget being accessed.
          """
          self.browser.switch_to_main_frame()
          parents = [p for p in self.hierarchy if getattr(p, 'FRAME', None)]
          for parent in parents:
              self.browser.switch_to_frame(getattr(parent, 'FRAME'))
  
  
  class ParametrizedView(View):
      """View that needs parameters to be run.
  
      In order to use this class, you need to specify parameters in the :py:attribute:`PARAMETERS`
      attribute.
  
      Then a parametrized view could be defined like this:
  
      .. code-block:: python
  
          class AView(View):
              # some widgets, .... etc
  
              class thing(ParametrizedView):
                  PARAMETERS = ('thing_name', )
                  ROOT = ParametrizedLocator('.//div[./h2[normalize-space(.)={thing_name|quote}]]')
  
                  a_widget = SomeWidget()
  
          view = AView(browser)
  
      This will not work:
  
      .. code-block:: python
  
          view.thing.a_widget  # Throws an error
  
      You now need to pass the required parameter as an argument to the view. Just like a method:
  
      .. code-block:: python
  
          view.a_thing(thing_name='snafu').a_widget
          # Or alternatively positionally
          view.a_thing('snafu').a_widget
  
      This is enough to support the ``fill` interface. If you want to ``read`` the parametrized view
      as well, you need to implement some logic which tells it what aprameters are available. That
      is achieved by implementing an ``all`` classmethod on the parametrized view:
  
      .. code-block:: python
  
          # inside class thing(ParametrizedView):
          @classmethod
          def all(cls, browser):
              elements = browser.elements('some locator')
              # You then need to scavenge the values for PARAMETERS
              # ParametrizedView expects such format of parameters:
              return [  # List of all occurences of the parametrized group
                  ('foo', )  # Tuple of all parameters that are necessary to look up the given group
                  ('snafu', )
              ]
  
      There can be any number of parameters, but bear mind that all of them are required and ``all``
      must always return the same number of parameters for each group.
  
      When filling, remember the keys of the fill dictionary are the parameters. If there is only one
      parameter, it can just be the parameter itself. If there are multiple parameters for the groups,
      then a tuple is expected as a key, containing all the parameter values. The values of the
      dictionary are then passed into each parametrized group to be filled as an ordinary view.
      """
  
      #: Tuple of parameter names that this view takes.
      PARAMETERS = ()
  
      @classmethod
      def all(cls, browser):
          """Method that returns tuples of parameters that correspond to PARAMETERS attribute.
  
          It is required for proper functionality of :py:meth:`read` so it knows the exact instances
          of the view.
  
          Returns:
              An iterable that contains tuples. Values in the tuples must map exactly to the keys in
              the PARAMETERS class attribute.
          """
          raise NotImplementedError('You need to implement the all() classmethod')
  
  
  class ParametrizedViewRequest(object):
      """An intermediate object handling the argument retrieval and subsequent correct view
      instantiation.
  
      See :py:class:`ParametrizedView` for more info.
      """
      def __init__(self, parent_object, view_class, *args, **kwargs):
          self.parent_object = parent_object
          self.view_class = view_class
          self.args = args
          self.kwargs = kwargs
  
      def __call__(self, *args, **kwargs):
          if len(args) > len(self.view_class.PARAMETERS):
              raise TypeError(
                  'You passed more parameters than {} accepts'.format(self.view_class.__name__))
          param_dict = {}
          for passed_arg, required_arg in zip(args, self.view_class.PARAMETERS):
              param_dict[required_arg] = passed_arg
          for key, value in kwargs.items():
              if key not in self.view_class.PARAMETERS:
                  raise TypeError('Unknown view parameter {}'.format(key))
              param_dict[key] = value
  
          for param in self.view_class.PARAMETERS:
              if param not in param_dict:
                  raise TypeError(
                      'You did not pass the required parameter {} into {}'.format(
                          param, self.view_class.__name__))
  
          new_kwargs = copy(self.kwargs)
          if 'additional_context' not in self.kwargs:
              new_kwargs['additional_context'] = {}
          new_kwargs['additional_context'].update(param_dict)
          # And finally, set up a nice logger
          parent_logger = self.parent_object.logger
          current_name = self.view_class.__name__
          # Now add the params to the name so it is class_name(args)
          current_name += call_sig((), param_dict)  # no args because we process everything into dict
          new_kwargs['logger'] = create_child_logger(parent_logger, current_name)
          result = self.view_class(self.parent_object, *self.args, **new_kwargs)
          self.parent_object.child_widget_accessed(result)
          return result
  
      def __getitem__(self, int_or_slice):
          """Emulates list-like behaviour.
  
          Maps into the dict-like structure by utilizing all() to get the list of all items and then
          it picks the one selected by the list-like accessor. Supports both integers and slices.
          """
          all_items = self.view_class.all(self.parent_object.browser)
          items = all_items[int_or_slice]
          single = isinstance(int_or_slice, int)
          if single:
              items = [items]
          views = []
          for args in items:
              views.append(self(*args))
  
          if single:
              return views[0]
          else:
              return views
  
      def __iter__(self):
          for args in self.view_class.all(self.parent_object.browser):
              yield self(*args)
  
      def __len__(self):
          return len(self.view_class.all(self.parent_object.browser))
  
      def __getattr__(self, attr):
          raise AttributeError(
              'This is not an instance of {}. You need to call this object and pass the required '
              'parameters of the view.'.format(self.view_class.__name__))
  
      def read(self):
          # Special handling of the parametrized views
          all_presences = self.view_class.all(self.parent_object.browser)
          value = {}
          for param_tuple in all_presences:
              # For each presence store it in a dictionary
              args = param_tuple
              if len(param_tuple) < 2:
                  # Single value - no tuple
                  param_tuple = param_tuple[0]
              value[param_tuple] = self(*args).read()
          return value
  
      def fill(self, value):
          was_change = False
          if not isinstance(value, dict):
              raise ValueError('When filling parametrized view a dict is required')
          for param_tuple, fill_value in value.items():
              if not isinstance(param_tuple, tuple):
                  param_tuple = (param_tuple, )
              if self(*param_tuple).fill(fill_value):
                  was_change = True
          return was_change
  