# CommentsSupport mixin

import sys, os, string, re, email, email.Errors
from mailbox import UnixMailbox, PortableUnixMailbox
from urllib import quote
from cStringIO import StringIO

from AccessControl import getSecurityManager, ClassSecurityInfo
from App.Common import absattr
from DateTime import DateTime
from Globals import InitializeClass

import Permissions
from Regexps import fromlineexpr
from Utils import BLATHER, html_quote, DateTimeSyntaxError, \
  stringBefore, stringBeforeAndIncluding, stringAfter, stringAfterAndIncluding


class CommentsSupport:
    """
    I manage comments stored as rfc2822 messages in a wiki page.

    This mixin class looks for comments in mbox/RFC2822 format in the page
    text, and provides services for parsing and displaying them.
    Everything above the first comment is considered the document part,
    the rest of the page is considered the discussion part.

    Prior to 0.30 we called this MessageSupport. When working with
    comments, we represent them as email.Message.Message objects.

    So that we can recognize comments/messages without extra markup or too
    many false positives, we require each message to begin with BAW's "strict"
    From line regexp from the python mailbox module.

    See also Editing.py.
    """
    security = ClassSecurityInfo()

    # accessors

    security.declareProtected(Permissions.View, 'supportsComments')
    def supportsComments(self):
        """does this page parse embedded rfc2822 messages ?"""
        return re.search(r'(?i)(msg)',self.pageTypeId()) is not None

    security.declareProtected(Permissions.View, 'hasComments')
    def hasComments(self):
        """does this page have one or more rfc2822-style comments ?"""
        return self.messageCount() > 0

    security.declareProtected(Permissions.View, 'commentCount')
    def commentCount(self):
        """
        The number of comments in this page.
        """
        return len(re.findall(fromlineexpr,self.discussionPart()))

    security.declareProtected(Permissions.View, 'documentPart')
    def documentPart(self):
        """
        This page's text from beginning up to the first message, if any.
        """
        return re.split(fromlineexpr,self.text(),1)[0]

    document = documentPart

    security.declareProtected(Permissions.View, 'discussionPart')
    def discussionPart(self):
        """
        This page's text from the first comment to the end (or '').
        """
        return stringAfterAndIncluding(fromlineexpr,self.text())

    security.declareProtected(Permissions.View, 'mailbox')
    def mailbox(self):
        """
        Return the messages on this page as a Mailbox (iterator of Message)
        """
        # from mailbox docs: this is defensive against ill-formed MIME
        # messages in the mailbox, but you have to be prepared to receive
        # the empty string from the mailbox's `next()' method
        def msgfactory(fp):
            try:
                return email.message_from_file(fp)
            except email.Errors.MessageParseError:
                BLATHER('message parsing error in',self.id())
                return ''
        return UnixMailbox(StringIO(self.text()), msgfactory)

    def comments(self):
        """
        Return this page's comments as a list of Messages.
        """
        msgs = []
        mbox = self.mailbox()
        m = mbox.next()
        while m is not None:
            msgs.append(m)
            m = mbox.next()
        return msgs


    # utilities

    def fromLineFrom(self,email,date):
        """
        Generate a conformant mbox From line from email and date strings.

        (unless date is unparseable, in which case we omit that part)
        """
        # "email" is in fact a real name or zwiki username - adapt it
        email = re.sub(r'\s','',email) or 'unknown'
        try:
            d = DateTime(date)
            return 'From %s %s %s %d %02d:%02d:%02d %s %d\n' % (
                email,d.aDay(),d.aMonth(),d.day(),d.hour(),
                d.minute(),d.second(),d.timezone(),d.year())
        except (DateTimeSyntaxError,AttributeError,IndexError):
            return 'From %s\n' % email

    def messageIdFromTime(self,time):
        """
        Generate a somewhat unique email message-id based on a DateTime
        """
        msgid = time.strftime('%Y%m%d%H%M%S')+time.rfc822()[-5:]+'@'
        if hasattr(self,'REQUEST'):
            msgid += re.sub(r'http://','',self.REQUEST.get('SERVER_URL',''))
        msgid = '<%s>' % msgid
        return msgid

    security.declareProtected(Permissions.View, 'upgradeComments')
    def upgradeComments(self,REQUEST=None):
        """
        Update the format of any comments on this page.
        """
        # XXX it would be good if this could upgrade old-style
        # (non-rfc2822) zwiki comments to the new style
        pass

        # convert From: to proper From delimiters, no longer needed
        #if re.search(r'\n\nFrom: ',self.text()):
        #    msgs = ''
        #    mailbox = PortableUnixMailbox(StringIO(
        #        re.sub(r'\n\nFrom: ',r'\n\nFrom \nFrom: ',self.text())))
        #    m = mailbox.next()
        #    while m is not None:
        #        msgs += self.fromLineFrom(m.get('from'),m.get('date'))
        #        msgs += string.join(m.headers,'')
        #        msgs += '\n'
        #        msgs += m.fp.read()
        #        m = mailbox.next()
        #    new = re.split('\n\nFrom: ',self.text(),1)[0]+'\n\n'+msgs
        #    self.edit(text=new, REQUEST=REQUEST,log='upgraded comments')
        #    BLATHER('upgraded comments on',self.id())

    # backwards compatibility
    supportsMessages = supportsComments
    hasMessages = hasComments
    messageCount = commentCount
    messagesPart = discussionPart
    messages = comments
    upgradeMessages = upgradeComments

InitializeClass(CommentsSupport)

