package cz.senslog.analytics.utils.validator;

import cz.senslog.analytics.domain.*;

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

import static cz.senslog.analytics.domain.ThresholdMode.*;

public class Validator<T> {

    private static final Map<ThresholdMode, 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(ThresholdMode 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.thresholdMode(), 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);
    }

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