# vim: expandtab
import re, time
from StringIO import StringIO

from genshi.builder import tag

from trac.core import *
from trac.wiki.formatter import format_to_html
from trac.util import TracError
from trac.util.text import to_unicode
from trac.web.api import IRequestFilter, RequestDone
from trac.web.chrome import add_script
from trac.wiki.api import parse_args, IWikiMacroProvider
from trac.wiki.macros import WikiMacroBase
from trac.wiki.model import WikiPage

from macropost.api import IMacroPoster

class AddCommentMacro(WikiMacroBase):
    """A macro to add comments to a page. Usage:
    {{{
    [[AddComment]]
    }}}
    The macro accepts one optional argument that allows appending
    to the wiki page even though user may not have modify permission:
    {{{
    [[AddComment(appendonly)]]
    }}}
    """
    implements(IWikiMacroProvider, IRequestFilter, IMacroPoster)

    def expand_macro(self, formatter, name, content):
        
        args, kw = parse_args(content)
        req = formatter.req
        context = formatter.context
        
        # Prevent multiple inclusions - store a temp in req
        if hasattr(req, 'addcommentmacro'):
            raise TracError('\'AddComment\' macro cannot be included twice.')
        req.addcommentmacro = True
        
        # Prevent it being used outside of wiki page context
        resource = context.resource
        if not resource.realm == 'wiki':
            raise TracError('\'AddComment\' macro can only be used in Wiki pages.')
        
        # Setup info and defaults
        authname = req.authname
        page = WikiPage(self.env, resource)
        page_url = req.href.wiki(resource.id)
        wikipreview = req.args.get("preview", "")
        
        # Can this user add a comment to this page?
        appendonly = ('appendonly' in args)
        cancomment = False
        if page.readonly:
            if 'WIKI_ADMIN' in req.perm(resource):
                cancomment = True
        elif 'WIKI_MODIFY' in req.perm(resource):
            cancomment = True
        elif appendonly and 'WIKI_VIEW' in req.perm(resource):
            cancomment = True
        else:
            raise TracError('Error: Insufficient privileges to AddComment')
        
        # Get the data from the POST
        comment = req.args.get("addcomment", "")
        preview = req.args.get("previewaddcomment", "")
        cancel = req.args.get("canceladdcomment", "")
        submit = req.args.get("submitaddcomment", "")
        if not cancel and req.authname == 'anonymous':
            authname = req.args.get("authoraddcomment", authname)
        
        # Ensure [[AddComment]] is not present in comment, so that infinite
        # recursion does not occur.
        comment = to_unicode(re.sub('(^|[^!])(\[\[AddComment)', '\\1!\\2', comment))
        
        the_preview = the_message = the_form = tag()

        # If we are submitting or previewing, inject comment as it should look
        if cancomment and comment and (preview or submit):
            heading = tag.h4("Comment by ", authname, " on ",
                        to_unicode(time.strftime('%c', time.localtime())),
                        id="commentpreview")
            if preview:
                the_preview = tag.div(heading,
                                format_to_html(self.env, context, comment),
                                class_="wikipage", id="preview")
        
        # Check the form_token
        form_ok = True
        if submit and req.args.get('__FORM_TOKEN','') != req.form_token:
            form_ok = False
            the_message = tag.div(tag.strong("ERROR: "),
                "AddComment received incorrect form token. "
                "Do you have cookies enabled?",
                class_="system-message")
        
        # When submitting, inject comment before macro
        if comment and submit and cancomment and form_ok:
            submitted = False
            newtext = ""
            for line in page.text.splitlines():
                if line.find('[[AddComment') == 0:
                    newtext += "==== Comment by %s on %s ====\n%s\n\n" % (
                            authname,
                            to_unicode(time.strftime('%c', time.localtime())),
                            comment)
                    submitted = True
                newtext += line+"\n"
            if submitted:
                page.text = newtext
                page.save(authname, 'Comment added.', req.environ['REMOTE_ADDR'])
                # We can't redirect from macro as it will raise RequestDone
                # which like other macro errors gets swallowed in the Formatter.
                # We need to re-raise it in a post_process_request instead.
                try:
                    req._outheaders = []
                    req.redirect(page_url)
                except RequestDone:
                    req.addcomment_raise = True
            else:
                the_message = tag.div(tag.strong("ERROR: "), "[[AddComment]] "
                          "macro call must be the only content on its line. "
                          "Could not add comment.",
                          class_="system-message")

        the_form = tag.form(
                    tag.fieldset(
                        tag.legend("Add comment"),
                        tag.div(
                            (wikipreview and "Page preview..." or None),
                            tag.textarea((not cancel and comment or ""),
                                        class_="wikitext",
                                        id="addcomment",
                                        name="addcomment",
                                        cols=80, rows=5,
                                    disabled=(not cancomment and "disabled" or None)),
                            class_="field"
                        ),
                        (req.authname == 'anonymous' and tag.div(
                            tag.label("Your email or username:",
                                    for_="authoraddcomment"),
                            tag.input(id="authoraddcomment", type="text",
                                    size=30, value=authname)
                        ) or None),
                        tag.input(type="hidden", name="__FORM_TOKEN",
                                        value=req.form_token),
                        tag.div(
                            tag.input(value="Add comment", type="submit",
                                    name="submitaddcomment", size=30,
                                    disabled=(not cancomment and "disabled" or None)),
                            tag.input(value="Preview comment", type="submit",
                                    name="previewaddcomment",
                                    disabled=(not cancomment and "disabled" or None)),
                            tag.input(value="Cancel", type="submit",
                                    name="canceladdcomment",
                                    disabled=(not cancomment and "disabled" or None)),
                            class_="buttons"
                        ),
                    ),
                    method="post",
                    action=page_url+"#commenting",
                )
        
        add_script(req, 'common/js/wikitoolbar.js')
        
        return tag.div(the_preview, the_message, the_form, id="commenting")
    
    # IMacroPoster method
    
    def process_macro_post(self, req):
        self.log.debug('AddCommentMacro: Got a POST')

    # IRequestFilter methods

    def pre_process_request(self, req, handler):
        return handler

    def post_process_request(self, req, template, data, content_type):
        if hasattr(req, 'addcomment_raise'):
            self.env.log.debug("AddCommentMacro: Re-raising RequestDone from redirect")
            raise RequestDone
        return template, data, content_type
