# -*- coding: utf-8 -*-
#
# Copyright (C) 2004-2009 Edgewall Software
# Copyright (C) 2004 Oliver Rutherfurd
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://trac.edgewall.org/log/.
#
# Author: Daniel Lundin
#         Oliver Rutherfurd (initial implementation)
#         Nuutti Kotivuori (role support)
#
# Trac support for reStructured Text, including a custom 'trac' directive
#
# 'trac' directive code by Oliver Rutherfurd, overhauled by cboos.
#
# Inserts `reference` nodes for TracLinks into the document tree.

__docformat__ = 'reStructuredText'

from distutils.version import StrictVersion
import re
try:
    from docutils import nodes
    from docutils.core import publish_parts
    from docutils.parsers import rst
    from docutils import __version__
    has_docutils = True
except ImportError:
    has_docutils = False

from trac.core import *
from trac.mimeview.api import IHTMLPreviewRenderer, content_to_unicode
from trac.util.html import Element, Markup
from trac.util.translation import _
from trac.web.href import Href
from trac.wiki.api import WikiSystem
from trac.wiki.formatter import WikiProcessor, Formatter, extract_link

if has_docutils and StrictVersion(__version__) < StrictVersion('0.6'):
    # Monkey-patch "raw" role handler in docutils to add a missing check
    # See docutils bug #2845002 on SourceForge
    def raw_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
        if not inliner.document.settings.raw_enabled:
            msg = inliner.reporter.warning('raw (and derived) roles disabled')
            prb = inliner.problematic(rawtext, rawtext, msg)
            return [prb], [msg]
        return _raw_role(role, rawtext, text, lineno, inliner, options,
                         content)
    
    from docutils.parsers.rst import roles
    raw_role.options = roles.raw_role.options
    _raw_role = roles.raw_role
    roles.raw_role = raw_role
    roles.register_canonical_role('raw', raw_role)


class ReStructuredTextRenderer(Component):
    """
    Renders plain text in reStructuredText format as HTML.
    """
    implements(IHTMLPreviewRenderer)

    can_render = False
    
    def __init__(self):
        if has_docutils:
            if StrictVersion(__version__) < StrictVersion('0.3.9'):
                self.log.warning('Docutils version >= %s required, '
                                 '%s found' % ('0.3.9', __version__))
            else:
                self.can_render = True
                self.env.systeminfo.append(('Docutils', __version__))
        
    def get_quality_ratio(self, mimetype):
        if self.can_render and mimetype == 'text/x-rst':
            return 8
        return 0

    def render(self, context, mimetype, content, filename=None, rev=None):
        def trac_get_reference(rawtext, target, text):
            fulltext = text and target+' '+text or target
            link = extract_link(self.env, context, fulltext)
            uri = None
            missing = False
            if isinstance(link, Element):
                linktext = Markup(link).striptags()
                # the following is a bit hackish, but it takes into account:
                #  - an eventual trailing '?' for missing wiki pages
                #  - space eventually introduced due to split_page_names option
                if linktext.rstrip('?').replace(' ', '') != target:
                    text = linktext
                uri = link.attrib.get('href', '')
                missing = 'missing' in link.attrib.get('class', '')
            else:
                uri = context.href.wiki(target)
                missing = not WikiSystem(self.env).has_page(target)
            if uri:                    
                reference = nodes.reference(rawtext, text or target)
                reference['refuri']= uri
                if missing:
                    reference['classes'].append('missing')
                return reference
            return None

        def trac(name, arguments, options, content, lineno,
                 content_offset, block_text, state, state_machine):
            """Inserts a `reference` node into the document 
            for a given `TracLink`_, based on the content 
            of the arguments.

            Usage::

              .. trac:: target [text]

            ``target`` may be any `TracLink`_, provided it doesn't
            embed a space character (e.g. wiki:"..." notation won't work).

            ``[text]`` is optional.  If not given, ``target`` is
            used as the reference text.

            .. _TracLink: http://trac.edgewall.org/wiki/TracLinks
            """
            link = arguments[0]
            if len(arguments) == 2:
                text = arguments[1]
            else:
                text = None
            reference = trac_get_reference(block_text, link, text)
            if reference:
                if isinstance(state, rst.states.SubstitutionDef):
                    return [reference]
                p = nodes.paragraph()
                p += reference
                return [p]
            # didn't find a match (invalid TracLink),
            # report a warning
            warning = state_machine.reporter.warning(
                    '%s is not a valid TracLink' % (arguments[0]),
                    nodes.literal_block(block_text, block_text),
                    line=lineno)
            return [warning]

        def trac_role(name, rawtext, text, lineno, inliner, options={},
                      content=[]):
            args  = text.split(" ",1)
            link = args[0]
            if len(args)==2:
                text = args[1]
            else:
                text = None
            reference = trac_get_reference(rawtext, link, text)
            if reference:
                return [reference], []
            warning = nodes.warning(None, nodes.literal_block(text,
                'WARNING: %s is not a valid TracLink' % rawtext))
            return warning, []

        # 1 required arg, 1 optional arg, spaces allowed in last arg
        trac.arguments = (1,1,1)
        trac.options = None
        trac.content = None
        rst.directives.register_directive('trac', trac)
        rst.roles.register_local_role('trac', trac_role)

        # The code_block could is taken from the leo plugin rst2
        def code_formatter(language, text):
            processor = WikiProcessor(Formatter(self.env, context), language)
            html = processor.process(text)
            raw = nodes.raw('', html, format='html')
            return raw
        
        def code_role(name, rawtext, text, lineno, inliner, options={},
                      content=[]):
            language = options.get('language')
            if not language:
                args  = text.split(':', 1)
                language = args[0]
                if len(args) == 2:
                    text = args[1]
                else:
                    text = ''
            reference = code_formatter(language, text)
            return [reference], []
        
        def code_block(name, arguments, options, content, lineno,
                       content_offset, block_text, state, state_machine):
            """
            Create a code-block directive for docutils.

            Usage: .. code-block:: language

            If the language can be syntax highlighted it will be.
            """
            language = arguments[0]
            text = '\n'.join(content)        
            reference = code_formatter(language, text)
            return [reference]

        # These are documented
        # at http://docutils.sourceforge.net/spec/howto/rst-directives.html.
        code_block.arguments = (
            1, # Number of required arguments.
            0, # Number of optional arguments.
            0) # True if final argument may contain whitespace.
    
        # A mapping from option name to conversion function.
        code_role.options = code_block.options = {
            'language' :
            rst.directives.unchanged # Return the text argument, unchanged
        }
        code_block.content = 1 # True if content is allowed.
        # Register the directive with docutils.
        rst.directives.register_directive('code-block', code_block)
        rst.roles.register_local_role('code-block', code_role)

        _inliner = rst.states.Inliner()
        _parser = rst.Parser(inliner=_inliner)
        content = content_to_unicode(self.env, content, mimetype)
        parts = publish_parts(content, writer_name='html', parser=_parser,
                              settings_overrides={'halt_level': 6, 
                                                  'file_insertion_enabled': 0, 
                                                  'raw_enabled': 0})
        return parts['html_body']
