#
# spyne - Copyright (C) Spyne contributors.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
#

"""
The ``spyne.protoco.soap.soap12`` module contains the implementation of a
subset of the Soap 1.2 standard.

This modules is EXPERIMENTAL.
More info can be found at: https://www.w3.org/TR/soap12-part1/
"""

import logging

from lxml.builder import E

from spyne.protocol.soap.soap11 import Soap11
from spyne.protocol.xml import _append
from spyne.util.six import string_types
from spyne.util.etreeconv import root_dict_to_etree
from spyne.const.xml import NS_SOAP12_ENV, NS_XML, PREFMAP


logger = logging.getLogger(__name__)
logger_invalid = logging.getLogger(__name__ + ".invalid")


class Soap12(Soap11):
    """
    The base implementation of a subset of the Soap 1.2 standard. The
    document is available here: http://www.w3.org/TR/soap12/
    """
    mime_type = 'application/soap+xml; charset=utf-8'

    soap_env = PREFMAP[NS_SOAP12_ENV]
    ns_soap_env = NS_SOAP12_ENV

    type = set(Soap11.type)
    type.discard('soap11')
    type.update(('soap', 'soap12'))

    def generate_subcode(self, value, subcode=None):
        subcode_node = E("{%s}Subcode" % self.ns_soap_env)
        subcode_node.append(E("{%s}Value" % self.ns_soap_env, value))
        if subcode:
            subcode_node.append(subcode)
        return subcode_node

    def gen_fault_codes(self, faultstring):
        faultstrings = faultstring.split('.')
        value = faultstrings.pop(0)
        if value == 'Client':
            value = '%s:Sender' % self.soap_env
        elif value == 'Server':
            value = '%s:Receiver' % self.soap_env
        else:
            raise TypeError('Wrong fault code, got', type(faultstring))

        return value, faultstrings

    def generate_faultcode(self, element):
        nsmap = element.nsmap
        faultcode = []
        faultcode.append(element.find('soap:Code/soap:Value', namespaces=nsmap).text)
        subcode = element.find('soap:Code/soap:Subcode', namespaces=nsmap)
        while subcode is not None:
            faultcode.append(subcode.find('soap:Value', namespaces=nsmap).text)
            subcode = subcode.find('soap:Subcode', namespaces=nsmap)

        return '.'.join(faultcode)

    def fault_to_parent(self, ctx, cls, inst, parent, ns, **_):
        reason = E("{%s}Reason" % self.ns_soap_env)
        reason.append(E("{%s}Text" % self.ns_soap_env, inst.faultstring,
                        **{'{%s}lang' % NS_XML: inst.lang}))

        subelts = [
            None,  # The code tag is put here down the road
            reason,
            E("{%s}Role" % self.ns_soap_env, inst.faultactor),
        ]

        return self._fault_to_parent_impl(ctx, cls, inst, parent, ns, subelts)

    def _fault_to_parent_impl(self, ctx, cls, inst, parent, ns, subelts, **_):
        assert isinstance(subelts, list)

        tag_name = "{%s}Fault" % self.ns_soap_env

        if isinstance(inst.faultcode, string_types):
            value, faultcodes = self.gen_fault_codes(inst.faultcode)

            code = E("{%s}Code" % self.ns_soap_env)
            code.append(E("{%s}Value" % self.ns_soap_env, value))

            child_subcode = False
            for value in faultcodes[::-1]:
                if child_subcode:
                    child_subcode = self.generate_subcode(value, child_subcode)
                else:
                    child_subcode = self.generate_subcode(value)

            if child_subcode != 0:
                code.append(child_subcode)

            subelts[0] = code

        if isinstance(inst.detail, dict):
            _append(subelts, E('{%s}Detail' % self.ns_soap_env,
                                               root_dict_to_etree(inst.detail)))

        elif inst.detail is None:
            pass

        else:
            raise TypeError('Fault detail Must be dict, got', type(inst.detail))

        return self.gen_members_parent(ctx, cls, inst, parent, tag_name,
                                                        subelts, add_type=False)

    def schema_validation_error_to_parent(self, ctx, cls, inst, parent, ns, **_):
        subelts = [
            E("{%s}Reason" % self.soap_env, inst.faultstring),
            E("{%s}Role" % self.soap_env, inst.faultactor),
        ]

        return self._fault_to_parent_impl(ctx, cls, inst, parent, ns, subelts)

    def fault_from_element(self, ctx, cls, element):
        nsmap = element.nsmap

        code = self.generate_faultcode(element)
        reason = element.find("soap:Reason/soap:Text", namespaces=nsmap).text.strip()
        role = element.find("soap:Role", namespaces=nsmap)
        node = element.find("soap:Node", namespaces=nsmap)
        detail = element.find("soap:Detail", namespaces=nsmap)
        faultactor = ''
        if role is not None:
            faultactor += role.text.strip()
        if node is not None:
            faultactor += node.text.strip()
        return cls(faultcode=code, faultstring=reason,
                   faultactor=faultactor, detail=detail)
