"""
    MoinMoin - VisualSiteMap action

    Idea is based on the webdot.py action.
    
    More or less redid it from scratch. Differs from the webdot action in several ways:
    * Uses the dot executable, not webdot, since webdot's not available on windows.
    * All links up to the search depth are displayed. So you get the full map, not only a part of it.
    * There's no maximal limit to the displayed nodes. Again, I wanted the full map.
    * Nodes are marked during depth first visit, so each node is visited only once.
    * The visit method in class LocalSiteMap gets the whole tree as parameter. That way additional treenode information
      may be shown in the graph.
    * All edges between nodes contained in the graph are displayed, even if MAX_DEPTH is exceeded that way.
    * Search depth may be controlled through the command line argument 'depth': ?action=VisualSiteMap&depth=1
    * Experimental: Optional depth controls (set DEPTH_CONTROL = 1 and give it a try)
    * Nodes linked more then STRONG_LINK_NR times are highlighted using the STRONG_COLOR

    Add this to your stylesheet:
    img.sitemap
    {
      border-width: 1;
      border-color: #000000;
    }

    $Id$
"""
    

# Imports
import string,sys,re,os;
from MoinMoin import config, wikiutil, webapi, user;
from MoinMoin.Page import Page;

# Graph controls
DEFAULT_DEPTH = 2;
STRONG_LINK_NR = 4;
ROOT_URL   = "/MyWiki";

# Experimental: Optional controls for interactive modification of the search depth.
DEPTH_CONTROL = 0;
MAX_DEPTH  = 4;

# This should be a public path on your web server. The dot files, images and map files are created in this directory and
# served from there.
CACHE_DIR  = "C:/DocumentRoot/cache/";
CACHE_URL  = "http://MyWiki/cache/";

# Absolute location of the dot (or neato) executable.
DOT_EXE    = "C:/Programme/ATT/GraphViz/bin/neato.exe";

# Colors of boxes and edges.
BOX_COLOR  ="#E0F0FF";
ROOT_COLOR = "#FFE0E0";
STRONG_COLOR = "#E0FFE0";
EDGE_COLOR ="#888888";

# Categories are filtered in some way.
CATEGORY_STRING = "^Kategorie"

# Code starts here
def execute(pagename, request):
    _ = request.getText;
    
    maxdepth = int(DEFAULT_DEPTH);
    if DEPTH_CONTROL and request.form.has_key('depth'):
      maxdepth = int(request.form['depth'].value);
    
    if int(maxdepth) > int(MAX_DEPTH):
      maxdepth = MAX_DEPTH;
      
    webapi.http_headers(request);
    wikiutil.send_title(request, _('Visual Map of %s') % (pagename), pagename=pagename);

    wikiname = wikiutil.quoteWikiname(pagename);
    dotfilename = '%s/%s.dot' % (CACHE_DIR, wikiname);
    pngfilename = '%s/%s.png' % (CACHE_DIR, wikiname);
    pngurl      = '%s/%s.png' % (CACHE_URL, wikiname);
    mapfilename = '%s/%s.cmap' % (CACHE_DIR, wikiname);

    dotfile = open(dotfilename,'w');
    
    dotfile.write('digraph G {\n');
    dotfile.write('  URL="%s";\n' % wikiname);
    dotfile.write('  ratio=compress;\n');
    dotfile.write('  overlap=false;\n');
    dotfile.write('  concentrate=true;\n');
    dotfile.write('  edge [color="%s"];\n' % EDGE_COLOR);
    dotfile.write('  node [URL="%s/\N", ' % ROOT_URL);
    dotfile.write('fontcolor=black, fontname=%s , fontsize=%s, style=filled, color="%s"]\n' % ("arial","8", BOX_COLOR));
    dotfile.write(LocalSiteMap(pagename, maxdepth).output(request));
    dotfile.write('}\n');
    dotfile.close();
    
    os.system('%s -Tpng -o%s %s' % (DOT_EXE, pngfilename, dotfilename));
    os.system('%s -Tcmap -o%s %s' % (DOT_EXE, mapfilename, dotfilename));
    
    print '<center><img class="sitemap" border=1 src="%s" usemap="#map1"></center>' % (pngurl);
    print '<map name="map1">';
    mapfile = open(mapfilename,'r');
    for row in mapfile.readlines():
        print row;
    mapfile.close();
      
    print '</map>';
    
    if DEPTH_CONTROL:
      print '<p align="center">';
      if maxdepth > 1:
          print '<a href="%s/%s?action=VisualSiteMap&depth=%s">Less</a>' % (ROOT_URL, pagename, maxdepth-1);
      else:
          print 'Less';

      print ' | ';

      if maxdepth < MAX_DEPTH:
          print '<a href="%s/%s?action=VisualSiteMap&depth=%s">More</a>' % (ROOT_URL, pagename, maxdepth+1);
      else:
          print 'More';
      print '</p>';
      
    print '<p align="center"><small>Search depth is %s. Nodes linked more than %s times are highlighted.</small></p>' % (maxdepth, STRONG_LINK_NR);

    wikiutil.send_footer(request, pagename);
    
    sys.exit(0);

class LocalSiteMap:
    def __init__(self, name, maxdepth):
        self.name = name;
        self.result = [];
        self.maxdepth = maxdepth;

    def output(self, request):
        pagebuilder = GraphBuilder(request, self.maxdepth);
        root = pagebuilder.build_graph(self.name);
        # write nodes
        for node in pagebuilder.all_nodes:
            uname=unicode(node.name, config.charset).encode('UTF-8');
            self.append('  %s'% wikiutil.quoteWikiname(node.name));
            if node.depth > 0:
                if node.linkedto >= STRONG_LINK_NR:
                    self.append('  [label="%s",color="%s"];\n' % (uname, STRONG_COLOR));
                else:
                    self.append('  [label="%s"];\n' % (uname));

            else:
                self.append('[label="%s",shape=box,style=filled,color="%s"];\n' % (uname, ROOT_COLOR));
        # write edges
        for edge in pagebuilder.all_edges:
            self.append('  %s->%s;\n' % (wikiutil.quoteWikiname(edge[0].name),wikiutil.quoteWikiname(edge[1].name)));
            
        return string.join(self.result, '');

    def append(self, text):
        self.result.append(text);


class GraphBuilder:
    
    def __init__(self, request, maxdepth):
        self.request = request;
        self.maxdepth = maxdepth;
        self.all_nodes = [];
        self.all_edges = [];
        
    def is_ok(self, child):
        if not self.request.user.may.read(child):
            return 0;
        if Page(child).exists() and (not re.search(r'%s' % CATEGORY_STRING,child)):
            return 1;
        return 0;

    def build_graph(self, name):
        # Reuse generated trees
        nodesMap = {};
        root = Node(name);
        nodesMap[name] = root;
        root.visited = 1;
        self.all_nodes.append(root);
        self.recurse_build([root], 1, nodesMap);
        return root;

    def recurse_build(self, nodes, depth, nodesMap):
        # collect all nodes of the current search depth here for the next recursion step
        child_nodes = [];
        # iterate over the nodes
        for node in nodes:
            # print "<h2>Kids of %s</h2>" % node.name;
            for child in Page(node.name).getPageLinks(self.request):            
                if self.is_ok(child):
                    # print "Child %s" % child;
                    # Create the node with the given name
                    if not nodesMap.has_key(child):
                        # create the new node and store it
                        newNode = Node(child);
                        newNode.depth = depth;
                        # print "is new";
                    else:
                        newNode = nodesMap[child];
                        # print "is old";
                    # print ". <br>";
                    # If the current depth doesn't exceed the maximum depth, add newNode to recursion step
                    if (int(depth) <= int(self.maxdepth)):
                        # The node is appended to the nodes list for the next recursion step.
                        nodesMap[child] = newNode;
                        self.all_nodes.append(newNode);
                        child_nodes.append(newNode);
                        node.append(newNode);
                        # Draw an edge.
                        self.all_edges.append((node, newNode));
                        newNode.linkedto += 1;
        # recurse, if the current recursion step yields children
        if len(child_nodes):
            self.recurse_build(child_nodes, depth+1, nodesMap);

class Node:
    def __init__(self, name):
        self.name = name;
        self.children = [];
        self.visited = 0;
        self.linkedfrom = 0;
        self.linkedto = 0;
        self.depth = 0;

    def append(self, node):
        self.children.append(node);
        self.linkedfrom += 1;
