/*-
 *  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.io.Serializable;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

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

import de.hft.stuttgart.citydoctor2.datastructure.GmlId;

/**
 * Interface to indicate that this object can be checked by Checks.
 * 
 * @author Matthias Betz - 12bema1bif@hft-stuttgart.de
 *
 */
public abstract class Checkable implements Serializable {

	private static final long serialVersionUID = -1707871839265057882L;

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

	private EnumMap<CheckId, CheckResult> checkResults = new EnumMap<>(CheckId.class);
	private boolean isValidated = false;
	
	protected void setValidated(boolean validated) {
		isValidated = validated;
	}
	
	public boolean isValidated() {
		return isValidated;
	}

	/**
	 * This object will be visited by the given check. Indirect implementers have to
	 * call <code>super.visit()</code> to ensure that every layer of classes is
	 * correctly checked. This method should call the visit method of each Checkable
	 * object contained in the implementing class.<br>
	 * <b>Note:</b> every object should only be visited once, therefore a clear
	 * structure has to be constructed in order to know which object is contained
	 * where. For example a Polygon has vertices as references but the actual
	 * storage location is in Geometry. Therefore only in Geometry the call to visit
	 * vertices is made.
	 * 
	 * @param c the check from which the check method is called with the Checkable
	 *          instance as parameter.
	 */
	public abstract void accept(Check c);

	/**
	 * The GML-ID of the checkable. This is necessary so specific features can be
	 * excluded via the ID.
	 * 
	 * @return the GML-ID
	 */
	public abstract GmlId getGmlId();

	/**
	 * This method checks if the object or any object contained within this
	 * checkable has an error. It counts as an error if the result status if the
	 * given check is <code>DEPENDENCIES_NOT_MET<code>.
	 * 
	 * @param checkIdentifier the name of the check for which an error is searched
	 * @return true if an error has been found with the given check
	 */
	public boolean containsError(CheckId checkIdentifier) {
		CheckResult cs = getCheckResult(checkIdentifier);
		if (cs == null) {
			return false;
		}
		ResultStatus rs = cs.getResultStatus();
		return rs == ResultStatus.ERROR || rs == ResultStatus.DEPENDENCIES_NOT_MET;
	}

	/**
	 * Returns the check result for the given CheckId
	 * 
	 * @param id the id
	 * @return the check result or null if none was found
	 */
	public CheckResult getCheckResult(CheckId id) {
		return checkResults.get(id);
	}

	/**
	 * Returns the check result for the given Check.
	 * 
	 * @param c the check
	 * @return the check result or null if none was found
	 */
	public CheckResult getCheckResult(Check c) {
		return getCheckResult(c.getCheckId());
	}

	/**
	 * 
	 * @return all check results for this checkable.
	 */
	public Map<CheckId, CheckResult> getAllCheckResults() {
		return checkResults;
	}

	/**
	 * Checks whether this checkable has a DependencyNotMetError for the given
	 * CheckId.
	 * 
	 * @param id the CheckId for which the error is searched.
	 * @return true if it contains such an error, false if not or no CheckResult is
	 *         available for the given id.
	 */
	public boolean hasDependencyNotMetError(CheckId id) {
		CheckResult cr = checkResults.get(id);
		if (cr == null) {
			return false;
		}
		return cr.getResultStatus() == ResultStatus.DEPENDENCIES_NOT_MET;
	}

	/**
	 * Checks whether this checkable has an error. Dependency errors are not
	 * considered for this function. This will only check this checkable and not
	 * traverse any checkables contained in this instance.
	 * 
	 * @return true if it has an error, otherwise false
	 */
	public boolean hasAnyError() {
		for (CheckResult cr : checkResults.values()) {
			if (cr.getResultStatus() == ResultStatus.ERROR) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Checks whether any check results have been reported for this checkable
	 * 
	 * @return true if any checks have reported results, false otherwise
	 */
	public boolean hasCheckResults() {
		return !checkResults.isEmpty();
	}

	/**
	 * Adds the result of a check to this checkable.
	 * 
	 * @param cr the check result
	 */
	public void addCheckResult(CheckResult cr) {
		checkResults.put(cr.getCheckIdentifier(), cr);
		if (cr.getResultStatus() == ResultStatus.ERROR) {
			logger.debug("{} has found an error of type {}", cr.getCheckIdentifier(), cr.getError().getErrorId());
		} else if (cr.getResultStatus() == ResultStatus.WARNING) {
			logger.debug("{} has found a warning of type {}", cr.getCheckIdentifier(), cr.getError().getErrorId());
		}
	}

	/**
	 * Clears all errors from this checkable
	 */
	public void clearCheckResults() {
		setValidated(false);
		checkResults.clear();
	}

	/**
	 * Removes all errors from this instance and all contained checkables.
	 */
	public abstract void clearAllContainedCheckResults();

	/**
	 * 
	 * @return false if the checkable or all checkables contained in this one don't
	 *         have any error.
	 */
	public boolean containsAnyError() {
		for (CheckResult cr : checkResults.values()) {
			if (cr.getResultStatus() == ResultStatus.ERROR
					|| cr.getResultStatus() == ResultStatus.DEPENDENCIES_NOT_MET) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Collects all errors from this checkable and all contained checkables and adds
	 * them to the given list. DEPENDENCY_NOT_MET errors are excluded from this.
	 * 
	 * @param errors the collection in which the errors are added.
	 */
	public void collectContainedErrors(List<CheckError> errors) {
		for (CheckResult cr : checkResults.values()) {
			if (cr.getResultStatus() == ResultStatus.ERROR) {
				errors.add(cr.getError());
			}
		}
	}

	/**
	 * As which class is this object being tested. This is used when there are
	 * different implementations for the same object type. For example a polygon can
	 * be a link to another polygon as well as the polygon which contains the actual
	 * data. Checks declare which objects they check, which would be Polygon in this
	 * example. Now there are two kinds of polygons but both are treated as
	 * polygons.
	 * 
	 * @return the class for which the object should be treated as.
	 */
	public Class<? extends Checkable> getCheckClass() {
		return getClass();
	}

}
