# This program is free software; you can redistribute it and/or modify
# it under the terms of the (LGPL) GNU Lesser General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library Lesser General Public License for more details at
# ( http://www.gnu.org/licenses/lgpl.html ).
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# written by: Jeff Ortel ( jortel@redhat.com )

"""
Provides base classes for XML->object I{unmarshalling}.
"""

from logging import getLogger
from suds.compat import basestring
from suds.umx import Content
from suds.umx.attrlist import AttrList
from suds.sax.text import Text
from suds.sudsobject import Factory, merge


log = getLogger(__name__)

reserved = {
    'class': 'cls',
    'def': 'dfn',
}


class Core:
    """
    The abstract XML I{node} unmarshaller.  This class provides the
    I{core} unmarshalling functionality.
    """

    def process(self, content):
        """
        Process an object graph representation of the xml I{node}.
        @param content: The current content being unmarshalled.
        @type content: L{Content}
        @return: A suds object.
        @rtype: L{Object}
        """
        self.reset()
        return self.append(content)

    def append(self, content):
        """
        Process the specified node and convert the XML document into
        a I{suds} L{object}.
        @param content: The current content being unmarshalled.
        @type content: L{Content}
        @return: A I{append-result} tuple as: (L{Object}, I{value})
        @rtype: I{append-result}
        @note: This is not the proper entry point.
        @see: L{process()}
        """
        self.start(content)
        self.append_attributes(content)
        self.append_children(content)
        self.append_text(content)
        self.end(content)
        return self.postprocess(content)

    def postprocess(self, content):
        """
        Perform final processing of the resulting data structure as follows:
          - Mixed values (children and text) will have a result of the
             I{content.node}.
          - Semi-simple values (attributes, no-children and text) will have a
             result of a property object.
          - Simple values (no-attributes, no-children with text nodes) will
             have a string result equal to the value of the
             content.node.getText().
        @param content: The current content being unmarshalled.
        @type content: L{Content}
        @return: The post-processed result.
        @rtype: I{any}
        """
        node = content.node
        if len(node.children) and node.hasText():
            return node
        attributes = AttrList(node.attributes)
        if (
            attributes.rlen() and
            not len(node.children) and
            node.hasText()
        ):
                p = Factory.property(node.name, node.getText())
                return merge(content.data, p)
        if len(content.data):
            return content.data
        lang = attributes.lang()
        if content.node.isnil():
            return None
        if not len(node.children) and content.text is None:
            if self.nillable(content):
                return None
            else:
                return Text('', lang=lang)
        if isinstance(content.text, basestring):
            return Text(content.text, lang=lang)
        else:
            return content.text

    def append_attributes(self, content):
        """
        Append attribute nodes into L{Content.data}.
        Attributes in the I{schema} or I{xml} namespaces are skipped.
        @param content: The current content being unmarshalled.
        @type content: L{Content}
        """
        attributes = AttrList(content.node.attributes)
        for attr in attributes.real():
            name = attr.name
            value = attr.value
            self.append_attribute(name, value, content)

    def append_attribute(self, name, value, content):
        """
        Append an attribute name/value into L{Content.data}.
        @param name: The attribute name
        @type name: basestring
        @param value: The attribute's value
        @type value: basestring
        @param content: The current content being unmarshalled.
        @type content: L{Content}
        """
        key = name
        key = '_%s' % reserved.get(key, key)
        setattr(content.data, key, value)

    def append_children(self, content):
        """
        Append child nodes into L{Content.data}
        @param content: The current content being unmarshalled.
        @type content: L{Content}
        """
        for child in content.node:
            cont = Content(child)
            cval = self.append(cont)
            key = reserved.get(child.name, child.name)
            if key in content.data:
                v = getattr(content.data, key)
                if isinstance(v, list):
                    v.append(cval)
                else:
                    setattr(content.data, key, [v, cval])
                continue
            if self.unbounded(cont):
                if cval is None:
                    setattr(content.data, key, [])
                else:
                    setattr(content.data, key, [cval, ])
            else:
                setattr(content.data, key, cval)

    def append_text(self, content):
        """
        Append text nodes into L{Content.data}
        @param content: The current content being unmarshalled.
        @type content: L{Content}
        """
        if content.node.hasText():
            content.text = content.node.getText()

    def reset(self):
        pass

    def start(self, content):
        """
        Processing on I{node} has started.  Build and return
        the proper object.
        @param content: The current content being unmarshalled.
        @type content: L{Content}
        @return: A subclass of Object.
        @rtype: L{Object}
        """
        content.data = Factory.object(content.node.name)

    def end(self, content):
        """
        Processing on I{node} has ended.
        @param content: The current content being unmarshalled.
        @type content: L{Content}
        """
        pass

    def bounded(self, content):
        """
        Get whether the content is bounded (not a list).
        @param content: The current content being unmarshalled.
        @type content: L{Content}
        @return: True if bounded, else False
        @rtype: boolean
        '"""
        return not self.unbounded(content)

    def unbounded(self, content):
        """
        Get whether the object is unbounded (a list).
        @param content: The current content being unmarshalled.
        @type content: L{Content}
        @return: True if unbounded, else False
        @rtype: boolean
        '"""
        return False

    def nillable(self, content):
        """
        Get whether the object is nillable.
        @param content: The current content being unmarshalled.
        @type content: L{Content}
        @return: True if nillable, else False
        @rtype: boolean
        '"""
        return False
