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

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

import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.tesselation.JoglTesselator;
import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon;
import de.hft.stuttgart.citydoctor2.utils.CopyHandler;
import de.hft.stuttgart.citydoctor2.utils.Copyable;

/**
 * A polygon that actually contains the rings and points representing the
 * geometry
 * 
 * @author Matthias Betz
 *
 */
public class ConcretePolygon extends Polygon {

	private static final long serialVersionUID = -2208347892270418372L;

	private LinearRing exterior;
	private List<LinearRing> innerRings;
	private BoundarySurface partOfSurface;
	private Installation partfOfInstallation;
	private Geometry parent;
	private LinkedPolygon linkedFromPolygon;
	
	public ConcretePolygon() {
		// TODO Auto-generated constructor stub
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.hft.stuttgart.citydoctor2.datastructure.Polygon#calculateNormal()
	 */
	@Override
	public Vector3d calculateNormalNormalized() {
		return exterior.calculateNormalNormalized();
	}
	
	@Override
	public Vector3d calculateNormal() {
		return exterior.calculateNormal();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.hft.stuttgart.citydoctor2.datastructure.Polygon#tesselate()
	 */
	@Override
	public TesselatedPolygon tesselate() {
		return JoglTesselator.tesselatePolygon(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.hft.stuttgart.citydoctor2.datastructure.Polygon#getExteriorRing()
	 */
	@Override
	public LinearRing getExteriorRing() {
		return exterior;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.hft.stuttgart.citydoctor2.datastructure.Polygon#getInnerRings()
	 */
	@Override
	public List<LinearRing> getInnerRings() {
		if (innerRings == null) {
			return Collections.emptyList();
		}
		return innerRings;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.hft.stuttgart.citydoctor2.datastructure.Polygon#isPointInsideExteriorRing(
	 * de.hft.stuttgart.citydoctor2.math.Vector3d)
	 */
	@Override
	public boolean isPointInsideExteriorRing(Vector3d v) {
		return exterior.isPointInside(v);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.hft.stuttgart.citydoctor2.datastructure.Polygon#getParent()
	 */
	@Override
	public Geometry getParent() {
		return parent;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.hft.stuttgart.citydoctor2.datastructure.Polygon#setParent(de.hft.stuttgart
	 * .citydoctor2.datastructure.Geometry)
	 */
	@Override
	public void setParent(Geometry geometry) {
		parent = geometry;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.hft.stuttgart.citydoctor2.datastructure.Polygon#setExteriorRing(de.hft.
	 * stuttgart.citydoctor2.datastructure.LinearRing)
	 */
	@Override
	public void setExteriorRing(LinearRing extRing) {
		exterior = extRing;
		extRing.setParent(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.hft.stuttgart.citydoctor2.datastructure.Polygon#accept(de.hft.stuttgart.
	 * citydoctor2.check.Check)
	 */
	@Override
	public void accept(Check c) {
		if (c.canExecute(this)) {
			c.check(this);
		}
		if (exterior != null) {
			exterior.accept(c);
		}
		if (innerRings != null) {
			for (LinearRing lr : innerRings) {
				lr.accept(c);
			}
		}
		setValidated(true);
	}

	@Override
	public Class<? extends Checkable> getCheckClass() {
		return Polygon.class;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.hft.stuttgart.citydoctor2.datastructure.Polygon#isPolygonConnectedViaPoint
	 * (de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon)
	 */
	@Override
	public boolean isPolygonConnectedViaPoint(Polygon other) {
		return exterior.isRingConnectedViaPoint(other.getExteriorRing());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.hft.stuttgart.citydoctor2.datastructure.Polygon#containsAnyError()
	 */
	@Override
	public boolean containsAnyError() {
		if (super.containsAnyError()) {
			return true;
		}
		if (exterior != null && exterior.containsAnyError()) {
			return true;
		}
		if (innerRings != null) {
			for (LinearRing lr : innerRings) {
				if (lr.containsAnyError()) {
					return true;
				}
			}
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.hft.stuttgart.citydoctor2.datastructure.Polygon#collectContainedErrors(
	 * java.util.List)
	 */
	@Override
	public void collectContainedErrors(List<CheckError> errors) {
		super.collectContainedErrors(errors);
		if (exterior != null) {
			exterior.collectContainedErrors(errors);
		}
		if (innerRings != null) {
			for (LinearRing lr : innerRings) {
				lr.collectContainedErrors(errors);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.hft.stuttgart.citydoctor2.datastructure.Polygon#
	 * clearAllContainedCheckResults()
	 */
	@Override
	public void clearAllContainedCheckResults() {
		super.clearCheckResults();
		if (exterior != null) {
			exterior.clearAllContainedCheckResults();
		}
		if (innerRings != null) {
			for (LinearRing lr : innerRings) {
				lr.clearAllContainedCheckResults();
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.hft.stuttgart.citydoctor2.datastructure.Polygon#containsError(de.hft.
	 * stuttgart.citydoctor2.check.CheckId)
	 */
	@Override
	public boolean containsError(CheckId checkIdentifier) {
		boolean hasError = super.containsError(checkIdentifier);
		if (hasError) {
			return true;
		}
		if (exterior != null && exterior.containsError(checkIdentifier)) {
			return true;
		}
		if (innerRings != null) {
			for (LinearRing lr : innerRings) {
				if (lr.containsError(checkIdentifier)) {
					return true;
				}
			}
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.hft.stuttgart.citydoctor2.datastructure.Polygon#addInteriorRing(de.hft.
	 * stuttgart.citydoctor2.datastructure.LinearRing)
	 */
	@Override
	public void addInteriorRing(LinearRing inter) {
		if (inter == null) {
			return;
		}
		if (innerRings == null) {
			innerRings = new ArrayList<>(2);
		}
		innerRings.add(inter);
		inter.setParent(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.hft.stuttgart.citydoctor2.datastructure.Polygon#getPartOfSurface()
	 */
	@Override
	public BoundarySurface getPartOfSurface() {
		return partOfSurface;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.hft.stuttgart.citydoctor2.datastructure.Polygon#setPartOfSurface(de.hft.
	 * stuttgart.citydoctor2.datastructure.BoundarySurface)
	 */
	@Override
	public void setPartOfSurface(BoundarySurface bs) {
		partOfSurface = bs;
	}

	@Override
	public void removeInnerRing(LinearRing ring) {
		if (innerRings == null) {
			return;
		}
		innerRings.remove(ring);
		// remove vertex association
		emptyRing(ring);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.hft.stuttgart.citydoctor2.datastructure.Polygon#setPartOfInstallation(de.
	 * hft.stuttgart.citydoctor2.datastructure.BuildingInstallation)
	 */
	@Override
	public void setPartOfInstallation(Installation bi) {
		partfOfInstallation = bi;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.hft.stuttgart.citydoctor2.datastructure.Polygon#getPartOfInstallation()
	 */
	@Override
	public Installation getPartOfInstallation() {
		return partfOfInstallation;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.hft.stuttgart.citydoctor2.datastructure.Polygon#toString()
	 */
	@Override
	public String toString() {
		return "Polygon [id=" + getGmlId() + "]";
	}

	@Override
	void anonymize() {
		setGmlId(GmlId.generateId());
		exterior.anonymize();
		if (innerRings != null) {
			for (LinearRing lr : innerRings) {
				lr.anonymize();
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.hft.stuttgart.citydoctor2.datastructure.Polygon#hasPointAsCorner(de.hft.
	 * stuttgart.citydoctor2.datastructure.Vertex)
	 */
	@Override
	public boolean hasPointAsCorner(Vertex v) {
		boolean hasPoint = exterior.hasPointAsCorner(v);
		if (hasPoint) {
			return true;
		}
		if (innerRings != null) {
			for (LinearRing lr : innerRings) {
				if (lr.hasPointAsCorner(v)) {
					return true;
				}
			}
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.hft.stuttgart.citydoctor2.datastructure.Polygon#removeRings()
	 */
	@Override
	public void removeRings() {
		emptyRing(exterior);
		exterior = null;
		clearInteriorRings();
	}

	private void clearInteriorRings() {
		if (innerRings == null) {
			return;
		}
		for (LinearRing lr : innerRings) {
			emptyRing(lr);
		}
		innerRings.clear();
	}

	private void emptyRing(LinearRing lr) {
		if (isLinkedTo()) {
			Geometry geom2 = linkedFromPolygon.getParent();
			for (Vertex v : lr.getVertices()) {
				v.removeAdjacency(lr, geom2);
			}
		}
		for (Vertex v : lr.getVertices()) {
			v.removeAdjacency(lr, parent);
		}
	}

	@Override
	public boolean isLinkedTo() {
		return linkedFromPolygon != null;
	}

	@Override
	public LinkedPolygon getLinkedFromPolygon() {
		return linkedFromPolygon;
	}

	@Override
	public boolean isLink() {
		return false;
	}

	@Override
	public ConcretePolygon getOriginal() {
		return this;
	}

	void setLinkedTo(LinkedPolygon linkedPolygon) {
		linkedFromPolygon = linkedPolygon;
	}
	
	@Override
	public void prepareForChecking() {
		parent.prepareForChecking();
	}
	
	@Override
	public void clearMetaInformation() {
		parent.clearMetaInformation();
	}

	@Override
	public void collectInstances(CopyHandler handler) {
		handler.addInstance(exterior);
		if (innerRings != null) {
			for (LinearRing lr : innerRings) {
				handler.addInstance(lr);
			}
		}
		handler.addInstance(partOfSurface);
		handler.addInstance(partfOfInstallation);
		handler.addInstance(parent);
		handler.addInstance(linkedFromPolygon);
	}
	

	@Override
	public Copyable createCopyInstance() {
		return new ConcretePolygon();
	}
	
	@Override
	public void fillValues(Copyable original, CopyHandler handler) {
		super.fillValues(original, handler);
		ConcretePolygon originalPoly = (ConcretePolygon) original;
		exterior = handler.getCopyInstance(originalPoly.exterior);
		if (originalPoly.innerRings != null) {
			innerRings = new ArrayList<>(originalPoly.innerRings.size());
			for (LinearRing originalRing : originalPoly.innerRings) {
				innerRings.add(handler.getCopyInstance(originalRing));
			}
		}
		parent = handler.getCopyInstance(originalPoly.parent);
		partfOfInstallation = handler.getCopyInstance(originalPoly.partfOfInstallation);
		partOfSurface = handler.getCopyInstance(originalPoly.partOfSurface);
		linkedFromPolygon = handler.getCopyInstance(originalPoly.linkedFromPolygon);
	}

	@Override
	public void remove() {
		parent.removePolygon(this);
	}

	@Override
	public double getArea() {
		if (exterior == null) {
			return 0;
		}
		double area = exterior.getArea();
		if (innerRings == null || innerRings.isEmpty()) {
			return area;
		}
		for (LinearRing innerRing : innerRings) {
			area -= innerRing.getArea();
		}
		return area;
	}
	
	

}
