package dendrarium.trees.svg;

import dendrarium.trees.Forest;
import dendrarium.trees.Node;
import dendrarium.trees.NodeChildren;
import dendrarium.trees.NonterminalNode;
import dendrarium.trees.TerminalNode;
import dendrarium.utils.Pair;
import dendrarium.utils.XMLUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jdom.DocType;
import org.jdom.Document;
import org.jdom.Element;

/**
 *
 * @author Tomasz Badowski
 */
public class TreeDrawer {

    private Forest forest;

    private Node disamb;

    String ns = "http://www.w3.org/2000/svg";

    private Integer treeBottom = 0;

    private Integer imageBottom;

    private Map<Node, SubtreePosition> spMap;

    Map<Integer, String> links;

    /**
     * pta
     */
    private int posX = 0,  posY = 0;

    public int getPosX() {
        return posX;
    }

    public int getPosY() {
        return posY;
    }

    /**
     * pta
     */
    public byte[] getSVGTree(Forest forest, Node node) {
        Document doc = drawDocTree(forest, node);
        return XMLUtils.writePrettyByteArrayJDOM(doc);
    }

    public void writeSVGTree(Forest forest, Node node, String name) {
        Document doc = drawDocTree(forest, node);
        XMLUtils.writePrettyFileJDOM(name, doc);
    }

    Document drawDocTree(Forest forest, Node node) {
        DocType docType = SVGJdom.getStandardDocType();
        Element svg = drawSVGTree(forest, node);
        Document doc = new Document(svg, docType);
        return doc;
    }

//    public Document drawInteractiveDocTree(Forest forest, Node node, Map<Integer, String> map){
       /*     String dtdConstrainedElement = "html";
    String dtdPublicID = "-//W3C//DTD XHTML 1.0 Frameset//EN";
    String dtdSystemID ="DTD/xhtml1-frameset.dtd";
    DocType docType = new DocType(dtdConstrainedElement,
    dtdPublicID, dtdSystemID);*/
    /* Namespace nsSVG = Namespace.getNamespace("svg","http://www.w3.org/2000/svg");
    a.setAttribute("xmlns", link, nsSVG);
    Element svg = drawInteractiveSVGTree(forest, node, map);
    Document doc = new Document(svg, docType);
    Element a = new Element("a", ns);
    a.setAttribute("onclick", link);
    Namespace nsXlink = Namespace.getNamespace("xlink","http://www.w3.org");
    a.setAttribute("href", link, nsXlink);
    return a;*/
    //      return doc;
    //  }
    Element drawSVGTree(Forest forest, Node disambNode) {
        Element svg = SVGJdom.getStandardSVG();
        this.forest = forest;
        this.disamb = disambNode;
        this.spMap = new HashMap<Node, SubtreePosition>();

        String strFontSize = new Integer(DrawConfig.getTextFontSize()).toString();
        Integer bottomTableSize = (maxLinesNumber(forest.getRoot())) *
                DrawConfig.getTextVertDistance() + DrawConfig.getTextFontSize() / 2;
        /*
         * wysokość tabelki na dole, uwzględnia
         * liczbę atrybutów + text w terminalu/nierozstrzygniętym nieterminalu + 1
         * linię odstępu na dole
         */
        treeBottom = treeHeight(forest.getRoot()) * DrawConfig.getLevelSize() + DrawConfig.getTextFontSize();
        imageBottom = bottomTableSize + treeBottom;
        drawTree(forest, disambNode, svg);
        Integer imageWidth = spMap.get(forest.getRoot()).getX2();
//        svg.setAttribute("viewBox", "0 0 " + imageWidth.toString() + " " + imageBottom.toString());
        svg.setAttribute("viewBox", "-2 -2 " + (imageWidth + 4) + " " + (imageBottom + 4));

        Integer realWidth = imageWidth / DrawConfig.getVirtualToReal();
        Integer realHeight = imageBottom / DrawConfig.getVirtualToReal();

        svg.setAttribute("width", realWidth.toString() + "cm");
        svg.setAttribute("height", realHeight.toString() + "cm");
        SVGJdom.drawHorizLine(0, imageWidth, treeBottom, svg);
        SVGJdom.drawHorizLine(0, imageWidth, imageBottom, svg);
        SVGJdom.drawVertLine(0, treeBottom, imageBottom, svg); /* lewa scianka tabeli na dole */

        /* pta */
        if (disambNode == null) {
            posX = imageWidth / 2;
            posY = 0;
        }

        return svg;
    }

    public Element drawInteractiveSVGTree(Forest forest, Node disambNode,
            Map<Integer, String> links) {
        Element svg = SVGJdom.getStandardPrefSVG();
        svg.setAttribute("version", "1.1");
        this.forest = forest;
        this.disamb = disambNode;
        this.spMap = new HashMap<Node, SubtreePosition>();
        this.links = links;
        String strFontSize = new Integer(DrawConfig.getTextFontSize()).toString();
        Integer bottomTableSize = (maxLinesNumber(forest.getRoot())) *
                DrawConfig.getTextVertDistance() + DrawConfig.getTextFontSize() / 2;
        /*
         * wysokość tabelki na dole, uwzględnia
         * liczbę atrybutów + text w terminalu/nierozstrzygniętym nieterminalu + 1
         * linię odstępu na dole
         */
        treeBottom = treeHeight(forest.getRoot()) * DrawConfig.getLevelSize() + DrawConfig.getTextFontSize();
        imageBottom = bottomTableSize + treeBottom;
        drawTree(forest, disambNode, svg);
        Integer imageWidth = spMap.get(forest.getRoot()).getX2();
//        svg.setAttribute("viewBox", "0 0 " + imageWidth.toString() + " " + imageBottom.toString());
        svg.setAttribute("viewBox", "-2 -2 " + (imageWidth + 4) + " " + (imageBottom + 4));
        Integer realWidth = imageWidth / DrawConfig.getVirtualToReal();
        Integer realHeight = imageBottom / DrawConfig.getVirtualToReal();
        svg.setAttribute("width", realWidth.toString() + "cm");
        svg.setAttribute("height", realHeight.toString() + "cm");
        SVGJdom.drawHorizLine(0, imageWidth, treeBottom, svg);
        SVGJdom.drawHorizLine(0, imageWidth, imageBottom, svg);
        SVGJdom.drawVertLine(0, treeBottom, imageBottom, svg); /* lewa scianka tabeli na dole */

        /* pta */
        if (disambNode == null) {
            posX = imageWidth / 2;
            posY = 0;
        }

        return svg;
    }

    int max(int x, int y) {
        return x > y ? x : y;
    }

    int treeHeight(Node node) {
        int height = 0;
        if (node instanceof TerminalNode) {
            height = 0;
        } else if (node.isDrawLeaf()) { /*nieterminal i liść*/
            height = 1;
        } else { /* nieterminal i nie liść */
            NodeChildren nc = ((NonterminalNode) node).getChosenChildren();
            for (Node child : nc.getChildren()) {
                height = max(height, treeHeight(child) + 1);
            }
        }
        return height;
    }

    int maxLinesNumber(Node node) {
        /*
         * Zwraca maksymalną liczbę linii w lisciach
         */
        int maxLinesNo = 0;
        if (node.isDrawLeaf()) {
            if (node instanceof TerminalNode) {
                /* pta: +2 bo orth i base juz nie sa atrybutami */
                maxLinesNo = node.getAttributes().size() + 2;
            } else {   /* Nieterminal */
                maxLinesNo = 1;
            }
        } else {
            NodeChildren nc = ((NonterminalNode) node).getChosenChildren();
            for (Node child : nc.getChildren()) {
                maxLinesNo = max(maxLinesNo, maxLinesNumber(child));
            }
        }
        return maxLinesNo;
    }

    private int bottomTableColumnWidth(String text) {
        return SVGJdom.textWidth(text) + 2 * DrawConfig.getTableColumnPillow();
    }

    private int ceilDivide(int max, int size) {
        /*Dzielenie z zaokraglaniem do gory*/
        return (max + size - 1) / size;
    }

    private void drawDisambLevel(Node node, Element svg) {
        SubtreePosition spNd = spMap.get(node);
        SVGJdom.drawVertLine(spNd.middle(),
                levelToHeight(spNd.getLevel()), treeBottom, svg);
    //   SVGJdom.drawHorizTriangle(spNd.getX1(), spNd.getX2(), treeBottom,
    //             levelToHeight(spNd.getLevel()) - DrawConfig.getLevelSize()*2/5, svg);
    }

    private Integer columnWidth(Node node) {
        int max = 0, l;
        for (Pair<String, String> attr : node.getAttributes()) {
            String value = attr.getF2();

            l = bottomTableColumnWidth(value);
            max = l > max ? l : max;
        }
        return max;
    }

    private void drawLevel(NonterminalNode node, Element svg) {
        /* rysowanie prostych od node do jego dzieci */
        List<Node> ncList = node.getChosenChildren().getChildren();
        int newHeight = levelToHeight(spMap.get(node).getLevel());
        if (ncList.size() > 1) {
            SVGJdom.drawHorizLine((spMap.get(ncList.get(0)).middle()),
                    spMap.get(ncList.get(ncList.size() - 1)).middle(),
                    newHeight, svg);
        }
        for (Node nd : ncList) {
            SubtreePosition spNd = spMap.get(nd);
            SVGJdom.drawVertLine(spNd.middle(),
                    levelToHeight(spNd.getLevel()), newHeight, svg);
        }
    }

    private int drawTerminalLeaf(TerminalNode node, Integer x,
            Integer widthFloor, Element svg) {

        int width;
        String color = DrawConfig.getTextColor();
        int tempY = treeBottom + DrawConfig.getTextVertDistance();

        SVGJdom.drawText(node.getOrth(), x + DrawConfig.getTextFontWidth(), tempY,
                color, DrawConfig.getTextFontSize().toString(), svg);
        tempY += DrawConfig.getTextVertDistance();
        SVGJdom.drawText(node.getBase(), x + DrawConfig.getTextFontWidth(), tempY,
                color, DrawConfig.getTextFontSize().toString(), svg);
        tempY += DrawConfig.getTextVertDistance();
        for (Pair<String, String> p : node.getAttributes()) {
            String value = p.getF2();

            SVGJdom.drawText(value, x + DrawConfig.getTextFontWidth(), tempY,
                    color, DrawConfig.getTextFontSize().toString(), svg);
            tempY += DrawConfig.getTextVertDistance();
        }

        width = columnWidth(node);

        int xRight = x + max(width, widthFloor);
        SVGJdom.drawVertLine(xRight, treeBottom, imageBottom, svg); /* Prawa
        scianka komorki tabelki*/
        return xRight;
    }

    private void drawTree(Forest forest, Node disambNode, Element svg) {
        Integer widthFloor = SVGJdom.rectangleWidth(SVGJdom.textWidth(forest.getRoot().getSymbol()));
        //System.out.println("WidthFloor: " + widthFloor.toString() + "\n");
        drawSubtree(forest.getRoot(), disambNode, 0, widthFloor, svg);
        SubtreePosition spRoot = spMap.get(forest.getRoot());
        int y = levelToHeight(spRoot.getLevel());
        /*
         * Pionowa linia do korzenia.
         */
        SVGJdom.drawVertLine(spRoot.middle(), y, y - 2 * DrawConfig.getTextFontSize(), svg);
        drawNodes(svg);  /* Rysujemy wezły. Robimy to na koniec, zeby znalazly
    sie nad liniami*/
    }

    private int drawUndisambSubtree(Node node, Node disambNode, Integer x,
            Integer widthFloor, Element svg) {
        /* Rysowanie poddrzewa  nierozstrzyganietego wezla -aktualnie rozstrzyganego lub nie */
        String color = DrawConfig.getDisambTextColor();
        int textY = treeBottom + DrawConfig.getTextVertDistance();

        SVGJdom.drawText(forest.wordRange(node.getFrom(), node.getTo()), x +
                DrawConfig.getTextFontWidth(), textY, color,
                DrawConfig.getTextFontSize().toString(), svg);

        int width = bottomTableColumnWidth(forest.wordRange(node.getFrom(), node.getTo()));
        widthFloor = max(widthFloor, SVGJdom.rectangleWidth(SVGJdom.textWidth(node.getSymbol())));
        int xRight = x + max(width, widthFloor);
        SVGJdom.drawVertLine(xRight, treeBottom, imageBottom, svg);
        return xRight;
    }

    private void drawSubtree(Node node, Node disambNode, Integer xLeft,
            Integer upperWidth, Element svg) {
        Integer xRight;
        if (node.isDrawLeaf()) {
            if (node instanceof TerminalNode) { /* Terminal */
                xRight = drawTerminalLeaf((TerminalNode) node, xLeft, upperWidth, svg);
                spMap.put(node, new SubtreePosition(xLeft, xRight, 0));
            } else { /* Nierozstrzygniety wezel - aktualnie rozstrzygany lub nie */
                xRight = drawUndisambSubtree(node, disambNode, xLeft, upperWidth, svg);
                spMap.put(node, new SubtreePosition(xLeft, xRight, 1));
            }
        } else { /* Rozstrzygniety nieterminal */
            List<Node> ncList = ((NonterminalNode) node).getChosenChildren().getChildren();
            int childUpperWidth = ceilDivide(max(upperWidth,
                    SVGJdom.rectangleWidth(SVGJdom.textWidth(node.getSymbol()))), ncList.size());
            Integer tmpX = xLeft, tmpLevel = 0;

            for (Node child : ncList) {
                drawSubtree(child, disambNode, tmpX, childUpperWidth, svg);
                tmpLevel = max(spMap.get(child).getLevel(), tmpLevel);
                tmpX = spMap.get(child).getX2();
            }
            spMap.put(node, new SubtreePosition(xLeft, tmpX, tmpLevel + 1));
            drawLevel((NonterminalNode) node, svg);
        }
    }

    private void drawNode(NonterminalNode node, Element svg) {
        /* rysowanie rozstrzygnietego wezla */
        SubtreePosition sp;
        int symbolWidth = SVGJdom.textWidth(node.getSymbol());
        sp = spMap.get(node);
        int y = levelToHeight(sp.getLevel()) - DrawConfig.getTextFontSize();
        int x = sp.middle();
        if (disamb != null && node.getId() == disamb.getId()) {
            /* pta */
            posX = x;
            posY = y;

            SVGJdom.drawInteractiveNodeWithRule(x, y, symbolWidth, "pink", "red", node.getSymbol(), node.getLabel(),
                    node.getChosenChildren().getRule(), links.get(node.getId()), svg);
        } else {
            SVGJdom.drawInteractiveNodeWithRule(x, y, symbolWidth, DrawConfig.getFillColorByChoiceType(node.getChoiceType()), "black", node.getSymbol(), node.getLabel(),
                    node.getChosenChildren().getRule(), links.get(node.getId()), svg);
        }
    /* else error */
    /*        else{
    SVGJdom.drawNodeWithRule(x, y, symbolWidth, node.getSymbol(), node.getLabel(),
    node.getChosenChildren().getRule(), svg);
    } */
    }

    private void drawUndisambNode(NonterminalNode node, Element svg) {
        /* Rysowanie nierozstrzygnietego wezla  - aktualnie rozstrzyganego lub nie,
        lub wezla blednego*/
        SubtreePosition sp;
        int symbolWidth = SVGJdom.textWidth(node.getSymbol());
        sp = spMap.get(node);
        int y = levelToHeight(sp.getLevel()) - 2 * DrawConfig.getTextFontSize();
        int x = sp.middle();
        if (disamb != null && node.getId() == disamb.getId()) {
            /* pta */
            posX = x;
            posY = y;

            SVGJdom.drawInteractiveSVGNode(x, y, symbolWidth, node.getSymbol(), node.getLabel(),
                    "pink", "red",
                    links.get(node.getId()), svg);
        } else {
            SVGJdom.drawInteractiveSVGNode(x, y, symbolWidth, node.getSymbol(), node.getLabel(),
                    DrawConfig.getFillColorByChoiceType(node.getChoiceType()), "black",
                    links.get(node.getId()), svg);
        }
        drawDisambLevel(node, svg);
    }

    private void drawNodes(Element svg) {

        SubtreePosition sp;
        String rule;
        int symbolY, symbolX;
        for (Node nd : spMap.keySet()) {
            if (!nd.isDrawLeaf()) { /* zwykly rozstrzygniety wezel */
                drawNode((NonterminalNode) nd, svg);
            } else if (nd instanceof NonterminalNode) { /* lisc i nie terminal to
                nierozstrzygniety wezel */
                drawUndisambNode((NonterminalNode) nd, svg);
            }
        }
    }

    private int levelToHeight(int level) {
        /* Zamienia abstrakcyjny poziom na odpowiadajaca mu wysokosc w svg*/
        int position = treeBottom - (level) * DrawConfig.getLevelSize();
        if (level > 0) {
            position = position + DrawConfig.getLevelSize() * 4 / 5;
        }
        return position;
    }
}