package cz.senslog.analytics.utils.validator;

import cz.senslog.analytics.domain.AttributeName;
import cz.senslog.analytics.domain.Threshold;
import cz.senslog.analytics.domain.ThresholdRule;
import cz.senslog.analytics.domain.ValidationResult;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

public class Validator<T> {

    private static final Map<String, 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(String 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(ThresholdRule rule, Double value) {
        if (rule == null || value == null) return false;
        return checkThresholdValue(rule.mode(), value, rule.value());
    }

    public interface AttributeMapping<T> {
        Validator<T> addMapping(AttributeName 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(AttributeName property, Function<T, Supplier<Double>> getter) {
                validator.map.put(property, getter);
                return validator;
            }
        };
    }

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

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

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

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

    public static <T> ValidationResult validate(T object, Threshold threshold, Map<AttributeName, Function<T, Supplier<Double>>> attributeMapping) {
        Double value = attributeMapping.getOrDefault(threshold.attribute(), (o) -> () -> null).apply(object).get();
        ValidationResult result = new ValidationResult(threshold.attribute(), value);
        for (ThresholdRule rule : threshold.rules()) {
            if (checkThresholdValue(rule, value)) {
                result.addRecord(rule.mode(), rule.value());
            }
        }
        return result;
    }
}