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

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.citygml4j.builder.jaxb.CityGMLBuilderException;
import org.citygml4j.xml.io.writer.CityGMLWriteException;

import de.hft.stuttgart.citydoctor2.check.Checker;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.parameter.ArgumentParser;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;

/**
 * The main class for starting the complete validation process. Parsing of
 * console parameters also happens here.
 * 
 * @author Matthias Betz
 *
 */
public class CityDoctorValidation {

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

	private CityDoctorValidation() {

	}

	/**
	 * Main entry point for the validation process. Parsing of command line
	 * parameters happens here, then the validation process is called.
	 * 
	 * @param args command line parameters
	 * @throws IOException             When an exception while writing the report
	 *                                 files happens.
	 * @throws CityGmlParseException   When an error while parsing the cityGML file
	 *                                 happens.
	 * @throws InvalidGmlFileException If the cityGML file is not valid according to
	 *                                 the cityGML schema.
	 * @throws CityGMLWriteException 
	 * @throws CityGMLBuilderException 
	 */
	public static void main(String[] args) throws CityGmlParseException, IOException, InvalidGmlFileException, CityGMLBuilderException, CityGMLWriteException {
		ArgumentParser argParser = new ArgumentParser(args);
		String inputFile = getInputFile(argParser);
		String xmlOutput = getXmlOutput(argParser);
		String pdfOutput = getPdfOutput(argParser);
		String outputFile = getOutputFile(argParser, true);
		ValidationConfiguration config = getValidationConfig(argParser);

		startValidationProcess(new File(inputFile), xmlOutput, pdfOutput, config, outputFile);
	}

	private static String getOutputFile(ArgumentParser argParser, boolean optional) {
		String outputFile;
		if (!argParser.containsOption("out")) {
			if (optional) {
				return null;
			}
			logger.error("No output file specified. (-out [FILE])");
			System.exit(11);
		}
		List<String> outFiles = argParser.getValues("out");
		if (outFiles.size() != 1) {
			logger.error("Specify exactly one file as output.");
			System.exit(12);
		}
		outputFile = outFiles.get(0);
		return outputFile;
	}

	/**
	 * Validates the cityGML input file with a standard validation configuration.
	 * 
	 * @param inputFile The cityGML file which is validated
	 * @param xmlOutput The XML report file location. Can be null.
	 * @param pdfOutput The PDF report file location. Can be null.
	 * @throws InterruptedException    When the reading thread gets interrupted.
	 * @throws IOException             When an exception while writing the report
	 *                                 files happens.
	 * @throws CityGmlParseException   When an error while parsing the cityGML file
	 *                                 happens.
	 * @throws InvalidGmlFileException If the cityGML file is not valid according to
	 *                                 the cityGML schema.
	 */
	public static void validate(File inputFile, File xmlOutput, File pdfOutput)
			throws IOException, CityGmlParseException, InvalidGmlFileException {
		validate(inputFile, xmlOutput, pdfOutput, null);
	}

	/**
	 * Validates the cityGML input file with a specified validation configuration.
	 * 
	 * @param inputFile            The cityGML file which is validated
	 * @param xmlOutput            The XML report file location. Can be null.
	 * @param pdfOutput            The PDF report file location. Can be null.
	 * @param validationConfigFile The validation configuration file location.
	 * @throws IOException             When an exception while writing the report
	 *                                 files happens.
	 * @throws CityGmlParseException   When an error while parsing the cityGML file
	 *                                 happens.
	 * @throws InvalidGmlFileException If the cityGML file is not valid according to
	 *                                 the cityGML schema.
	 */
	public static void validate(File inputFile, File xmlOutput, File pdfOutput, File validationConfigFile)
			throws IOException, CityGmlParseException, InvalidGmlFileException {
		ValidationConfiguration config;
		if (validationConfigFile == null) {
			config = ValidationConfiguration.loadStandardValidationConfig();
			config.setUseStreaming(true);
		} else {
			config = ValidationConfiguration.loadValidationConfig(validationConfigFile.getAbsolutePath());
		}
		String xmlOutputPath = null;
		if (xmlOutput != null) {
			xmlOutputPath = xmlOutput.getAbsolutePath();
		}
		String pdfOutputPath = null;
		if (pdfOutput != null) {
			pdfOutputPath = pdfOutput.getAbsolutePath();
		}
		try {
			startValidationProcess(inputFile, xmlOutputPath, pdfOutputPath, config, null);
		} catch (CityGMLBuilderException | CityGMLWriteException e) {
			// this does not happen as no output file is specified
			logger.catching(e);
		}
	}

	/**
	 * This function will handle the complete validation process
	 * 
	 * @param inputFile  the gml file path that is going to be checked
	 * @param xmlOutput  the output path for the xml report (optional)
	 * @param pdfOutput  the output path for the pdf report (optional)
	 * @param config     the configuration path for the validation plan
	 * @param outputFile storing the validated gml file with quality ade
	 * @throws IOException
	 * @throws CityGmlParseException
	 * @throws InvalidGmlFileException
	 * @throws CityGMLWriteException   if something goes wrong while writing the gml
	 *                                 file
	 * @throws CityGMLBuilderException
	 */
	public static void startValidationProcess(File inputFile, String xmlOutput, String pdfOutput,
			ValidationConfiguration config, String outputFile) throws IOException, CityGmlParseException,
			InvalidGmlFileException, CityGMLBuilderException, CityGMLWriteException {

		if (config.isUseStreaming()) {
			Checker.streamCheck(inputFile, xmlOutput, pdfOutput, config, outputFile);
		} else {
			CityDoctorModel model = CityGmlParser.parseCityGmlFile(inputFile.getAbsolutePath(),
					config.getParserConfiguration());
			Checker c = new Checker(config, model);
			c.runChecks(xmlOutput, pdfOutput, null);
			if (outputFile != null) {
				model.saveAs(outputFile);
			}
		}
	}

	public static String getPdfOutput(ArgumentParser argParser) {
		if (argParser.containsOption("pdfreport")) {
			List<String> reportFiles = argParser.getValues("pdfreport");
			if (reportFiles.size() != 1) {
				logger.error("Specify exactly one file as pdfReport");
				System.exit(6);
			}
			return reportFiles.get(0);
		}
		return null;
	}

	public static String getXmlOutput(ArgumentParser argParser) {
		if (argParser.containsOption("xmlreport")) {
			List<String> reportFiles = argParser.getValues("xmlreport");
			if (reportFiles.size() != 1) {
				logger.error("Specify exactly one file as xmlReport");
				System.exit(5);
			}
			return reportFiles.get(0);
		}
		return null;
	}

	private static ValidationConfiguration getValidationConfig(ArgumentParser argParser) throws FileNotFoundException {
		return getValidationConfig(argParser, true);
	}

	public static ValidationConfiguration getValidationConfig(ArgumentParser argParser, boolean optional)
			throws FileNotFoundException {
		if (argParser.containsOption("config")) {
			List<String> configFiles = argParser.getValues("config");
			if (configFiles.size() != 1) {
				logger.error("Specify exactly one file as configuration");
				System.exit(4);
			}
			return ValidationConfiguration.loadValidationConfig(configFiles.get(0));
		} else {
			if (optional) {
				logger.warn("No configuration file given, loading default configuration");
				return ValidationConfiguration.loadStandardValidationConfig();
			} else {
				logger.error("No configuration file specified with -config [FILE]");
				System.exit(7);
				return null;
			}
		}
	}

	@SuppressWarnings("unused")
	private static String getOutputFile(ArgumentParser argParser, String inputFile) {
		if (argParser.containsOption("out")) {
			List<String> outFiles = argParser.getValues("out");
			if (outFiles.size() != 1) {
				logger.error("Specify exactly one file as output.");
				System.exit(3);
			}
			return outFiles.get(0);
		} else {
			return inputFile.substring(0, inputFile.lastIndexOf('.')) + "_healed.gml";
		}
	}

	private static String getInputFile(ArgumentParser argParser) {
		return getInputFile(argParser, false);
	}

	public static String getInputFile(ArgumentParser argParser, boolean optional) {
		String inputFile;
		if (!argParser.containsOption("in")) {
			if (optional) {
				return null;
			}
			logger.error("No input file specified. (-in [FILE])");
			System.exit(1);
		}
		List<String> inFiles = argParser.getValues("in");
		if (inFiles.size() != 1) {
			logger.error("Specify exactly one file as input.");
			System.exit(2);
		}
		inputFile = inFiles.get(0);
		return inputFile;
	}

}
