/**
 * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gravitee.gateway.services.healthcheck.verticle;

import io.gravitee.common.event.Event;
import io.gravitee.common.event.EventListener;
import io.gravitee.common.event.EventManager;
import io.gravitee.common.util.ChangeListener;
import io.gravitee.common.util.ObservableSet;
import io.gravitee.definition.model.Endpoint;
import io.gravitee.definition.model.services.schedule.Trigger;
import io.gravitee.gateway.handlers.api.definition.Api;
import io.gravitee.gateway.reactor.Reactable;
import io.gravitee.gateway.reactor.ReactorEvent;
import io.gravitee.gateway.services.healthcheck.EndpointHealthcheckResolver;
import io.gravitee.gateway.services.healthcheck.EndpointRule;
import io.gravitee.gateway.services.healthcheck.http.HttpEndpointRuleHandler;
import io.gravitee.gateway.services.healthcheck.reporter.StatusReporter;
import io.gravitee.gateway.services.healthcheck.rule.EndpointRuleHandler;
import io.gravitee.node.api.Node;
import io.gravitee.plugin.alert.AlertEventProducer;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.*;

/**
 * @author David BRASSELY (david.brassely at graviteesource.com)
 * @author Azize ELAMRANI (azize.elamrani at graviteesource.com)
 * @author GraviteeSource Team
 */
public class EndpointHealthcheckVerticle extends AbstractVerticle implements EventListener<ReactorEvent, Reactable> {

    private final static Logger LOGGER = LoggerFactory.getLogger(EndpointHealthcheckVerticle.class);

    @Autowired
    private EventManager eventManager;

    @Autowired
    private StatusReporter statusReporter;

    @Autowired
    private EndpointHealthcheckResolver endpointResolver;

    @Autowired
    private AlertEventProducer alertEventProducer;

    @Autowired
    private Node node;

    private final Map<Api, List<EndpointRuleTrigger>> apiTimers = new HashMap<>();

    @Override
    public void start(final Future<Void> startedResult) {
        eventManager.subscribeForEvents(this, ReactorEvent.class);
    }

    @Override
    public void onEvent(Event<ReactorEvent, Reactable> event) {
        final Api api = (Api) event.content();

        switch (event.type()) {
            case DEPLOY:
                startHealthCheck(api);
                break;
            case UNDEPLOY:
                stopHealthCheck(api);
                break;
            case UPDATE:
                stopHealthCheck(api);
                startHealthCheck(api);
                break;
        }
    }

    private void startHealthCheck(Api api) {
        api.getProxy().getGroups()
                .stream()
                .filter(group -> group.getEndpoints() != null)
                .forEach(group -> {
                    final Set<Endpoint> endpoints = group.getEndpoints();
                    if (endpoints instanceof ObservableSet) {
                        apiTimers.put(api, new ArrayList<>());
                        ((ObservableSet) endpoints).addListener(new EndpointsListener(api));
                    }
                });

        // Configure triggers on resolved API endpoints
        final List<EndpointRule> healthCheckEndpoints = endpointResolver.resolve(api);
        if (!healthCheckEndpoints.isEmpty()) {
            LOGGER.info("Health-check for API id[{}] name[{}] is enabled", api.getId(), api.getName());
            apiTimers.put(api, new ArrayList<>());
            healthCheckEndpoints.forEach(rule -> addTrigger(api, rule));
        }
    }

    private void stopHealthCheck(Api api) {
        removeTriggers(api);
    }

    private long getDelayMillis(Trigger trigger) {
        switch (trigger.getUnit()) {
            case MILLISECONDS:
                return trigger.getRate();
            case SECONDS:
                return trigger.getRate() * 1000;
            case MINUTES:
                return trigger.getRate() * 1000 * 60;
            case HOURS:
                return trigger.getRate() * 1000 * 60 * 60;
        }

        return -1;
    }

    private void addTrigger(Api api, EndpointRule rule) {
        EndpointRuleHandler runner = rule.createRunner(vertx, rule);
        runner.setStatusHandler(statusReporter);
        runner.setAlertEventProducer(alertEventProducer);
        runner.setNode(node);

        long timerId = vertx.setPeriodic(getDelayMillis(rule.trigger()), runner);
        apiTimers.get(api).add(new EndpointRuleTrigger(timerId, rule.endpoint()));

        LOGGER.info("Add health-check trigger id[{}] for endpoint name[{}] target[{}] each rate[{}] unit[{}]",
                timerId,
                rule.endpoint().getName(), rule.endpoint().getTarget(),
                rule.trigger().getRate(), rule.trigger().getUnit());
    }

    private void removeTriggers(Api api) {
        List<EndpointRuleTrigger> triggers = apiTimers.remove(api);
        if (triggers != null) {
            LOGGER.info("Stop health-check for API id[{}] name[{}]", api.getId(), api.getName());
            triggers.forEach(trigger -> vertx.cancelTimer(trigger.getTimerId()));
        }
    }

    private void removeTrigger(Api api, Endpoint endpoint) {
        List<EndpointRuleTrigger> endpointRuleTriggers = apiTimers.get(api);
        if (endpointRuleTriggers != null) {
            Optional<EndpointRuleTrigger> endpointRuleTrigger = endpointRuleTriggers
                    .stream()
                    .filter(trigger -> trigger.getEndpoint().equals(endpoint)).findFirst();

            endpointRuleTrigger.ifPresent(trigger -> {
                LOGGER.info("Remove health-check trigger id[{}] for endpoint name[{}] type[{}] target[{}]",
                        trigger.getTimerId(),
                        endpoint.getName(), endpoint.getType(), endpoint.getTarget());
                vertx.cancelTimer(trigger.getTimerId());
                endpointRuleTriggers.remove(trigger);
            });
        }
    }

    private class EndpointsListener implements ChangeListener<Endpoint> {

        private final Api api;

        EndpointsListener(Api api) {
            this.api = api;
        }

        @Override
        public boolean preAdd(Endpoint endpoint) {
            return false;
        }

        @Override
        public boolean postAdd(Endpoint endpoint) {
            EndpointRule rule = endpointResolver.resolve(api, endpoint);
            if (rule != null) {
                addTrigger(api, rule);
            }
            return false;
        }

        @Override
        public boolean preRemove(Endpoint endpoint) {
            return false;
        }

        @Override
        public boolean postRemove(Endpoint endpoint) {
            removeTrigger(api, endpoint);
            return false;
        }
    }

    private class EndpointRuleTrigger {
        private final long timerId;
        private final Endpoint endpoint;

        EndpointRuleTrigger(long timerId, Endpoint endpoint) {
            this.timerId = timerId;
            this.endpoint = endpoint;
        }

        long getTimerId() {
            return timerId;
        }

        Endpoint getEndpoint() {
            return endpoint;
        }
    }
}

@@ -4694,32 +4694,119 @@
 1000 * 60 * 60;%0A
+            case DAYS:%0A                return trigger.getRate() * 1000 * 60 * 60 * 24;%0A
         %7D%0A%0A     

handle DAYS time unit for the trigger