import os
from zope.interface import Interface, implements
from twisted.internet import reactor, defer
from nevow import rend, appserver, inevow, \
     stan, loaders, flat
from formless import annotate, webform, iformless

from ldaptor.protocols.ldap import ldapclient, ldapsyntax, ldapconnector, \
     distinguishedname
from ldaptor import ldapfilter
from ldaptor.protocols import pureldap

class ILDAPConfig(Interface):
    """Addressbook configuration retrieval."""

    def getBaseDN(self):
        """Get the LDAP base DN, as a DistinguishedName."""

    def getServiceLocationOverrides(self):
        """
        Get the LDAP service location overrides, as a mapping of
        DistinguishedName to (host, port) tuples.
        """

class LDAPConfig(object):
    implements(ILDAPConfig)

    def __init__(self,
                 baseDN,
                 serviceLocationOverrides=None):
        self.baseDN = distinguishedname.DistinguishedName(baseDN)
        self.serviceLocationOverrides = {}
        if serviceLocationOverrides is not None:
            for k,v in serviceLocationOverrides.items():
                dn = distinguishedname.DistinguishedName(k)
                self.serviceLocationOverrides[dn]=v

    def getBaseDN(self):
        return self.baseDN

    def getServiceLocationOverrides(self):
        return self.serviceLocationOverrides

class IAddressBookSearch(annotate.TypedInterface):
    def search(sn = annotate.String(label="Last name"),
               givenName = annotate.String(label="First name"),
               telephoneNumber = annotate.String(),
               description = annotate.String()):
        pass
    search = annotate.autocallable(search)

class CurrentSearch(object):
    implements(IAddressBookSearch, inevow.IContainer)
    data = {}

    def _getSearchFilter(self):
        filters = []
        for attr,value in self.data.items():
            if value is not None:
                f = ldapfilter.parseMaybeSubstring(attr, value)
                filters.append(f)
        if not filters:
            return None
        searchFilter = pureldap.LDAPFilter_and(
            [pureldap.LDAPFilter_equalityMatch(
            attributeDesc=pureldap.LDAPAttributeDescription('objectClass'),
            assertionValue=pureldap.LDAPAssertionValue('addressbookPerson'))]
            + filters)
        return searchFilter

    def search(self, **kw):
        for k,v in kw.items():
            if v is None:
                del kw[k]
        self.data = kw
        return self

    def __nonzero__(self):
        return bool(self.data)

    def __iter__(self):
        if self.data is None:
            return
        for k,v in self.data.items():
            yield (k,v)

    def child(self, context, name):
        if name == 'searchFilter':
            return self._getSearchFilter()
        if name != 'results':
            return None
        config = context.locate(ILDAPConfig)

        c=ldapconnector.LDAPClientCreator(reactor, ldapclient.LDAPClient)
        d=c.connectAnonymously(config.getBaseDN(),
                               config.getServiceLocationOverrides())

        def _search(proto, base, searchFilter):
            baseEntry = ldapsyntax.LDAPEntry(client=proto, dn=base)
            d=baseEntry.search(filterObject=searchFilter)
            return d

        d.addCallback(_search, config.getBaseDN(), self._getSearchFilter())
        return d

def LDAPFilterSerializer(original, context):
    return original.asText()

# TODO need to make this pretty some day.
for c in [
    pureldap.LDAPFilter_and,
    pureldap.LDAPFilter_or,
    pureldap.LDAPFilter_not,
    pureldap.LDAPFilter_substrings,
    pureldap.LDAPFilter_equalityMatch,
    pureldap.LDAPFilter_greaterOrEqual,
    pureldap.LDAPFilter_lessOrEqual,
    pureldap.LDAPFilter_approxMatch,
    pureldap.LDAPFilter_present,
    pureldap.LDAPFilter_extensibleMatch,
    ]:
    flat.registerFlattener(LDAPFilterSerializer, c)

class AddressBookResource(rend.Page):
    docFactory = loaders.xmlfile(
        'searchform.xhtml',
        templateDir=os.path.split(os.path.abspath(__file__))[0])

    def configurable_(self, context):
        try:
            i = context.locate(inevow.IHand)
        except KeyError:
            i = CurrentSearch()
        return i

    def data_search(self, context, data):
        d = defer.maybeDeferred(self.locateConfigurable, context, '')
        def cb(configurable):
            return configurable.original
        d.addCallback(cb)
        return d

    def child_form_css(self, request):
        return webform.defaultCSS

    def render_input(self, context, data):
        formDefaults = context.locate(iformless.IFormDefaults)
        methodDefaults = formDefaults.getAllDefaults('search')
        conf = self.configurable_(context)
        for k,v in conf:
            methodDefaults[k] = v
        return webform.renderForms()

    def render_haveSearch(self, context, data):
        r=context.tag.allPatterns(str(bool(data)))
        return context.tag.clear()[r]

    def render_searchFilter(self, context, data):
        return data.asText()

    def render_iterateMapping(self, context, data):
        headers = context.tag.allPatterns('header')
        keyPattern = context.patternGenerator('key')
        valuePattern = context.patternGenerator('value')
        divider = context.patternGenerator('divider', default=stan.invisible)
        content = [(keyPattern(data=key),
                    valuePattern(data=value),
                    divider())
                   for key, value in data.items()]
        if not content:
            content = context.tag.allPatterns('empty')
        else:
            # No divider after the last thing.
            content[-1] = content[-1][:-1]
        footers = context.tag.allPatterns('footer')

        return context.tag.clear()[ headers, content, footers ]

def getSite(config):
    form = AddressBookResource()
    form.remember(config, ILDAPConfig)
    site = appserver.NevowSite(form)
    return site
