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

import org.citygml4j.factory.GMLGeometryFactory;
import org.citygml4j.model.citygml.core.AbstractCityObject;

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.parser.ParserConfiguration;

/**
 * Abstract class for general features such as Buildings, Water objects, ...
 * <br>
 * Contains the geometries
 * 
 * @author Matthias Betz
 *
 */
public abstract class CityObject extends GmlElement {

	private static final long serialVersionUID = 651712070755024188L;

	private List<Geometry> geometryList = new ArrayList<>();

	/**
	 * Recreates the CityGML4j geometry in this object. The CityGML4j geometry is
	 * deleted from the data structure as was mapped to the city doctor data
	 * structure. For writing CityGML files the geometry has to be restored. Also
	 * maps changes to geometry back to CityGML4j
	 * 
	 * @param factory needed to create CityGML4j data structures
	 * @param config  contains information whether a city doctor point was
	 *                transformed to a different coordinate system. It would need to
	 *                be transformed to the original coordinate system
	 */
	public abstract void reCreateGeometries(GMLGeometryFactory factory, ParserConfiguration config);

	/**
	 * Getter for the CityGML4j data object
	 * 
	 * @return the CityGML4j data object
	 */
	public abstract AbstractCityObject getGmlObject();

	/**
	 * Remove the CityGML4j geometries from the data structure to save memory
	 */
	public abstract void unsetGmlGeometries();

	@Override
	public CityObject copy() {
		return (CityObject) super.copy();
	}

	public void addGeometry(Geometry geom) {
		Objects.requireNonNull(geom);
		geometryList.add(geom);
		geom.setParent(this);
	}

	public List<Geometry> getGeometries() {
		return geometryList;
	}

	public void removeGeometry(Lod lod) {
		Iterator<Geometry> it = geometryList.iterator();
		while (it.hasNext()) {
			Geometry next = it.next();
			if (next.getLod() == lod) {
				it.remove();
			}
		}
	}

	public Geometry getHighestLodGeometry() {
		Geometry highestLodGeometry = null;
		Lod highestLod = null;
		for (Geometry geom : geometryList) {
			if (highestLod == null || geom.getLod().isHigher(highestLod)) {
				highestLodGeometry = geom;
				highestLod = geom.getLod();
			}
		}
		return highestLodGeometry;
	}

	public abstract FeatureType getFeatureType();

	@Override
	public void accept(Check c) {
		super.accept(c);
		if (c.canExecute(this)) {
			c.check(this);
		}
		for (Geometry geom : geometryList) {
			geom.accept(c);
		}
	}

	@Override
	public boolean containsError(CheckId checkIdentifier) {
		boolean hasError = super.containsError(checkIdentifier);
		if (hasError) {
			return true;
		}
		for (Geometry geom : geometryList) {
			if (geom.containsError(checkIdentifier)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public void clearAllContainedCheckResults() {
		super.clearCheckResults();
		for (Geometry geom : geometryList) {
			geom.clearAllContainedCheckResults();
		}
	}

	@Override
	public void collectContainedErrors(List<CheckError> errors) {
		super.collectContainedErrors(errors);
		for (Geometry geom : geometryList) {
			geom.collectContainedErrors(errors);
		}
	}

	@Override
	public boolean containsAnyError() {
		boolean hasError = super.containsAnyError();
		if (hasError) {
			return true;
		}
		for (Geometry geom : geometryList) {
			if (geom.containsAnyError()) {
				return true;
			}
		}
		return false;
	}

	public Geometry getGeometry(GeometryType type, Lod lod) {
		for (Geometry geom : geometryList) {
			if (geom.getType() == type && geom.getLod() == lod) {
				return geom;
			}
		}
		return null;
	}
}
