# unproudly borrowed from pypy : 
# http://codespeak.net/svn/pypy/trunk/pypy/tool/rest/rst.py
""" reStructuredText generation tools

    provides an api to build a tree from nodes, which can be converted to
    ReStructuredText on demand

    note that not all of ReST is supported, a usable subset is offered, but
    certain features aren't supported, and also certain details (like how links
    are generated, or how escaping is done) can not be controlled
"""

import re

def escape(txt):
    """escape ReST markup"""
    if not isinstance(txt, str) and not isinstance(txt, unicode):
        txt = str(txt)
    # XXX this takes a very naive approach to escaping, but it seems to be
    # sufficient...
    for c in '\\*`|:_':
        txt = txt.replace(c, '\\%s' % (c,))
    return txt

class RestError(Exception):
    """ raised on containment errors (wrong parent) """

class AbstractMetaclass(type):
    def __new__(cls, *args):
        obj = super(AbstractMetaclass, cls).__new__(cls, *args)
        parent_cls = obj.parentclass
        if parent_cls is None:
            return obj
        if not isinstance(parent_cls, list):
            class_list = [parent_cls]
        else:
            class_list = parent_cls
        if obj.allow_nesting:
            class_list.append(obj)
        
        for _class in class_list:
            if not _class.allowed_child:
                _class.allowed_child = {obj:True}
            else:
                _class.allowed_child[obj] = True
        return obj

class AbstractNode(object):
    """ Base class implementing rest generation
    """
    sep = ''
    __metaclass__ = AbstractMetaclass
    parentclass = None # this exists to allow parent to know what
        # children can exist
    allow_nesting = False
    allowed_child = {}
    defaults = {}
    
    _reg_whitespace = re.compile('\s+')

    def __init__(self, *args, **kwargs):
        self.parent = None
        self.children = []
        for child in args:
            self._add(child)
        for arg in kwargs:
            setattr(self, arg, kwargs[arg])
    
    def join(self, *children):
        """ add child nodes
        
            returns a reference to self
        """
        for child in children:
            self._add(child)
        return self
    
    def add(self, child):
        """ adds a child node
            
            returns a reference to the child
        """
        self._add(child)
        return child
        
    def _add(self, child):
        if child.__class__ not in self.allowed_child:
            raise RestError("%r cannot be child of %r" % \
                (child.__class__, self.__class__))
        self.children.append(child)
        child.parent = self
    
    def __getitem__(self, item):
        return self.children[item]
    
    def __setitem__(self, item, value):
        self.children[item] = value

    def text(self):
        """ return a ReST string representation of the node """
        return self.sep.join([child.text() for child in self.children])
    
    def wordlist(self):
        """ return a list of ReST strings for this node and its children """ 
        return [self.text()]

class Rest(AbstractNode):
    """ Root node of a document """
    
    sep = "\n\n"
    def __init__(self, *args, **kwargs):
        AbstractNode.__init__(self, *args, **kwargs)
        self.links = {}
    
    def render_links(self, check=False):
        """render the link attachments of the document"""
        assert not check, "Link checking not implemented"
        if not self.links:
            return ""
        link_texts = []
        # XXX this could check for duplicates and remove them...
        for link, target in self.links.iteritems():
            link_texts.append(".. _`%s`: %s" % (escape(link), target))
        return "\n" + "\n".join(link_texts) + "\n\n"

    def text(self):
        outcome = []
        if (isinstance(self.children[0], Transition) or
                isinstance(self.children[-1], Transition)):
            raise ValueError, ('document must not begin or end with a '
                               'transition')
        for child in self.children:
            outcome.append(child.text())
        
        # always a trailing newline
        text = self.sep.join([i for i in outcome if i]) + "\n"
        return text + self.render_links()

class Transition(AbstractNode):
    """ a horizontal line """
    parentclass = Rest

    def __init__(self, char='-', width=80, *args, **kwargs):
        self.char = char
        self.width = width
        super(Transition, self).__init__(*args, **kwargs)
        
    def text(self):
        return (self.width - 1) * self.char

class Paragraph(AbstractNode):
    """ simple paragraph """

    parentclass = Rest
    sep = " "
    indent = ""
    # FIXME
    width = 880
    
    def __init__(self, *args, **kwargs):
        # make shortcut
        args = list(args)
        for num, arg in enumerate(args):
            if isinstance(arg, str):
                args[num] = Text(arg)
        super(Paragraph, self).__init__(*args, **kwargs)
    
    def text(self):
        texts = []
        for child in self.children:
            texts += child.wordlist()
        
        buf = []
        outcome = []
        lgt = len(self.indent)
        
        def grab(buf):
            outcome.append(self.indent + self.sep.join(buf))
        
        texts.reverse()
        while texts:
            next = texts[-1]
            if not next:
                texts.pop()
                continue
            if lgt + len(self.sep) + len(next) <= self.width or not buf:
                buf.append(next)
                lgt += len(next) + len(self.sep)
                texts.pop()
            else:
                grab(buf)
                lgt = len(self.indent)
                buf = []
        grab(buf)
        return "\n".join(outcome)
    
class SubParagraph(Paragraph):
    """ indented sub paragraph """

    indent = " "
    
class Title(Paragraph):
    """ title element """

    parentclass = Rest
    belowchar = "="
    abovechar = ""
    
    def text(self):
        txt = self._get_text()
        lines = []
        if self.abovechar:
            lines.append(self.abovechar * len(txt))
        lines.append(txt)
        if self.belowchar:
            lines.append(self.belowchar * len(txt))
        return "\n".join(lines)

    def _get_text(self):
        txt = []
        for node in self.children:
            txt += node.wordlist()
        return ' '.join(txt)

class AbstractText(AbstractNode):
    parentclass = [Paragraph, Title]
    start = ""
    end = ""
    def __init__(self, _text):
        self._text = _text
    
    def text(self):
        text = self.escape(self._text)
        return self.start + text + self.end

    def escape(self, text):
        if not isinstance(text, str) and not isinstance(text, unicode):
            text = str(text)
        if self.start:
            text = text.replace(self.start, '\\%s' % (self.start,))
        if self.end and self.end != self.start:
            text = text.replace(self.end, '\\%s' % (self.end,))
        return text
    
class Text(AbstractText):
    def wordlist(self):
        text = escape(self._text)
        return self._reg_whitespace.split(text)

class LiteralBlock(AbstractText):
    parentclass = Rest
    start = '::\n\n'

    def text(self):
        if not self._text.strip():
            return ''
        text = self.escape(self._text).split('\n')
        for i, line in enumerate(text):
            if line.strip():
                text[i] = '  %s' % (line,)
        return self.start + '\n'.join(text)

class Em(AbstractText):
    start = "*"
    end = "*"

class Strong(AbstractText):
    start = "**"
    end = "**"

class Quote(AbstractText):
    start = '``'
    end = '``'

class Anchor(AbstractText):
    start = '_`'
    end = '`'

class Footnote(AbstractText):
    def __init__(self, note, symbol=False):
        raise NotImplemented('XXX')

class Citation(AbstractText):
    def __init__(self, text, cite):
        raise NotImplemented('XXX')

class ListItem(Paragraph):
    allow_nesting = True
    item_chars = '*+-'
    
    def text(self):
        idepth = self.get_indent_depth()
        indent = self.indent + (idepth + 1) * '  '
        txt = '\n\n'.join(self.render_children(indent))
        ret = []
        item_char = self.item_chars[idepth]
        ret += [indent[len(item_char)+1:], item_char, ' ', txt[len(indent):]]
        return ''.join(ret)
    
    def render_children(self, indent):
        txt = []
        buffer = []
        def render_buffer(fro, to):
            if not fro:
                return
            p = Paragraph(indent=indent, *fro)
            p.parent = self.parent
            to.append(p.text())
        for child in self.children:
            if isinstance(child, AbstractText):
                buffer.append(child)
            else:
                if buffer:
                    render_buffer(buffer, txt)
                    buffer = []
                txt.append(child.text())

        render_buffer(buffer, txt)
        return txt

    def get_indent_depth(self):
        depth = 0
        current = self
        while (current.parent is not None and
                isinstance(current.parent, ListItem)):
            depth += 1
            current = current.parent
        return depth

class OrderedListItem(ListItem):
    item_chars = ["#."] * 5

class DListItem(ListItem):
    item_chars = None
    def __init__(self, term, definition, *args, **kwargs):
        self.term = term
        super(DListItem, self).__init__(definition, *args, **kwargs)

    def text(self):
        idepth = self.get_indent_depth()
        indent = self.indent + (idepth + 1) * '  '
        txt = '\n\n'.join(self.render_children(indent))
        ret = []
        ret += [indent[2:], self.term, '\n', txt]
        return ''.join(ret)

class Link(AbstractText):
    start = '`'
    end = '`_'

    def __init__(self, _text, target):
        self._text = _text
        self.target = target
        self.rest = None
    
    def text(self):
        if self.rest is None:
            self.rest = self.find_rest()
        if self.rest.links.get(self._text, self.target) != self.target:
            raise ValueError('link name %r already in use for a different '
                             'target' % (self.target,))
        self.rest.links[self._text] = self.target
        return AbstractText.text(self)

    def find_rest(self):
        # XXX little overkill, but who cares...
        next = self
        while next.parent is not None:
            next = next.parent
        return next

class InternalLink(AbstractText):
    start = '`'
    end = '`_'
    
class LinkTarget(Paragraph):
    def __init__(self, name, target):
        self.name = name
        self.target = target
    
    def text(self):
        return ".. _`%s`:%s\n" % (self.name, self.target)

class Substitution(AbstractText):
    def __init__(self, text, **kwargs):
        raise NotImplemented('XXX')

class Directive(Paragraph):
    indent = '   '
    def __init__(self, name, *args, **options):
        self.name = name
        self.content = args
        super(Directive, self).__init__()
        self.options = options
        
    def text(self):
        # XXX not very pretty...
        txt = '.. %s::' % (self.name,)
        options = '\n'.join(['    :%s: %s' % (k, v) for (k, v) in
                             self.options.iteritems()])
        if options:
            txt += '\n%s' % (options,)

        if self.content:
            txt += '\n'
            for item in self.content:
                txt += '\n    ' + item
        
        return txt