"""
This module's purpose is to enable us to present internals of objects
in well-defined way to operator. To do this we can define "views"
on some objects. View is a definition of how to present object
and relations to other objects which also have their views defined.

By using views we can avoid making all interesting internal values
public. They will stay private and only "view" will access them
(think friend-class from C++)
"""
import logging
import types

from ryu.services.protocols.bgp.operator.views import fields

LOG = logging.getLogger('bgpspeaker.operator.views.base')


class RdyToFlattenCollection(object):
    pass


class RdyToFlattenList(list, RdyToFlattenCollection):
    pass


class RdyToFlattenDict(dict, RdyToFlattenCollection):
    pass


class OperatorAbstractView(object):
    """Abstract base class for operator views. It isn't meant to be
    instantiated.
    """

    def __init__(self, obj, filter_func=None):
        """Init

        :param obj: data model for view. In other words object we
            are creating view for. In case of ListView it should be
            a list and in case of DictView it should be a dict.
        :param filter_func: function to filter models
        """
        self._filter_func = filter_func
        self._fields = self._collect_fields()
        self._obj = obj

    @classmethod
    def _collect_fields(cls):
        names = [attr for attr in dir(cls)
                 if isinstance(getattr(cls, attr), fields.Field)]
        return {name: getattr(cls, name) for name in names}

    def combine_related(self, field_name):
        """Combines related views. In case of DetailView it just returns
            one-element list containing related view wrapped in
            CombinedViewsWrapper.

            In case of ListView and DictView it returns a list of related views
            for every element of model collection also wrapped
            in CombinedViewsWrapper.

        :param field_name: field name of related view
        :returns: vectorized form of related views. You can access them
            as if you had only one view and you will receive flattened list
            of responses from related views. Look at docstring of
            CombinedViewsWrapper
        """
        raise NotImplementedError()

    def c_rel(self, *args, **kwargs):
        """Shortcut for combine_related. Look above
        """
        return self.combine_related(*args, **kwargs)

    def get_field(self, field_name):
        """Get value of data field.

        :return: value of data-field of this view
        """
        raise NotImplementedError()

    def encode(self):
        """Representation of view which is using only python standard types.

        :return: dict representation of this views data. However it
            doesn't have to be a dict. In case of ListView it would
            return a list. It should return wrapped types
            for list - RdyToFlattenList, for dict - RdyToFlattenDict
        """
        raise NotImplementedError()

    @property
    def model(self):
        """Getter for data model being presented by this view. Every view is
        associatetd with some data model.

        :return: underlaying data of this view
        """
        raise NotImplementedError()

    def apply_filter(self, filter_func):
        """Sets filter function to apply on model