# -*- 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 elemetns 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_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 = []

    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
            differnt 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 _langAttr(self, lang=None):
        """ Return lang and dir attribute

        DONT USE, use langAttr instead

        TODO: update all clients that use this to use langAttr and
        delete this!

        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
            differnt from the content lang.
        @rtype: string
        @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 ''
        result = ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
        return result

    # Primitive formater functions #####################################

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

    def formatAttributes(self, attr=None):
        """ Return formated atributes string

        @param attr: dict containing keys and values
        @rtype: string ?
        @return: formated attribtues or empty string
        """
        if attr:
            attr = [' %s="%s"' % (k, v) for k, v in attr.items()]           
            return ''.join(attr)
        return ''
    
    def openInlineTag(self, tag, attr=None):
        """ Open a tag with optional attribues
        
        @param tag: html tag, string
        @parm attr: dict with tag attribues
        @rtype: string ?
        @return: open tag with attributes
        """
        # Add to inlineStack
        self._inlineStack.append(tag)
        # Format
        return '<%s%s>' % (tag, self.formatAttributes(attr))

    def closeInlineTag(self, tag):
        """ Close inline tag, remove from stack

        @param tag: html tag, string
        @rtype: string ?
        @return: closing tag
        """
        # Pull from stack, ignore order, that not our problem. The code
        # that calls us should keep correct calling order.
        tagindex = self._inlineStack.index(tag)
        if tagindex != -1:
            del self._inlineStack[tagindex]
        return '</%s>' % tag

    def openBlockTag(self, tag, newline=False, attr=None):
        """ Open a tag with optional attribues
        
        @param tag: html tag, string
        @param newline: render tag on a separate line
        @parm attr: dict with tag attribues
        @rtype: string ?
        @return: open tag with attributes
        """
        result = []
        # Close p tags, our parser like to wrap everyting in p
        if self.in_p:
            self.in_p = 0
            result.append(self.paragraph(False))

        # Format with attribtues and newliine
        if newline:
            result.append('\n')
        attr = self.formatAttributes(attr)
        result.append('<%s%s>' % (tag, attr))
        return ''.join(result)

    def closeBlockTag(self, tag, newline=False):
        """ Close inline tag, remove from stack

        @param tag: html tag, string
        @param newline: render tag on a new line
        @rtype: string ?
        @return: closing tag
        """
        # Close all tags in inline stack
        # Work on a copy, because closeInlineTag manipulate the stack
        result = []
        stack = self.inlineStack[:]
        stack.reverse()
        for tag in stack:
            result.append(self.closeInlineTag(tag))
        # Format with newline
        if newline:
            result.append('\n')
        result.append('</%s>\n' % (tag))
        return ''.join(result)

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

    def startContent(self, content_id='content', **kwargs):
        """ Start content div """        
        if content_id!='content':
            aid = 'top_%s' % (content_id,)
        else:
            aid = 'top'
        self._content_ids.append(content_id)
        return '<div id="%(id)s" %(lang_attr)s>\n%(anchor)s\n' % {
            'id': content_id,
            'lang_attr': self._langAttr(self.request.content_lang),
            'anchor': self.anchordef(aid)
            }

    def endContent(self):
        try:
            cid = self._content_ids.pop()
        except:
            cid = 'content'
        if cid!='content':
            aid = 'bottom_%s' % (cid,)
        else:
            aid = 'bottom'
        return '%s\n</div>\n' % self.anchordef(aid)

    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    
        """
        if lang_name != self.request.current_lang:
            dir = i18n.getDirection(lang_name)
            return ['<span lang="%(lang_name)s" dir="%(dir)s">' % {
                'lang_name': lang_name, 'dir': dir},
                    '</span>'] [not on]
        
        return ''            
                
    def sysmsg(self, on, **kw):
        return ['%s<div class="message">' % self.existParagraph(), '</div>\n'][not on]

    
    # Links ##############################################################
    
    def pagelink(self, on, pagename='', **kw):
        """ Link to a page.

            See wikiutil.link_tag() for possible keyword parameters.
        """
        apply(FormatterBase.pagelink, (self, on, pagename), kw)
        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)
        href = wikiutil.join_wiki(wikiurl, wikitail)

        # return InterWiki hyperlink
        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=1,
                        pretty_url=kw.get('pretty_url', 0), css = html_class) +
                icon)

    def url(self, on, url=None, css=None, **kw):
        """
            Keyword params:
                title - title attribute
                ... some more (!!! TODO) 
        """
        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>' % (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 and Text Attributes ###########################################
    
    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.openInlineTag(tag)
        return self.closeInlineTag(tag)

    def emphasis(self, on):
        tag = 'em'
        if on:
            return self.openInlineTag(tag)
        return self.closeInlineTag(tag)

    def underline(self, on):
        tag = 'u'
        if on:
            return self.openInlineTag(tag)
        return self.closeInlineTag(tag)

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

    def sup(self, on):
        tag = 'sup'
        if on:
            return self.openInlineTag(tag)
        return self.closeInlineTag(tag)

    def sub(self, on):
        tag = 'sub'
        if on:
            return self.openInlineTag(tag)
        return self.closeInlineTag(tag)

    def code(self, on):
        tag = 'tt'

        # Maybe we don't need this, becuse whe have tt will be in inlineStack.
        self._in_code = on
        
        if on:
            return self.openInlineTag(tag)
        return self.closeInlineTag(tag)
        
    def small(self, on):
        tag = 'small'
        if on:
            return self.openInlineTag(tag)
        return self.closeInlineTag(tag)
                                                                                
    def big(self, on):
        tag = 'big'
        if on:
            return self.openInlineTag(tag)
        return self.closeInlineTag(tag)


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

    def preformatted(self, on):
        FormatterBase.preformatted(self, on)
        tag = 'pre'
        if on:
            return self.openBlockTag(tag)
        return self.closeBlockTag(tag)
                
    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:
            self._in_code_area = 1
            self._in_code_line = 0
            self._code_area_state = [ci, show, start, step, start]
            if self._code_area_num == 0 and self._code_area_state[1] >= 0:
                res += """<script language='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>
"""
            res += self.openBlockTag('div', attr={'class': 'codearea'})
##             res += self.existParagraph() + '<div class="codearea">\n'
            if self._code_area_state[1] >= 0:
                res += '<script>document.write(\'<a href="#" onClick="return togglenumber(\\\'%s\\\',%d,%d);" class="codenumbers">1,2,3</a>\')</script>' % (self._code_area_state[0], self._code_area_state[2], self._code_area_state[3])
            res += '<pre id="%s" class="codearea">' % (self._code_area_state[0], )
        else:
            res = ''
            if self._in_code_line:
                res += self.code_line(0)
            res += '</pre>'
            if self._code_area_state[1] >= 0:
                res += '<script>document.write(\'<a href="#" onClick="return togglenumber(\\\'%s\\\',%d,%d);" class="codenumbers">1,2,3</a>\')</script>' % (self._code_area_state[0], self._code_area_state[2], self._code_area_state[3])

            # This will close all user left open inline tags
            res += self.closeBlockTag('div')

            self._in_code_area = 0
            self._code_area_num += 1
        return 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.openBlockTag(tag, attr=self.langAttr())
        return self.closeBlockTag(tag)
            
    def rule(self, size=0):
        if size:
            return self.openBlockTag('hr', attr={'size': size})
        return self.openBlockTag('hr')
                
    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 = self.langAttr()
            if type is not None:
                attr['type'] = type
            if start is not None:
                attr['start'] = start
            return self.openBlockTag(tag, attr=attr)
        return self.closeBlockTag(tag)
    
    def bullet_list(self, on):
        tag = 'ul'
        if on:
            attr = self.langAttr()
            return self.openBlockTag(tag, attr=attr)
        return self.closeBlockTag(tag)
           
    def listitem(self, on, **kw):
        """ List item inherit its lang from the list. """
        tag = 'li'
        self._in_li = on != 0
        if on:
            css_class = kw.get('css_class', None)
            attrs = ''
            if css_class: attrs += ' class="%s"' % (css_class,)
            style = kw.get('style', None)
            if style:  attrs += ' style="%s"' % style
            result = '<li%s>' % (attrs,)
        else:
            result = '</li>\n'
        return result

    def definition_list(self, on):
        result = ['%s<dl>' % self.exitsParagraph(), '</dl>\n'][not on]
        return result

    def definition_term(self, on):
        return ['<dt%s>' % (self._langAttr()), '</dt>\n'][not on]

    def definition_desc(self, on):
        return ['<dd%s>' % self._langAttr(), '</dd>\n'][not on]

    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
        if not on:
            return '</h%d>\n' % heading_depth


        # 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 += ". "

        id_text = ''
        if id:
          id_text = ' id="%s"' % id

        result = '<h%d%s>' % (heading_depth, id_text)
        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 #############################################################

    # TODO: find better solution for bgcolor, align, valign (deprecated in html4)
    # do not remove current code before making working compliant code

    _allowed_table_attrs = {
        'table': ['class', 'width', 'bgcolor'],
        'row': ['class', 'width', 'align', 'valign', 'bgcolor'],
        '': ['colspan', 'rowspan', 'class', 'width', 'align', 'valign', 'bgcolor'],
    }

    def _checkTableAttr(self, attrs, prefix):
        if not attrs: return ''

        result = ''
        for key, val in attrs.items():
            if prefix and key[:len(prefix)] != prefix: continue
            key = key[len(prefix):]
            if key not in self._allowed_table_attrs[prefix]: continue
            result = '%s %s=%s' % (result, key, val)
        return result

    def table(self, on, attrs={}):
        if on:
            # Enclose table inside a div to get correct alignment
            # when using language macros
            attrs = attrs and attrs.copy() or {}
            result = '%(endpara)s<div%(lang)s>\n<table%(tableAttr)s>' % {
                'endpara': self.exitsParagraph(),
                'lang': self._langAttr(),
                'tableAttr': self._checkTableAttr(attrs, 'table')
            }
        else:
            result = '</table>\n</div>'
        return '%s\n' % result
    
    def table_row(self, on, attrs={}):
        if on:
            result = '<tr%s>' % self._checkTableAttr(attrs, 'row')
        else:
            result = '</tr>'
        return '%s\n' % result

    def table_cell(self, on, attrs={}):
        if on:
            result = '<td%s>' % self._checkTableAttr(attrs, '')
        else:
            result = '</td>'
        return '%s\n' % result

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