/*-
 *  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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import org.citygml4j.core.ade.ADEException;
import org.citygml4j.core.model.core.AbstractFeatureProperty;
import org.citygml4j.core.model.core.CityModel;
import org.citygml4j.xml.writer.CityGMLWriteException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito;

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.CheckResult;
import de.hft.stuttgart.citydoctor2.check.ErrorId;
import de.hft.stuttgart.citydoctor2.check.ErrorReport;
import de.hft.stuttgart.citydoctor2.check.ErrorType;
import de.hft.stuttgart.citydoctor2.check.ErrorVisitor;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.exceptions.CityDoctorWriteException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.quality.model.properties.GlobalParametersProperty;
import de.hft.stuttgart.quality.model.properties.ParameterProperty;
import de.hft.stuttgart.quality.model.types.CityObjectProperties;
import de.hft.stuttgart.quality.model.types.GlobalParameters;
import de.hft.stuttgart.quality.model.types.Parameter;
import de.hft.stuttgart.quality.model.types.Validation;
import de.hft.stuttgart.quality.model.types.ValidationPlan;

public class CityDoctorModelTest {

	@Rule
	public TemporaryFolder folder = new TemporaryFolder();

	@Test(expected = IllegalArgumentException.class)
	public void testCityDoctorModelNoConfig() {
		new CityDoctorModel(null, null);
	}

	@Test
	public void testSetValidated() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		model.setValidated(Mockito.mock(ValidationPlan.class));
		assertTrue(model.isValidated());
	}

	@Test
	public void testAddGlobalError() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		CheckError err = Mockito.mock(CheckError.class);
		model.addGlobalError(err);
		assertEquals(err, model.getGlobalErrors().get(0));
	}

	@Test
	public void testAddGlobalErrors() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		CheckError err1 = Mockito.mock(CheckError.class);
		CheckError err2 = Mockito.mock(CheckError.class);
		List<CheckError> errors = new ArrayList<>();
		errors.add(err1);
		errors.add(err2);
		model.addGlobalErrors(errors);
		assertTrue(model.getGlobalErrors().contains(err1));
		assertTrue(model.getGlobalErrors().contains(err2));
	}

	@Test
	public void testGetFile() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		assertEquals(f, model.getFile());
	}

	@Test
	public void testCreateFeatureStream() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		Building b = Mockito.mock(Building.class);
		Vegetation v = Mockito.mock(Vegetation.class);
		model.addBuilding(b);
		model.addVegetation(v);
		assertTrue(model.createFeatureStream().anyMatch(c -> c == b));
		assertTrue(model.createFeatureStream().anyMatch(c -> c == v));
	}

	@Test
	public void testSaveAsWithoutValidation() throws CityGMLWriteException, ADEException,
			IOException, CityGmlParseException, InvalidGmlFileException, CityDoctorWriteException {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		CityModel cModel = new CityModel();
		model.setCityModel(cModel);
		Building b = new Building();
		b.addGeometry(GeometryTestUtils.createDummyGeometry(GeometryType.SOLID));
		org.citygml4j.core.model.building.Building gmlBuilding = new org.citygml4j.core.model.building.Building();
		gmlBuilding.setId("testId");
		b.setGmlObject(gmlBuilding);
		model.addBuilding(b);
		File saveFile = folder.newFile();
		model.saveAs(saveFile.getAbsolutePath());
		assertTrue(saveFile.exists());
		assertTrue(saveFile.length() > 0);

		CityDoctorModel parsedModel = CityGmlParser.parseCityGmlFile(saveFile.getAbsolutePath(), config);
		Building parsedBuilding = parsedModel.getBuildings().get(0);
		assertEquals("testId", parsedBuilding.getGmlId().getGmlString());
		assertEquals(1, parsedBuilding.getGeometries().size());
	}

	@Test
	public void testSaveAsWithValidation() throws CityGMLWriteException, ADEException,
			IOException, CityGmlParseException, InvalidGmlFileException, CityDoctorWriteException {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		CityModel cModel = new CityModel();
		ValidationPlan plan = new ValidationPlan();
		plan.setId("CD_PLAN_" + UUID.randomUUID().toString());
//		cModel.getFeatureMembers().add(new AbstractFeatureProperty(plan));
		GlobalParameters globParams = new GlobalParameters();
		plan.setGlobalParameters(new GlobalParametersProperty(globParams));
		plan.getGlobalParameters().getObject().getParameters().add(new ParameterProperty(new Parameter()));
		model.setValidated(plan);
		model.setCityModel(cModel);
		Building b = new Building();
		b.accept(Mockito.mock(Check.class));
		CheckError error = new CheckError() {
			private static final long serialVersionUID = -5620026648733746435L;

			@Override
			public ErrorType getType() {
				return ErrorType.ERROR;
			}

			@Override
			public ErrorId getErrorId() {
				return ErrorId.GE_P_HOLE_OUTSIDE;
			}

			@Override
			public GmlElement getFeature() {
				return null;
			}

			@Override
			public void accept(ErrorVisitor errorVisitor) {
			}

			@Override
			public boolean accept(HealingMethod method, ModificationListener l) {
				return false;
			}

			@Override
			public void report(ErrorReport report) {
			}
			
		};
		b.addCheckResult(new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.ERROR, error));
		b.addGeometry(GeometryTestUtils.createDummyGeometry(GeometryType.SOLID));
		org.citygml4j.core.model.building.Building gmlBuilding = new org.citygml4j.core.model.building.Building();
		gmlBuilding.setId("testId");
		b.setGmlObject(gmlBuilding);
		model.addBuilding(b);
		File saveFile = folder.newFile();
		model.saveAs(saveFile.getAbsolutePath());
		assertTrue(saveFile.exists());
		assertTrue(saveFile.length() > 0);

		CityDoctorModel parsedModel = CityGmlParser.parseCityGmlFile(saveFile.getAbsolutePath(), config);
		Building parsedBuilding = parsedModel.getBuildings().get(0);
		assertEquals("testId", parsedBuilding.getGmlId().getGmlString());
		assertEquals(1, parsedBuilding.getGeometries().size());
		List<CityObjectProperties> props = parsedBuilding.getGmlObject().getADEProperties(CityObjectProperties.class);
		assertEquals(1, props.size());
		List<AbstractFeatureProperty> featureMembers = parsedModel.getCityModel().getFeatureMembers();
		assertEquals(2, featureMembers.size());
		
		
		Validation val = (Validation) featureMembers.get(0).getObject();
		de.hft.stuttgart.quality.model.types.Error errorStatistics = val.getStatistics().getObject().getErrors().get(0).getObject();
		assertEquals(de.hft.stuttgart.quality.model.enums.ErrorId.GE_P_HOLE_OUTSIDE, errorStatistics.getName());
		assertEquals(1, errorStatistics.getOccurrences());
		
		ValidationPlan resultPlan = (ValidationPlan) featureMembers.get(1).getObject();
		assertEquals(val.getValidationPlan().getHref().substring(1), resultPlan.getId());
	}

	@Test
	public void testCollectErrors() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		Building b = new Building();
		model.addBuilding(b);
		CheckError error = Mockito.mock(CheckError.class);
		CheckResult result = new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.ERROR, error);
		b.addCheckResult(result);
		assertTrue(model.collectErrors().contains(error));
	}

	@Test
	public void testGetFileName() {
		File f = new File("test");
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		assertEquals("test", model.getFileName());
		assertEquals(f, model.getFile());
	}

	@Test
	public void testAddBridge() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		BridgeObject b = Mockito.mock(BridgeObject.class);
		model.addBridge(b);
		assertEquals(b, model.getBridges().get(0));
	}

	@Test
	public void testAddBuilding() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		Building b = Mockito.mock(Building.class);
		model.addBuilding(b);
		assertEquals(b, model.getBuildings().get(0));
	}

	@Test
	public void testSetCityModel() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		CityModel cModel = Mockito.mock(CityModel.class);
		model.setCityModel(cModel);
		assertEquals(cModel, model.getCityModel());
	}

	@Test
	public void testAddWater() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		WaterObject water = Mockito.mock(WaterObject.class);
		model.addWater(water);
		assertEquals(water, model.getWater().get(0));
	}

	@Test
	public void testAddLand() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		LandObject land = Mockito.mock(LandObject.class);
		model.addLand(land);
		assertEquals(land, model.getLand().get(0));
	}

	@Test
	public void testAddVegetation() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		Vegetation veg = Mockito.mock(Vegetation.class);
		model.addVegetation(veg);
		assertEquals(veg, model.getVegetation().get(0));
	}

	@Test
	public void testAddTransportation() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		TransportationObject trans = Mockito.mock(TransportationObject.class);
		model.addTransportation(trans);
		assertEquals(trans, model.getTransportation().get(0));
	}

	@Test
	public void testGetNumberOfFeatures() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		Building b = Mockito.mock(Building.class);
		TransportationObject trans = Mockito.mock(TransportationObject.class);
		model.addBuilding(b);
		model.addTransportation(trans);
		assertEquals(2, model.getNumberOfFeatures());
	}

	@Test
	public void testGetParserConfig() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		assertEquals(config, model.getParserConfig());
	}

	@Test
	public void testReplaceBuilding() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		Building b1 = Mockito.mock(Building.class);
		Building b2 = Mockito.mock(Building.class);
		model.addBuilding(b1);
		assertEquals(b1, model.getBuildings().get(0));
		model.replaceFeature(b1, b2);
		assertEquals(b2, model.getBuildings().get(0));
	}
	
	@Test(expected = IllegalStateException.class)
	public void testReplaceBuildingMissing() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		Building b1 = Mockito.mock(Building.class);
		Building b2 = Mockito.mock(Building.class);
		model.replaceFeature(b1, b2);
	}
	
	@Test
	public void testReplaceVegetation() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		Vegetation v1 = Mockito.mock(Vegetation.class);
		Vegetation v2 = Mockito.mock(Vegetation.class);
		model.addVegetation(v1);
		assertEquals(v1, model.getVegetation().get(0));
		model.replaceFeature(v1, v2);
		assertEquals(v2, model.getVegetation().get(0));
	}
	
	@Test(expected = IllegalStateException.class)
	public void testReplaceVegetationMissing() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		Vegetation v1 = Mockito.mock(Vegetation.class);
		Vegetation v2 = Mockito.mock(Vegetation.class);
		model.replaceFeature(v1, v2);
	}
	
	@Test
	public void testReplaceTransportation() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		TransportationObject t1 = Mockito.mock(TransportationObject.class);
		TransportationObject t2 = Mockito.mock(TransportationObject.class);
		model.addTransportation(t1);
		assertEquals(t1, model.getTransportation().get(0));
		model.replaceFeature(t1, t2);
		assertEquals(t2, model.getTransportation().get(0));
	}
	
	@Test(expected = IllegalStateException.class)
	public void testReplaceTransportationMissing() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		TransportationObject t1 = Mockito.mock(TransportationObject.class);
		TransportationObject t2 = Mockito.mock(TransportationObject.class);
		model.replaceFeature(t1, t2);
	}
	
	@Test
	public void testReplaceWater() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		WaterObject t1 = Mockito.mock(WaterObject.class);
		WaterObject t2 = Mockito.mock(WaterObject.class);
		model.addWater(t1);
		assertEquals(t1, model.getWater().get(0));
		model.replaceFeature(t1, t2);
		assertEquals(t2, model.getWater().get(0));
	}
	
	@Test(expected = IllegalStateException.class)
	public void testReplaceWaterMissing() {
		File f = Mockito.mock(File.class);
		ParserConfiguration config = new ParserConfiguration(8, false);
		CityDoctorModel model = new CityDoctorModel(config, f);
		WaterObject t1 = Mockito.mock(WaterObject.class);
		WaterObject t2 = Mockito.mock(WaterObject.class);
		model.replaceFeature(t1, t2);
	}

}
