/*-
 *  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.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.citygml4j.core.model.construction.WallSurface;
import org.citygml4j.core.model.core.AbstractSpaceBoundaryProperty;
import org.citygml4j.core.util.geometry.GeometryFactory;
import org.junit.Test;
import org.mockito.Mockito;
import org.xmlobjects.gml.model.geometry.aggregates.MultiSurfaceProperty;
import org.xmlobjects.gml.model.geometry.primitives.AbstractRing;
import org.xmlobjects.gml.model.geometry.primitives.AbstractSolid;
import org.xmlobjects.gml.model.geometry.primitives.AbstractSurface;
import org.xmlobjects.gml.model.geometry.primitives.Shell;
import org.xmlobjects.gml.model.geometry.primitives.Solid;
import org.xmlobjects.gml.model.geometry.primitives.SolidProperty;
import org.xmlobjects.gml.model.geometry.primitives.SurfaceProperty;

import de.hft.stuttgart.citydoctor2.check.AbstractCheck;
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.ResultStatus;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.Copyable;

public class AbstractBuildingTest {

	@Test
	public void testAccept() {
		AbstractBuilding ab = createAbstractBuilding();
		ab.addBoundarySurface(new BoundarySurface(null));
		ab.addBuildingInstallation(new BuildingInstallation());
		ab.addBuildingInstallation(new BuildingInstallation());
		AtomicInteger abCounter = new AtomicInteger(0);
		AtomicInteger bsCounter = new AtomicInteger(0);
		AtomicInteger biCounter = new AtomicInteger(0);
		AbstractCheck c = new AbstractCheck() {

			@Override
			public void check(AbstractBuilding ab) {
				abCounter.incrementAndGet();
			}

			@Override
			public void check(BoundarySurface bs) {
				bsCounter.incrementAndGet();
			}
		};
		ab.accept(c);
		assertEquals(1, abCounter.intValue());
		assertEquals(1, bsCounter.intValue());
		assertEquals(0, biCounter.intValue());

		AbstractCheck c2 = new AbstractCheck() {

			@Override
			public void check(BuildingInstallation bi) {
				biCounter.incrementAndGet();
			}
		};
		ab.accept(c2);
		assertEquals(2, biCounter.intValue());
	}

	@Test
	public void testPrepareForChecking() {
		BuildingInstallation biMock = mock(BuildingInstallation.class);
		BoundarySurface bsMock = mock(BoundarySurface.class);

		AbstractBuilding ab = createAbstractBuilding();
		ab.addBoundarySurface(bsMock);
		ab.addBuildingInstallation(biMock);
		ab.prepareForChecking();
		Mockito.verify(biMock, Mockito.times(1)).prepareForChecking();
		Mockito.verify(bsMock, Mockito.times(1)).prepareForChecking();
	}

	@Test
	public void testClearMetaInformation() {
		BuildingInstallation biMock = mock(BuildingInstallation.class);
		BoundarySurface bsMock = mock(BoundarySurface.class);

		AbstractBuilding ab = createAbstractBuilding();
		ab.addBoundarySurface(bsMock);
		ab.addBuildingInstallation(biMock);
		ab.clearMetaInformation();
		Mockito.verify(biMock, Mockito.times(1)).clearMetaInformation();
		Mockito.verify(bsMock, Mockito.times(1)).clearMetaInformation();
	}

	@Test
	public void testContainsError() {
		AbstractBuilding ab = createAbstractBuilding();
		assertFalse(ab.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
		ab.addCheckResult(new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.ERROR, mock(CheckError.class)));
		assertTrue(ab.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
	}

	@Test
	public void testContainsErrorInBs() {
		AbstractBuilding ab = createAbstractBuilding();
		BoundarySurface bsMock = mock(BoundarySurface.class);
		ab.addBoundarySurface(bsMock);
		assertFalse(ab.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
		Mockito.when(bsMock.containsError(CheckId.C_GE_P_HOLE_OUTSIDE)).thenReturn(true);
		assertTrue(ab.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
	}

	@Test
	public void testContainsErrorInBi() {
		AbstractBuilding ab = createAbstractBuilding();
		BuildingInstallation biMock = mock(BuildingInstallation.class);
		ab.addBuildingInstallation(biMock);
		assertFalse(ab.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
		Mockito.when(biMock.containsError(CheckId.C_GE_P_HOLE_OUTSIDE)).thenReturn(true);
		assertTrue(ab.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
	}

	@Test
	public void testClearAllContainedCheckResults() {
		BuildingInstallation biMock = mock(BuildingInstallation.class);
		BoundarySurface bsMock = mock(BoundarySurface.class);

		AbstractBuilding ab = createAbstractBuilding();
		ab.addCheckResult(new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.OK, null));
		ab.addBoundarySurface(bsMock);
		ab.addBuildingInstallation(biMock);

		assertEquals(1, ab.getAllCheckResults().size());
		ab.clearAllContainedCheckResults();
		Mockito.verify(biMock, Mockito.times(1)).clearAllContainedCheckResults();
		Mockito.verify(bsMock, Mockito.times(1)).clearAllContainedCheckResults();
		assertTrue(ab.getAllCheckResults().isEmpty());
	}

	@Test
	public void testContainsAnyError() {
		AbstractBuilding ab = createAbstractBuilding();
		assertFalse(ab.containsAnyError());
		ab.addCheckResult(new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.OK, null));
		assertFalse(ab.containsAnyError());
		ab.addCheckResult(
				new CheckResult(CheckId.C_GE_P_INNER_RINGS_NESTED, ResultStatus.ERROR, mock(CheckError.class)));
		assertTrue(ab.containsAnyError());
	}

	@Test
	public void testContainsAnyErrorInBs() {
		AbstractBuilding ab = createAbstractBuilding();
		BoundarySurface bsMock = mock(BoundarySurface.class);
		ab.addBoundarySurface(bsMock);
		assertFalse(ab.containsAnyError());
		Mockito.when(bsMock.containsAnyError()).thenReturn(true);
		assertTrue(ab.containsAnyError());
	}

	@Test
	public void testContainsAnyErrorInBi() {
		AbstractBuilding ab = createAbstractBuilding();
		BuildingInstallation biMock = mock(BuildingInstallation.class);
		ab.addBuildingInstallation(biMock);
		assertFalse(ab.containsAnyError());
		Mockito.when(biMock.containsAnyError()).thenReturn(true);
		assertTrue(ab.containsAnyError());
	}

	@Test
	public void testCollectContainedErrors() {
		BuildingInstallation biMock = mock(BuildingInstallation.class);
		BoundarySurface bsMock = mock(BoundarySurface.class);

		AbstractBuilding ab = createAbstractBuilding();
		ab.addBoundarySurface(bsMock);
		ab.addBuildingInstallation(biMock);
		ab.addCheckResult(new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.ERROR, mock(CheckError.class)));
		List<CheckError> errors = new ArrayList<>();
		ab.collectContainedErrors(errors);
		Mockito.verify(biMock, Mockito.times(1)).collectContainedErrors(errors);
		Mockito.verify(bsMock, Mockito.times(1)).collectContainedErrors(errors);
		assertEquals(1, errors.size());
	}

	@Test
	public void testReCreateGeometriesSolid() {
		Geometry geom = GeometryTestUtils.createDummyGeometry(GeometryType.SOLID);

		AbstractBuilding ab = createAbstractBuilding();
		org.citygml4j.core.model.building.AbstractBuilding gmlAb = new org.citygml4j.core.model.building.Building();
		ab.setCityGmlBuilding(gmlAb);
		ab.addGeometry(geom);
		GeometryFactory factory = GeometryFactory.newInstance();
		ParserConfiguration config = new ParserConfiguration(8, false);
		ab.reCreateGeometries(factory, config);
		AbstractSolid aSolid = gmlAb.getLod2Solid().getObject();
		assertTrue(aSolid instanceof Solid);
		Solid solid = (Solid) aSolid;
		Shell geometry = solid.getExterior().getObject();
		List<SurfaceProperty> surfaceMember = geometry.getSurfaceMembers();
		assertEquals(1, surfaceMember.size());
		AbstractSurface polySurface = surfaceMember.get(0).getObject();
		assertTrue(polySurface instanceof org.xmlobjects.gml.model.geometry.primitives.Polygon);
		var gmlPoly = (org.xmlobjects.gml.model.geometry.primitives.Polygon) polySurface;
		AbstractRing ring = gmlPoly.getExterior().getObject();
		var gmlRing = (org.xmlobjects.gml.model.geometry.primitives.LinearRing) ring;
		List<Double> posList = gmlRing.toCoordinateList3D();
		double[] expectedValues = new double[] { 427583.301, 6003502.571, 9.711, 427583.304, 6003502.574, 9.713,
				427583.304, 6003502.574, 4.097, 427583.301, 6003502.571, 4.097, 427583.301, 6003502.571, 9.711 };
		for (int i = 0; i < posList.size(); i++) {
			assertEquals(expectedValues[i], posList.get(i), 0.00000001);
		}
	}

	@Test
	public void testReCreateGeometriesMultiSurface() {
		Geometry geom = GeometryTestUtils.createDummyGeometry(GeometryType.MULTI_SURFACE);

		AbstractBuilding ab = createAbstractBuilding();
		org.citygml4j.core.model.building.AbstractBuilding gmlAb = new org.citygml4j.core.model.building.Building();
		ab.setCityGmlBuilding(gmlAb);
		ab.addGeometry(geom);
		GeometryFactory factory = GeometryFactory.newInstance();
		ParserConfiguration config = new ParserConfiguration(8, false);
		ab.reCreateGeometries(factory, config);
		assertNotNull(gmlAb.getLod2MultiSurface());
		assertNotNull(gmlAb.getLod2MultiSurface().getObject());
	}

	@Test
	public void testReCreateGeometriesBs() {
		BoundarySurface bsMock = mock(BoundarySurface.class);
		AbstractBuilding ab = createAbstractBuilding();
		List<Geometry> geometries = new ArrayList<>();
		geometries.add(GeometryTestUtils.createDummyGeometry(GeometryType.MULTI_SURFACE));
		Mockito.when(bsMock.getGeometries()).thenReturn(geometries);
		ab.addBoundarySurface(bsMock);
		org.citygml4j.core.model.building.AbstractBuilding gmlAb = new org.citygml4j.core.model.building.Building();
		ab.setCityGmlBuilding(gmlAb);
		GeometryFactory factory = GeometryFactory.newInstance();
		ParserConfiguration config = new ParserConfiguration(8, false);
		ab.reCreateGeometries(factory, config);
		Mockito.verify(bsMock).reCreateGeometries(factory, config);
	}

	@Test
	public void testReCreateGeometriesEmptyBs() {
		WallSurface ws = new WallSurface();
		BoundarySurface bsMock = mock(BoundarySurface.class);
		Mockito.when(bsMock.getGmlObject()).thenReturn(ws);
		AbstractBuilding ab = createAbstractBuilding();
		ab.addBoundarySurface(bsMock);
		GeometryFactory factory = GeometryFactory.newInstance();
		ParserConfiguration config = new ParserConfiguration(8, false);
		org.citygml4j.core.model.building.AbstractBuilding gmlAb = new org.citygml4j.core.model.building.Building();
		ab.setCityGmlBuilding(gmlAb);
		ab.reCreateGeometries(factory, config);
		gmlAb.addBoundary(new AbstractSpaceBoundaryProperty());
		gmlAb.addBoundary(new AbstractSpaceBoundaryProperty(new WallSurface()));
		gmlAb.addBoundary(new AbstractSpaceBoundaryProperty(ws));
		assertEquals(3, gmlAb.getBoundaries().size());
		ab.reCreateGeometries(factory, config);
		assertEquals(2, gmlAb.getBoundaries().size());
	}

	@Test
	public void testReCreateGeometriesEmptyBi() {
		BuildingInstallation biMock = mock(BuildingInstallation.class);
		AbstractBuilding ab = createAbstractBuilding();
		ab.addBuildingInstallation(biMock);
		GeometryFactory factory = GeometryFactory.newInstance();
		ParserConfiguration config = new ParserConfiguration(8, false);
		org.citygml4j.core.model.building.AbstractBuilding gmlAb = new org.citygml4j.core.model.building.Building();
		ab.setCityGmlBuilding(gmlAb);
		ab.reCreateGeometries(factory, config);
		Mockito.verify(biMock).reCreateGeometries(factory, config);
	}

	@Test
	public void testUnsetGmlGeometries() {
		BuildingInstallation biMock = mock(BuildingInstallation.class);
		BoundarySurface bsMock = mock(BoundarySurface.class);

		AbstractBuilding ab = createAbstractBuilding();
		ab.addBoundarySurface(bsMock);
		ab.addBuildingInstallation(biMock);
		org.citygml4j.core.model.building.AbstractBuilding gmlAb = new org.citygml4j.core.model.building.Building();
		gmlAb.setLod1Solid(new SolidProperty());
		gmlAb.setLod2Solid(new SolidProperty());
		gmlAb.setLod3Solid(new SolidProperty());
		gmlAb.getDeprecatedProperties().setLod4Solid(new SolidProperty());
		gmlAb.getDeprecatedProperties().setLod1MultiSurface(new MultiSurfaceProperty());
		gmlAb.setLod2MultiSurface(new MultiSurfaceProperty());
		gmlAb.setLod3MultiSurface(new MultiSurfaceProperty());
		gmlAb.getDeprecatedProperties().setLod4MultiSurface(new MultiSurfaceProperty());
		ab.setCityGmlBuilding(gmlAb);
		ab.unsetGmlGeometries();
		Mockito.verify(biMock).unsetGmlGeometries();
		Mockito.verify(bsMock).unsetGmlGeometries();
		assertNull(gmlAb.getLod1Solid());
		assertNull(gmlAb.getLod2Solid());
		assertNull(gmlAb.getLod3Solid());
		assertNull(gmlAb.getDeprecatedProperties().getLod4Solid());
		assertNull(gmlAb.getDeprecatedProperties().getLod1MultiSurface());
		assertNull(gmlAb.getLod2MultiSurface());
		assertNull(gmlAb.getLod3MultiSurface());
		assertNull(gmlAb.getDeprecatedProperties().getLod4MultiSurface());
	}

	@Test
	public void testGetFeatureType() {
		AbstractBuilding ab = createAbstractBuilding();
		assertEquals(FeatureType.BUILDING, ab.getFeatureType());
	}

	private AbstractBuilding createAbstractBuilding() {
		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;

			@Override
			public Copyable createCopyInstance() {
				return null;
			}

		};
		return ab;
	}

}
