/*******************************************************************************
 * This file is part of OpenNMS(R).
 *
 * Copyright (C) 2016 The OpenNMS Group, Inc.
 * OpenNMS(R) is Copyright (C) 1999-2016 The OpenNMS Group, Inc.
 *
 * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
 *
 * OpenNMS(R) is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * OpenNMS(R) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with OpenNMS(R).  If not, see:
 *      http://www.gnu.org/licenses/
 *
 * For more information contact:
 *     OpenNMS(R) Licensing <license@opennms.org>
 *     http://www.opennms.org/
 *     http://www.opennms.com/
 *******************************************************************************/

package org.opennms.features.topology.app.internal.jung;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.apache.commons.collections15.Transformer;
import org.apache.commons.collections15.map.LazyMap;

import com.google.common.base.Preconditions;

import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Forest;
import edu.uci.ics.jung.graph.Graph;

/**
 * A custom layout to be able to draw a hierarchy.
 * This implementation is inspired by {@link edu.uci.ics.jung.algorithms.layout.TreeLayout}.
 * Unfortunately this implementation only works if 2 parents do not have the same child (see {@link edu.uci.ics.jung.graph.util.TreeUtils#getRoots(Forest)}).
 *
 * @param <V>
 * @param <E>
 */
public class HierarchyLayout<V, E> implements Layout<V, E> {

    private Dimension size = new Dimension(600, 600);
    private Graph<V, E> graph;
    private Map<V, Integer> basePositions = new HashMap<V, Integer>();

    private Map<V, Point2D> locations =
            LazyMap.decorate(new HashMap<V, Point2D>(),
                    new Transformer<V, Point2D>() {
                        public Point2D transform(V arg0) {
                            return new Point2D.Double();
                        }
                    });

    private final int distX;
    private int distY;
    private final V root;
    private transient Point m_currentPoint = new Point();
    private transient Set<V> alreadyDone = new HashSet<V>();

    /**
     * Creates an instance for the specified graph, X distance, and Y distance.
     */
    public HierarchyLayout(Graph<V, E> g, V root, int distx, int disty) {
        if (root != null) {
            Preconditions.checkArgument(g.containsVertex(root), "The root Vertex must be a Vertex in the provided Graph");
        }
        Preconditions.checkArgument(distx >= 1 && disty >= 1, "X and Y distances must each be positive");
        this.graph = Objects.requireNonNull(g, "Graph must not be null");;
        this.distX = distx;
        this.distY = disty;
        this.root = root;
        buildTree();
    }

    protected void buildTree() {
        this.m_currentPoint = new Point(0, 20);
        if (root != null  && graph != null) {
            calculateDimensionX(root);
            m_currentPoint.x += this.basePositions.get(root) / 2 + this.distX;
            buildTree(root, this.m_currentPoint.x);
        }
    }

    protected void buildTree(V v, int x) {
        if (!alreadyDone.contains(v)) {
            alreadyDone.add(v);

            //go one level further down
            this.m_currentPoint.y += this.distY;
            this.m_currentPoint.x = x;

            this.setCurrentPositionFor(v);

            int sizeXofCurrent = basePositions.get(v);
            int lastX = x - sizeXofCurrent / 2;
            int sizeXofChild;
            int startXofChild;

            for (V element : graph.getSuccessors(v)) {
                sizeXofChild = this.basePositions.get(element);
                startXofChild = lastX + sizeXofChild / 2;
                buildTree(element, startXofChild);
                lastX = lastX + sizeXofChild + distX;
            }
            this.m_currentPoint.y -= this.distY;
        }
    }

    private int calculateDimensionX(V v) {
        int size = 0;
        int childrenNum = graph.getSuccessors(v).size();
        if (childrenNum != 0) {
            for (V element : graph.getSuccessors(v)) {
                size += calculateDimensionX(element) + distX;
            }
        }
        size = Math.max(0, size - distX);
        basePositions.put(v, size);
        return size;
    }

    private void setCurrentPositionFor(V vertex) {
        int x = m_currentPoint.x;
        int y = m_currentPoint.y;
        if (x < 0) size.width -= x;

        if (x > size.width - distX)
            size.width = x + distX;

        if (y < 0) size.height -= y;
        if (y > size.height - distY)
            size.height = y + distY;
        locations.get(vertex).setLocation(m_currentPoint);
    }

    @Override
    public Graph<V, E> getGraph() {
        return graph;
    }

    @Override
    public Dimension getSize() {
        return size;
    }

    @Override
    public void initialize() {

    }

    @Override
    public boolean isLocked(V v) {
        return false;
    }

    @Override
    public void lock(V v, boolean state) {
    }

    @Override
    public void reset() {
    }

    @Override
    public void setSize(Dimension d) {
        throw new IllegalArgumentException("setSize(Dimension) is not supported");
    }

    @Override
    public void setGraph(Graph<V, E> graph) {
        Objects.requireNonNull(graph, "Graph must not be null");
        this.graph = graph;
        buildTree();
    }

    @Override
    public void setInitializer(Transformer<V, Point2D> initializer) {
    }

    /**
     * Returns the center of this layout's area.
     */
    private Point2D getCenter() {
        return new Point2D.Double(size.getWidth() / 2, size.getHeight() / 2);
    }

    @Override
    public void setLocation(V v, Point2D location) {
        locations.get(v).setLocation(location);
    }

    @Override
    public Point2D transform(V v) {
        return locations.get(v);
    }
}
@@ -1885,51 +1885,23 @@
 out%7D
-.%0A * Unfortunately this implementation
+, which
  only
+%0A *
  wor
@@ -6546,8 +6546,9 @@
 %0A    %7D%0A%7D
+%0A

Clarify that the drawback only applies to the original TreeLayout