/*-
 *  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.core.util.geometry.GeometryFactory;
import org.locationtech.proj4j.BasicCoordinateTransform;
import org.locationtech.proj4j.ProjCoordinate;
import org.xmlobjects.gml.model.geometry.aggregates.MultiSurface;
import org.xmlobjects.gml.model.geometry.complexes.CompositeSurface;
import org.xmlobjects.gml.model.geometry.primitives.AbstractRingProperty;
import org.xmlobjects.gml.model.geometry.primitives.Shell;
import org.xmlobjects.gml.model.geometry.primitives.ShellProperty;
import org.xmlobjects.gml.model.geometry.primitives.Solid;
import org.xmlobjects.gml.model.geometry.primitives.SurfaceProperty;

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.xmlobjects.gml.model.geometry.primitives.Polygon createGmlPolygon(GeometryFactory factory,
			Polygon cdPoly, ParserConfiguration config) {
		if (cdPoly.getExteriorRing() == null) {
			return null;
		}
		org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = new org.xmlobjects.gml.model.geometry.primitives.Polygon();
		// exterior ring
		LinearRing extLr = cdPoly.getExteriorRing();
		if (extLr.getVertices().size() < 3) {
			// this ring does not have enough points in it
			// this leads to errors when exporting therefore ignore it
			return null;
		}
		org.xmlobjects.gml.model.geometry.primitives.LinearRing gmlLr = createGmlRing(factory, config, extLr);
		gmlPoly.setExterior(new AbstractRingProperty(gmlLr));

		// interior rings
		for (LinearRing lr : cdPoly.getInnerRings()) {
			gmlLr = createGmlRing(factory, config, lr);
			if (lr.getVertices().size() < 3) {
				// this ring does not have enough points in it
				// this leads to errors when exporting therefore ignore it
				return null;
			}
			gmlPoly.getInterior().add(new AbstractRingProperty(gmlLr));
		}
		gmlPoly.setId(cdPoly.getGmlId().getGmlString());
		return gmlPoly;
	}

	public static org.xmlobjects.gml.model.geometry.primitives.LinearRing createGmlRing(GeometryFactory factory,
			ParserConfiguration config, LinearRing lr) {

		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.getFromMeters();
			}
			ringValues.add(x);
			ringValues.add(y);
			ringValues.add(z);
		}

		org.xmlobjects.gml.model.geometry.primitives.LinearRing gmlLr = factory.createLinearRing(ringValues, 3);
		gmlLr.setId(lr.getGmlId().getGmlString());
		return gmlLr;
	}

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

		CompositeSurface comp = new CompositeSurface();
		List<SurfaceProperty> surfaceMember = comp.getSurfaceMembers();
		for (Polygon cdPoly : geom.getPolygons()) {
			if (!cdPoly.isLink()) {
				org.xmlobjects.gml.model.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;
		}
		Solid solid = new Solid();
		Shell shell = new Shell(surfaceMember);
		solid.setExterior(new ShellProperty(shell));
		solid.setId(geom.getGmlId().getGmlString());
		return solid;
	}

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

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

	public static CompositeSurface createCompositeSurface(Geometry geom, GeometryFactory factory,
			ParserConfiguration config) {
		List<SurfaceProperty> surfaces = new ArrayList<>();
		for (Polygon cdPoly : geom.getPolygons()) {
			org.xmlobjects.gml.model.geometry.primitives.Polygon gmlPoly = createGmlPolygon(factory, cdPoly, config);
			if (gmlPoly != null) {
				surfaces.add(new SurfaceProperty(gmlPoly));
			}
		}
		if (surfaces.isEmpty()) {
			return null;
		}
		return new CompositeSurface(surfaces);
	}

}
