/*-
 *  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.datastructure;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.utils.SerializablePair;

/**
 * Contains the vertex information of a point in a linear ring
 * 
 * @author Matthias Betz
 *
 */
public class Vertex extends Vector3d {

	private static final long serialVersionUID = -5525361920397934892L;

	private List<SerializablePair<Geometry, HashSet<Polygon>>> adjacentPolygons = new ArrayList<>(2);
	private List<SerializablePair<Geometry, HashSet<LinearRing>>> adjacentRings = new ArrayList<>(2);

	private List<Vertex> neighbors;

	public Vertex(double x, double y, double z) {
		super(x, y, z);
	}

	public Vertex(Vector3d vec) {
		super(vec);
	}

	public void addNeighbor(Vertex vec) {
		if (neighbors == null) {
			neighbors = new ArrayList<>(1);
		}
		neighbors.add(vec);
	}

	public List<Vertex> getNeighbors() {
		if (neighbors == null) {
			return Collections.emptyList();
		}
		return neighbors;
	}

	private Set<LinearRing> getAdjacentRingsWithoutNeightbor(Geometry geom) {
		for (SerializablePair<Geometry, HashSet<LinearRing>> adjacency : adjacentRings) {
			if (adjacency.getValue0() == geom) {
				return adjacency.getValue1();
			}
		}
		throw new IllegalStateException("Requested adjacent rings with Geometry not containing this vertex");
	}

	public Set<LinearRing> getAdjacentRings(Geometry geom) {
		for (SerializablePair<Geometry, HashSet<LinearRing>> adjacency : adjacentRings) {
			if (adjacency.getValue0() == geom) {
				if (neighbors == null || neighbors.isEmpty()) {
					return adjacency.getValue1();
				} else {
					Set<LinearRing> linearRings = new HashSet<>();
					linearRings.addAll(adjacency.getValue1());
					for (Vertex neighbor : neighbors) {
						linearRings.addAll(neighbor.getAdjacentRingsWithoutNeightbor(geom));
					}
					return linearRings;
				}
			}
		}
		throw new IllegalStateException("Requested adjacent rings with Geometry not containing this vertex");
	}

	void addAdjacentRing(Polygon poly, LinearRing ring, Geometry geom) {
		findAdjacentRingsForGeometry(geom).add(ring);
		findAdjacentPolygonsForGeometry(geom).add(poly);
	}

	private Set<LinearRing> findAdjacentRingsForGeometry(Geometry geom) {
		HashSet<LinearRing> adjacendRingsSet = null;
		for (SerializablePair<Geometry, HashSet<LinearRing>> adjacency : adjacentRings) {
			if (adjacency.getValue0() == geom) {
				adjacendRingsSet = adjacency.getValue1();
			}
		}
		if (adjacendRingsSet == null) {
			adjacendRingsSet = new HashSet<>(4);
			adjacentRings.add(new SerializablePair<>(geom, adjacendRingsSet));
		}
		return adjacendRingsSet;
	}

	private Set<Polygon> findAdjacentPolygonsForGeometry(Geometry geom) {
		HashSet<Polygon> adjacendPolygonsSet = null;
		for (SerializablePair<Geometry, HashSet<Polygon>> adjacency : adjacentPolygons) {
			if (adjacency.getValue0() == geom) {
				adjacendPolygonsSet = adjacency.getValue1();
			}
		}
		if (adjacendPolygonsSet == null) {
			adjacendPolygonsSet = new HashSet<>(4);
			adjacentPolygons.add(new SerializablePair<>(geom, adjacendPolygonsSet));
		}
		return adjacendPolygonsSet;
	}

	public Set<Polygon> getAdjacentPolygonsWithoutNeighbor(Geometry geom) {
		for (SerializablePair<Geometry, HashSet<Polygon>> adjacency : adjacentPolygons) {
			if (adjacency.getValue0() == geom) {
				return adjacency.getValue1();
			}
		}
		throw new IllegalStateException("Requested adjacent polygons with Geometry not containing this vertex");
	}

	public Set<Polygon> getAdjacentPolygons(Geometry geom) {
		for (SerializablePair<Geometry, HashSet<Polygon>> adjacency : adjacentPolygons) {
			if (adjacency.getValue0() == geom) {
				if (neighbors == null || neighbors.isEmpty()) {
					return adjacency.getValue1();
				} else {
					Set<Polygon> polygons = new HashSet<>();
					polygons.addAll(adjacency.getValue1());
					for (Vertex neighbor : neighbors) {
						polygons.addAll(neighbor.getAdjacentPolygonsWithoutNeighbor(geom));
					}
					return polygons;
				}
			}
		}
		throw new IllegalStateException("Requested adjacent polygons with Geometry not containing this vertex");
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		return prime * result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (!super.equals(obj)) {
			return false;
		}
		return getClass() == obj.getClass();
	}

	@Override
	public String toString() {
		return "Vertex [x=" + getX() + ", y=" + getY() + ", z=" + getZ() + "]";
	}

	public void clearAdjacentRings(Geometry geometry) {
		findAdjacentRingsForGeometry(geometry).clear();
		findAdjacentPolygonsForGeometry(geometry).clear();
	}

	void removeAdjacency(LinearRing lr, Polygon p, Geometry geom) {
		findAdjacentPolygonsForGeometry(geom).remove(p);
		findAdjacentRingsForGeometry(geom).remove(lr);
	}

	public boolean equalsWithNeighbors(Vertex other) {
		if (this.equals(other)) {
			return true;
		}
		for (Vertex v : other.getNeighbors()) {
			if (this.equals(v)) {
				return true;
			}
		}
		return checkNeighbors(other);
	}

	private boolean checkNeighbors(Vertex other) {
		if (neighbors != null) {
			for (Vertex v : neighbors) {
				if (v.equals(other)) {
					return true;
				}
				for (Vertex otherNeighbor : other.getNeighbors()) {
					if (otherNeighbor.equals(v)) {
						return true;
					}
				}
			}
		}
		return false;
	}
}
