/*
 * DBeaver - Universal Database Manager
 * Copyright (C) 2010-2023 DBeaver Corp and others
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Created on Jul 14, 2004
 */
package org.jkiss.dbeaver.erd.ui.layout.algorithm.direct;

import org.eclipse.draw2d.graph.*;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Creates dummy edges between nodes, to be used with NodeJoiningDirectedGraphLayout
 *
 * @author Serge Rider
 */
public class ClusterEdgeCreator {

    //sets up maximum depth of recursion to set up initial cluster list
    private static final int INITIAL_RECURSION_DEPTH = 3;

    private NodeList nodeList;
    private EdgeList edgeList;
    private DirectedGraph graph;

    //List edgesAdded;
    private List<Node> encountered = new ArrayList<>();
    private List<Cluster> clusters = new ArrayList<>();

    private Cluster currentCluster = null;

    public ClusterEdgeCreator() {
        super();
    }

    public void visit(DirectedGraph graph) {

        try {

            this.graph = graph;
            this.nodeList = graph.nodes;
            this.edgeList = graph.edges;
            //edgesAdded = new ArrayList();

            //iterate through all of the nodes in the node list
            for (Iterator<?> iter = nodeList.iterator(); iter.hasNext();) {
                Node node = (Node) iter.next();

                //check whether we have already come across this node
                if (!encountered.contains(node)) {
                    //create a new cluster for this node
                    currentCluster = new Cluster();
                    clusters.add(currentCluster);
                    encountered.add(node);
                    currentCluster.set.add(node);

                    // recursively add any other nodes reachable from it
                    int depth = INITIAL_RECURSION_DEPTH;
                    recursivelyAddToCluster(node, depth);
                }
            }

/*
            for (Iterator<Cluster> iter = clusters.iterator(); iter.hasNext();) {
                Cluster cluster = iter.next();
            }
*/

            coalesceRemainingClusters();

            joinClusters();

        }
        catch (RuntimeException e) {
            e.printStackTrace();
            throw e;
        }

    }

    /**
     * If recursion fails to join all the remaining
     */
    private void coalesceRemainingClusters() {
    }

    /**
     * Joins the clusters together
     */
    private void joinClusters() {
        if (clusters.size() > 1) {

            Node sourceNode = null;
            Node targetNode = null;

            //add an edge from each successive cluster to next
            for (Iterator<Cluster> iter = clusters.iterator(); iter.hasNext();) {
                Cluster cluster = iter.next();
                if (sourceNode != null) {
                    //use first node in set as target node
                    targetNode = cluster.set.get(0);
                    newDummyEdge(sourceNode, targetNode);
                }

                //set up source node for the next iteration using last node in
                // set
                sourceNode = cluster.set.get(cluster.set.size() - 1);

            }

        }
    }

    private void recursivelyAddToCluster(Node node, int depth) {

        if (depth > 3) {
            // do nothing
        } else {
            depth++;
            EdgeList incoming = node.incoming;
            for (Iterator<?> iter = incoming.iterator(); iter.hasNext();) {
                Edge edge = (Edge) iter.next();
                Node incomingNode = edge.source;

                if (!encountered.contains(incomingNode)) {
                    encountered.add(incomingNode);
                    currentCluster.set.add(incomingNode);
                    recursivelyAddToCluster(incomingNode, depth);
                }
            }

            EdgeList outgoing = node.outgoing;
            for (Iterator<?> iter = outgoing.iterator(); iter.hasNext();) {
                Edge edge = (Edge) iter.next();
                Node outgoingNode = edge.target;

                if (!encountered.contains(outgoingNode)) {
                    encountered.add(outgoingNode);
                    currentCluster.set.add(outgoingNode);
                    recursivelyAddToCluster(outgoingNode, depth);
                }
            }
        }

    }

    /**
     * creates a new dummy edge to be used in the graph
     */
    private Edge newDummyEdge(Node sourceNode, Node targetNode) {
        //boolean addedEdge;
        DummyEdgePart edgePart = new DummyEdgePart();
        Edge edge = new Edge(edgePart, sourceNode, targetNode);
        edge.weight = 2;

        //add the new edge to the edge list
        edgeList.add(edge);

        //targetNode = sourceNode;
        //addedEdge = true;
        return edge;
    }

    /**
     * Very thin wrapper around List
     */
    private class Cluster {

        List<Node> set = new ArrayList<>();

        public String toString() {
            return set.toString();
        }
    }

}