/*-
 *  Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
 * 
 *  This file is part of CityDoctor2.
 *
 *  CityDoctor2 is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  CityDoctor2 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with CityDoctor2.  If not, see <https://www.gnu.org/licenses/>.
 */
package de.hft.stuttgart.citydoctor2.math.graph;

import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;

/**
 * Graph containing connected polygons
 * 
 * @author Matthias Betz
 *
 */
public class PolygonGraph {

	private Map<Polygon, PolygonNode> nodeMap;

	public PolygonGraph() {
		nodeMap = new HashMap<>();
	}

	private PolygonNode createOrGetNode(Polygon p) {
		PolygonNode node = nodeMap.get(p);
		if (node != null) {
			return node;
		} else {
			node = new PolygonNode(p);
			nodeMap.put(p, node);
			return node;
		}
	}

	public void addPolygonConnectionsFromVertex(Vertex v, Geometry geom) {
		for (Polygon i : v.getAdjacentPolygons(geom)) {
			for (Polygon j : v.getAdjacentPolygons(geom)) {
				if (i != j) {
					PolygonNode node1 = createOrGetNode(i);
					PolygonNode node2 = createOrGetNode(j);
					node1.addChild(node2);
					node2.addChild(node1);
				}
			}
		}
	}

	public void addEdge(Edge e) {
		if (e.getAdjacentPolygons().size() >= 2) {
			// in case more than 2 polygons are there
			// will also result in a manifold edge error
			for (Polygon p1 : e.getAdjacentPolygons()) {
				for (Polygon p2 : e.getAdjacentPolygons()) {
					if (p1 == p2) {
						continue;
					}
					PolygonNode node1 = createOrGetNode(p1);
					PolygonNode node2 = createOrGetNode(p2);
					node1.addChild(node2);
				}
			}
		}
	}

	public List<List<Polygon>> findConnectedComponents() {
		for (PolygonNode node : nodeMap.values()) {
			node.setVisited(false);
		}
		List<List<Polygon>> components = new ArrayList<>();

		for (PolygonNode node : nodeMap.values()) {
			if (!node.visited()) {
				List<Polygon> component = new ArrayList<>();
				Deque<PolygonNode> stack = new LinkedList<>();
				stack.push(node);
				dfs(stack, component);
				components.add(component);
			}
		}
		return components;
	}

	private void dfs(Deque<PolygonNode> stack, List<Polygon> component) {
		while (!stack.isEmpty()) {
			PolygonNode node = stack.pop();
			node.setVisited(true);
			component.add(node.getContent());
			for (PolygonNode child : node.getChildren()) {
				if (!child.visited()) {
					child.setVisited(true);
					stack.push(child);
				}
			}

		}
	}

}
