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

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import de.hft.stuttgart.citydoctor2.check.error.DependenciesNotMetError;
import de.hft.stuttgart.citydoctor2.datastructure.AbstractBuilding;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
import de.hft.stuttgart.citydoctor2.datastructure.BridgeObject;
import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.Installation;
import de.hft.stuttgart.citydoctor2.datastructure.BuildingPart;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.LandObject;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Opening;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.ReliefObject;
import de.hft.stuttgart.citydoctor2.datastructure.TinObject;
import de.hft.stuttgart.citydoctor2.datastructure.TransportationObject;
import de.hft.stuttgart.citydoctor2.datastructure.Vegetation;
import de.hft.stuttgart.citydoctor2.datastructure.WaterObject;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;

/**
 * The general check class containing the methods which will be called by check
 * engine. To create a new check override one or more check(...) methods and
 * implement the {@link Check#getApplicableToClasses()} method by returning a
 * list of classes you wish to check.<br>
 * CheckResult objects can be attached to every Checkable. If the check has
 * parameters override the {@link Check#init(Map, ParserConfiguration)} method
 * to get the value of a parameter if a user has specified one in the validation
 * plan. It will be contained in the Map as a string. If you have parameters you
 * will need to override the {@link Check#getDefaultParameter()} method as well
 * to declare which parameters you have, which name and default value they have.
 * The name will be the key for the Map in the init method previously
 * mentioned.<br>
 * If your check has dependencies you can declare them in the
 * {@link Check#getDependencies()} method. Be sure not to create cyclic
 * dependencies as that would result in undefined behavior.
 * 
 * @author Matthias Betz
 *
 */
public abstract class Check {

	private List<Class<Checkable>> applicableToClasses = new ArrayList<>(2);

	@SuppressWarnings("unchecked")
	protected Check() {
		Method[] declaredMethods = getClass().getDeclaredMethods();
		for (Method m : declaredMethods) {
			if ("check".equals(m.getName())) {
				Class<?>[] parameterTypes = m.getParameterTypes();
				if (parameterTypes.length == 1 && Checkable.class.isAssignableFrom(parameterTypes[0])) {
					applicableToClasses.add((Class<Checkable>) parameterTypes[0]);
				}
			}
		}
	}

	/**
	 * Returns all classes for which the check needs to be executed
	 * 
	 * @return a list of classes which the check applies to
	 */
	public List<Class<Checkable>> getApplicableToClasses() {
		return applicableToClasses;
	}

	/**
	 * A list of dependencies which the check depends on. Each of those dependencies
	 * needs to be executed and must have reported no error before this check can be
	 * executed.
	 * 
	 * @return a list of CheckIds which the check depends on.
	 */
	public List<CheckId> getDependencies() {
		return Collections.emptyList();
	}
	
	public abstract Set<Requirement> appliesToRequirements();

	/**
	 * Getter for the check id.
	 * 
	 * @return the check id.
	 */
	public abstract CheckId getCheckId();

	/**
	 * Getter for the check type.
	 * 
	 * @return the check type
	 */
	public abstract RequirementType getType();

	/**
	 * Checks whether the check can be executed on this checkable, meaning the
	 * checkable or its content can not have any error of any check dependent on
	 * this check. <br>
	 * If the check cannot be executed a CheckResult will be created with the
	 * ResultStatus = DEPENDENCIES_NOT_MET.
	 * 
	 * @param c   the checkable
	 * @param crc container for all check results
	 * @return true if the check can be executed, false if the checkable itself or
	 *         one of its containing checkables have an error.
	 */
	public boolean canExecute(Checkable c) {
		// ignore objects where this check doesn't apply to
		if (!canBeApplied(c)) {
			return false;
		}
		// check that object doesn't have errors for dependencies of this check
		for (CheckId dependencyCheck : getDependencies()) {
			boolean hasError = c.containsError(dependencyCheck);
			if (hasError) {
				// check whether a result was already inserted
				if (!c.hasDependencyNotMetError(getCheckId())) {
					// insert dependency error
					CheckError err = new DependenciesNotMetError(dependencyCheck);
					CheckResult cr = new CheckResult(getCheckId(), ResultStatus.DEPENDENCIES_NOT_MET, err);
					c.addCheckResult(cr);
				}
				return false;
			}
		}
		return true;
	}

	private boolean canBeApplied(Checkable c) {
		for (Class<Checkable> checkableClass : getApplicableToClasses()) {
			if (checkableClass.isAssignableFrom(c.getCheckClass())) {
				return true;
			}
		}
		return false;
	}

	/**
	 * check anything
	 * 
	 * @param checkable a checkable
	 */
	public void check(Checkable checkable) {

	}

	/**
	 * check buildings and building parts
	 * 
	 * @param ab building or building part
	 */
	public void check(AbstractBuilding ab) {

	}

	/**
	 * check boundary surfaces
	 * 
	 * @param bs a boundary surface
	 */
	public void check(BoundarySurface bs) {

	}

	/**
	 * check bridges
	 * 
	 * @param bo a bridge
	 */
	public void check(BridgeObject bo) {

	}

	/**
	 * check only buildings
	 * 
	 * @param b a building
	 */
	public void check(Building b) {

	}

	/**
	 * check building installations
	 * 
	 * @param bi a building installation
	 */
	public void check(Installation bi) {

	}

	/**
	 * check only building parts
	 * 
	 * @param bp a building part
	 */
	public void check(BuildingPart bp) {

	}

	/**
	 * check all city objects
	 * 
	 * @param co a city object
	 */
	public void check(CityObject co) {

	}

	/**
	 * check land use objects
	 * 
	 * @param lo a land use object
	 */
	public void check(LandObject lo) {

	}

	/**
	 * check openings
	 * 
	 * @param o an opening
	 */
	public void check(Opening o) {

	}

	/**
	 * check transportation objects
	 * 
	 * @param to a transportation object
	 */
	public void check(TransportationObject to) {

	}

	/**
	 * check vegetation objects
	 * 
	 * @param veg a vegetation object
	 */
	public void check(Vegetation veg) {

	}

	/**
	 * check water objects
	 * 
	 * @param wo a water object
	 */
	public void check(WaterObject wo) {

	}

	/**
	 * check geometries
	 * 
	 * @param geom a geometry
	 */
	public void check(Geometry geom) {

	}

	/**
	 * check polygons
	 * 
	 * @param poly a polygon
	 */
	public void check(Polygon poly) {

	}

	/**
	 * check linear rings
	 * 
	 * @param ring a linear ring
	 */
	public void check(LinearRing ring) {

	}
	
	public void check(TinObject tin) {
		
	}
	
	public void check(ReliefObject relief) {
		
	}

	/**
	 * The initialization method of this check. It will be called before any check
	 * method will be executed. Override this if you want to have configurable
	 * parameters.
	 * 
	 * @param params the parameter map containing the parameters for the check in
	 *               String form. The key should be the same String provided by the
	 *               {@link Requirement#getDefaultParameter()} method
	 * @param config sometimes there are global parameters which can be used by
	 *               checks. Those are be stored in this container
	 */
	public void init(Map<String, String> params, ParserConfiguration config) {

	}

	@Override
	public String toString() {
		return "Check [id=" + getCheckId() + "]";
	}

	/**
	 * Create a new instance of this check. This is needed to keep the checks
	 * dynamic
	 * 
	 * @return a new instance of this check
	 */
	public abstract Check createNewInstance();

}
