/*-
 *  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.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer;

import de.hft.stuttgart.citydoctor2.checks.CheckPrototype;
import de.hft.stuttgart.citydoctor2.checks.Checks;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;

/**
 * The validation configuration class represented in the yaml configuration
 * file. Here configuration files can be loaded and written as well as filled
 * with missing data, when the configuration file is not complete.
 * 
 * @author Matthias Betz
 *
 */
public class ValidationConfiguration implements Serializable {

	public static final String CHECK_FOR_SOLID_XML = "checkForSolid.xml";
	private static final String NUMBER_OF_ROUNDING_PLACES_DEFAULT = "8";
	private static final String MIN_VERTEX_DISTANCE_DEFAULT = "0.0001";
	private static final long serialVersionUID = -8020055032177740646L;
	private static final Logger logger = LogManager.getLogger(ValidationConfiguration.class);

	
	private Map<String, String> globalParameters;
	private boolean xmlValidation = false;
	private boolean useStreaming = false;
	private FilterConfiguration filter;
	private Map<String, RequirementConfiguration> requirements;

	private ParserConfiguration parserConfig;

	public static ValidationConfiguration loadValidationConfig(String validationFile) throws FileNotFoundException {
		return loadValidationConfig(new FileInputStream(validationFile));
	}

	public static ValidationConfiguration loadValidationConfig(InputStream stream) {
		Yaml yaml = new Yaml(new Constructor(ValidationConfiguration.class));
		ValidationConfiguration config = yaml.load(stream);
		config.validateConfiguration();
		return config;
	}

	public static ValidationConfiguration loadStandardValidationConfig() {
		ValidationConfiguration config = new ValidationConfiguration();
		config.requirements = new HashMap<>();
		for (CheckPrototype c : Checks.getCheckPrototypes()) {
			for (Requirement req : c.checksRequirements()) {
				RequirementConfiguration reqConfig = new RequirementConfiguration();
				reqConfig.setEnabled(true);
				for (DefaultParameter param : req.getDefaultParameter()) {
					reqConfig.getParameters().put(param.getName(), param.getValue());
				}
				config.requirements.put(req.getId(), reqConfig);
			}
		}
		config.globalParameters = new HashMap<>();
		config.globalParameters.put(GlobalParameters.NUMBER_OF_ROUNDING_PLACES, NUMBER_OF_ROUNDING_PLACES_DEFAULT);
		config.globalParameters.put(GlobalParameters.MIN_VERTEX_DISTANCE, MIN_VERTEX_DISTANCE_DEFAULT);
		config.setSchematronFilePathInGlobalParameters(CHECK_FOR_SOLID_XML);
		return config;
	}

	public void saveAs(File f) throws IOException {
		DumperOptions options = new DumperOptions();
		options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
		options.setPrettyFlow(true);
		Representer rep = new ValidationConfigurationRepresenter();
		rep.addClassTag(ValidationConfiguration.class, Tag.MAP);
		Yaml yaml = new Yaml(rep, options);
		try (BufferedWriter bw = new BufferedWriter(new FileWriter(f))) {
			yaml.dump(this, bw);
		}
	}

	public boolean isXmlValidation() {
		return xmlValidation;
	}

	public void setXmlValidation(boolean xmlValidation) {
		this.xmlValidation = xmlValidation;
	}

	public boolean isUseStreaming() {
		return useStreaming;
	}

	public void setUseStreaming(boolean useStreaming) {
		this.useStreaming = useStreaming;
	}

	public FilterConfiguration getFilter() {
		return filter;
	}

	public void setFilter(FilterConfiguration filter) {
		this.filter = filter;
	}

	public Map<String, RequirementConfiguration> getRequirements() {
		if (requirements == null) {
			requirements = new HashMap<>();
		}
		return requirements;
	}

	public Map<String, String> getGlobalParameters() {
		if (globalParameters == null) {
			globalParameters = new HashMap<>();
		}
		return globalParameters;
	}
	
	public void setGlobalParameters(Map<String, String> globalParameters) {
		this.globalParameters = globalParameters;
	}

	/**
	 * Validates the configuration, adds all missing requirements as enabled.
	 * 
	 */
	public void validateConfiguration() {
		logger.trace("Validating configuration");
		Map<String, Requirement> availableRequirements = Checks.getAvailableRequirements();

		for (Entry<String, RequirementConfiguration> entry : requirements.entrySet()) {
			Requirement req = availableRequirements.get(entry.getKey());
			if (req != null) {
				for (DefaultParameter param : req.getDefaultParameter()) {
					RequirementConfiguration config = entry.getValue();
					config.getParameters().computeIfAbsent(param.getName(), k -> param.getValue());
				}
			}
		}
		for (Requirement req : availableRequirements.values()) {
			requirements.computeIfAbsent(req.getId(), id -> {
				RequirementConfiguration cConfig = new RequirementConfiguration();
				cConfig.setEnabled(true);
				for (DefaultParameter param : req.getDefaultParameter()) {
					cConfig.getParameters().put(param.getName(), param.getValue());
				}
				return cConfig;
			});
		}
		globalParameters.computeIfAbsent(GlobalParameters.MIN_VERTEX_DISTANCE, k -> MIN_VERTEX_DISTANCE_DEFAULT);
		globalParameters.computeIfAbsent(GlobalParameters.NUMBER_OF_ROUNDING_PLACES,
				k -> NUMBER_OF_ROUNDING_PLACES_DEFAULT);
	}

	public int getNumberOfRoundingPlaces() {
		return Integer.parseInt(globalParameters.get(GlobalParameters.NUMBER_OF_ROUNDING_PLACES));
	}

	public void setNumberOfRoundingPlacesInGlobalParameters(int numberOfRoundingPlaces) {
		globalParameters.put(GlobalParameters.NUMBER_OF_ROUNDING_PLACES, Integer.toString(numberOfRoundingPlaces));
	}

	public ParserConfiguration getParserConfiguration() {
		if (parserConfig == null) {
			parserConfig = new ParserConfiguration(getNumberOfRoundingPlaces(), xmlValidation);
		}
		return parserConfig;
	}

	public void setParserConfig(ParserConfiguration parserConfig) {
		this.parserConfig = parserConfig;
	}

	public void setMinVertexDistanceInGlobalParameters(double minVertexDistance) {
		globalParameters.put(GlobalParameters.MIN_VERTEX_DISTANCE, Double.toString(minVertexDistance));
	}

	public double getMinVertexDistance() {
		return Double.parseDouble(globalParameters.get(GlobalParameters.MIN_VERTEX_DISTANCE));
	}

	public String getSchematronFilePath() {
		return globalParameters.get(GlobalParameters.SCHEMATRON_PATH);
	}

	public String getNumberOfRoundingPlacesAsString() {
		return globalParameters.get(GlobalParameters.NUMBER_OF_ROUNDING_PLACES);
	}

	public String getMinVertexDistanceAsString() {
		return globalParameters.get(GlobalParameters.MIN_VERTEX_DISTANCE);
	}

	public void setSchematronFilePathInGlobalParameters(String string) {
		globalParameters.put(GlobalParameters.SCHEMATRON_PATH, string);
	}
	
	public void setRequirements(Map<String, RequirementConfiguration> requirements) {
		this.requirements = requirements;
	}
}
