/*-
 *  Copyright 2022 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.mapper.citygml3;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.citygml4j.core.model.construction.AbstractConstructionSurface;
import org.citygml4j.core.model.construction.AbstractFillingSurface;
import org.citygml4j.core.model.construction.AbstractFillingSurfaceProperty;
import org.citygml4j.core.model.construction.CeilingSurface;
import org.citygml4j.core.model.construction.DoorSurface;
import org.citygml4j.core.model.construction.FloorSurface;
import org.citygml4j.core.model.construction.GroundSurface;
import org.citygml4j.core.model.construction.InteriorWallSurface;
import org.citygml4j.core.model.construction.OuterCeilingSurface;
import org.citygml4j.core.model.construction.OuterFloorSurface;
import org.citygml4j.core.model.construction.RoofSurface;
import org.citygml4j.core.model.construction.WallSurface;
import org.citygml4j.core.model.construction.WindowSurface;
import org.citygml4j.core.model.core.AbstractThematicSurface;
import org.citygml4j.core.model.core.ClosureSurface;
import org.citygml4j.core.visitor.ObjectWalker;
import org.xmlobjects.gml.model.geometry.aggregates.MultiSurface;
import org.xmlobjects.gml.model.geometry.aggregates.MultiSurfaceProperty;
import org.xmlobjects.gml.model.geometry.primitives.AbstractSurface;
import org.xmlobjects.gml.model.geometry.primitives.SurfaceProperty;

import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurfaceType;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Opening;
import de.hft.stuttgart.citydoctor2.datastructure.OpeningType;
import de.hft.stuttgart.citydoctor2.datastructure.SurfaceFeatureType;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;

public class SurfaceMapper extends ObjectWalker {

	private List<BoundarySurface> surfaces = new ArrayList<>();
	private Map<String, ConcretePolygon> polygonMap;
	private List<ResolvableReference> references;
	private Map<Vertex, Vertex> vertexMap;
	private ParserConfiguration config;
	
	public SurfaceMapper(Map<String, ConcretePolygon> polygonMap, List<ResolvableReference> references,
			Map<Vertex, Vertex> vertexMap, ParserConfiguration config) {
		this.polygonMap = polygonMap;
		this.references = references;
		this.vertexMap = vertexMap;
		this.config = config;
	}

	@Override
	public void visit(WallSurface wallSurface) {
		BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL,
				wallSurface);
		handleConstructionSurface(wallSurface, surface);
		surfaces.add(surface);
	}
	
	private void handleConstructionSurface(AbstractConstructionSurface gmlSurface, BoundarySurface cdSurface) {
		for (AbstractFillingSurfaceProperty fillingProp : gmlSurface.getFillingSurfaces()) {
			if (fillingProp.isSetObject()) {
				readAbstractFillingSurface(fillingProp.getObject(), cdSurface);
			}
		}
		handleThematicSurface(gmlSurface, cdSurface);
		cdSurface.unsetGmlGeometries();
	}
	

	private void readAbstractFillingSurface(AbstractFillingSurface afs, BoundarySurface cdSurface) {
		Opening o;
		if (afs instanceof DoorSurface ds) {
			o = new Opening(OpeningType.DOOR, SurfaceFeatureType.BUILDING, cdSurface, ds);
		} else if (afs instanceof WindowSurface ws){
			o = new Opening(OpeningType.WINDOW, SurfaceFeatureType.BUILDING, cdSurface, ws);
		} else {
			o = new Opening(OpeningType.UNKNOWN, SurfaceFeatureType.BUILDING, cdSurface, afs);
		}
		cdSurface.addOpening(o);
		parseAndAddMultiSurface(afs.getLod0MultiSurface(), Lod.LOD0, o);
		parseAndAddMultiSurface(afs.getLod1MultiSurface(), Lod.LOD1, o);
		parseAndAddMultiSurface(afs.getLod2MultiSurface(), Lod.LOD2, o);
		parseAndAddMultiSurface(afs.getLod3MultiSurface(), Lod.LOD3, o);
		parseAndAddMultiSurface(afs.getDeprecatedProperties().getLod4MultiSurface(), Lod.LOD4, o);
		o.unsetGmlGeometries();
	}

	private void handleThematicSurface(AbstractThematicSurface gmlSurface, CityObject cdSurface) {
		Citygml3FeatureMapper.parseId(gmlSurface, cdSurface);
		parseAndAddMultiSurface(gmlSurface.getLod0MultiSurface(), Lod.LOD0, cdSurface);
		parseAndAddMultiSurface(gmlSurface.getLod1MultiSurface(), Lod.LOD1, cdSurface);
		parseAndAddMultiSurface(gmlSurface.getLod2MultiSurface(), Lod.LOD2, cdSurface);
		parseAndAddMultiSurface(gmlSurface.getLod3MultiSurface(), Lod.LOD3, cdSurface);
		parseAndAddMultiSurface(gmlSurface.getDeprecatedProperties().getLod4MultiSurface(), Lod.LOD4, cdSurface);
	}
	
	private void parseAndAddMultiSurface(MultiSurfaceProperty msp, Lod lod, CityObject cdSurface) {
		if (msp == null || msp.getObject() == null) {
			return;
		}
		Geometry geom = parseMultiSurface(msp.getObject(), lod);
		cdSurface.addGeometry(geom);
	}
	
	private Geometry parseMultiSurface(MultiSurface ms, Lod lod) {
		Geometry geom = new Geometry(GeometryType.MULTI_SURFACE, lod);
		Citygml3GeometryMapper geometryMapper = new Citygml3GeometryMapper(config, vertexMap);
		readSurfaceMember(geom, geometryMapper, ms.getSurfaceMember());
		return geom;
	}
	
	private void readSurfaceMember(Geometry geom, Citygml3GeometryMapper geometryMapper,
			List<SurfaceProperty> surfaceMember) {
		for (SurfaceProperty prop : surfaceMember) {
			if (prop.getHref() != null) {
				references.add(new ResolvableReference(prop.getHref(), geom));
				continue;
			}
			if (prop.getObject() != null) {
				AbstractSurface as = prop.getObject();
				as.accept(geometryMapper);
			}
		}

		List<ConcretePolygon> polygons = geometryMapper.getPolygons();
		for (ConcretePolygon concretePoly : polygons) {
			geom.addPolygon(concretePoly);
			if (concretePoly.hasExistingGmlId()) {
				polygonMap.put(concretePoly.getGmlId().getGmlString(), concretePoly);
			}
		}
	}

	@Override
	public void visit(FloorSurface floorSurface) {
		BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.FLOOR,
				floorSurface);
		handleConstructionSurface(floorSurface, surface);
		surfaces.add(surface);
	}

	@Override
	public void visit(CeilingSurface ceilingSurface) {
		BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.CEILING,
				ceilingSurface);
		handleConstructionSurface(ceilingSurface, surface);
		surfaces.add(surface);
	}

	@Override
	public void visit(GroundSurface groundSurface) {
		BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.GROUND,
				groundSurface);
		handleConstructionSurface(groundSurface, surface);
		surfaces.add(surface);
	}

	@Override
	public void visit(InteriorWallSurface interiorWallSurface) {
		BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.INTERIOR_WALL,
				interiorWallSurface);
		handleConstructionSurface(interiorWallSurface, surface);
		surfaces.add(surface);
	}

	@Override
	public void visit(OuterCeilingSurface outerCeilingSurface) {
		BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.OUTER_CEILING,
				outerCeilingSurface);
		handleConstructionSurface(outerCeilingSurface, surface);
		surfaces.add(surface);
	}

	@Override
	public void visit(OuterFloorSurface outerFloorSurface) {
		BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.OUTER_FLOOR,
				outerFloorSurface);
		handleConstructionSurface(outerFloorSurface, surface);
		surfaces.add(surface);
	}

	@Override
	public void visit(RoofSurface roofSurface) {
		BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.ROOF,
				roofSurface);
		handleConstructionSurface(roofSurface, surface);
		surfaces.add(surface);
	}

	@Override
	public void visit(ClosureSurface closureSurface) {
		BoundarySurface surface = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.CLOSURE,
				closureSurface);
		handleThematicSurface(closureSurface, surface);
		surfaces.add(surface);
	}
	
	public List<BoundarySurface> getSurfaces() {
		return surfaces;
	}

}
