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

import java.util.List;

import de.hft.stuttgart.citydoctor2.datastructure.BoundingBox;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.Vector3d;

/**
 * Utility class for calculating axis aligned bounding boxes for different
 * inputs.
 * 
 * @author Matthias Betz
 *
 */
public class BoundingBoxCalculator {

	private BoundingBoxCalculator() {
		// only static use
	}

	/**
	 * This will calculate two points where every other point in the geometry is
	 * between those two. If the geometry does not contain points or is null, an
	 * empty array is returned. This is considered an axis aligned bounding box.
	 * Only the exterior rings of the polygons is used as inner rings should be
	 * within the exterior or they are faulty.
	 * 
	 * @param a list of polygons containing the vertices
	 * @return an BoundingBox containing the two end points of the bounding box.
	 */
	public static BoundingBox calculateBoundingBox(List<Polygon> polygons) {
		double lowX = Double.MAX_VALUE;
		double highX = Double.NEGATIVE_INFINITY;
		double lowY = Double.MAX_VALUE;
		double highY = Double.NEGATIVE_INFINITY;
		double lowZ = Double.MAX_VALUE;
		double highZ = Double.NEGATIVE_INFINITY;
		for (Polygon p : polygons) {
			// only need to check exterior rings
			for (Vertex v : p.getExteriorRing().getVertices()) {
				if (v.getX() < lowX) {
					lowX = v.getX();
				} else if (v.getX() > highX) {
					highX = v.getX();
				}
				if (v.getY() < lowY) {
					lowY = v.getY();
				} else if (v.getY() > highY) {
					highY = v.getY();
				}
				if (v.getZ() < lowZ) {
					lowZ = v.getZ();
				} else if (v.getZ() > highZ) {
					highZ = v.getZ();
				}
			}
		}
		Vector3d[] result = new Vector3d[2];
		result[0] = new Vector3d(lowX, lowY, lowZ);
		result[1] = new Vector3d(highX, highY, highZ);
		return BoundingBox.of(result);
	}

	/**
	 * This will calculate an axis aligned bounding box for the complete GML model.
	 * 
	 * @param model the model
	 * @return the bounding box of the model
	 */
	public static BoundingBox calculateBoundingBox(CityDoctorModel model) {
		Vector3d low = new Vector3d(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE);
		Vector3d high = new Vector3d(Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE);

		findMinMax(low, high, model.getBuildings());
		findMinMax(low, high, model.getBridges());
		findMinMax(low, high, model.getLand());
		findMinMax(low, high, model.getTransportation());
		findMinMax(low, high, model.getWater());
		findMinMax(low, high, model.getVegetation());

		Vector3d[] result = new Vector3d[2];
		result[0] = low;
		result[1] = high;
		return BoundingBox.of(result);
	}

	private static void findMinMax(Vector3d low, Vector3d high, List<? extends CityObject> features) {
		for (CityObject co : features) {
			findMinMax(low, high, co);
		}
	}

	private static void findMinMax(Vector3d low, Vector3d high, CityObject co) {
		for (Geometry geom : co.getGeometries()) {
			if (geom.getVertices() == null) {
				geom.updateVertices();
			}
			for (Vertex v : geom.getVertices()) {
				if (v.getX() < low.getX()) {
					low.setX(v.getX());
				} else if (v.getX() > high.getX()) {
					high.setX(v.getX());
				}
				if (v.getY() < low.getY()) {
					low.setY(v.getY());
				} else if (v.getY() > high.getY()) {
					high.setY(v.getY());
				}
				if (v.getZ() < low.getZ()) {
					low.setZ(v.getZ());
				} else if (v.getZ() > high.getZ()) {
					high.setZ(v.getZ());
				}
			}
		}
	}

}
