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

import org.citygml4j.factory.DimensionMismatchException;
import org.citygml4j.factory.GMLGeometryFactory;
import org.citygml4j.model.gml.geometry.aggregates.MultiSurface;
import org.citygml4j.model.gml.geometry.complexes.CompositeSurface;
import org.citygml4j.model.gml.geometry.primitives.AbstractSurface;
import org.citygml4j.model.gml.geometry.primitives.Exterior;
import org.citygml4j.model.gml.geometry.primitives.Interior;
import org.citygml4j.model.gml.geometry.primitives.Solid;
import org.citygml4j.model.gml.geometry.primitives.SurfaceProperty;
import org.osgeo.proj4j.BasicCoordinateTransform;
import org.osgeo.proj4j.ProjCoordinate;

import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;

/**
 * Utility class dedicated to create CityGML4j datastructures.
 * 
 * @author Matthias Betz
 *
 */
public final class CityGmlUtils {

	private CityGmlUtils() {
		// util class
	}

	public static org.citygml4j.model.gml.geometry.primitives.Polygon createGmlPolygon(GMLGeometryFactory factory,
			Polygon cdPoly, ParserConfiguration config) {
		if (cdPoly.getExteriorRing() == null) {
			return null;
		}
		org.citygml4j.model.gml.geometry.primitives.Polygon gmlPoly = new org.citygml4j.model.gml.geometry.primitives.Polygon();
		try {
			// exterior ring
			LinearRing extLr = cdPoly.getExteriorRing();
			org.citygml4j.model.gml.geometry.primitives.LinearRing gmlLr = createGmlRing(factory, config, extLr);
			gmlPoly.setExterior(new Exterior(gmlLr));

			// interior rings
			for (LinearRing lr : cdPoly.getInnerRings()) {
				gmlLr = createGmlRing(factory, config, lr);
				gmlPoly.addInterior(new Interior(gmlLr));
			}
			gmlPoly.setId(cdPoly.getGmlId().getGmlString());
			return gmlPoly;
		} catch (DimensionMismatchException e) {
			// cannot happen as every vertex has 3 coordinates
			return null;
		}
	}

	private static org.citygml4j.model.gml.geometry.primitives.LinearRing createGmlRing(GMLGeometryFactory factory,
			ParserConfiguration config, LinearRing lr) throws DimensionMismatchException {
		ProjCoordinate p1 = new ProjCoordinate();
		ProjCoordinate p2 = new ProjCoordinate();
		List<Double> ringValues = new ArrayList<>();
		BasicCoordinateTransform trans = config.getOriginalTransform();
		for (Vertex v : lr.getVertices()) {
			double x = v.getX();
			double y = v.getY();
			double z = v.getZ();
			if (trans != null) {
				p1.x = x;
				p1.y = y;
				trans.transform(p1, p2);
				x = p2.x;
				y = p2.y;
				z = z * config.getFromMetres();
			}
			ringValues.add(x);
			ringValues.add(y);
			ringValues.add(z);
		}
		org.citygml4j.model.gml.geometry.primitives.LinearRing gmlLr = factory.createLinearRing(ringValues, 3);
		gmlLr.setId(lr.getGmlId().getGmlString());
		return gmlLr;
	}

	public static Solid createSolid(Geometry geom, GMLGeometryFactory factory, ParserConfiguration config) {
		if (geom.getType() != GeometryType.SOLID) {
			throw new IllegalArgumentException("Only solids are allowed");
		}

		List<SurfaceProperty> surfaceMember = new ArrayList<>();
		for (Polygon cdPoly : geom.getPolygons()) {
			if (!cdPoly.isLink()) {
				org.citygml4j.model.gml.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
				if (gmlPoly != null) {
					surfaceMember.add(new SurfaceProperty(gmlPoly));
				}
			} else {
				// add reference to polygon
				surfaceMember.add(new SurfaceProperty("#" + cdPoly.getGmlId().getGmlString()));
			}
		}

		if (surfaceMember.isEmpty()) {
			return null;
		}
		CompositeSurface comp = new CompositeSurface();
		comp.setSurfaceMember(surfaceMember);
		Solid solid = new Solid();
		solid.setExterior(new SurfaceProperty(comp));
		solid.setId(geom.getGmlId().getGmlString());
		return solid;
	}

	public static MultiSurface createMultiSurface(Geometry geom, GMLGeometryFactory factory,
			ParserConfiguration config) {
		if (geom.getType() != GeometryType.MULTI_SURFACE) {
			throw new IllegalArgumentException("This can only handle MultiSurfaces");
		}
		List<AbstractSurface> surfaces = new ArrayList<>();
		for (Polygon cdPoly : geom.getPolygons()) {
			if (!cdPoly.isLink()) {
				// is not part of a boundary surface
				org.citygml4j.model.gml.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
				if (gmlPoly != null) {
					surfaces.add(gmlPoly);
				}
			} else {
				// add reference to polygon
				CompositeSurface cs = new CompositeSurface();
				cs.addSurfaceMember(new SurfaceProperty("#" + cdPoly.getGmlId().getGmlString()));
				surfaces.add(cs);
			}
		}
		if (surfaces.isEmpty()) {
			return null;
		}
		MultiSurface ms = new MultiSurface(surfaces);
		ms.setId(geom.getGmlId().getGmlString());
		return ms;
	}

	public static MultiSurface createMultiSurface(List<Polygon> polygons, GMLGeometryFactory factory,
			ParserConfiguration config) {
		List<AbstractSurface> surfaces = new ArrayList<>();
		for (Polygon cdPoly : polygons) {
			org.citygml4j.model.gml.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
			if (gmlPoly != null) {
				surfaces.add(gmlPoly);
			}
		}
		if (surfaces.isEmpty()) {
			return null;
		}
		return new MultiSurface(surfaces);
	}

}
