/*-
 *  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.concurrent.atomic.AtomicInteger;

import org.citygml4j.factory.GMLGeometryFactory;
import org.citygml4j.model.citygml.building.WallSurface;
import org.citygml4j.model.citygml.core.AbstractCityObject;
import org.citygml4j.model.gml.geometry.aggregates.MultiSurfaceProperty;
import org.junit.Test;
import org.mockito.Mockito;

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.test.util.AbstractCheck;

public class BoundarySurfaceTest {

	@Test
	public void testAccept() {
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, null);
		Opening oMock = Mockito.mock(Opening.class);
		bs.addOpening(oMock);
		AtomicInteger bsCounter = new AtomicInteger(0);
		AbstractCheck c = new AbstractCheck() {

			@Override
			public void check(BoundarySurface bs) {
				bsCounter.incrementAndGet();
			}
		};
		bs.accept(c);
		Mockito.verify(oMock).accept(c);
		assertEquals(1, bsCounter.intValue());
	}

	@Test
	public void testAcceptWithoutFittingCheck() {
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, null);
		Opening oMock = Mockito.mock(Opening.class);
		bs.addOpening(oMock);
		AtomicInteger bsCounter = new AtomicInteger(0);
		AbstractCheck c = new AbstractCheck() {

			@Override
			public void check(BridgeObject bo) {
				bsCounter.incrementAndGet();
			}
		};
		bs.accept(c);
		Mockito.verify(oMock).accept(c);
		assertEquals(0, bsCounter.intValue());
	}

	@Test
	public void testPrepareForChecking() {
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, null);
		Opening oMock = Mockito.mock(Opening.class);
		bs.addOpening(oMock);
		bs.prepareForChecking();
		Mockito.verify(oMock).prepareForChecking();
	}

	@Test
	public void testClearMetaInformation() {
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, null);
		Opening oMock = Mockito.mock(Opening.class);
		bs.addOpening(oMock);
		bs.clearMetaInformation();
		Mockito.verify(oMock).clearMetaInformation();
	}

	@Test
	public void testContainsError() {
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, null);
		assertFalse(bs.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
		bs.addCheckResult(
				new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.ERROR, Mockito.mock(CheckError.class)));
		assertTrue(bs.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
		assertFalse(bs.containsError(CheckId.C_GE_P_INNER_RINGS_NESTED));
	}

	@Test
	public void testContainsErrorInOpening() {
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, null);
		assertFalse(bs.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
		Opening o = new Opening(OpeningType.WINDOW, SurfaceFeatureType.BUILDING, bs, null);
		bs.addOpening(o);
		o.addCheckResult(
				new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.ERROR, Mockito.mock(CheckError.class)));
		assertTrue(bs.containsError(CheckId.C_GE_P_HOLE_OUTSIDE));
		assertFalse(bs.containsError(CheckId.C_GE_P_INNER_RINGS_NESTED));
	}

	@Test
	public void testClearAllContainedCheckResults() {
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, null);
		Opening oMock = Mockito.mock(Opening.class);
		bs.addCheckResult(
				new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.ERROR, Mockito.mock(CheckError.class)));
		assertFalse(bs.getAllCheckResults().isEmpty());
		bs.addOpening(oMock);
		bs.clearAllContainedCheckResults();
		Mockito.verify(oMock).clearAllContainedCheckResults();
		assertTrue(bs.getAllCheckResults().isEmpty());
	}

	@Test
	public void testContainsAnyError() {
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, null);
		Opening oMock = Mockito.mock(Opening.class);
		bs.addOpening(oMock);
		assertFalse(bs.containsAnyError());
		Mockito.verify(oMock).containsAnyError();
		bs.addCheckResult(
				new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.ERROR, Mockito.mock(CheckError.class)));
		assertTrue(bs.containsAnyError());
	}

	@Test
	public void testContainsAnyErrorInOpeing() {
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, null);
		Opening oMock = Mockito.mock(Opening.class);
		bs.addOpening(oMock);
		Mockito.when(oMock.containsAnyError()).thenReturn(true);
		assertTrue(bs.containsAnyError());
	}

	@Test
	public void testCollectContainedErrors() {
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, null);
		Opening oMock = Mockito.mock(Opening.class);
		bs.addCheckResult(
				new CheckResult(CheckId.C_GE_P_HOLE_OUTSIDE, ResultStatus.ERROR, Mockito.mock(CheckError.class)));
		bs.addOpening(oMock);

		List<CheckError> errors = new ArrayList<>();
		bs.collectContainedErrors(errors);
		assertEquals(1, errors.size());
		Mockito.verify(oMock).collectContainedErrors(errors);
	}

	@Test
	public void testReCreateGeometriesWithoutId() {
		WallSurface ws = new WallSurface();
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, ws);
		bs.reCreateGeometries(new GMLGeometryFactory(), new ParserConfiguration(8, false));
		assertNotNull(ws.getId());
	}

	@Test
	public void testReCreateGeometriesWithId() {
		WallSurface ws = new WallSurface();
		ws.setId("test");
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, ws);
		bs.reCreateGeometries(new GMLGeometryFactory(), new ParserConfiguration(8, false));
		// nothing has changed
		assertEquals("test", ws.getId());
		assertNull(ws.getLod2MultiSurface());
		assertNull(ws.getLod3MultiSurface());
		assertNull(ws.getLod4MultiSurface());
	}

	@Test
	public void testReCreateGeometriesMultiSurfaceLod2() {
		WallSurface ws = new WallSurface();
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, ws);
		bs.addGeometry(GeometryTestUtils.createDummyGeometry(GeometryType.MULTI_SURFACE));
		bs.reCreateGeometries(new GMLGeometryFactory(), new ParserConfiguration(8, false));
		assertNotNull(ws.getLod2MultiSurface());
		assertNull(ws.getLod3MultiSurface());
		assertNull(ws.getLod4MultiSurface());
	}

	@Test
	public void testReCreateGeometriesMultiSurfaceBridgeLod2() {
		org.citygml4j.model.citygml.bridge.WallSurface ws = new org.citygml4j.model.citygml.bridge.WallSurface();
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BRIDGE, BoundarySurfaceType.WALL, ws);
		bs.addGeometry(GeometryTestUtils.createDummyGeometry(GeometryType.MULTI_SURFACE));
		bs.reCreateGeometries(new GMLGeometryFactory(), new ParserConfiguration(8, false));
		assertNotNull(ws.getLod2MultiSurface());
		assertNull(ws.getLod3MultiSurface());
		assertNull(ws.getLod4MultiSurface());
	}

	@Test
	public void testReCreateGeometriesMultiSurfaceBridgeLod3() {
		org.citygml4j.model.citygml.bridge.WallSurface ws = new org.citygml4j.model.citygml.bridge.WallSurface();
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BRIDGE, BoundarySurfaceType.WALL, ws);
		bs.addGeometry(GeometryTestUtils.createDummyGeometry(GeometryType.MULTI_SURFACE, Lod.LOD3));
		bs.reCreateGeometries(new GMLGeometryFactory(), new ParserConfiguration(8, false));
		assertNull(ws.getLod2MultiSurface());
		assertNotNull(ws.getLod3MultiSurface());
		assertNull(ws.getLod4MultiSurface());
	}

	@Test
	public void testReCreateGeometriesMultiSurfaceBridgeLod4() {
		org.citygml4j.model.citygml.bridge.WallSurface ws = new org.citygml4j.model.citygml.bridge.WallSurface();
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BRIDGE, BoundarySurfaceType.WALL, ws);
		bs.addGeometry(GeometryTestUtils.createDummyGeometry(GeometryType.MULTI_SURFACE, Lod.LOD4));
		bs.reCreateGeometries(new GMLGeometryFactory(), new ParserConfiguration(8, false));
		assertNull(ws.getLod2MultiSurface());
		assertNull(ws.getLod3MultiSurface());
		assertNotNull(ws.getLod4MultiSurface());
	}

	@Test
	public void testReCreateGeometriesMultiSurfaceTunnelLod2() {
		org.citygml4j.model.citygml.tunnel.WallSurface ws = new org.citygml4j.model.citygml.tunnel.WallSurface();
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.TUNNEL, BoundarySurfaceType.WALL, ws);
		bs.addGeometry(GeometryTestUtils.createDummyGeometry(GeometryType.MULTI_SURFACE));
		bs.reCreateGeometries(new GMLGeometryFactory(), new ParserConfiguration(8, false));
		assertNotNull(ws.getLod2MultiSurface());
		assertNull(ws.getLod3MultiSurface());
		assertNull(ws.getLod4MultiSurface());
	}

	@Test
	public void testReCreateGeometriesMultiSurfaceTunnelLod3() {
		org.citygml4j.model.citygml.tunnel.WallSurface ws = new org.citygml4j.model.citygml.tunnel.WallSurface();
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.TUNNEL, BoundarySurfaceType.WALL, ws);
		bs.addGeometry(GeometryTestUtils.createDummyGeometry(GeometryType.MULTI_SURFACE, Lod.LOD3));
		bs.reCreateGeometries(new GMLGeometryFactory(), new ParserConfiguration(8, false));
		assertNull(ws.getLod2MultiSurface());
		assertNotNull(ws.getLod3MultiSurface());
		assertNull(ws.getLod4MultiSurface());
	}

	@Test
	public void testReCreateGeometriesMultiSurfaceTunnelLod4() {
		org.citygml4j.model.citygml.tunnel.WallSurface ws = new org.citygml4j.model.citygml.tunnel.WallSurface();
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.TUNNEL, BoundarySurfaceType.WALL, ws);
		bs.addGeometry(GeometryTestUtils.createDummyGeometry(GeometryType.MULTI_SURFACE, Lod.LOD4));
		bs.reCreateGeometries(new GMLGeometryFactory(), new ParserConfiguration(8, false));
		assertNull(ws.getLod2MultiSurface());
		assertNull(ws.getLod3MultiSurface());
		assertNotNull(ws.getLod4MultiSurface());
	}

	@Test
	public void testReCreateGeometriesMultiSurfaceLod3() {
		WallSurface ws = new WallSurface();
		ws.setId("test");
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, ws);
		bs.addGeometry(GeometryTestUtils.createDummyGeometry(GeometryType.MULTI_SURFACE, Lod.LOD3));
		bs.reCreateGeometries(new GMLGeometryFactory(), new ParserConfiguration(8, false));
		assertNull(ws.getLod2MultiSurface());
		assertNotNull(ws.getLod3MultiSurface());
		assertNull(ws.getLod4MultiSurface());
	}

	@Test
	public void testReCreateGeometriesMultiSurfaceLod4() {
		WallSurface ws = new WallSurface();
		ws.setId("test");
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, ws);
		bs.addGeometry(GeometryTestUtils.createDummyGeometry(GeometryType.MULTI_SURFACE, Lod.LOD4));
		bs.reCreateGeometries(new GMLGeometryFactory(), new ParserConfiguration(8, false));
		assertNull(ws.getLod2MultiSurface());
		assertNull(ws.getLod3MultiSurface());
		assertNotNull(ws.getLod4MultiSurface());
	}

	@Test(expected = IllegalStateException.class)
	public void testReCreateGeometriesMultiSurfaceLod1() {
		WallSurface ws = new WallSurface();
		ws.setId("test");
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, ws);
		bs.addGeometry(GeometryTestUtils.createDummyGeometry(GeometryType.MULTI_SURFACE, Lod.LOD1));
		bs.reCreateGeometries(new GMLGeometryFactory(), new ParserConfiguration(8, false));
	}

	@Test(expected = IllegalStateException.class)
	public void testReCreateGeometriesSolid() {
		WallSurface ws = new WallSurface();
		ws.setId("test");
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, ws);
		bs.addGeometry(GeometryTestUtils.createDummyGeometry(GeometryType.SOLID));
		bs.reCreateGeometries(new GMLGeometryFactory(), new ParserConfiguration(8, false));
	}

	@Test
	public void testReCreateGeometriesOpenings() {
		WallSurface ws = new WallSurface();
		ws.setId("test");
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, ws);
		Opening oMock = Mockito.mock(Opening.class);
		bs.addOpening(oMock);
		GMLGeometryFactory factory = new GMLGeometryFactory();
		ParserConfiguration config = new ParserConfiguration(8, false);
		bs.reCreateGeometries(factory, config);
		Mockito.verify(oMock).reCreateGeometries(factory, config);
	}

	@Test
	public void testGetGmlObject() {
		WallSurface ws = new WallSurface();
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, ws);
		assertEquals(ws, bs.getGmlObject());
	}

	@Test
	public void testUnsetGmlGeometriesBuilding() {
		WallSurface ws = new WallSurface();
		ws.setLod2MultiSurface(new MultiSurfaceProperty());
		ws.setLod3MultiSurface(new MultiSurfaceProperty());
		ws.setLod4MultiSurface(new MultiSurfaceProperty());
		assertNotNull(ws.getLod2MultiSurface());
		assertNotNull(ws.getLod3MultiSurface());
		assertNotNull(ws.getLod4MultiSurface());
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BUILDING, BoundarySurfaceType.WALL, ws);
		Opening oMock = Mockito.mock(Opening.class);
		bs.addOpening(oMock);
		bs.unsetGmlGeometries();
		Mockito.verify(oMock).unsetGmlGeometries();
		assertNull(ws.getLod2MultiSurface());
		assertNull(ws.getLod3MultiSurface());
		assertNull(ws.getLod4MultiSurface());
	}

	@Test
	public void testUnsetGmlGeometriesBridge() {
		org.citygml4j.model.citygml.bridge.WallSurface ws = new org.citygml4j.model.citygml.bridge.WallSurface();
		ws.setLod2MultiSurface(new MultiSurfaceProperty());
		ws.setLod3MultiSurface(new MultiSurfaceProperty());
		ws.setLod4MultiSurface(new MultiSurfaceProperty());
		assertNotNull(ws.getLod2MultiSurface());
		assertNotNull(ws.getLod3MultiSurface());
		assertNotNull(ws.getLod4MultiSurface());
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.BRIDGE, BoundarySurfaceType.WALL, ws);
		bs.unsetGmlGeometries();
		assertNull(ws.getLod2MultiSurface());
		assertNull(ws.getLod3MultiSurface());
		assertNull(ws.getLod4MultiSurface());
	}

	@Test
	public void testUnsetGmlGeometriesTunnel() {
		org.citygml4j.model.citygml.tunnel.WallSurface ws = new org.citygml4j.model.citygml.tunnel.WallSurface();
		ws.setLod2MultiSurface(new MultiSurfaceProperty());
		ws.setLod3MultiSurface(new MultiSurfaceProperty());
		ws.setLod4MultiSurface(new MultiSurfaceProperty());
		assertNotNull(ws.getLod2MultiSurface());
		assertNotNull(ws.getLod3MultiSurface());
		assertNotNull(ws.getLod4MultiSurface());
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.TUNNEL, BoundarySurfaceType.WALL, ws);
		bs.unsetGmlGeometries();
		assertNull(ws.getLod2MultiSurface());
		assertNull(ws.getLod3MultiSurface());
		assertNull(ws.getLod4MultiSurface());
	}

	@Test(expected = UnsupportedOperationException.class)
	public void testCopy() {
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.TUNNEL, BoundarySurfaceType.WALL, null);
		bs.copy();
	}

	@Test
	public void testGetFeatureType() {
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.TUNNEL, BoundarySurfaceType.WALL, null);
		assertEquals(FeatureType.BOUNDARY_SURFACE, bs.getFeatureType());
	}

	@Test
	public void testBoundarySurfaceAbstractCityObject() {
		AbstractCityObject aco = Mockito.mock(AbstractCityObject.class);
		BoundarySurface bs = new BoundarySurface(aco);
		assertEquals(aco, bs.getGmlObject());
	}
	
	@Test
	public void testSetFeatureType() {
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.TUNNEL, BoundarySurfaceType.WALL, null);
		bs.setFeatureType(SurfaceFeatureType.BRIDGE);
		assertEquals(SurfaceFeatureType.BRIDGE, bs.getSurfaceFeatureType());
	}

	@Test
	public void testSetType() {
		BoundarySurface bs = new BoundarySurface(SurfaceFeatureType.TUNNEL, BoundarySurfaceType.WALL, null);
		bs.setType(BoundarySurfaceType.ROOF);
		assertEquals(BoundarySurfaceType.ROOF, bs.getType());
	}

	@Test
	public void testSetGmlObject() {
		AbstractCityObject aco = Mockito.mock(AbstractCityObject.class);
		BoundarySurface bs = new BoundarySurface(null);
		bs.setGmlObject(aco);
		assertEquals(aco, bs.getGmlObject());
	}

	@Test
	public void testGetOpenings() {
		Opening oMock = Mockito.mock(Opening.class);
		BoundarySurface bs = new BoundarySurface(null);
		bs.addOpening(oMock);
		assertEquals(oMock, bs.getOpenings().get(0));
	}

	@Test
	public void testSetParent() {
		BoundarySurface bs = new BoundarySurface(null);
		CityObject co = Mockito.mock(CityObject.class);
		bs.setParent(co);
		assertEquals(co, bs.getParent());
	}
}
