package eu.dnetlib.data.transform;

import eu.dnetlib.data.proto.FieldTypeProtos;
import org.apache.commons.lang.StringUtils;

import java.time.Year;
import java.util.*;
import java.util.stream.Collectors;

import static java.util.Collections.reverseOrder;
import static java.util.Map.Entry.comparingByValue;
import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang.StringUtils.endsWith;
import static org.apache.commons.lang.StringUtils.substringBefore;

public class DatePicker {

    private static final String DATE_PATTERN = "\\d{4}-\\d{2}-\\d{2}";
    private static final String DATE_DEFAULT_SUFFIX = "01-01";
    private static final int YEAR_LB = 1300;
    private static final int YEAR_UB = Year.now().getValue() + 5;

    public static FieldTypeProtos.StringField pick(final Collection<String> dateofacceptance) {

        final Map<String, Integer> frequencies = dateofacceptance
                .parallelStream()
                .filter(StringUtils::isNotBlank)
                .collect(
                        Collectors.toConcurrentMap(
                                w -> w, w -> 1, Integer::sum));

        if (frequencies.isEmpty()) {
            return FieldTypeProtos.StringField.newBuilder().setValue("").build();
        }

        final FieldTypeProtos.StringField.Builder date = FieldTypeProtos.StringField.newBuilder().setValue(frequencies.keySet().iterator().next());

        // let's sort this map by values first, filtering out invalid dates
        final Map<String, Integer> sorted = frequencies
                .entrySet()
                .stream()
                .filter(d -> StringUtils.isNotBlank(d.getKey()))
                .filter(d -> d.getKey().matches(DATE_PATTERN))
                .filter(d -> inRange(d.getKey()))
                .sorted(reverseOrder(comparingByValue()))
                .collect(
                        toMap(
                                Map.Entry::getKey,
                                Map.Entry::getValue, (e1, e2) -> e2,
                                LinkedHashMap::new));

        // shortcut
        if (sorted.size() == 0) {
            return date.build();
        }

        // voting method (1/3 + 1) wins
        if (sorted.size() >= 3) {
            final int acceptThreshold = (sorted.size() / 3) + 1;
            final List<String> accepted = sorted.entrySet().stream()
                    .filter(e -> e.getValue() >= acceptThreshold)
                    .map(e -> e.getKey())
                    .collect(Collectors.toList());

            // cannot find strong majority
            if (accepted.isEmpty()) {
                final int max = sorted.values().iterator().next();
                Optional<String> first = sorted.entrySet().stream()
                        .filter(e -> e.getValue() == max && !endsWith(e.getKey(), DATE_DEFAULT_SUFFIX))
                        .map(Map.Entry::getKey)
                        .findFirst();
                if (first.isPresent()) {
                    return date.setValue(first.get()).build();
                }

                return date.setValue(sorted.keySet().iterator().next()).build();
            }

            if (accepted.size() == 1) {
                return date.setValue(accepted.get(0)).build();
            } else {
                final Optional<String> first = accepted.stream()
                        .filter(d -> !endsWith(d, DATE_DEFAULT_SUFFIX))
                        .findFirst();
                if (first.isPresent()) {
                    return date.setValue(first.get()).build();
                }

                return date.build();
            }

            //1st non YYYY-01-01 is returned
        } else {
            if (sorted.size() == 2) {
                for (Map.Entry<String, Integer> e : sorted.entrySet()) {
                    if (!endsWith(e.getKey(), DATE_DEFAULT_SUFFIX)) {
                        return date.setValue(e.getKey()).build();
                    }
                }
            }

            // none of the dates seems good enough, return the 1st one
            return date.setValue(sorted.keySet().iterator().next()).build();
        }
    }

    private static boolean inRange(final String date) {
        final int year = Integer.parseInt(substringBefore(date, "-"));
        return year >= YEAR_LB && year <= YEAR_UB;
    }

}
