import six
import wsme.types

try:
    from lxml import etree as ET
    use_lxml = True
except ImportError:
    from xml.etree import cElementTree as ET  # noqa
    use_lxml = False


def xml_tostring(el, pretty_print=False):
    if use_lxml:
        return ET.tostring(el, pretty_print=pretty_print)
    return ET.tostring(el)


class NS(object):
    def __init__(self, url):
        self.url = url

    def __call__(self, name):
        return self.qn(name)

    def __str__(self):
        return self.url

    def qn(self, name):
        return '{%s}%s' % (self.url, name)

wsdl_ns = NS("http://schemas.xmlsoap.org/wsdl/")
soap_ns = NS("http://schemas.xmlsoap.org/wsdl/soap/")
xs_ns = NS("http://www.w3.org/2001/XMLSchema")
soapenc_ns = NS("http://schemas.xmlsoap.org/soap/encoding/")


class WSDLGenerator(object):
    def __init__(
            self,
            tns,
            types_ns,
            soapenc,
            service_name,
            complex_types,
            funclist,
            arrays,
            baseURL,
            soap_array,
            soap_type,
            soap_fname):

        self.tns = NS(tns)
        self.types_ns = NS(types_ns)
        self.soapenc = soapenc
        self.service_name = service_name
        self.complex_types = complex_types
        self.funclist = funclist
        self.arrays = arrays
        self.baseURL = baseURL or ''
        self.soap_array = soap_array
        self.soap_fname = soap_fname
        self.soap_type = soap_type

    def gen_complex_type(self, cls):
        complexType = ET.Element(xs_ns('complexType'))
        complexType.set('name', cls.__name__)
        sequence = ET.SubElement(complexType, xs_ns('sequence'))
        for attrdef in wsme.types.list_attributes(cls):
            soap_type = self.soap_type(attrdef.datatype, str(self.types_ns))
            if soap_type is None:
                continue
            element = ET.SubElement(sequence, xs_ns('element'))
            element.set('name', attrdef.name)
            element.set('type', soap_type)
            element.set('minOccurs', '1' if attrdef.mandatory else '0')
            element.set('maxOccurs', '1')
        return complexType

    def gen_array(self, array):
        complexType = ET.Element(xs_ns('complexType'))
        complexType.set('name', self.soap_array(array, False))
        ET.SubElement(
            ET.SubElement(complexType, xs_ns('sequence')),
            xs_ns('element'),
            name='item',
            maxOccurs='unbounded',
            nillable='true',
            type=self.soap_type(array.item_type, self.types_ns)
        )
        return complexType

    def gen_function_types(self, path, funcdef):
        args_el = ET.Element(
            xs_ns('element'),
            name=self.soap_fname(path, funcdef)
        )

        sequence = ET.SubElement(
            ET.SubElement(args_el, xs_ns('complexType')),
            xs_ns('sequence')
        )

        for farg in funcdef.arguments:
            t = self.soap_type(farg.datatype, True)
            if t is None:
                continue
            element = ET.SubElement(
                sequence, xs_ns('element'),
                name=farg.name,
                type=self.soap_type(farg.datatype, True)
            )
            if not farg.mandatory:
                element.set('minOccurs', 0)

        response_el = ET.Element(
            xs_ns('element'),
            name=self.soap_fname(path, funcdef) + 'Response'
        )
        element = ET.SubElement(
            ET.SubElement(
                ET.SubElement(
                    response_el,
                    xs_ns('complexType')
                ),
                xs_ns('sequence')
            ),
            xs_ns('element'),
            name='result'
        )
        return_soap_type = self.soap_type(funcdef.return_type, True)
        if return_soap_type is not None:
            element.set('type', return_soap_type)

        return args_el, response_el

    def gen_types(self):
        types = ET.Element(wsdl_ns('types'))
        schema = ET.SubElement(types, xs_ns('schema'))
        schema.set('elementFormDefault', 'qualified')
        schema.set('targetNamespace', str(self.types_ns))
        for cls in self.complex_types:
            schema.append(self.gen_complex_type(cls))
        for array in self.arrays:
            schema.append(self.gen_array(array))
        for path, funcdef in self.funclist:
            schema.extend(self.gen_function_types(path, funcdef))
        return types

    def gen_functions(self):
        messages = []

        binding = ET.Element(
            wsdl_ns('binding'),
            name='%s_Binding' % self.service_name,
            type='%s_PortType' % self.service_name
        )
        ET.SubElement(
            binding,
            soap_ns('binding'),
            style='document',
            transport='http://schemas.xmlsoap.org/soap/http'
        )

        portType = ET.Element(
            wsdl_ns('portType'),
            name='%s_PortType' % self.service_name
        )

        for path, funcdef in self.funclist:
            soap_fname = self.soap_fname(path, funcdef)

            # message
            req_message = ET.Element(
                wsdl_ns('message'),
                name=soap_fname + 'Request',
                xmlns=str(self.types_ns)
            )
            ET.SubElement(
                req_message,
                wsdl_ns('part'),
                name='parameters',
                element='types:%s' % soap_fname
            )
            messages.append(req_message)

            res_message = ET.Element(
                wsdl_ns('message'),
                name=soap_fname + 'Response',
                xmlns=str(self.types_ns)
            )
            ET.SubElement(
                res_message,
                wsdl_ns('part'),
                name='parameters',
                element='types:%sResponse' % soap_fname
            )
            messages.append(res_message)

            # portType/operation
            operation = ET.SubElement(
                portType,
                wsdl_ns('operation'),
                name=soap_fname
            )
            if funcdef.doc:
                ET.SubElement(
                    operation,
                    wsdl_ns('documentation')
                ).text = funcdef.doc
            ET.SubElement(
                operation, wsdl_ns('input'),
                message='tns:%sRequest' % soap_fname
            )
            ET.SubElement(
                operation, wsdl_ns('output'),
                message='tns:%sResponse' % soap_fname
            )

            # binding/operation
            operation = ET.SubElement(
                binding,
                wsdl_ns('operation'),
                name=soap_fname
            )
            ET.SubElement(
                operation,
                wsdl_ns('operation'),
                soapAction=soap_fname
            )
            ET.SubElement(
                ET.SubElement(
                    operation,
                    wsdl_ns('input')
                ),
                soap_ns('body'),
                use='literal'
            )
            ET.SubElement(
                ET.SubElement(
                    operation,
                    wsdl_ns('output')
                ),
                soap_ns('body'),
                use='literal'
            )

        return messages + [portType, binding]

    def gen_service(self):
        service = ET.Element(wsdl_ns('service'), name=self.service_name)
        ET.SubElement(
            service,
            wsdl_ns('documentation')
        ).text = six.u('WSDL File for %s') % self.service_name
        ET.SubElement(
            ET.SubElement(
                service,
                wsdl_ns('port'),
                binding='tns:%s_Binding' % self.service_name,
                name='%s_PortType' % self.service_name
            ),
            soap_ns('address'),
            location=self.baseURL
        )

        return service

    def gen_definitions(self):
        attrib = {
            'name': self.service_name,
            'targetNamespace': str(self.tns)
        }
        if use_lxml:
            definitions = ET.Element(
                wsdl_ns('definitions'),
                attrib=attrib,
                nsmap={
                    'xs': str(xs_ns),
                    'soap': str(soap_ns),
                    'types': str(self.types_ns),
                    'tns': str(self.tns)
                }
            )
        else:
            definitions = ET.Element(wsdl_ns('definitions'), **attrib)
            definitions.set('xmlns:types', str(self.types_ns))
            definitions.set('xmlns:tns', str(self.tns))

        definitions.set('name', self.service_name)
        definitions.append(self.gen_types())
        definitions.extend(self.gen_functions())
        definitions.append(self.gen_service())
        return definitions

    def generate(self, format=False):
        return xml_tostring(self.gen_definitions(), pretty_print=format)
