# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - TableOfContents Macro

    Optional integer argument: maximal depth of listing.
    
    Modified by Steve Tindle at SQI, Inc. (steve.tindle@sqi-inc.com) on 2006-06-26. When 
    section-numbers are used, numbered lists replaced with actual section numbering 
    (1.2.3, etc). TableOfContents acts normally if section-numbers are disabled.
    
    Further modifications to have it generate the TOC of a remote page

    @copyright: 2000, 2001, 2002 by Jrgen Hermann <jh@web.de>
    @license: GNU GPL, see COPYING for details.
"""

import re, sha
from MoinMoin import config, wikiutil
from MoinMoin.Page import Page

#Dependencies = ["page"]
Dependencies = ["time"] # works around MoinMoinBugs/TableOfContentsLacksLinks

# from macro Include (keep in sync!)
_arg_heading = r'(?P<heading>,)\s*(|(?P<hquote>[\'"])(?P<htext>.+?)(?P=hquote))'
_arg_level = r',\s*(?P<level>\d*)'
_arg_from = r'(,\s*from=(?P<fquote>[\'"])(?P<from>.+?)(?P=fquote))?'
_arg_to = r'(,\s*to=(?P<tquote>[\'"])(?P<to>.+?)(?P=tquote))?'
_arg_sort = r'(,\s*sort=(?P<sort>(ascending|descending)))?'
_arg_items = r'(,\s*items=(?P<items>\d+))?'
_arg_skipitems = r'(,\s*skipitems=(?P<skipitems>\d+))?'
_arg_titlesonly = r'(,\s*(?P<titlesonly>titlesonly))?'
_arg_editlink = r'(,\s*(?P<editlink>editlink))?'
_args_re_pattern = r'^(?P<name>[^,]+)(%s(%s)?%s%s%s%s%s%s%s)?$' % (
    _arg_heading, _arg_level, _arg_from, _arg_to, _arg_sort, _arg_items,
    _arg_skipitems, _arg_titlesonly, _arg_editlink)

# from Include, too, but with extra htext group around header text
_title_re = r"^(?P<heading>(?P<hmarker>=+)\s(?P<htext>.*)\s(?P=hmarker))$"

class TableOfContents:
    """
    TOC Macro wraps all global variables without disturbing threads
    """

    def __init__(self, macro, args):
        self.macro = macro
        
        self.inc_re = re.compile(r"^\[\[Include\((.*)\)\]\]")
        self.arg_re = re.compile(_args_re_pattern)
        self.head_re = re.compile(_title_re) # single lines only
        self.pre_re = re.compile(r'\{\{\{.+?\}\}\}', re.S)
        
        self.result = []
        self.baseindent = 0
        self.indent = 0
        self.lineno = 0
        self.titles = {}

        self.include_macro = None
        
        self._base_depth = None
        self._show_section_numbers = None
        self.request = self.macro.request
        self._fmt_hd_counters = []
        self.setsec_re = re.compile(r"^\[\[SetSection\((.*)\)\]\]")
        

        # check numbering, possibly changing the default
        if self._show_section_numbers is None:
            #self._show_section_numbers = self.macro.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)
                
        try:
            self.mindepth = int(macro.request.getPragma('section-numbers', 1))
        except (ValueError, TypeError):
            self.mindepth = 1

        #try:
        #    self.maxdepth = max(int(args), 1)
        #except (ValueError, TypeError):
        self.maxdepth = 99
        self.pages = args.split(',')

    def IncludeMacro(self, *args, **kwargs):
        if self.include_macro is None:
            self.include_macro = wikiutil.importPlugin(self.macro.request.cfg,
                                                       'macro', "Include")
        return self.pre_re.sub('',apply(self.include_macro, args, kwargs)).split('\n')

    def run(self):
        for page in self.pages:
            self.baseindent = 0
            self.indent = 0
            self.lineno = 0
            self.titles = {}
            lines = self.IncludeMacro(self.macro, page, called_by_toc=1)
            self.process_lines(lines, page)
            # Close pending lists
            for i in range(self.baseindent, self.indent):
                self.result.append(self.macro.formatter.listitem(0))
                if self._show_section_numbers:
                    self.result.append(self.macro.formatter.bullet_list(0))
                else:
                    self.result.append(self.macro.formatter.number_list(0))
            
        return self.macro.formatter.div(1,attr={'class':"tableOfContents"}) \
               + ''.join(self.result) + self.macro.formatter.div(0)

    def process_lines(self, lines, pagename):
        for line in lines:
            # Filter out the headings
            self.lineno = self.lineno + 1
            
            match = self.setsec_re.match(line)
            if match:
                # [[SetSection]]
                section = int(match.group(1)) - 1
                self._fmt_hd_counters = [section]
            
            match = self.inc_re.match(line)
            if match:
                # this is an [[Include()]] line.
                # now parse the included page and do the work on it.

                ## get heading and level from Include() line.
                tmp = self.arg_re.match(match.group(1))
                if tmp and tmp.group("name"):
                    inc_pagename = tmp.group("name")
                else:
                    # no pagename?  ignore it
                    continue
                if tmp.group("heading"):
                    if tmp.group("htext"):
                        heading = tmp.group("htext")
                    else:
                        heading = inc_pagename
                    if tmp.group("level"):
                        level = int(tmp.group("level"))
                    else:
                        level = 1
                    inc_page_lines = ["%s %s %s" %("=" * level, heading, "=" * level)]
                else:
                    inc_page_lines = []

                inc_page_lines = inc_page_lines + self.IncludeMacro(self.macro, match.group(1), called_by_toc=1)
                
                self.process_lines(inc_page_lines, inc_pagename)
            else:
                self.parse_line(line, pagename)

    def parse_line(self, line, pagename):
        # FIXME this also finds "headlines" in {{{ code sections }}}:
        match = self.head_re.match(line)
        if not match: return
        title_text = match.group('htext').strip()
        pntt = pagename + title_text
        self.titles.setdefault(pntt, 0)
        self.titles[pntt] += 1

        # Get new indent level
        newindent = len(match.group('hmarker'))
        
        if not self._base_depth:
            self._base_depth = newindent
        
        if newindent > self.maxdepth: return
        if newindent < self.mindepth: 
            self._fmt_hd_counters = self.request._fmt_hd_counters[:]
            return
        if not self.indent:
            self.baseindent = newindent - 1
            self.indent = self.baseindent
        
        number = self.heading(newindent)

        # Close lists
        for i in range(0,self.indent-newindent):
            self.result.append(self.macro.formatter.listitem(0))
            if self._show_section_numbers:
                self.result.append(self.macro.formatter.bullet_list(0))
            else:
                self.result.append(self.macro.formatter.number_list(0))

        # Open Lists
        for i in range(0,newindent-self.indent):
            if self._show_section_numbers:
                self.result.append(self.macro.formatter.bullet_list(1, type="none"))
            else:
                self.result.append(self.macro.formatter.number_list(1))
            self.result.append(self.macro.formatter.listitem(1))

        # Add the heading
        unique_id = ''
        if self.titles[pntt] > 1:
            unique_id = '-%d' % (self.titles[pntt],)

        # close last listitem if same level
        if self.indent == newindent:
            self.result.append(self.macro.formatter.listitem(0))
            
        if self.indent >= newindent:
            self.result.append(self.macro.formatter.listitem(1))
            
        linkpage = Page(self.macro.request, pagename, formatter=self.macro.formatter)
        anchor = "head-" + sha.new(pntt.encode(config.charset)).hexdigest() + unique_id
        linkOn = linkpage.link_to(self.macro.request, on=1, anchor=anchor)
        linkOff = linkpage.link_to(self.macro.request, on=0, anchor=anchor)
        
        self.result.append(linkOn +
                           self.macro.formatter.text(number + title_text) +
                           linkOff)

        # Set new indent level
        self.indent = newindent
        
    def heading(self, depth, **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)


        heading_depth = depth # + 1

        # create section number
        number = ''
        if self._show_section_numbers:
            # count headings on all levels
            self._fmt_hd_counters = self._fmt_hd_counters[:count_depth]
            while len(self._fmt_hd_counters) < count_depth:
                self._fmt_hd_counters.append(0)
            self._fmt_hd_counters[-1] = self._fmt_hd_counters[-1] + 1
            number = '.'.join(map(str, self._fmt_hd_counters[self._show_section_numbers-1:]))
            if number: number += ". "

        return number

def execute(macro, args):
    toc=TableOfContents(macro,args)
    return toc.run()
