package com.atlassian.confluence.extra.graphviz;

import com.atlassian.confluence.links.OutgoingLink;
import com.atlassian.confluence.pages.Page;
import com.atlassian.confluence.pages.PageManager;
import com.atlassian.confluence.renderer.PageContext;
import com.atlassian.confluence.spaces.Space;
import com.atlassian.confluence.spaces.SpaceManager;
import com.atlassian.confluence.util.GeneralUtil;
import com.atlassian.confluence.setup.BootstrapManager;
import com.atlassian.confluence.core.ContentEntityObject;
import com.atlassian.confluence.labels.LabelManager;
import com.atlassian.confluence.labels.Label;
import com.atlassian.confluence.labels.LabelParser;
import com.atlassian.renderer.RenderContext;
import com.atlassian.renderer.v2.RenderMode;
import com.atlassian.renderer.v2.SubRenderer;
import com.atlassian.renderer.v2.macro.BaseMacro;
import com.atlassian.renderer.v2.macro.MacroException;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class SpaceGraphMacro
        extends BaseMacro
{

    private SpaceManager spaceManager;
    private PageManager pageManager;
    private BootstrapManager bootstrapManager;
    private LabelManager labelManager;
    private SubRenderer subRenderer;

    public boolean isInline()
    {
        return false;
    }

    public boolean hasBody()
    {
        return false;
    }

    // irrelevant since we have no body
    public RenderMode getBodyRenderMode()
    {
        return RenderMode.NO_RENDER;
    }


    public void setPageManager(PageManager pageManager)
    {
        this.pageManager = pageManager;
    }


    /**
     * Render a {graph} macro.
     * <p/>
     * The body of the macro is surrounded with 'graph {', '}' and is given some formatting defaults before being passed to dot.
     */
    public String execute(Map parameters, String body, RenderContext renderContext) throws MacroException
    {
        String spaceKey = (String) parameters.get("0");
        if (spaceKey == null)
        {
            spaceKey = ((PageContext) renderContext).getSpaceKey();
        }
        Space space = spaceManager.getSpace(spaceKey);
        if (space == null)
        {
            throw new MacroException("No space with key '" + spaceKey + "' exists.");
        }
        String label = (String) parameters.get("label");
        List pages = spaceManager.getPages(space, true);
        StringBuffer dotCommand = new StringBuffer();
        Label theLabel = null;
        if (label != null)
        {
            theLabel = labelManager.getLabel(LabelParser.parse(label));
        }
        for (Iterator i = pages.iterator(); i.hasNext();)
        {
            Page p = (Page) i.next();
            List labels = p.getLabels();
            if (!includePage(p, theLabel))
            {
                continue;
            }
            addPage(dotCommand, spaceKey, p.getTitle(), "");
            List children = p.getChildren();
            for (Iterator j = children.iterator(); j.hasNext();)
            {
                Page child = (Page) j.next();
                if (includePage(child, theLabel))
                {
                    dotCommand.append("\"").append(p.getTitle()).append("\" -> ").append("\"").append(child.getTitle()).append("\";\n");
                }
            }
            List outgoingLinks = p.getOutgoingLinks();
            for (Iterator j = outgoingLinks.iterator(); j.hasNext();)
            {
                OutgoingLink l = (OutgoingLink) j.next();
                String prefix = "";
                String destSpaceKey = l.getDestinationSpaceKey();
                Space destSpace = spaceManager.getSpace(destSpaceKey);
                if (destSpace != null)
                {
                    if (!l.getDestinationSpaceKey().equals(spaceKey))
                    {
                        prefix = destSpace.getName() + ": ";
                    }
                    String destPageTitle = l.getDestinationPageTitle();
                    // make sure this page really exists
                    Page destinationPage = pageManager.getPage(destSpaceKey, destPageTitle);
                    if (destinationPage != null && includePage(destinationPage, theLabel))
                    {
                        if (!prefix.equals(""))
                        {
                            addPage(dotCommand, destSpaceKey, destPageTitle, prefix);
                        }
                        dotCommand.append("\"").append(p.getTitle()).append("\" -> ").append("\"").append(prefix)
                                .append(destPageTitle).append("\" [style=dashed];\n");
                    }
                }
            }
        }

        String defaults =
                "pack = \"true\"; ratio = \"compress\"; size = \"10,10\"; edge [arrowsize=0.8]; node [width=0.1, shape=rect, style=filled, fillcolor=lightyellow, fontname=\"Verdana\", fontsize=8];";
        String command = "digraph {" + defaults + "\n" + dotCommand.toString() + "\n}";
        return GraphVizHelper.generateGraph(command, 1, 1, subRenderer, renderContext);
    }

    private boolean includePage(Page p, Label theLabel)
    {
        return theLabel == null || p.getLabels().contains(theLabel);
    }

    private void addPage(StringBuffer buf, String spaceKey, String pageTitle, String prefix)
    {
        String nodeName = (prefix + pageTitle).replaceAll("\"", "\\\\\"");
        String label = nodeName.replaceAll(" +","\\\\n");
        buf.append("\"").append(nodeName)
                .append("\" [URL=\"" + bootstrapManager.getWebAppContextPath() + "/display/" + spaceKey + "/" + GeneralUtil.urlEncode(pageTitle) + "\", label=\"" + label + "\" tooltip=\"" + nodeName + "\"];");
    }

    public void setSpaceManager(SpaceManager spaceManager)
    {
        this.spaceManager = spaceManager;
    }

    public void setBootstrapManager(BootstrapManager bootstrapManager)
    {
        this.bootstrapManager = bootstrapManager;
    }

    public void setLabelManager(LabelManager labelManager)
    {
        this.labelManager = labelManager;
    }

    public void setSubRenderer(SubRenderer subRenderer)
    {
        this.subRenderer = subRenderer;
    }
}
