# 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 )

"""
The I{service definition} provides a textual representation of a service.
"""

from logging import getLogger
from suds import tostr
import suds.metrics as metrics
from suds.sax import Namespace

log = getLogger(__name__)


class ServiceDefinition:
    """
    A service definition provides an object used to generate a textual
    description of a service.
    @ivar wsdl: A wsdl.
    @type wsdl: L{wsdl.Definitions}
    @ivar service: The service object.
    @type service: L{suds.wsdl.Service}
    @ivar ports: A list of port-tuple: (port, [(method-name, pdef)])
    @type ports: [port-tuple,..]
    @ivar prefixes: A list of remapped prefixes.
    @type prefixes: [(prefix,uri),..]
    @ivar types: A list of type definitions
    @type types: [I{Type},..]
    """

    def __init__(self, wsdl, service):
        """
        @param wsdl: A wsdl object
        @type wsdl: L{Definitions}
        @param service: A service B{name}.
        @type service: str
        """
        self.wsdl = wsdl
        self.service = service
        self.ports = []
        self.params = []
        self.types = []
        self.prefixes = []
        self.addports()
        self.paramtypes()
        self.publictypes()
        self.getprefixes()
        self.pushprefixes()

    def pushprefixes(self):
        """
        Add our prefixes to the wsdl so that when users invoke methods
        and reference the prefixes, the will resolve properly.
        """
        for ns in self.prefixes:
            self.wsdl.root.addPrefix(ns[0], ns[1])

    def addports(self):
        """
        Look through the list of service ports and construct a list of tuples
        where each tuple is used to describe a port and it's list of methods
        as: (port, [method]).  Each method is tuple: (name, [pdef,..] where
        each pdef is a tuple: (param-name, type).
        """
        timer = metrics.Timer()
        timer.start()
        for port in self.service.ports:
            p = self.findport(port)
            for op in port.binding.operations.values():
                m = p[0].method(op.name)
                binding = m.binding.input
                method = (m.name, binding.param_defs(m))
                p[1].append(method)
                metrics.log.debug("method '%s' created: %s", m.name, timer)
            p[1].sort()
        timer.stop()

    def findport(self, port):
        """
        Find and return a port tuple for the specified port.
        Created and added when not found.
        @param port: A port.
        @type port: I{service.Port}
        @return: A port tuple.
        @rtype: (port, [method])
        """
        for p in self.ports:
            if p[0] == p:
                return p
        p = (port, [])
        self.ports.append(p)
        return p

    def getprefixes(self):
        """
        Add prefixes foreach namespace referenced by parameter types.
        """
        namespaces = []
        for l in (self.params, self.types):
            for t, r in l:
                ns = r.namespace()
                if ns[1] is None:
                    continue
                if ns[1] in namespaces:
                    continue
                if Namespace.xs(ns) or Namespace.xsd(ns):
                    continue
                namespaces.append(ns[1])
                if t == r:
                    continue
                ns = t.namespace()
                if ns[1] is None:
                    continue
                if ns[1] in namespaces:
                    continue
                namespaces.append(ns[1])
        namespaces.sort()
        for u in namespaces:
            p = self.nextprefix()
            ns = (p, u)
            self.prefixes.append(ns)

    def paramtypes(self):
        """ get all parameter types """
        for m in [p[1] for p in self.ports]:
            for p in [p[1] for p in m]:
                for pd in p:
                    if pd[1] in self.params:
                        continue
                    item = (pd[1], pd[1].resolve())
                    self.params.append(item)

    def publictypes(self):
        """ get all public types """
        for t in self.wsdl.schema.types.values():
            if t in self.params:
                continue
            if t in self.types:
                continue
            item = (t, t)
            self.types.append(item)
        self.types.sort(key=lambda x: x[0].name)

    def nextprefix(self):
        """
        Get the next available prefix.  This means a prefix starting with 'ns'
        with a number appended as (ns0, ns1, ..) that is not already defined
        on the wsdl document.
        """
        used = [ns[0] for ns in self.prefixes]
        used += [ns[0] for ns in self.wsdl.root.nsprefixes.items()]
        for n in range(0, 1024):
            p = 'ns%d' % n
            if p not in used:
                return p
        raise Exception('prefixes exhausted')

    def getprefix(self, u):
        """
        Get the prefix for the specified namespace (uri)
        @param u: A namespace uri.
        @type u: str
        @return: The namspace.
        @rtype: (prefix, uri).
        """
        for ns in Namespace.all:
            if u == ns[1]:
                return ns[0]
        for ns in self.prefixes:
            if u == ns[1]:
                return ns[0]
        raise Exception('ns (%s) not mapped' % u)

    def xlate(self, type):
        """
        Get a (namespace) translated I{qualified} name for specified type.
        @param type: A schema type.
        @type type: I{suds.xsd.sxbasic.SchemaObject}
        @return: A translated I{qualified} name.
        @rtype: str
        """
        resolved = type.resolve()
        name = resolved.name
        if type.unbounded():
            name += '[]'
        ns = resolved.namespace()
        if ns[1] == self.wsdl.tns[1]:
            return name
        prefix = self.getprefix(ns[1])
        return ':'.join((prefix, name))

    def description(self, html=False):
        """
        Get a textual description of the service for which this object
        represents.
        @return: A textual description.
        @rtype: str
        """
        s = []
        if html:
            indent = lambda n: '<p>%*s' % (n * 3, ' ')
            line = '<hr/>'
        else:
            indent = lambda n: '\n%*s' % (n * 3, ' ')
            line = '\n' + '-'*80
        s.append('Service ( %s ) tns="%s"' %
                 (self.service.name, self.wsdl.tns[1]))
        s.append(indent(1))
        s.append('Prefixes (%d)' % len(self.prefixes))
        for p in self.prefixes:
            s.append(indent(2))
            s.append('%s = "%s"' % p)
        s.append(indent(1))
        s.append('Ports (%d):' % len(self.ports))
        for p in self.ports:
            s.append(indent(2))
            s.append('(%s)' % p[0].name)
            s.append(indent(3))
            s.append('Methods (%d):' % len(p[1]))
            for m in p[1]:
                sig = []
                s.append(indent(4))
                sig.append(m[0])
                sig.append('(')
                for p in m[1]:
                    sig.append(self.xlate(p[1]))
                    sig.append(' ')
                    sig.append(p[0])
                    sig.append(', ')
                sig.append(')')
                try:
                    s.append(''.join(sig))
                except:
                    pass
            s.append(indent(3))
            s.append('Types (%d):' % len(self.types))
            for t in self.types:
                s.append(indent(4))
                s.append(self.xlate(t[0]))
        s.append(line)
        return ''.join(s)

    def __str__(self):
        try:
            return self.description()
        except Exception as e:
            log.exception(e)
        return tostr(e)

    def __repr__(self):
        return self.__str__()

    def html(self):
        try:
            return self.description(html=True)
        except Exception as e:
            log.exception(e)
        return tostr(e)
