package de.hft.stuttgart.citygml.green.osm;

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

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Polygon;
import org.xmlobjects.gml.model.geometry.aggregates.MultiSurface;
import org.xmlobjects.gml.model.geometry.primitives.SurfaceProperty;

public class TreeGenerator {
	
	private static final double TRUNK_INCREASE_FACTOR = 1.2;
	public static MultiSurface generateTree(Coordinate point, double trunkRadius, double trunkHeight,
			double crownXRadius, double crownYRadius, double crownHeight) {

		List<Coordinate> trunkWestCoordinateList = new ArrayList<>();
		trunkWestCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y - trunkRadius, 0));
		double drawTrunkHeight = trunkHeight * TRUNK_INCREASE_FACTOR;
		trunkWestCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y - trunkRadius, drawTrunkHeight));
		trunkWestCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y + trunkRadius, drawTrunkHeight));
		trunkWestCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y + trunkRadius, 0));
		trunkWestCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y - trunkRadius, 0));
		Polygon trunkWestPolygon = convertPolygon(trunkWestCoordinateList);
		var trunkWestConvertedPolygon = GreenEnricher.convertToCityGmlPoly(trunkWestPolygon);
		MultiSurface ms = new MultiSurface();
		ms.getSurfaceMember().add(new SurfaceProperty(trunkWestConvertedPolygon));

		List<Coordinate> trunkEastCoordinateList = new ArrayList<>();
		trunkEastCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y - trunkRadius, 0));
		trunkEastCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y + trunkRadius, 0));
		trunkEastCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y + trunkRadius, drawTrunkHeight));
		trunkEastCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y - trunkRadius, drawTrunkHeight));
		trunkEastCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y - trunkRadius, 0));
		Polygon trunkEastPolygon = convertPolygon(trunkEastCoordinateList);
		var trunkEastConvertedPolygon = GreenEnricher.convertToCityGmlPoly(trunkEastPolygon);
		ms.getSurfaceMember().add(new SurfaceProperty(trunkEastConvertedPolygon));

		List<Coordinate> trunkSouthCoordinateList = new ArrayList<>();
		trunkSouthCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y - trunkRadius, 0));
		trunkSouthCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y - trunkRadius, 0));
		trunkSouthCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y - trunkRadius, drawTrunkHeight));
		trunkSouthCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y - trunkRadius, drawTrunkHeight));
		trunkSouthCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y - trunkRadius, 0));
		Polygon trunkSouthPolygon = convertPolygon(trunkSouthCoordinateList);
		var trunkSouthConvertedPolygon = GreenEnricher.convertToCityGmlPoly(trunkSouthPolygon);
		ms.getSurfaceMember().add(new SurfaceProperty(trunkSouthConvertedPolygon));

		List<Coordinate> trunkNorthCoordinateList = new ArrayList<>();
		trunkNorthCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y + trunkRadius, 0));
		trunkNorthCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y + trunkRadius, drawTrunkHeight));
		trunkNorthCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y + trunkRadius, drawTrunkHeight));
		trunkNorthCoordinateList.add(new Coordinate(point.x + trunkRadius, point.y + trunkRadius, 0));
		trunkNorthCoordinateList.add(new Coordinate(point.x - trunkRadius, point.y + trunkRadius, 0));
		Polygon trunkNorthPolygon = convertPolygon(trunkNorthCoordinateList);
		var trunkNorthConvertedPolygon = GreenEnricher.convertToCityGmlPoly(trunkNorthPolygon);
		ms.getSurfaceMember().add(new SurfaceProperty(trunkNorthConvertedPolygon));

		/*-
		 * Spheroid formula:
		 * x = a * sin(thetha) * cos(phi)
		 * y = b * sin(thetha) * sin(phi)
		 * z = c * cos(thetha)
		 * 
		 * 0 <= theta <= pi
		 * 0 <= phi	 < 2 pi
		 */

		// create crown
		// divide by 2 to get radius, height is diameter
		double c = crownHeight / 2D;

		double a = crownXRadius;
		double b = crownYRadius;

		int numberOfThetaRings = 5;
		int numberOfPhiRings = 10;

		double thetaDelta = Math.PI / (numberOfThetaRings - 1);
		double phiDelta = Math.PI * 2 / numberOfPhiRings;

		List<List<Coordinate>> ringCoordinates = new ArrayList<>();

		// top point triangles
		Coordinate topPoint = new Coordinate(point.x, point.y, point.z + c + trunkHeight + c);
		ringCoordinates.add(Collections.singletonList(topPoint));
		for (int i = 1; i < numberOfThetaRings - 1; i++) {
			double theta = i * thetaDelta;
			List<Coordinate> coords = new ArrayList<>();
			ringCoordinates.add(coords);
			for (int j = 0; j < numberOfPhiRings; j++) {
				double phi = j * phiDelta;
				double x = a * Math.sin(theta) * Math.cos(phi) + point.x;
				double y = b * Math.sin(theta) * Math.sin(phi) + point.y;
				double z = c * Math.cos(theta) + point.z + trunkHeight + c;
				Coordinate p1 = new Coordinate(x, y, z);
				coords.add(p1);
			}
		}
		Coordinate bottomPoint = new Coordinate(point.x, point.y, point.z + trunkHeight);
		ringCoordinates.add(Collections.singletonList(bottomPoint));
		
		List<Coordinate> secondRing = ringCoordinates.get(1);
		for (int i = 0; i < secondRing.size(); i++) {
			Coordinate[] coordArray = new Coordinate[] { topPoint, secondRing.get(i), secondRing.get((i + 1) % secondRing.size()), topPoint };
			Polygon polygon = GreenEnricher.GEOM_FACTORY.createPolygon(coordArray);
			var convertedPoly = GreenEnricher.convertToCityGmlPoly(polygon);
			ms.getSurfaceMember().add(new SurfaceProperty(convertedPoly));
		}
		for (int i = 1; i < ringCoordinates.size() - 2; i++) {
			List<Coordinate> topRing = ringCoordinates.get(i);
			List<Coordinate> bottomRing = ringCoordinates.get(i + 1);
			for (int j = 0; j < topRing.size(); j++) {
				int nextRingIndex = (j + 1) % topRing.size();
				Coordinate p1 = topRing.get(j);
				Coordinate p2 = topRing.get(nextRingIndex);
				Coordinate p3 = bottomRing.get(j);
				Coordinate p4 = bottomRing.get(nextRingIndex);
				Coordinate[] coordArray = new Coordinate[] { p1, p3, p4, p2, p1 };
				Polygon polygon = GreenEnricher.GEOM_FACTORY.createPolygon(coordArray);
				var convertedPoly = GreenEnricher.convertToCityGmlPoly(polygon);
				ms.getSurfaceMember().add(new SurfaceProperty(convertedPoly));
			}
		}
		
		// bottom point triangles
		List<Coordinate> bottomRing = ringCoordinates.get(ringCoordinates.size() - 2);
		for (int i = 0; i < bottomRing.size(); i++) {
			int nextRingIndex = (i + 1) % bottomRing.size();
			Coordinate p1 = bottomRing.get(i);
			Coordinate p2 = bottomRing.get(nextRingIndex);
			Coordinate[] coordArray = new Coordinate[] { p2, p1, bottomPoint, p2 };
			Polygon polygon = GreenEnricher.GEOM_FACTORY.createPolygon(coordArray);
			var convertedPoly = GreenEnricher.convertToCityGmlPoly(polygon);
			ms.getSurfaceMember().add(new SurfaceProperty(convertedPoly));
		}

		return ms;
	}

	private static Polygon convertPolygon(List<Coordinate> trunkWestCoordinateList) {
		return GreenEnricher.GEOM_FACTORY
				.createPolygon(trunkWestCoordinateList.toArray(new Coordinate[trunkWestCoordinateList.size()]));
	}

}
