package cz.senslog.analytics.utils.validator;

import cz.senslog.analytics.domain.*;
import cz.senslog.analytics.domain.ComparisonOperator;
import cz.senslog.analytics.utils.validator.domain.ValidationResult;

import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

import static cz.senslog.analytics.domain.ComparisonOperator.*;
import static java.util.Collections.emptyList;

public class Validator<T> {

    private static final Map<ComparisonOperator, BiFunction<Double, Double, Boolean>> functions;

    static {
        functions = new HashMap<>(6);
        functions.put(GT, (val, ths) -> val > ths);
        functions.put(GE, (val, ths) -> val >= ths);
        functions.put(LT, (val, ths) -> val < ths);
        functions.put(LE, (val, ths) -> val <= ths);
        functions.put(EQ, Double::equals);
        functions.put(NE, (val, ths) -> !val.equals(ths));
    }

    public static boolean checkThresholdValue(ComparisonOperator mode, Double value, Double threshold) {
        if (mode == null || value == null || threshold == null) return false;
        return functions.getOrDefault(mode, (val, ths) -> false).apply(value, threshold);
    }

    public static boolean checkThresholdValue(ThresholdDimensionRule rule, Double value) {
        if (rule == null || value == null) return false;
        return checkThresholdValue(rule.comparisonOperator(), value, rule.value());
    }

    public interface AttributeMapping<T> {
        Validator<T> addMapping(AttributeType property, Function<T, Supplier<Double>> getter);
    }


    public static <T> AttributeMapping<T> create() {
        return new AttributeMapping<>() {
            private final Validator<T> validator = new Validator<>();
            @Override
            public Validator<T> addMapping(AttributeType property, Function<T, Supplier<Double>> getter) {
                validator.map.put(property, getter);
                return validator;
            }
        };
    }

    private final Map<AttributeType, Function<T, Supplier<Double>>> map;

    private Validator() {
        this.map = new HashMap<>();
    }

    public Validator<T> addMapping(AttributeType property, Function<T, Supplier<Double>> getter) {
        map.put(property, getter);
        return this;
    }

    public ValidationResult[] validate(T object, Threshold threshold) {
        return validate(object, threshold, map);
    }

    private static <T> ValidationResult validate(T object, ThresholdDimension dimension, Map<AttributeType, Function<T, Supplier<Double>>> attributeMapping) {
        final Double testingValue = attributeMapping.getOrDefault(dimension.attributeType(), (o) -> () -> null).apply(object).get();
        final List<ValidationResult.Rule> violatedRules = new ArrayList<>(dimension.rules().size());
        boolean passed = true;
        for (ThresholdDimensionRule rule : dimension.rules()) {
            passed &= checkThresholdValue(rule, testingValue);
            violatedRules.add(new ValidationResult.Rule(rule.comparisonOperator(), rule.value()));
        }
        return new ValidationResult(dimension.attributeType(), testingValue, passed ? violatedRules : emptyList());
    }

    private static <T> ValidationResult[] validate(T object, Threshold threshold, Map<AttributeType, Function<T, Supplier<Double>>> attributeMapping) {
        final ValidationResult[] results = new ValidationResult[threshold.thresholdDimensions().size()];
        int resultIndex = 0;
        for (ThresholdDimension dimension : threshold.thresholdDimensions()) {
            results[resultIndex++] = validate(object, dimension, attributeMapping);
        }
        return results;
    }
}