package ru.yandex.webmaster3.core.solomon.metric;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author avhaliullin
 */
public class SolomonGroupingUtil {
    public static final String AGGREGATE_LABEL_VALUE = "(all)";

    public static <BasicCounter extends SolomonCounter> Collection<BasicCounter> fetchBasicCountersForGroupings(Map<SolomonKey, BasicCounter> acc, String indicator, Collection<Set<String>> groupings, SolomonKey key, Function<SolomonKey, BasicCounter> basicCounterProvider) {
        Map<SolomonKey, BasicCounter> newBasicCounters = new HashMap<>();
        for (Set<String> includeLabels : groupings) {
            SolomonKey groupKey = key;
            for (String label : key.getLabels().keySet()) {
                if (!includeLabels.contains(label)) {
                    groupKey = groupKey.withLabel(label, AGGREGATE_LABEL_VALUE);
                }
            }
            if (indicator != null) {
                groupKey = groupKey.withLabel(SolomonKey.LABEL_INDICATOR, indicator);
            }
            newBasicCounters.put(groupKey, acc.computeIfAbsent(groupKey, basicCounterProvider));
        }
        return newBasicCounters.values();
    }

    public static <C extends SolomonCounter> SolomonCounter registerCounterWithGroupings(
            Map<SolomonKey, C> acc,
            String indicator,
            Collection<Set<String>> groupings,
            SolomonKey key,
            Function<SolomonKey, C> basicCounterProvider) {
        return new MultiCounter(new ArrayList<>(fetchBasicCountersForGroupings(acc, indicator, groupings, key, basicCounterProvider)));
    }

    public static Collection<Set<String>> generateAllGroupings(List<String> labels, boolean generateGrandTotal, boolean generateMaxResolution) {
        List<Set<String>> result = new ArrayList<>();
        result.add(Collections.emptySet());
        for (String label : labels) {
            List<Set<String>> toAdd = result.stream().map(HashSet::new).collect(Collectors.toList()); // deep copy
            toAdd.forEach(item -> item.add(label));
            result.addAll(toAdd);
        }
        if (!generateGrandTotal || !generateMaxResolution) {
            result = result.stream()
                    .filter(grouping -> (generateGrandTotal || !grouping.isEmpty()) && (generateMaxResolution || grouping.size() < labels.size()))
                    .collect(Collectors.toList());
        }
        return result;
    }

    private static class MultiCounter implements SolomonCounter {
        private final List<SolomonCounter> basicCounter;

        MultiCounter(List<SolomonCounter> basicCounter) {
            this.basicCounter = basicCounter;
        }

        @Override
        public void add(long value) {
            basicCounter.forEach(c -> c.add(value));
        }

        @Override
        public long get() {
            throw new UnsupportedOperationException();
        }
    }
}
