/*-
 *  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 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;
import de.hft.stuttgart.citydoctor2.utils.Localization;

/**
 * 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 {

	private static final long serialVersionUID = -8020055032177740646L;

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

	private int numberOfRoundingPlaces = 8;
	private double minVertexDistance = 0.0001;

	private String schematronFilePath = null;

	private boolean xmlValidation = false;
	private boolean useStreaming = false;
	private FilterConfiguration filter;
	private Map<CheckId, CheckConfiguration> checks;

	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.checks = new HashMap<>();
		for (CheckPrototype c : Checks.getCheckPrototypes()) {
			CheckConfiguration cConfig = new CheckConfiguration();
			cConfig.setEnabled(true);
			if (!c.getDefaultParameter().isEmpty()) {
				Map<String, String> paramMap = new HashMap<>();
				for (DefaultParameter param : c.getDefaultParameter()) {
					paramMap.put(param.getName(), param.getDefaultValue());
				}
				cConfig.setParameters(paramMap);
			}
			config.checks.put(c.getCheckId(), cConfig);
		}
		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 String getSchematronFilePath() {
		return schematronFilePath;
	}

	public void setSchematronFilePath(String schematronFilePath) {
		this.schematronFilePath = schematronFilePath;
	}

	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<CheckId, CheckConfiguration> getChecks() {
		if (checks == null) {
			checks = new HashMap<>();
		}
		return checks;
	}

	public void setChecks(Map<CheckId, CheckConfiguration> checks) {
		this.checks = checks;
	}

	/**
	 * Validates the configuration, adds all missing checks as enabled, reenables
	 * checks that are necessary to perform other checks
	 * 
	 * @return true if it reenabled disabled checks, false otherwise
	 */
	public boolean validateConfiguration() {
		for (CheckPrototype c : Checks.getCheckPrototypes()) {
			// enable all checks that are missing in the validation config
			checks.computeIfAbsent(c.getCheckId(), id -> {
				CheckConfiguration cConfig = new CheckConfiguration();
				cConfig.setEnabled(true);
				return cConfig;
			});
		}
		boolean reenabledChecks = reenableNecessaryChecks();
		if (schematronFilePath != null && !schematronFilePath.isEmpty()) {
			File f = new File(schematronFilePath);
			if (!f.exists() || !f.isFile()) {
				schematronFilePath = null;
				if (logger.isWarnEnabled()) {
					logger.warn(Localization.getText("ValidationConfiguration.missingSchematron"), f.getAbsolutePath());
				}
			}
		}
		return reenabledChecks;
	}

	private boolean reenableNecessaryChecks() {
		boolean reenabledChecks = false;
		for (java.util.Map.Entry<CheckId, CheckConfiguration> e : checks.entrySet()) {
			if (!e.getValue().isEnabled()) {
				continue;
			}
			CheckPrototype c = Checks.getCheckPrototypeForId(e.getKey());
			for (CheckId dep : c.getDependencies()) {
				CheckConfiguration checkConfig = checks.get(dep);
				if (!checkConfig.isEnabled()) {
					checkConfig.setEnabled(true);
					reenabledChecks = true;
					if (logger.isWarnEnabled()) {
						logger.warn(Localization.getText("ValidationConfiguration.reenable"), dep, c.getCheckId());
					}
				}
			}
			insertMissingParametersWithDefaultParameters(e, c);
		}
		return reenabledChecks;
	}

	private void insertMissingParametersWithDefaultParameters(java.util.Map.Entry<CheckId, CheckConfiguration> e,
			CheckPrototype c) {
		for (DefaultParameter param : c.getDefaultParameter()) {
			String configuredValue = e.getValue().getParameters().get(param.getName());
			if (configuredValue == null) {
				e.getValue().getParameters().put(param.getName(), param.getDefaultValue());
			}
		}
	}

	public int getNumberOfRoundingPlaces() {
		return numberOfRoundingPlaces;
	}

	public void setNumberOfRoundingPlaces(int numberOfRoundingPlaces) {
		this.numberOfRoundingPlaces = numberOfRoundingPlaces;
	}

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

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

	public void setMinVertexDistance(double minVertexDistance) {
		this.minVertexDistance = minVertexDistance;
	}

	public double getMinVertexDistance() {
		return minVertexDistance;
	}
}
