package cz.senslog.analytics.module;

import cz.senslog.analytics.domain.*;
import cz.senslog.analytics.repository.ConfigurationRepository;
import cz.senslog.analytics.utils.validator.ThresholdChecker;
import cz.senslog.analytics.module.api.Module;
import cz.senslog.analytics.module.api.ModuleDescriptor;
import cz.senslog.analytics.module.api.SimpleModule;
import cz.senslog.analytics.utils.validator.Validator;
import cz.senslog.analytics.utils.Tuple;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Stream;

import static java.util.Collections.emptyMap;

public class ObservationReceiverModule extends SimpleModule {

    private static final Logger logger = LogManager.getLogger(ObservationReceiverModule.class);


    private static final String MODULE_NAME = "RECEIVE";
    private static final String DEFAULT_SOURCE_NAME = "unknown";

    private final ConfigurationRepository repo;

    private Map<Sensor, List<Group>> sensorToGroupMap;
    private ThresholdChecker<Observation> thresholdChecker;

    private static final Validator<Observation> VALIDATOR;

    static {
        VALIDATOR = Validator.<Observation>create()
                .addMapping(AttributeName.VAL, o -> o::value);
    }

    private AtomicInteger counter = new AtomicInteger(1);

    public ObservationReceiverModule(ConfigurationRepository repo) {
        this.repo = repo;
        this.sensorToGroupMap = emptyMap();
        this.thresholdChecker = ThresholdChecker.disabled();
    }

    @Override
    public void configure() {
        repo.loadSensorToGroupsMapping()
                .onSuccess(data -> sensorToGroupMap = data)
                .onFailure(logger::catching);

        repo.loadSensorThresholds()
                .onSuccess(data -> thresholdChecker = new ThresholdChecker<>(data, VALIDATOR, this::notifyIfViolated))
                .onFailure(logger::catching);
    }

    private void notifyIfViolated(ViolationReport report) {
        Optional<Sensor> sourceOpt = sensorToGroupMap.keySet().stream().filter(s -> s.id() == report.sourceId()).findFirst();
        String sourceName = sourceOpt.map(s -> String.format("%d/%d", s.unitId(), s.sensorId())).orElse(DEFAULT_SOURCE_NAME);
        notify(new ThresholdViolationNotification(MODULE_NAME, report.sourceId(), sourceName, report.violatedData(), report.timestamp()));
    }

    @Override
    public void run() {
        // consume -> flatMap -> thsChecker -> filter(collector_type != null) -> publish
        //-> consume -> map(collector_type == null) -> thsChecker -> map(collector_type != null) -> publish
        eventBus().<Observation>consumer(id(), h -> Stream.of(h.body())
                .flatMap(mappingToGroups())
                .filter(o -> thresholdChecker.check().test(o.getItem2()))
                .forEach(o -> o.getItem1().ifPresent(mId -> eventBus().publish(mId, o.getItem2())))
        );
        
//        eventBus().<Observation>consumer(id(), v -> Stream.of(v.body())
//                .filter(o -> sensorToGroupMap.containsKey(o.source()))
//                .filter(thresholdChecker.check())
//                .flatMap(mapToGroups())
//                .forEach(t -> eventBus().publish(t.getItem1(), t.getItem2())));
    }

    private Function<Observation, Stream<Tuple<Optional<String>, Observation>>> mappingToGroups() {
        return o -> {
//            System.out.println(counter.getAndIncrement());
            if (sensorToGroupMap.containsKey(o.source())) {
                return sensorToGroupMap.get(o.source()).stream()
                        .map(g -> Tuple.of(collectorTypeToId(g), new Observation(new Sensor(o.source(), g.id()), o.value(), o.timestamp())));
            } else {
                return Stream.empty();
            }
        };
    }

//    private Function<Observation, Stream<Tuple<Optional<String>, Observation>>> mapToGroups() {
//        return o -> sensorToGroupMap.getOrDefault(o.source(), emptyList()) // TODO map directly to thresholdId
//                .stream()
//                .map(g -> Tuple.of(
//                        collectorTypeToId(g),
//                        Observation.of(new Sensor(o.source(), g.id()), o.value(), o.timestamp()))
//                ).filter(t -> t.getItem1().isPresent()).map(t -> Tuple.of(t.getItem1().get(), t.getItem2()));
//    }

    private static Optional<String> collectorTypeToId(Group g) {
        ModuleDescriptor descriptor = Module.collectorOf(g.type());
        return descriptor != null ? Optional.of(descriptor.id()) : Optional.empty();
    }
}