/*-
 *  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.reporting;

import java.util.Collection;

import org.osgeo.proj4j.BasicCoordinateTransform;
import org.osgeo.proj4j.ProjCoordinate;

import de.hft.stuttgart.citydoctor2.check.ErrorReport;
import de.hft.stuttgart.citydoctor2.checkresult.EdgeReport;
import de.hft.stuttgart.citydoctor2.checkresult.EdgesReport;
import de.hft.stuttgart.citydoctor2.checkresult.ErrorDetails;
import de.hft.stuttgart.citydoctor2.checkresult.GeometryReport;
import de.hft.stuttgart.citydoctor2.checkresult.GeometryReportLod;
import de.hft.stuttgart.citydoctor2.checkresult.GeometryReportType;
import de.hft.stuttgart.citydoctor2.checkresult.ParameterReport;
import de.hft.stuttgart.citydoctor2.checkresult.ParentReport;
import de.hft.stuttgart.citydoctor2.checkresult.PolygonReport;
import de.hft.stuttgart.citydoctor2.checkresult.PolygonsReport;
import de.hft.stuttgart.citydoctor2.checkresult.RingReport;
import de.hft.stuttgart.citydoctor2.checkresult.RingType;
import de.hft.stuttgart.citydoctor2.checkresult.SurfaceReport;
import de.hft.stuttgart.citydoctor2.checkresult.VertexReport;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;

/**
 * Error handler to create detailed report on errors
 * 
 * @author Matthias Betz
 *
 */
public class XmlStreamErrorHandler implements ErrorReport {

	private ErrorDetails details;
	private BasicCoordinateTransform originalTransform;

	public XmlStreamErrorHandler(ParserConfiguration config) {
		originalTransform = config.getOriginalTransform();
		details = new ErrorDetails();
	}

	@Override
	public void add(Polygon p) {
		PolygonReport report = createPolygonReport(p);
		details.getPolygon().add(report);
	}

	private PolygonReport createPolygonReport(Polygon p) {
		PolygonReport report = new PolygonReport();
		if (!p.getGmlId().isGenerated()) {
			report.setGmlId(p.getGmlId().getGmlString());
		}
		LinearRing exteriorRing = p.getExteriorRing();
		RingReport extReport = createRingReport(exteriorRing);
		report.setExteriorRing(extReport);

		if (!p.getInnerRings().isEmpty()) {
			for (LinearRing lr : p.getInnerRings()) {
				RingReport rep = createRingReport(lr);
				report.getInteriorRing().add(rep);
			}
		}
		return report;
	}

	private RingReport createRingReport(LinearRing lr) {
		RingReport ringReport = new RingReport();
		if (!lr.getGmlId().isGenerated()) {
			ringReport.setGmlId(lr.getGmlId().getGmlString());
		}
		if (lr.getType() == LinearRingType.EXTERIOR) {
			ringReport.setType(RingType.EXTERIOR);
		} else if (lr.getType() == LinearRingType.INTERIOR) {
			ringReport.setType(RingType.INTERIOR);
		} else {
			throw new IllegalStateException("Unknown ring type found: " + lr.getType());
		}
		for (Vertex v : lr.getVertices()) {
			VertexReport vReport = createVertexReport(v);
			ringReport.getVertices().add(vReport);
		}
		return ringReport;
	}

	private VertexReport createVertexReport(Vector3d v) {
		VertexReport vReport = new VertexReport();
		double x = v.getX();
		double y = v.getY();
		double z = v.getZ();
		if (originalTransform != null) {
			ProjCoordinate p1 = new ProjCoordinate();
			ProjCoordinate p2 = new ProjCoordinate();
			p1.x = v.getX();
			p1.y = v.getY();
			originalTransform.transform(p1, p2);
			x = p2.x;
			y = p2.y;
		}
		vReport.setX(x);
		vReport.setY(y);
		vReport.setZ(z);
		return vReport;
	}

	private void addRing(LinearRing lr) {
		RingReport report = createRingReport(lr);
		details.getRing().add(report);
	}

	private void addVertex(Vector3d v) {
		addVertex(v, null);
	}

	private void addVertex(Vector3d v, String name) {
		VertexReport rep = createVertexReport(v);
		if (name != null) {
			rep.setName(name);
		}
		details.getVertex().add(rep);
	}

	@Override
	public void add(LinearRing lr) {
		addRing(lr);
	}

	@Override
	public void add(Geometry geom) {
		addGeometry(geom);
	}

	private void addGeometry(Geometry geom) {
		GeometryReport rep = new GeometryReport();
		if (!geom.getGmlId().isGenerated()) {
			rep.setGmlId(geom.getGmlId().getGmlString());
		}
		rep.setType(GeometryReportType.valueOf(geom.getType().toString()));
		rep.setLod(GeometryReportLod.valueOf(geom.getLod().toString()));

		ParentReport parent = new ParentReport();
		parent.setGmlId(geom.getParent().getGmlId().getGmlString());
		parent.setType(geom.getParent().getFeatureType().toString());
		rep.setParent(parent);
		details.getGeometry().add(rep);
	}

	@Override
	public void add(Vertex v) {
		addVertex(v);
	}

	@Override
	public void add(String name, Vertex v) {
		addVertex(v, name);
	}

	@Override
	public void add(Edge e) {
		EdgeReport rep = createEdgeReport(e);
		details.getEdge().add(rep);
	}

	private EdgeReport createEdgeReport(Edge e) {
		return createEdgeReport(e, null);
	}

	private EdgeReport createEdgeReport(Edge e, String name) {
		EdgeReport rep = new EdgeReport();
		if (name != null) {
			rep.setName(name);
		}
		VertexReport from = createVertexReport(e.getFrom());
		VertexReport to = createVertexReport(e.getTo());
		rep.setFrom(from);
		rep.setTo(to);
		return rep;
	}

	@Override
	public void add(String name, Edge e) {
		EdgeReport rep = createEdgeReport(e, name);
		details.getEdge().add(rep);
	}

	@Override
	public void add(String name, int value) {
		addParameter(name, "" + value);
	}

	private void addParameter(String name, String value) {
		ParameterReport rep = new ParameterReport();
		rep.setName(name);
		rep.setValue(value);
		details.getParameter().add(rep);
	}

	@Override
	public void add(String name, double value) {
		addParameter(name, "" + value);
	}

	@Override
	public void add(String name, String value) {
		addParameter(name, value);
	}

	@Override
	public void add(String name, Vector3d point) {
		addVertex(point, name);
	}

	@Override
	public void addPolygons(String name, Collection<Polygon> polygons) {
		PolygonsReport rep = new PolygonsReport();
		if (name != null) {
			rep.setName(name);
		}

		for (Polygon p : polygons) {
			PolygonReport polyRep;
			if (p.getGmlId().isGenerated()) {
				polyRep = createPolygonReport(p);
			} else {
				polyRep = new PolygonReport();
				polyRep.setGmlId(p.getGmlId().getGmlString());
			}
			rep.getPolygons().add(polyRep);
		}
		details.getPolygons().add(rep);
	}

	@Override
	public void addEdges(String name, Collection<Edge> edges) {
		EdgesReport rep = new EdgesReport();
		if (name != null) {
			rep.setName(name);
		}

		for (Edge e : edges) {
			EdgeReport eRep = createEdgeReport(e);
			rep.getEdges().add(eRep);
		}
		details.getEdges().add(rep);
	}

	@Override
	public void add(BoundarySurface bs) {
		SurfaceReport rep = new SurfaceReport();
		rep.setGmlId(bs.getGmlId().getGmlString());
		details.getSurface().add(rep);
	}

	public ErrorDetails getDetails() {
		return details;
	}

}
