/*-
 *  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.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

import org.citygml4j.CityGMLContext;
import org.citygml4j.builder.jaxb.CityGMLBuilder;
import org.citygml4j.builder.jaxb.CityGMLBuilderException;
import org.citygml4j.factory.GMLGeometryFactory;
import org.citygml4j.model.citygml.core.CityModel;
import org.citygml4j.model.module.citygml.CityGMLVersion;
import org.citygml4j.xml.io.CityGMLOutputFactory;
import org.citygml4j.xml.io.writer.CityGMLWriteException;
import org.citygml4j.xml.io.writer.CityModelInfo;
import org.citygml4j.xml.io.writer.CityModelWriter;

import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;

/**
 * The complete CityGML model containing all features that are used in
 * CityDoctor
 * 
 * @author Matthias Betz
 *
 */
public class CityDoctorModel {

	private static final String COULD_NOT_FIND_FEATURE = "Could not find feature: ";
	private List<Building> buildings;
	private List<Vegetation> vegetation;
	private List<BridgeObject> bridges;
	private List<LandObject> land;
	private List<TransportationObject> roads;
	private List<WaterObject> water;
	private CityModel cModel;
	private ParserConfiguration config;
	private String fileName;
	private File file;
	private List<CheckError> globalErrors;

	public CityDoctorModel(ParserConfiguration config, File file) {
		if (config == null) {
			throw new IllegalArgumentException("Parser configuration may not be null");
		}
		this.fileName = file.getName();
		this.config = config;
		this.file = file;
		buildings = new ArrayList<>();
		vegetation = new ArrayList<>();
		bridges = new ArrayList<>();
		land = new ArrayList<>();
		roads = new ArrayList<>();
		water = new ArrayList<>();
		globalErrors = new ArrayList<>();
	}

	public void addGlobalError(CheckError err) {
		globalErrors.add(err);
	}

	public void addGlobalErrors(List<? extends CheckError> generalErrors) {
		globalErrors.addAll(generalErrors);
	}

	public List<CheckError> getGlobalErrors() {
		return globalErrors;
	}

	public File getFile() {
		return file;
	}

	public Stream<CityObject> createFeatureStream() {
		return Stream.of(buildings.stream(), vegetation.stream(), bridges.stream(), land.stream(), roads.stream(),
				water.stream()).flatMap(co -> co);
	}

	public void saveAs(String file) throws CityGMLBuilderException, CityGMLWriteException {
		CityGMLContext gmlContext = CityGMLContext.getInstance();
		CityGMLBuilder builder = gmlContext.createCityGMLBuilder();
		CityGMLOutputFactory factory = builder.createCityGMLOutputFactory();
		try (CityModelWriter writer = factory.createCityModelWriter(new File(file))) {
			writer.setCityModelInfo(new CityModelInfo(cModel));
			writer.setIndentString("  ");
			writer.setPrefixes(CityGMLVersion.DEFAULT);
			writer.setSchemaLocations(CityGMLVersion.DEFAULT);
			writer.writeStartDocument();
			GMLGeometryFactory gmlFactory = new GMLGeometryFactory();
			storeCityObjects(writer, buildings, gmlFactory);
			storeCityObjects(writer, vegetation, gmlFactory);
			storeCityObjects(writer, bridges, gmlFactory);
			storeCityObjects(writer, land, gmlFactory);
			storeCityObjects(writer, roads, gmlFactory);
			storeCityObjects(writer, water, gmlFactory);
			writer.writeEndDocument();
		}
	}

	private void storeCityObjects(CityModelWriter writer, List<? extends CityObject> cos, GMLGeometryFactory gmlFactory)
			throws CityGMLWriteException {
		for (CityObject co : cos) {
			co.reCreateGeometries(gmlFactory, config);
			writer.writeFeatureMember(co.getGmlObject());
			co.unsetGmlGeometries();
		}
	}

	public String getFileName() {
		return fileName;
	}

	public void addBridge(BridgeObject coBridge) {
		bridges.add(coBridge);
	}

	public void addBuilding(Building coBuilding) {
		buildings.add(coBuilding);
	}

	public List<Building> getBuildings() {
		return buildings;
	}

	public List<BridgeObject> getBridges() {
		return bridges;
	}

	public void setCityModel(CityModel cModel) {
		this.cModel = cModel;
	}

	public CityModel getCityModel() {
		return cModel;
	}

	public List<TransportationObject> getTransportation() {
		return roads;
	}

	public List<WaterObject> getWater() {
		return water;
	}

	public List<LandObject> getLand() {
		return land;
	}

	public List<Vegetation> getVegetation() {
		return vegetation;
	}

	public void addWater(WaterObject wo) {
		water.add(wo);
	}

	public void addLand(LandObject lo) {
		land.add(lo);
	}

	public void addVegetation(Vegetation veg) {
		vegetation.add(veg);
	}

	public void addTransportation(TransportationObject to) {
		roads.add(to);
	}

	public int getNumberOfFeatures() {
		return buildings.size() + vegetation.size() + bridges.size() + land.size() + roads.size() + water.size();
	}

	public ParserConfiguration getParserConfig() {
		return config;
	}

	public void replaceFeature(CityObject currentFeature, CityObject nextFeature) {
		if (nextFeature instanceof Building) {
			replaceBuilding(currentFeature, nextFeature);
		} else if (nextFeature instanceof BridgeObject) {
			replaceBridge(currentFeature, nextFeature);
		} else if (nextFeature instanceof TransportationObject) {
			replaceTransportationObject(currentFeature, nextFeature);
		} else if (nextFeature instanceof Vegetation) {
			replaceVegetation(currentFeature, nextFeature);
		} else if (nextFeature instanceof LandObject) {
			replaceLandObject(currentFeature, nextFeature);
		} else if (nextFeature instanceof WaterObject) {
			replaceWaterObject(currentFeature, nextFeature);
		}
	}

	private void replaceWaterObject(CityObject currentFeature, CityObject nextFeature) {
		int index = water.indexOf(currentFeature);
		if (index == -1) {
			throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in water objects");
		}
		water.set(index, (WaterObject) nextFeature);
	}

	private void replaceLandObject(CityObject currentFeature, CityObject nextFeature) {
		int index = land.indexOf(currentFeature);
		if (index == -1) {
			throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in land objects");
		}
		land.set(index, (LandObject) nextFeature);
	}

	private void replaceVegetation(CityObject currentFeature, CityObject nextFeature) {
		int index = vegetation.indexOf(currentFeature);
		if (index == -1) {
			throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in vegetation");
		}
		vegetation.set(index, (Vegetation) nextFeature);
	}

	private void replaceTransportationObject(CityObject currentFeature, CityObject nextFeature) {
		int index = roads.indexOf(currentFeature);
		if (index == -1) {
			throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in transportation objects");
		}
		roads.set(index, (TransportationObject) nextFeature);
	}

	private void replaceBridge(CityObject currentFeature, CityObject nextFeature) {
		int index = bridges.indexOf(currentFeature);
		if (index == -1) {
			throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in bridges");
		}
		bridges.set(index, (BridgeObject) nextFeature);
	}

	private void replaceBuilding(CityObject currentFeature, CityObject nextFeature) {
		int index = buildings.indexOf(currentFeature);
		if (index == -1) {
			throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in buildings");
		}
		buildings.set(index, (Building) nextFeature);
	}
}
