from types import ModuleType, ListType, TupleType
from Products.CMFPlone.interfaces.InterfaceTool import IInterfaceTool \
     as z2IInterfaceTool
from Products.CMFPlone.interfaces import IInterfaceTool
from Acquisition import aq_base
from Products.CMFCore.utils import registerToolInterface
from Products.CMFCore.utils import UniqueObject

from OFS.SimpleItem import SimpleItem
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from Products.CMFPlone.PloneBaseTool import PloneBaseTool

from Interface.Implements import getImplements, flattenInterfaces
from Interface import Interface
from Interface.IMethod import IMethod

from zope.interface import implements

_marker = ('module_finder',)

class InterfaceTool(PloneBaseTool, UniqueObject, SimpleItem):
    """ This tool exposes the interface package for TTW applications,
    by accepting a dotted name of an interface and exporting the
    IInterface API """

    __implements__ = (PloneBaseTool.__implements__, z2IInterfaceTool,
                      SimpleItem.__implements__, )

    implements(IInterfaceTool)

    id = 'portal_interface'
    meta_type= 'Portal Interface Tool'
    security = ClassSecurityInfo()

    security.declarePublic('objectImplements')
    def objectImplements(self, obj, dotted_name):
        """ Asserts if an object implements a given interface """
        obj = aq_base(obj)
        iface = resolveInterface(dotted_name)
        return iface.isImplementedBy(obj)

    security.declarePublic('classImplements')
    def classImplements(self, obj, dotted_name):
        """ Asserts if an object's class implements a given interface """
        klass = aq_base(obj).__class__
        iface = resolveInterface(dotted_name)
        return iface.isImplementedBy(klass)

    security.declarePublic('namesAndDescriptions')
    def namesAndDescriptions(self, dotted_name, all=0):
        """ Returns a list of pairs (name, description) for a given
        interface"""
        iface = resolveInterface(dotted_name)
        nd = iface.namesAndDescriptions(all=all)
        return [(n, d.getDoc()) for n, d in nd]

    security.declarePublic('getInterfacesOf')
    def getInterfacesOf(self, object):
        """Returns the list of interfaces which are implemented by the object
        """
        impl = getImplements(object)
        if impl:
            if type(impl) in (ListType, TupleType):
                result = flattenInterfaces(impl)
            else:
                result = (impl, )
            return [ iface for iface in result if iface is not Interface ]

    def getBaseInterfacesOf(self, object):
        """Returns all base interfaces of an object but no direct interfaces

        Base interfaces are the interfaces which are the super interfaces of the
        direct interfaces
        """
        ifaces = self.getInterfacesOf(object)
        bases = []
        for iface in ifaces:
            visitBaseInterfaces(iface, bases)
        return [biface for biface in bases if biface not in ifaces ]

    def getInterfaceInformations(self, iface):
        """Gets all useful informations from an iface

        * name
        * dotted name
        * trimmed doc string
        * base interfaces
        * methods with signature and trimmed doc string
        * attributes with trimemd doc string
        """
        bases = [ base for base in iface.getBases() if base is not Interface ]

        attributes = []
        methods = []
        for name, desc in iface.namesAndDescriptions():
            if IMethod.isImplementedBy(desc):
                methods.append({'signature' : desc.getSignatureString(),
                                'name' : desc.getName(),
                                'doc' : _trim_doc_string(desc.getDoc()),
                               })
            else:
                attributes.append({'name' : desc.getName(),
                                   'doc' : _trim_doc_string(desc.getDoc()),
                                  })

        result = {
            'name' : iface.getName(),
            'dotted_name' : getDottedName(iface),
            'doc' : _trim_doc_string(desc.getDoc()),
            'bases' : bases,
            'base_names' : [getDottedName(iface) for base in bases ],
            'attributes' : attributes,
            'methods' : methods,
            }

        return result

def resolveInterface(dotted_name):
    parts = dotted_name.split('.')
    m_name = '.'.join(parts[:-1])
    k_name = parts[-1]
    module = __import__(m_name, globals(), locals(), [k_name])
    klass = getattr(module, k_name)
    if not issubclass(klass, Interface):
        raise ValueError, '%r is not a valid Interface.' % dotted_name
    return klass

def getDottedName(iface):
    return "%s.%s" % (iface.__module__, iface.__name__)

def _trim_doc_string(text):
    """
    Trims a doc string to make it format
    correctly with structured text.
    """
    text = text.strip().replace('\r\n', '\n')
    lines = text.split('\n')
    nlines = [lines[0]]
    if len(lines) > 1:
        min_indent=None
        for line in lines[1:]:
            indent=len(line) - len(line.lstrip())
            if indent < min_indent or min_indent is None:
                min_indent=indent
        for line in lines[1:]:
            nlines.append(line[min_indent:])
    return '\n'.join(nlines)

def visitBaseInterfaces(iface, lst):
    bases = iface.getBases()
    for base in bases:
        if base is Interface or base in lst:
            return
        lst.append(base)
        visitBaseInterfaces(iface, lst)

class InterfaceFinder:

    _visited = {}
    _found = {}

    def findInterfaces(self, n=None, module=_marker):
        # return class reference info
        dict={}
        pairs = []
        if module is _marker:
            import Products
            module = Products
        self._visited[module] = None
        for sym in dir(module):
            ob=getattr(module, sym)
            if type(ob) is type(Interface) and \
               issubclass(ob, Interface) and \
               ob is not Interface:
                self.found(ob)
            elif type(ob) is ModuleType and ob not in self._visited.keys():
                self.findInterfaces(module=ob)

        ifaces = self._found.keys()
        ifaces.sort()
        ifaces.reverse()
        if n is not None:
            ifaces = ifaces[:n]
        return ifaces

    def found(self, iface):
        self._found[getDottedName(iface)] = iface

InitializeClass(InterfaceTool)
registerToolInterface('portal_interface', IInterfaceTool)
