# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - "text/html+css" Formatter

    @copyright: 2000 - 2004 by Jürgen Hermann <jh@web.de>
    @license: GNU GPL, see COPYING for details.
"""

from MoinMoin.formatter.base import FormatterBase
from MoinMoin import wikiutil, i18n, config
from MoinMoin.Page import Page

class Formatter(FormatterBase):
    """
        Send HTML data.
    """

    hardspace = '&nbsp;'

    def __init__(self, request, **kw):
        apply(FormatterBase.__init__, (self, request), kw)

        # inline tags stack. When an inline tag is called, it goes into
        # the stack. When a block element starts, all inline tags in
        # the stack are closed.
        self._inlineStack = []

        self._in_li = 0
        self._in_code = 0
        self._in_code_area = 0
        self._in_code_line = 0
        self._code_area_num = 0
        self._code_area_js = 0
        self._code_area_state = ['', 0, -1, -1, 0]
        self._show_section_numbers = None
        self._content_ids = []
        self.pagelink_preclosed = False
        self._is_included = kw.get('is_included',False)
        self.request = request
        self.cfg = request.cfg

        if not hasattr(request, '_fmt_hd_counters'):
            request._fmt_hd_counters = []

    # Primitive formatter functions #####################################

    # all other methods should use these to format tags. This keeps the
    # code clean and handle pathological cases like unclosed p and
    # inline tags.

    def langAttr(self, lang=None):
        """ Return lang and dir attribute

        Must be used on all block elements - div, p, table, etc.
        @param lang: if defined, will return attributes for lang. if not
            defined, will return attributes only if the current lang is
            different from the content lang.
        @rtype: dict
        @retrun: language attributes
        """
        if not lang:
            lang = self.request.current_lang
            # Actions that generate content in user language should change
            # the content lang from the default defined in cfg.
            if lang == self.request.content_lang:
                # lang is inherited from content div
                return {}

        attr = {'lang': lang, 'dir': i18n.getDirection(lang),}
        return attr

    def formatAttributes(self, attr=None):
        """ Return formatted attributes string

        @param attr: dict containing keys and values
        @rtype: string ?
        @return: formated attributes or empty string
        """
        if attr:
            attr = [' %s="%s"' % (k, v) for k, v in attr.items()]           
            return ''.join(attr)
        return ''

    # TODO: use set when we require Python 2.3
    # TODO: The list is not complete, add missing from dtd
    _blocks = 'p div pre table tr td ol ul dl li dt dd h1 h2 h3 h4 h5 h6 hr form'
    _blocks = dict(zip(_blocks.split(), [1] * len(_blocks)))

    def open(self, tag, newline=False, attr=None):
        """ Open a tag with optional attributes
        
        @param tag: html tag, string
        @param newline: render tag on a separate line
        @parm attr: dict with tag attributes
        @rtype: string ?
        @return: open tag with attributes
        """
        if tag in self._blocks:
            # Block elements
            result = []
            
            # Add language attributes, but let caller overide the default
            attributes = self.langAttr()
            if attr:
                attributes.update(attr)
            
            # Format
            attributes = self.formatAttributes(attributes)
            result.append('<%s%s>' % (tag, attributes))
            if newline:
                result.append('\n')
            return ''.join(result)
        else:
            # Inline elements
            # Add to inlineStack
            self._inlineStack.append(tag)
            # Format
            return '<%s%s>' % (tag, self.formatAttributes(attr))
       
    def close(self, tag, newline=False):
        """ Close tag

        @param tag: html tag, string
        @rtype: string ?
        @return: closing tag
        """
        if tag in self._blocks:
            # Block elements
            # Close all tags in inline stack
            # Work on a copy, because close(inline) manipulate the stack
            result = []
            stack = self._inlineStack[:]
            stack.reverse()
            for inline in stack:
                result.append(self.close(inline))
            # Format with newline
            if newline:
                result.append('\n')
            result.append('</%s>\n' % (tag))
            return ''.join(result)            
        else:
            # Inline elements 
            # Pull from stack, ignore order, that is not our problem.
            # The code that calls us should keep correct calling order.
            if tag in self._inlineStack:
                self._inlineStack.remove(tag)
            return '</%s>' % tag


    # Public methods ###################################################

    def startContent(self, content_id='content', **kwargs):
        """ Start page content div """

        # Setup id
        if content_id!='content':
            aid = 'top_%s' % (content_id,)
        else:
            aid = 'top'
        self._content_ids.append(content_id)
        result = []
        # Use the content language
        attr = self.langAttr(self.request.content_lang)
        attr['id'] = content_id
        result.append(self.open('div', newline=1, attr=attr))
        result.append(self.anchordef(aid))
        return ''.join(result)
        
    def endContent(self):
        """ Close page content div """

        # Setup id
        try:
            cid = self._content_ids.pop()
        except:
            cid = 'content'
        if cid!='content':
            aid = 'bottom_%s' % (cid,)
        else:
            aid = 'bottom'

        result = []
        result.append(self.anchordef(aid))
        result.append(self.close('div', newline=1))
        return ''.join(result) 

    def lang(self, on, lang_name):
        """ Insert text with specific lang and direction.
        
            Enclose within span tag if lang_name is different from
            the current lang    
        """
        tag = 'span'
        if lang_name != self.request.current_lang:
            # Enclose text in span using lang attributes
            if on:
                attr = self.langAttr(lang=lang_name)
                return self.open(tag, attr=attr)
            return self.close(tag)

        # Direction did not change, no need for span
        return ''            
                
    def sysmsg(self, on, **kw):
        tag = 'div'
        if on:
            return self.open(tag, attr={'class': 'message'})
        return self.close(tag)
    
    # Links ##############################################################
    
    def pagelink(self, on, pagename='', page=None, **kw):
        """ Link to a page.

            formatter.text_python will use an optimized call with a page!=None
            parameter. DO NOT USE THIS YOURSELF OR IT WILL BREAK.

            See wikiutil.link_tag() for possible keyword parameters.
        """
        apply(FormatterBase.pagelink, (self, on, pagename, page), kw)
        if page is None:
            page = Page(self.request, pagename, formatter=self);
            
        if self.request.user.show_nonexist_qm and on and not page.exists():
            self.pagelink_preclosed = True
            return (page.link_to(self.request, on=1, **kw) +
                    self.text("?") +
                    page.link_to(self.request, on=0, **kw))
        elif not on and self.pagelink_preclosed:
            self.pagelink_preclosed = False
            return ""
        else:
            return page.link_to(self.request, on=on, **kw)

    def interwikilink(self, on, interwiki='', pagename='', **kw):
        if not on: return '</a>'
        
        wikitag, wikiurl, wikitail, wikitag_bad = wikiutil.resolve_wiki(self.request, '%s:%s' % (interwiki, pagename))
        wikiurl = wikiutil.mapURL(self.request, wikiurl)

        if wikitag == 'Self': # for own wiki, do simple links
            href = wikiutil.join_wiki(wikiurl, wikiutil.AbsPageName(self.request, self.page.page_name, wikitail))
            return (self.url(1, href, unescaped=0, pretty_url=kw.get('pretty_url', 0)))
        else: # return InterWiki hyperlink
            href = wikiutil.join_wiki(wikiurl, wikitail)
            if wikitag_bad:
                html_class = 'badinterwiki'
            else:
                html_class = 'interwiki'

            icon = ''
            if self.request.user.show_fancy_links:
                icon = self.request.theme.make_icon('interwiki', {'wikitag': wikitag}) 
            return (self.url(1, href, title=wikitag, unescaped=0,
                pretty_url=kw.get('pretty_url', 0), css = html_class) +
                icon)
            # unescaped=1 was changed to 0 to make interwiki links with pages with umlauts (or other non-ascii) work

    def url(self, on, url=None, css=None, **kw):
        """
            Keyword params:
                title - title attribute
                ... some more (!!! TODO) 
        """
        if url is not None:
            url = wikiutil.mapURL(self.request, url)
        pretty = kw.get('pretty_url', 0)
        title = kw.get('title', None)

        #if not pretty and wikiutil.isPicture(url):
        #    # XXX
        #    return '<img src="%s" alt="%s">' % (url,url)

        # create link
        if not on:
            return '</a>'
        str = '<a'
        if css: 
            str = '%s class="%s"' % (str, css)
        if title:
            str = '%s title="%s"' % (str, title)
        str = '%s href="%s">' % (str, wikiutil.escape(url, 1))

        type = kw.get('type', '')

        if type=='www':
            str = '%s%s ' % (str, self.icon("www"))
        elif type=='mailto':
            str = '%s%s ' % (str, self.icon('mailto'))

        return str

    def anchordef(self, id):
        return '<a id="%s"></a>\n' % (id, )

    def anchorlink(self, on, name='', id = None):
        extra = ''
        if id:
            extra = ' id="%s"' % id
        return ['<a href="#%s"%s>' % (name, extra), '</a>'][not on]

    # Text ##############################################################
    
    def _text(self, text):
        if self._in_code:
            return wikiutil.escape(text).replace(' ', self.hardspace)
        return wikiutil.escape(text)

    # Inline ###########################################################
        
    def strong(self, on):
        tag = 'strong'
        if on:
            return self.open(tag)
        return self.close(tag)

    def emphasis(self, on):
        tag = 'em'
        if on:
            return self.open(tag)
        return self.close(tag)

    def underline(self, on):
        tag = 'span'
        if on:
            return self.open(tag, attr={'class': 'u'})
        return self.close(tag)

    def highlight(self, on):
        tag = 'strong'
        if on:
            return self.open(tag, attr={'class': 'highlight'})
        return self.close(tag)

    def sup(self, on):
        tag = 'sup'
        if on:
            return self.open(tag)
        return self.close(tag)

    def sub(self, on):
        tag = 'sub'
        if on:
            return self.open(tag)
        return self.close(tag)

    def code(self, on):
        tag = 'tt'
        # Maybe we don't need this, because we have tt will be in inlineStack.
        self._in_code = on        
        if on:
            return self.open(tag)
        return self.close(tag)
        
    def small(self, on):
        tag = 'small'
        if on:
            return self.open(tag)
        return self.close(tag)
                                                                                
    def big(self, on):
        tag = 'big'
        if on:
            return self.open(tag)
        return self.close(tag)


    # Block elements ####################################################

    def preformatted(self, on):
        FormatterBase.preformatted(self, on)
        tag = 'pre'
        if on:
            return self.open(tag, newline=1)
        return self.close(tag)
                
    # Use by code area
    _toggleLineNumbersScript = """
<script type="text/JavaScript">
function isnumbered(obj) {
  return obj.childNodes.length && obj.firstChild.childNodes.length && obj.firstChild.firstChild.className == 'LineNumber';
}
function nformat(num,chrs,add) {
  var nlen = Math.max(0,chrs-(''+num).length), res = '';
  while (nlen>0) { res += ' '; nlen-- }
  return res+num+add;
}
function addnumber(did, nstart, nstep) {
  var c = document.getElementById(did), l = c.firstChild, n = 1;
  if (!isnumbered(c))
    if (typeof nstart == 'undefined') nstart = 1;
    if (typeof nstep  == 'undefined') nstep = 1;
    n = nstart;
    while (l != null) {
      if (l.tagName == 'SPAN') {
        var s = document.createElement('SPAN');
        s.className = 'LineNumber'
        s.appendChild(document.createTextNode(nformat(n,4,' ')));
        n += nstep;
        if (l.childNodes.length)
          l.insertBefore(s, l.firstChild)
        else
          l.appendChild(s)
      }
      l = l.nextSibling;
    }
  return false;
}
function remnumber(did) {
  var c = document.getElementById(did), l = c.firstChild;
  if (isnumbered(c))
    while (l != null) {
      if (l.tagName == 'SPAN' && l.firstChild.className == 'LineNumber') l.removeChild(l.firstChild);
      l = l.nextSibling;
    }
  return false;
}
function togglenumber(did, nstart, nstep) {
  var c = document.getElementById(did);
  if (isnumbered(c)) {
    remnumber(did);
  } else {
    addnumber(did,nstart,nstep);
  }
  return false;
}
</script>
"""
    
    def code_area(self, on, code_id, code_type='code', show=0, start=-1, step=-1):
        res = []
        ci = self.request.makeUniqueID('CA-%s_%03d' % (code_id, self._code_area_num))
        if on:
            # Open a code area
            self._in_code_area = 1
            self._in_code_line = 0
            self._code_area_state = [ci, show, start, step, start]

            # Open the code div - using left to right always!
            attr = {'class': 'codearea', 'lang': 'en', 'dir': 'ltr'}
            res.append(self.open('div', attr=attr))

            # Add the script only in the first code area on the page
            if self._code_area_js == 0 and self._code_area_state[1] >= 0:
                res.append(self._toggleLineNumbersScript)
                self._code_area_js = 1

            # Add line number link, but only for JavaScript enabled browsers.
            if self._code_area_state[1] >= 0:
                toggleLineNumbersLink = r'''
<script type="text/javascript">
document.write('<a href="#" onClick="return togglenumber(\'%s\', %d, %d);" \
                class="codenumbers">Toggle line numbers<\/a>');
</script>
''' % (self._code_area_state[0], self._code_area_state[2], self._code_area_state[3])
                res.append(toggleLineNumbersLink)

            # Open pre - using left to right always!
            attr = {'id': self._code_area_state[0], 'lang': 'en', 'dir': 'ltr'}
            res.append(self.open('pre', newline=True, attr=attr))
        else:
            # Close code area
            res = []
            if self._in_code_line:
                res.append(self.code_line(0))
            res.append(self.close('pre'))
            res.append(self.close('div'))

            # Update state
            self._in_code_area = 0
            self._code_area_num += 1

        return ''.join(res)

    def code_line(self, on):
        res = ''
        if not on or (on and self._in_code_line):
            res += '</span>\n'
        if on:
            res += '<span class="line">'
            if self._code_area_state[1] > 0:
                res += '<span class="LineNumber">%4d </span>' % (self._code_area_state[4], )
                self._code_area_state[4] += self._code_area_state[3]
        self._in_code_line = on != 0
        return res

    def code_token(self, on, tok_type):
        return ['<span class="%s">' % tok_type, '</span>'][not on]

    # Paragraphs, Lines, Rules ###########################################
    
    def linebreak(self, preformatted=1):
        if self._in_code_area:
            preformatted = 1
        return ['\n', '<br>\n'][not preformatted]
        
    def paragraph(self, on):
        if self._terse:
            return ''
        FormatterBase.paragraph(self, on)
        if self._in_li:
            self._in_li = self._in_li + 1
        tag = 'p'
        if on:
            return self.open(tag)
        return self.close(tag)
            
    def rule(self, size=None):
        if size:
            # Add hr class: hr1 - hr6
            return self.open('hr', newline=1, attr={'class': 'hr%d' % size})
        return self.open('hr', newline=1)
                
    def icon(self, type):
        return self.request.theme.make_icon(type)

    def smiley(self, text):
        w, h, b, img = config.smileys[text.strip()]
        href = img
        if not href.startswith('/'):
            href = self.request.theme.img_url(img)
        return self.image(src=href, alt=text, width=str(w), height=str(h))

    # Lists ##############################################################

    def number_list(self, on, type=None, start=None):
        tag = 'ol'
        if on:
            attr = {}
            if type is not None:
                attr['type'] = type
            if start is not None:
                attr['start'] = start
            return self.open(tag, newline=1, attr=attr)
        return self.close(tag)
    
    def bullet_list(self, on):
        tag = 'ul'
        if on:
            return self.open(tag, newline=1)
        return self.close(tag)
           
    def listitem(self, on, **kw):
        """ List item inherit its lang from the list. """
        tag = 'li'
        self._in_li = on != 0
        if on:
            attr = {}
            css_class = kw.get('css_class', None)
            if css_class:
                attr['class'] = css_class
            style = kw.get('style', None)
            if style:
                attr['style'] = style
            return self.open(tag, attr=attr)
        return self.close(tag)

    def definition_list(self, on):
        tag = 'dl'
        if on:
            return self.open(tag, newline=1)
        return self.close(tag)

    def definition_term(self, on):
        tag = 'dt'
        if on:
            return self.open(tag)
        return self.close(tag)
        
    def definition_desc(self, on):
        tag = 'dd'
        if on:
            return self.open(tag)
        return self.close(tag)

    def heading(self, on, depth, id = None, **kw):
        # remember depth of first heading, and adapt counting depth accordingly
        if not self._base_depth:
            self._base_depth = depth

        count_depth = max(depth - (self._base_depth - 1), 1)

        # check numbering, possibly changing the default
        if self._show_section_numbers is None:
            self._show_section_numbers = self.cfg.show_section_numbers
            numbering = self.request.getPragma('section-numbers', '').lower()
            if numbering in ['0', 'off']:
                self._show_section_numbers = 0
            elif numbering in ['1', 'on']:
                self._show_section_numbers = 1
            elif numbering in ['2', '3', '4', '5', '6']:
                # explicit base level for section number display
                self._show_section_numbers = int(numbering)

        heading_depth = depth + 1

        # closing tag, with empty line after, to make source more readable
        if not on:
            return self.close('h%d' % heading_depth) + '\n'
            
        # create section number
        number = ''
        if self._show_section_numbers:
            # count headings on all levels
            self.request._fmt_hd_counters = self.request._fmt_hd_counters[:count_depth]
            while len(self.request._fmt_hd_counters) < count_depth:
                self.request._fmt_hd_counters.append(0)
            self.request._fmt_hd_counters[-1] = self.request._fmt_hd_counters[-1] + 1
            number = '.'.join(map(str, self.request._fmt_hd_counters[self._show_section_numbers-1:]))
            if number: number += ". "

        attr = {}
        if id:
            attr['id'] = id
        # Add space before heading, easier to check source code
        result = '\n' + self.open('h%d' % heading_depth, attr=attr)

        # TODO: convert this to readable code
        if self.request.user.show_topbottom:
            # TODO change top/bottom refs to content-specific top/bottom refs?
            result = ("%s%s%s%s%s%s%s%s" %
                      (result,
                       kw.get('icons',''),
                       self.url(1, "#bottom", unescaped=1),
                       self.icon('bottom'),
                       self.url(0),
                       self.url(1, "#top", unescaped=1),
                       self.icon('top'),
                       self.url(0)))
        return "%s%s%s" % (result, kw.get('icons',''), number)

    
    # Tables #############################################################

    _allowed_table_attrs = {
        'table': ['class', 'id', 'style'],
        'row': ['class', 'id', 'style'],
        '': ['colspan', 'rowspan', 'class', 'id', 'style'],
    }

    def _checkTableAttr(self, attrs, prefix):
        """ Check table attributes

        Convert from wikitable attributes to html 4 attributes.

        @param attrs: attribute dict
        @param prefix: used in wiki table attributes
        @rtyp: dict
        @return: valid table attributes
        """
        if not attrs:
            return {}

        result = {}
        s = "" # we collect synthesized style in s
        for key, val in attrs.items():
            # Ignore keys that don't start with prefix
            if prefix and key[:len(prefix)] != prefix:
                continue
            key = key[len(prefix):]
            val = val.strip('"')
            # remove invalid attrs from dict and synthesize style
            if key == 'width':
                s += "width: %s;" % val
            elif key == 'bgcolor':
                s += "background-color: %s;" % val
            elif key == 'align':
                s += "text-align: %s;" % val
            elif key == 'valign':
                s += "vertical-align: %s;" % val
            # Ignore unknown keys
            if key not in self._allowed_table_attrs[prefix]:
                continue
            result[key] = val
        if s:
            if result.has_key('style'):
                result['style'] += s
            else:
                result['style'] = s
        return result


    def table(self, on, attrs=None):
        """ Create table

        @param on: start table
        @param attrs: table attributes
        @rtype: string
        @return start or end tag of a table
        """
        result = []
        if on:
            # Open div to get correct alignment with table width smaller
            # then 100%
            result.append(self.open('div', newline=1))

            # Open table
            if not attrs:
                attrs = {}
            else:
                attrs = self._checkTableAttr(attrs, 'table')
            result.append(self.open('table', newline=1, attr=attrs))
        else:
            # Close table then div
            result.append(self.close('table'))
            result.append(self.close('div'))

        return ''.join(result)    
    
    def table_row(self, on, attrs=None):
        tag = 'tr'
        if on:
            if not attrs:
                attrs = {}
            else:
                attrs = self._checkTableAttr(attrs, 'row')
            return self.open(tag, newline=1, attr=attrs)
        return self.close(tag)
    
    def table_cell(self, on, attrs=None):
        tag = 'td'
        if on:
            if not attrs:
                attrs = {}
            else:
                attrs = self._checkTableAttr(attrs, '')
            return self.open(tag, newline=1, attr=attrs)
        return self.close(tag)

    def escapedText(self, text):
        return wikiutil.escape(text)
