/*-
 *  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 java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import org.citygml4j.factory.GMLGeometryFactory;
import org.citygml4j.model.citygml.building.BoundarySurfaceProperty;
import org.citygml4j.model.citygml.building.WallSurface;
import org.citygml4j.model.gml.geometry.aggregates.MultiSurfaceProperty;
import org.citygml4j.model.gml.geometry.complexes.CompositeSurface;
import org.citygml4j.model.gml.geometry.primitives.AbstractRing;
import org.citygml4j.model.gml.geometry.primitives.AbstractSolid;
import org.citygml4j.model.gml.geometry.primitives.AbstractSurface;
import org.citygml4j.model.gml.geometry.primitives.DirectPositionList;
import org.citygml4j.model.gml.geometry.primitives.Polygon;
import org.citygml4j.model.gml.geometry.primitives.Solid;
import org.citygml4j.model.gml.geometry.primitives.SolidProperty;
import org.citygml4j.model.gml.geometry.primitives.SurfaceProperty;
import org.junit.Test;
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.Requirement;
import de.hft.stuttgart.citydoctor2.check.RequirementType;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;

public class AbstractBuildingTest {

	@Test
	public void testAccept() {
		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -2487075796550827114L;
		};
		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);
		Check c = new Check() {

			@Override
			public Set<Requirement> appliesToRequirements() {
				return null;
			}

			@Override
			public CheckId getCheckId() {
				return null;
			}

			@Override
			public RequirementType getType() {
				return null;
			}

			@Override
			public Check createNewInstance() {
				return null;
			}

			@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());

		Check c2 = new Check() {

			@Override
			public Set<Requirement> appliesToRequirements() {
				return null;
			}

			@Override
			public CheckId getCheckId() {
				return null;
			}

			@Override
			public RequirementType getType() {
				return null;
			}

			@Override
			public Check createNewInstance() {
				return null;
			}

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

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

		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		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 = Mockito.mock(BuildingInstallation.class);
		BoundarySurface bsMock = Mockito.mock(BoundarySurface.class);

		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		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 = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		assertFalse(ab.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
		ab.addCheckResult(
				new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.ERROR, Mockito.mock(CheckError.class)));
		assertTrue(ab.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
	}

	@Test
	public void testContainsErrorInBs() {
		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		BoundarySurface bsMock = Mockito.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 = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		BuildingInstallation biMock = Mockito.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 = Mockito.mock(BuildingInstallation.class);
		BoundarySurface bsMock = Mockito.mock(BoundarySurface.class);

		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		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 = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		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, Mockito.mock(CheckError.class)));
		assertTrue(ab.containsAnyError());
	}

	@Test
	public void testContainsAnyErrorInBs() {
		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		BoundarySurface bsMock = Mockito.mock(BoundarySurface.class);
		ab.addBoundarySurface(bsMock);
		assertFalse(ab.containsAnyError());
		Mockito.when(bsMock.containsAnyError()).thenReturn(true);
		assertTrue(ab.containsAnyError());
	}

	@Test
	public void testContainsAnyErrorInBi() {
		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		BuildingInstallation biMock = Mockito.mock(BuildingInstallation.class);
		ab.addBuildingInstallation(biMock);
		assertFalse(ab.containsAnyError());
		Mockito.when(biMock.containsAnyError()).thenReturn(true);
		assertTrue(ab.containsAnyError());
	}

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

		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		ab.addBoundarySurface(bsMock);
		ab.addBuildingInstallation(biMock);
		ab.addCheckResult(
				new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.ERROR, Mockito.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 = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		org.citygml4j.model.citygml.building.AbstractBuilding gmlAb = new org.citygml4j.model.citygml.building.Building();
		ab.setCityGmlBuilding(gmlAb);
		ab.addGeometry(geom);
		GMLGeometryFactory factory = new GMLGeometryFactory();
		ParserConfiguration config = new ParserConfiguration(8, false);
		ab.reCreateGeometries(factory, config);
		AbstractSolid aSolid = gmlAb.getLod2Solid().getSolid();
		assertTrue(aSolid instanceof Solid);
		Solid solid = (Solid) aSolid;
		AbstractSurface geometry = solid.getExterior().getGeometry();
		assertTrue(geometry instanceof CompositeSurface);
		CompositeSurface cSurface = (CompositeSurface) geometry;
		List<SurfaceProperty> surfaceMember = cSurface.getSurfaceMember();
		assertEquals(1, surfaceMember.size());
		AbstractSurface polySurface = surfaceMember.get(0).getSurface();
		assertTrue(polySurface instanceof org.citygml4j.model.gml.geometry.primitives.Polygon);
		org.citygml4j.model.gml.geometry.primitives.Polygon gmlPoly = (Polygon) polySurface;
		AbstractRing ring = gmlPoly.getExterior().getRing();
		org.citygml4j.model.gml.geometry.primitives.LinearRing gmlRing = (org.citygml4j.model.gml.geometry.primitives.LinearRing) ring;
		DirectPositionList posList = gmlRing.getPosList();
		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};
		List<Double> values = posList.getValue();
		for (int i = 0; i < values.size(); i++) {
			assertEquals(expectedValues[i], values.get(i), 0.00000001);
		}
	}

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

		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		org.citygml4j.model.citygml.building.AbstractBuilding gmlAb = new org.citygml4j.model.citygml.building.Building();
		ab.setCityGmlBuilding(gmlAb);
		ab.addGeometry(geom);
		GMLGeometryFactory factory = new GMLGeometryFactory();
		ParserConfiguration config = new ParserConfiguration(8, false);
		ab.reCreateGeometries(factory, config);
		assertNotNull(gmlAb.getLod2MultiSurface());
		assertNotNull(gmlAb.getLod2MultiSurface().getGeometry());
	}

	@Test
	public void testReCreateGeometriesBs() {
		BoundarySurface bsMock = Mockito.mock(BoundarySurface.class);
		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		List<Geometry> geometries = new ArrayList<>();
		geometries.add(GeometryTestUtils.createDummyGeometry(GeometryType.MULTI_SURFACE));
		Mockito.when(bsMock.getGeometries()).thenReturn(geometries);
		ab.addBoundarySurface(bsMock);
		org.citygml4j.model.citygml.building.AbstractBuilding gmlAb = new org.citygml4j.model.citygml.building.Building();
		ab.setCityGmlBuilding(gmlAb);
		GMLGeometryFactory factory = new GMLGeometryFactory();
		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 = Mockito.mock(BoundarySurface.class);
		Mockito.when(bsMock.getGmlObject()).thenReturn(ws);
		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		ab.addBoundarySurface(bsMock);
		GMLGeometryFactory factory = new GMLGeometryFactory();
		ParserConfiguration config = new ParserConfiguration(8, false);
		org.citygml4j.model.citygml.building.AbstractBuilding gmlAb = new org.citygml4j.model.citygml.building.Building();
		ab.setCityGmlBuilding(gmlAb);
		ab.reCreateGeometries(factory, config);
		gmlAb.addBoundedBySurface(new BoundarySurfaceProperty());
		gmlAb.addBoundedBySurface(new BoundarySurfaceProperty(new WallSurface()));
		gmlAb.addBoundedBySurface(new BoundarySurfaceProperty(ws));
		assertEquals(3, gmlAb.getBoundedBySurface().size());
		ab.reCreateGeometries(factory, config);
		assertEquals(2, gmlAb.getBoundedBySurface().size());
	}
	
	@Test
	public void testReCreateGeometriesEmptyBi() {
		BuildingInstallation biMock = Mockito.mock(BuildingInstallation.class);
		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		ab.addBuildingInstallation(biMock);
		GMLGeometryFactory factory = new GMLGeometryFactory();
		ParserConfiguration config = new ParserConfiguration(8, false);
		org.citygml4j.model.citygml.building.AbstractBuilding gmlAb = new org.citygml4j.model.citygml.building.Building();
		ab.setCityGmlBuilding(gmlAb);
		ab.reCreateGeometries(factory, config);
		Mockito.verify(biMock).reCreateGeometries(factory, config);
	}
	
	@Test
	public void testUnsetGmlGeometries() {
		BuildingInstallation biMock = Mockito.mock(BuildingInstallation.class);
		BoundarySurface bsMock = Mockito.mock(BoundarySurface.class);

		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		ab.addBoundarySurface(bsMock);
		ab.addBuildingInstallation(biMock);
		org.citygml4j.model.citygml.building.AbstractBuilding gmlAb = new org.citygml4j.model.citygml.building.Building();
		gmlAb.setLod1Solid(new SolidProperty());
		gmlAb.setLod2Solid(new SolidProperty());
		gmlAb.setLod3Solid(new SolidProperty());
		gmlAb.setLod4Solid(new SolidProperty());
		gmlAb.setLod1MultiSurface(new MultiSurfaceProperty());
		gmlAb.setLod2MultiSurface(new MultiSurfaceProperty());
		gmlAb.setLod3MultiSurface(new MultiSurfaceProperty());
		gmlAb.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.getLod4Solid());
		assertNull(gmlAb.getLod1MultiSurface());
		assertNull(gmlAb.getLod2MultiSurface());
		assertNull(gmlAb.getLod3MultiSurface());
		assertNull(gmlAb.getLod4MultiSurface());
	}

	@Test
	public void testGetFeatureType() {
		AbstractBuilding ab = new AbstractBuilding() {
			private static final long serialVersionUID = -448362592456318541L;
		};
		assertEquals(FeatureType.BUILDING, ab.getFeatureType());
	}

}
