/*
 * Java Genetic Algorithm Library (@__identifier__@).
 * Copyright (c) @__year__@ Franz Wilhelmstötter
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Author:
 *    Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
 */
package io.jenetics.tool.evaluation;

import static java.io.File.createTempFile;
import static java.lang.String.format;
import static java.nio.file.Files.deleteIfExists;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Stream.concat;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import io.jenetics.internal.util.Args;
import io.jenetics.tool.trial.Gnuplot;
import io.jenetics.tool.trial.IO;
import io.jenetics.tool.trial.Params;
import io.jenetics.tool.trial.SampleSummary;
import io.jenetics.tool.trial.TrialMeter;
import io.jenetics.xml.stream.Reader;

/**
 * Helper class for creating Gnuplot diagrams from result files.
 *
 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
 * @version 4.0
 * @since 3.4
 */
public class Diagram {

	/**
	 * The available Gnuplot templates.
	 */
	public enum Template {

		/**
		 * Template for execution time termination diagrams.
		 */
		EXECUTION_TIME("execution_time_termination"),

		GENERATION_POPULATION_SIZE("generation_population_size"),

		/**
		 * Template for fitness threshold termination diagrams.
		 */
		FITNESS_THRESHOLD("fitness_threshold_termination"),

		/**
		 * Template for fitness convergence termination diagrams.
		 */
		FITNESS_CONVERGENCE("fitness_convergence_termination"),

		/**
		 * Template for population convergence termination diagrams.
		 */
		POPULATION_CONVERGENCE("population_convergence_termination"),

		/**
		/**
		 * Template for fixed generation termination diagrams.
		 */
		FIXED_GENERATION("fixed_generation_termination"),

		/**
		 * Template for steady fitness termination diagrams,
		 */
		STEADY_FITNESS("steady_fitness_termination"),

		/**
		 * Template for comparing different selectors.
		 */
		SELECTOR_COMPARISON("selector_comparison"),

		POPULATION_SIZE("population_size");

		private final String _name;
		private final String _path;

		Template(final String name) {
			_name = requireNonNull(name);
			_path = "/io/jenetics/tool/evaluation/" +
				requireNonNull(name) + ".gp";
		}

		public String getName() {
			return _name;
		}

		/**
		 * Return the template content as string.
		 *
		 * @return the template content
		 */
		public String content() {
			try (InputStream stream = Diagram.class.getResourceAsStream(_path)) {
				return IO.toText(stream);
			} catch (IOException e) {
				throw new UncheckedIOException(e);
			}
		}
	}

	/**
	 * Create a performance diagram.
	 *
	 * @param input the input data
	 * @param template the Gnuplot template to use
	 * @param params the diagram parameters (x-axis)
	 * @param output the output file
	 * @param summary the first summary data
	 * @param summaries the rest of the summary data
	 * @throws IOException if the diagram generation fails
	 * @throws NullPointerException of one of the parameters is {@code null}
	 * @throws IllegalArgumentException if the {@code params}, {@code generation}
	 *         and {@code fitness} doesn't have the same parameter count
	 */
	public static void create(
		final Path input,
		final Template template,
		final Params<?> params,
		final Path output,
		final SampleSummary summary,
		final SampleSummary... summaries
	)
		throws IOException
	{
		final Stream<SampleSummary> summaryStream = Stream.concat(
			Stream.of(summary), Stream.of(summaries)
		);
		summaryStream.forEach(s -> {
			if (params.size() != s.parameterCount()) {
				throw new IllegalArgumentException(format(
					"Parameters have different size: %d", params.size()
				));
			}
		});

		final Path templatePath = tempPath();
		try {
			IO.write(template.content(), templatePath);

			final Path dataPath = tempPath();
			try {
				final String data = IntStream.range(0, params.size())
					.mapToObj(i -> toLineString(i, params, summary, summaries))
					.collect(Collectors.joining("\n"));
				IO.write(data, dataPath);

				final Gnuplot gnuplot = new Gnuplot(templatePath);
				gnuplot.setEnv(params(input));
				gnuplot.create(dataPath, output);
			} finally {
				deleteIfExists(dataPath);
			}
		} finally {
			deleteIfExists(templatePath);
		}
	}

	private static Path tempPath() throws IOException {
		return createTempFile("__diagram_template__", "__").toPath();
	}

	private static String toLineString(
		final int index,
		final Params<?> params,
		final SampleSummary summary,
		final SampleSummary... summaries
	) {
		return concat(concat(
				Stream.of(params.get(index).toString().split(":")),
				DoubleStream.of(summary.getPoints().get(index).toArray())
					.mapToObj(Double::toString)),
				Stream.of(summaries)
					.flatMapToDouble(s -> DoubleStream.of(s.getPoints().get(index).toArray()))
					.mapToObj(Double::toString))
			.collect(Collectors.joining(" "));
	}

	public static void main(final String[] arguments) throws Exception {
		final Args args = Args.of(arguments);

		final Path input = args.arg("input")
			.map(Paths::get)
			.map(Path::toAbsolutePath)
			.get();

		final String[] samples = args.arg("samples")
			.map(s -> s.split(","))
			.orElse(new String[]{"Generation", "Fitness"});

		final TrialMeter<String> trial = TrialMeter.read(
			input,
			Reader.text()
		);
		final Params<String> params = trial.getParams();
		final SampleSummary summary = trial.getData(samples[0]).summary();
		final SampleSummary[] summaries = Arrays.stream(samples, 1, samples.length)
			.map(s -> trial.getData(s).summary())
			.toArray(SampleSummary[]::new);

		create(
			input,
			template(input),
			params,
			output(input),
			summary,
			summaries
		);
	}

	private static Template template(final Path path) {
		final String name = path.getFileName().toString()
			.split("-")[1]
			.split("\\.")[0];

		return Arrays.stream(Template.values())
			.filter(t -> t.getName().equals(name))
			.findFirst().orElseThrow();
	}

	private static Map<String, String> params(final Path path) {
		System.out.println(path.getFileName());
		final List<String> parts = param(path.getFileName().toString())
			.flatMap(p -> Stream.of(p.split("@")))
			.toList();

		final Map<String, String> params = new HashMap<>();
		for (int i = 0; i < parts.size(); ++i) {
			final String key = format("PARAM_%s", i);
			params.put(key, parts.get(i));
		}

		return params;
	}

	private static Stream<String> param(final String name) {
		final String[] parts = name.split("-");
		return parts.length == 3
			? Stream.of(parts[2].split("\\.")[0])
			: Stream.empty();
	}

	private static Path output(final Path path) {
		final String name = path.getFileName().toString().split("\\.")[0];
		return Paths.get(path.getParent().toString(), name + ".svg");
	}

}
