# encoding: utf-8

"""
Shared code for unit test data builders
"""

from __future__ import absolute_import, print_function, unicode_literals

from docx.oxml import parse_xml
from docx.oxml.ns import nsdecls


class BaseBuilder(object):
    """
    Provides common behavior for all data builders.
    """
    def __init__(self):
        self._empty = False
        self._nsdecls = ''
        self._text = ''
        self._xmlattrs = []
        self._xmlattr_method_map = {}
        for attr_name in self.__attrs__:
            base_name = (
                attr_name.split(':')[1] if ':' in attr_name else attr_name
            )
            method_name = 'with_%s' % base_name
            self._xmlattr_method_map[method_name] = attr_name
        self._child_bldrs = []

    def __getattr__(self, name):
        """
        Intercept attribute access to generalize "with_{xmlattr_name}()"
        methods.
        """
        if name in self._xmlattr_method_map:
            def with_xmlattr(value):
                xmlattr_name = self._xmlattr_method_map[name]
                self._set_xmlattr(xmlattr_name, value)
                return self
            return with_xmlattr
        else:
            tmpl = "'%s' object has no attribute '%s'"
            raise AttributeError(tmpl % (self.__class__.__name__, name))

    def clear(self):
        """
        Reset this builder back to initial state so it can be reused within
        a single test.
        """
        BaseBuilder.__init__(self)
        return self

    @property
    def element(self):
        """
        Element parsed from XML generated by builder in current state
        """
        elm = parse_xml(self.xml())
        return elm

    def with_child(self, child_bldr):
        """
        Cause new child element specified by *child_bldr* to be appended to
        the children of this element.
        """
        self._child_bldrs.append(child_bldr)
        return self

    def with_text(self, text):
        """
        Cause *text* to be placed between the start and end tags of this
        element. Not robust enough for mixed elements, intended only for
        elements having no child elements.
        """
        self._text = text
        return self

    def with_nsdecls(self, *nspfxs):
        """
        Cause the element to contain namespace declarations. By default, the
        namespace prefixes defined in the Builder class are used. These can
        be overridden by providing exlicit prefixes, e.g.
        ``with_nsdecls('a', 'r')``.
        """
        if not nspfxs:
            nspfxs = self.__nspfxs__
        self._nsdecls = ' %s' % nsdecls(*nspfxs)
        return self

    def xml(self, indent=0):
        """
        Return element XML based on attribute settings
        """
        indent_str = ' ' * indent
        if self._is_empty:
            xml = '%s%s\n' % (indent_str, self._empty_element_tag)
        else:
            xml = '%s\n' % self._non_empty_element_xml(indent)
        return xml

    def xml_bytes(self, indent=0):
        return self.xml(indent=indent).encode('utf-8')

    @property
    def _empty_element_tag(self):
        return '<%s%s%s/>' % (self.__tag__, self._nsdecls, self._xmlattrs_str)

    @property
    def _end_tag(self):
        return '</%s>' % self.__tag__

    @property
    def _is_empty(self):
        return len(self._child_bldrs) == 0 and len(self._text) == 0

    def _non_empty_element_xml(self, indent):
        indent_str = ' ' * indent
        if self._text:
            xml = ('%s%s%s%s' %
                   (indent_str, self._start_tag, self._text, self._end_tag))
        else:
            xml = '%s%s\n' % (indent_str, self._start_tag)
            for child_bldr in self._child_bldrs:
                xml += child_bldr.xml(indent+2)
            xml += '%s%s' % (indent_str, self._end_tag)
        return xml

    def _set_xmlattr(self, xmlattr_name, value):
        xmlattr_str = ' %s="%s"' % (xmlattr_name, str(value))
        self._xmlattrs.append(xmlattr_str)

    @property
    def _start_tag(self):
        return '<%s%s%s>' % (self.__tag__, self._nsdecls, self._xmlattrs_str)

    @property
    def _xmlattrs_str(self):
        """
        Return all element attributes as a string, like ' foo="bar" x="1"'.
        """
        return ''.join(self._xmlattrs)
