/*-
 *  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.Collection;
import java.util.List;

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

/**
 * An axis aligned bounding box represented by its two corners
 * 
 * @author Matthias Betz
 *
 */
public class BoundingBox {

	private Vector3d[] bbox;

	/**
	 * Creates an axis aligned bounding box containing all points of all polygons
	 * 
	 * @param polygons containing the points from which the box will be created
	 * @return the bounding box around all points
	 */
	public static BoundingBox of(Collection<? extends Polygon> polygons) {
		return BoundingBoxCalculator.calculateBoundingBox(polygons);
	}
	
	public static BoundingBox ofPoints(List<? extends Vector3d> points) {
		return BoundingBoxCalculator.calculateBoundingBoxFromPoints(points);
	}

	/**
	 * Creates an axis aligned bounding box of the whole model.
	 * 
	 * @param model the model containing the features with geometries used for the
	 *              bounding box
	 * @return the bounding box around all features
	 */
	public static BoundingBox of(CityDoctorModel model) {
		return BoundingBoxCalculator.calculateBoundingBox(model);
	}

	/**
	 * Creates a new bounding box with two vectors as corner points.
	 * 
	 * @param box the array of length 2 containing both corners
	 * @return the new bounding box
	 */
	public static BoundingBox of(Vector3d[] box) {
		return new BoundingBox(box);
	}

	private BoundingBox(Vector3d[] bbox) {
		if (bbox == null || bbox.length != 2) {
			throw new IllegalArgumentException("BoundingBox must be an array of the length 2");
		}
		this.bbox = bbox;
	}

	/**
	 * Calculates the volume of the box
	 * 
	 * @return the volume of the box
	 */
	public double getVolume() {
		double length = getDepth();
		double width = getWidth();
		double height = getHeight();
		return height * width * length;
	}

	/**
	 * Calculates the center of the bounding box
	 * 
	 * @return the center of the bounding box
	 */
	public Vector3d getCenter() {
		return bbox[0].plus(bbox[1].minus(bbox[0]).mult(0.5));
	}

	/**
	 * Getter for the corner array
	 * 
	 * @return the array containing the corner points
	 */
	public Vector3d[] getBox() {
		return bbox;
	}

	/**
	 * Calculates the width of the bounding box
	 * 
	 * @return the width of the bounding box
	 */
	public double getWidth() {
		return bbox[1].getY() - bbox[0].getY();
	}

	/**
	 * Calculates the height of the bounding box
	 * 
	 * @return the height of the bounding box
	 */
	public double getHeight() {
		return bbox[1].getZ() - bbox[0].getZ();
	}

	/**
	 * Calculates the depth of the bounding box
	 * 
	 * @return the depth of the bounding box
	 */
	public double getDepth() {
		return bbox[1].getX() - bbox[0].getX();
	}

	/**
	 * Returns the length of the longest side of the bounding box, ignoring the
	 * height. Only X and Y axis are considered
	 * 
	 * @return the length of the longest side in X or Y direction
	 */
	public double getLongestSide() {
		double width = getWidth();
		double depth = getDepth();
		if (width > depth) {
			return width;
		} else {
			return depth;
		}
	}

	/**
	 * Calculates the direction vector from the lower corner to the upper corner
	 * 
	 * @return the direction vector from the lower corner to the upper corner
	 */
	public Vector3d getDiagonal() {
		return bbox[1].minus(bbox[0]);
	}

	/**
	 * Calculates the distance between the corner points
	 * 
	 * @return the distance between the corner points
	 */
	public double getDiagonalLength() {
		return bbox[1].getDistance(bbox[0]);
	}
}
