# Copyright (c) 2005 Infrae. All rights reserved.
# See also LICENSE.txt
"""This module contains the history related classes and functions.
"""

# Zope3
from persistent import Persistent
from zope.interface import implements
from zope.component import ComponentLookupError
from zope.security.interfaces import NoInteraction
from zope.security.management import getInteraction

from zope.app import zapi
from zope.app.intid.interfaces import IIntIds
from zope.app.component.hooks import getSite
from zope.app.container.interfaces import INameChooser
from zope.app.container.ordered import OrderedContainer
from zope.app.mail.interfaces import IMailDelivery

# documentlibrary
from documentlibrary.core import interfaces, timezone
from documentlibrary.core.template import renderTemplate

class HistoryFolder(OrderedContainer):
    implements(interfaces.IHistoryFolder)

class HistoryEntry(Persistent):
    implements(interfaces.IHistoryEntryContained)

    def __init__(self, transition_event, principal):
        doc = transition_event.object
        self.doc_title = doc.title
        intids = zapi.getUtility(IIntIds)
        try:
            self.doc_id = intids.getId(doc)
        except KeyError:
            # object has been deleted, so no way to get doc_id anymore
            self.doc_id = None
        self.action = transition_event.transition.transition_id
        self.source_state = transition_event.source
        self.destination_state = transition_event.destination
        self.actor_id = principal.id
        self.comment = transition_event.comment
        self.timestamp = timezone.nowInUTC()
        self.message = getMessage(transition_event, principal, 'author', 'web')
        
    def getDocument(self):
        intids = zapi.getUtility(IIntIds)
        # XXX handle case when document doesn't exist anymore?
        if self.doc_id is None:
            return None
        return intids.getObject(self.doc_id)

    def getCategory(self):
        doc = self.getDocument()
        if doc is None:
            return None
        intids = zapi.getUtility(IIntIds)
        # XXX handle case where category doesn't exist anymore?
        return intids.getObject(doc.category)

def createEntry(transition_event, principal):
    history = getSite()['history']
    entry = HistoryEntry(transition_event, principal)
    name = INameChooser(history).chooseName('', entry)
    history[name] = entry

def sendEmail(transition_event, principal):
    sendEmailLibrarians(transition_event, principal)
    sendEmailAuthors(transition_event, principal)
    
def sendEmailAuthors(transition_event, principal):
    doc = transition_event.object
    from_address = getLibrarianFromAddress(doc)

    # there is no librarian for this category, so bail out
    # XXX is this right?
    if from_address is None:
        return

    message = getMessage(transition_event, principal, 'author', 'email',
                         from_address=from_address)
    if message is None:
        return

    to_addresses = [email for (firstname, lastname, email) in doc.authors]

    queueForEmail(    
        from_address, to_addresses, message)

def sendEmailLibrarians(transition_event, principal):
    doc = transition_event.object
    from_address = getLibrarianFromAddress(doc)
    # XXX is this right?
    if from_address is None:
        return
    
    message = getMessage(transition_event, principal, 'librarian', 'email',
                         from_address=from_address)
    if message is None:
        return
    
    librarians = doc.getCategory().getOwningLibrarians()
    to_addresses = zapi.getUtility(
        interfaces.IUserInfoLookup).getEmailAddresses(librarians)

    queueForEmail(from_address, to_addresses, message)

def queueForEmail(from_, to_addresses, message):
    # if we have no email addresses, don't send a thing
    if not to_addresses:
        return
    mailer = zapi.getUtility(IMailDelivery)
    mailer.send(from_, to_addresses, message.encode('UTF-8'))

def getLibrarianFromAddress(doc):
    username = doc.getCategory().getNearestLibrarian()
    if username is None:
        return None
    lookup = zapi.getUtility(interfaces.IUserInfoLookup)
    userinfo = lookup.getUserInfoDefault(username)
    return userinfo.email

def getMessage(transition_event, principal, audience, medium, **kw):
    doc = transition_event.object
    lookup = zapi.getUtility(interfaces.IUserInfoLookup)
    # XXX frustrating user prefix chaos..
    userid = principal.id
    if userid.startswith('documentlibrary'):
        userid = userid[len('documentlibrary'):]
    userinfo = lookup.getUserInfoDefault(userid)

    formats = []
    if doc.file_available:
        formats.append('original')
    if doc.pdf is not None:
        formats.append('pdf')
    if doc.plaintext is not None:
        formats.append('plaintext')
    formats = ', '.join(formats)
    author_emails = ', '.join(doc.authorEmails())

    # XXX ugh..
    groups = sorted(list(doc.access))
    if 'zope.Everybody' in groups:
        groups.remove('zope.Everybody')
        groups.insert(0, 'Everybody')
    groups = ', '.join(groups)

    availabledate = formatDate(doc.availabledate)
    expirydate = formatDate(doc.expirydate)
    if doc.expirydate is not None:
        expiry_days = (doc.expirydate - timezone.nowInUTC()).days
    else:
        # XXX should this ever happen in practice? it happens in tests
        expiry_days = None
    data = {
        'doc': doc,
        'user': userinfo,
        'category_name': doc.getCategoryName(),
        'foi_name': doc.getFoiTopicName(),
        'author_emails': author_emails,
        'groups': groups,
        'formats': formats,
        'availabledate': availabledate,
        'expirydate': expirydate,
        'expiry_days': expiry_days,
        }
    
    data.update(kw)
    if getattr(doc, '__parent__', None) is not None:
        data['doc_url'] = getUrl(doc)
        data['handle_url'] = getHandleUrl(doc)
    else:
        # XXX does this make sense?
        data['doc_url'] = '<object was deleted>'

    return renderMessage(transition_event.transition,
                         audience, medium, **data)

class MessageError(Exception):
    pass

class NoTemplateError(MessageError):
    pass

def renderMessage(transition, audience, medium, **kw):
    """Render message for transition with data.

    audience is either 'author' or 'librarian'.

    medium is either 'web' or 'email'. A web template may contain
    HTML.
    """
    template_name = transition.user_data.get('template')
    if template_name is None:
        # no template name found
        return None
    try:
        template = getSite()['templates'][
            '%s_%s_%s' % (template_name, audience, medium)].getSource()
    except KeyError:
        # no template for this name found, return None
        return None
    return renderTemplate(template, **kw)

def getUrl(obj):
    """This is scary but makes life a lot easier...
    """
    try:
        request = getInteraction().participations[0]
        return zapi.absoluteURL(obj, request)
    except (NoInteraction, ComponentLookupError):
        return 'Not a real url'

def getHandleUrl(obj):
    try:
        request = getInteraction().participations[0]
        return '%s/handle/%s' % (
            zapi.absoluteURL(getSite(), request), obj.handle_id)
    except (NoInteraction, ComponentLookupError):
        return 'Not a real url'
    
def formatDate(date):
    try:
        request = getInteraction().participations[0]
        return request.locale.dates.getFormatter('date', None).format(date)
    except (NoInteraction, ComponentLookupError):
        return 'Not a real date'
