package org.tinylog.impl.path.segments;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.ParsePosition;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQuery;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

import org.tinylog.core.internal.InternalLogger;

/**
 * Path segment for the date-time.
 */
public class DateTimeSegment implements PathSegment {

	private static final List<TemporalQuery<TemporalAccessor>> queries = Arrays.asList(
		ZonedDateTime::from,
		OffsetDateTime::from,
		LocalDateTime::from,
		LocalDate::from,
		OffsetTime::from,
		LocalTime::from,
		YearMonth::from,
		Year::from,
		MonthDay::from,
		Month::from,
		DayOfWeek::from
	);

	private final DateTimeFormatter formatter;
	private final String pattern;

	/**
	 * @param pattern The pattern to use for formatting date-times
	 * @param locale The locale to use for formatting date-times
	 */
	public DateTimeSegment(String pattern, Locale locale) {
		this.formatter = DateTimeFormatter.ofPattern(pattern, locale);
		this.pattern = pattern;
	}

	@Override
	public String findLatest(Path parentDirectory, String prefix) throws IOException {
		Optional<DateTimeTuple> latest = Files.list(parentDirectory)
			.map(path -> path.getFileName().toString())
			.filter(name -> name.startsWith(prefix))
			.map(name -> new DateTimeTuple(formatter, name.substring(prefix.length())))
			.filter(tuple -> tuple.value != null)
			.max(DateTimeTuple::compare);

		return latest.map(tuple -> tuple.text).orElse(null);
	}

	@Override
	public void resolve(StringBuilder pathBuilder, ZonedDateTime date) {
		int start = pathBuilder.length();
		formatter.formatTo(date, pathBuilder);

		boolean containsPosixFileSeparator = pathBuilder.indexOf("/", start) >= 0;
		boolean containsWindowsFileSeparator = pathBuilder.indexOf("\\", start) >= 0;
		if (containsPosixFileSeparator || containsWindowsFileSeparator) {
			InternalLogger.warn(
				null,
				"Date-time pattern \"{}\" must not contain a file separator, but resolves to \"{}\"",
				pattern,
				pathBuilder.substring(start)
			);
		}
	}

	/**
	 * Tuple for storing the date-time text together with the parsed {@link TemporalAccessor}.
	 */
	private static final class DateTimeTuple {

		private final String text;
		private final Comparable<?> value;

		/**
		 * @param formatter Date-time formatter for parsing the passed text
		 * @param text Parsable date-time text
		 */
		private DateTimeTuple(DateTimeFormatter formatter, String text) {
			ParsePosition position = new ParsePosition(0);
			Comparable<?> value = parse(formatter, text, position);

			this.text = value == null ? text : text.substring(0, position.getIndex());
			this.value = value;
		}

		/**
		 * Compares the stored comparable with the comparable from the passed tuple.
		 *
		 * @param other The tuple with the comparable value to compare with
		 * @return A negative integer if the comparable value of this tuple is less than the comparable value of the
		 *         passed tuple, zero if the comparable values of both tuples are equals, or a positive integer if the
		 *         comparable value of this tuple is greater than the comparable value of the passed tuple
		 */
		@SuppressWarnings({"rawtypes", "unchecked"})
		public int compare(DateTimeTuple other) {
			return ((Comparable) this.value).compareTo(other.value);
		}

		/**
		 * Parses the passed text by the passed date-time formatter.
		 *
		 * @param formatter Date-time formatter for parsing the passed text
		 * @param text Parsable date-time text
		 * @param position Updatable position for parsing
		 * @return The parsed comparable value or {@code null} if no comparable value could be found
		 */
		private static Comparable<?> parse(DateTimeFormatter formatter, String text, ParsePosition position) {
			TemporalAccessor parsedValue;
			try {
				parsedValue = formatter.parse(text, position);
			} catch (DateTimeParseException ex) {
				return null;
			}

			for (TemporalQuery<TemporalAccessor> query : queries) {
				try {
					return (Comparable<?>) parsedValue.query(query);
				} catch (RuntimeException ex) {
					// Ignore and continue
				}
			}

			char firstChar = text.charAt(0);
			if (firstChar >= '0' && firstChar <= '9') {
				try {
					return Long.valueOf(text.substring(0, position.getIndex()));
				} catch (NumberFormatException ex) {
					// Ignore and continue
				}
			}

			return null;
		}

	}

}

@@ -1647,16 +1647,76 @@
 ption %7B%0A
+%09%09if(!Files.exists(parentDirectory)) %7B%0A%09%09%09return null;%0A%09%09%7D%0A%0A
 %09%09Option

return null if parent directory doesn't exist in findLatest