/*-
 *  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.checks;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckId;
import de.hft.stuttgart.citydoctor2.check.Requirement;
import de.hft.stuttgart.citydoctor2.checks.geometry.AllPolygonsWrongOrientationCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.DuplicatePointsCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.HoleOutsideCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.InteriorDisconnectedCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.ManifoldVertexCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.MultipleConnectedComponentCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.NestedRingsCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.NonManifoldEdgeCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.PlanarCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.PolygonIntersectingRingsCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.PolygonSameOrientationCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.PolygonWrongOrientationCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.RingNotClosedCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.RingSelfIntCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.SolidNotClosedCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.SolidSelfIntCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.TooFewPointsCheck;
import de.hft.stuttgart.citydoctor2.checks.geometry.TooFewPolygonsCheck;
import de.hft.stuttgart.citydoctor2.checks.semantics.IsCeilingCheck;
import de.hft.stuttgart.citydoctor2.checks.semantics.IsFloorCheck;
import de.hft.stuttgart.citydoctor2.checks.semantics.IsGroundCheck;
import de.hft.stuttgart.citydoctor2.checks.semantics.IsWallCheck;
import de.hft.stuttgart.citydoctor2.checks.semantics.RoofSurfaceUnfragmentedCheck;
import de.hft.stuttgart.citydoctor2.utils.Localization;

/**
 * Container class for all checks. If new checks are added, they need to be
 * published here to be accessed by the checker instance.
 * 
 * @author Matthias Betz
 *
 */
public class Checks {
	
	public static final double MIN_VERTEX_DISTANCE_DEFAULT = 0.0001;

	private static final Logger logger = LogManager.getLogger(Checks.class);

	private static List<CheckPrototype> checkPrototypes;
	private static Map<CheckId, CheckPrototype> prototypeMap;
	private static Map<String, Requirement> availableRequirements;


	private Map<CheckId, Check> checkMap;

	static {
		checkPrototypes = new ArrayList<>();
		prototypeMap = new HashMap<>();

		// add new checks here
		// ring checks
		publish(new TooFewPointsCheck());
		publish(new RingNotClosedCheck());
		publish(new DuplicatePointsCheck());
		publish(new RingSelfIntCheck());
		
		// polygon checks
		publish(new PlanarCheck());
		publish(new PolygonSameOrientationCheck());
		publish(new HoleOutsideCheck());
		publish(new NestedRingsCheck());
		publish(new PolygonIntersectingRingsCheck());
		publish(new InteriorDisconnectedCheck());
		
		// solid checks
		publish(new MultipleConnectedComponentCheck());
		publish(new SolidNotClosedCheck());
		publish(new NonManifoldEdgeCheck());
		publish(new PolygonWrongOrientationCheck());
		publish(new AllPolygonsWrongOrientationCheck());
		publish(new TooFewPolygonsCheck());
		publish(new ManifoldVertexCheck());
		publish(new SolidSelfIntCheck());

		// semantic checks
		publish(new IsWallCheck());
		publish(new IsFloorCheck());
		publish(new IsCeilingCheck());
		publish(new IsGroundCheck());
		publish(new RoofSurfaceUnfragmentedCheck());
		
		// load checks from service loader
		ServiceLoader<Check> checkLoader = ServiceLoader.load(Check.class);
		for (Check c : checkLoader) {
			publish(c);
		}
	}

	/**
	 * Creates new checks for every available check prototype and stores them in
	 * this container. Access the checks with {@link Checks#getCheckForId(CheckId)}.
	 */
	public Checks() {
		checkMap = new HashMap<>();

		for (CheckPrototype proto : checkPrototypes) {
			Check check = proto.createCheck();
			checkMap.put(check.getCheckId(), check);
		}
	}

	private static Map<String, Requirement> collectRequirements() {
		Map<String, Requirement> requirements = new HashMap<>();
		for (CheckPrototype proto : checkPrototypes) {
			for (Requirement req : proto.checksRequirements()) {
				requirements.put(req.getId(), req);
			}
		}
		return requirements;
	}
	
	/**
	 * Gets all requirements for which there are checks that can verify the
	 * requirements. This is dependent on the available checks as only the checks
	 * know which requirements they check.
	 * 
	 * @return a set of available requirements
	 */
	public static  Map<String, Requirement> getAvailableRequirements() {
		if (availableRequirements == null) {
			availableRequirements = collectRequirements();
		}
		return availableRequirements;
	}
	
	private static void publish(Check check) {
		CheckPrototype prototype = new CheckPrototype(check);
		checkPrototypes.add(prototype);
		prototypeMap.put(prototype.getCheckId(), prototype);
	}

	/**
	 * 
	 * @return All available checks as prototypes, where they can be instantiated.
	 */
	public static List<CheckPrototype> getCheckPrototypes() {
		return checkPrototypes;
	}

	/**
	 * 
	 * @param key The check ID
	 * @return The prototype for the given ID or null if there is no such prototype.
	 */
	public static CheckPrototype getCheckPrototypeForId(CheckId key) {
		return prototypeMap.get(key);
	}

	/**
	 * 
	 * @param id The check ID
	 * @return The check for the check ID or null if it does not exist
	 */
	public Check getCheckForId(CheckId id) {
		Check c = checkMap.get(id);
		if (c == null && logger.isWarnEnabled()) {
			logger.warn(Localization.getText("Checks.missingCheck"), id);
		}
		return c;
	}
}
