/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.camel.support;

import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.StaticService;
import org.apache.camel.TimerListener;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A {@link TimerListener} manager which triggers the
 * {@link org.apache.camel.TimerListener} listeners once every second.
 * <p/>
 * Also ensure when adding and remove listeners, that they are correctly removed to avoid
 * leaking memory.
 * <p/>
 * From Camel 2.13 onwards the {@link TimerListenerManager} is only enabled if
 * {@link org.apache.camel.spi.ManagementStrategy#isLoadStatisticsEnabled()} is enabled.
 *
 * @see TimerListener
 */
public class TimerListenerManager extends ServiceSupport implements Runnable, CamelContextAware, StaticService {

    private static final Logger LOG = LoggerFactory.getLogger(TimerListenerManager.class);
    private final Set<TimerListener> listeners = new LinkedHashSet<TimerListener>();
    private CamelContext camelContext;
    private ScheduledExecutorService executorService;
    private volatile ScheduledFuture<?> task;
    private long interval = 1000L;

    public TimerListenerManager() {
    }

    @Override
    public void setCamelContext(CamelContext camelContext) {
        this.camelContext = camelContext;
    }

    @Override
    public CamelContext getCamelContext() {
        return camelContext;
    }

    /**
     * Gets the interval in millis.
     * <p/>
     * The default interval is 1000 millis.
     *
     * @return interval in millis.
     */
    public long getInterval() {
        return interval;
    }

    /**
     * Sets the interval in millis.
     *
     * @param interval interval in millis.
     */
    public void setInterval(long interval) {
        this.interval = interval;
    }

    @Override
    public void run() {
        LOG.trace("Running scheduled TimerListener task");

        if (!isRunAllowed()) {
            LOG.debug("TimerListener task cannot run as its not allowed");
            return;
        }

        for (TimerListener listener : listeners) {
            try {
                LOG.trace("Invoking onTimer on {}", listener);
                listener.onTimer();
            } catch (Throwable e) {
                // ignore
                LOG.debug("Error occurred during onTimer for TimerListener: " + listener + ". This exception will be ignored.", e);
            }
        }
    }

    /**
     * Adds the listener.
     * <p/>
     * It may be important to implement {@link #equals(Object)} and {@link #hashCode()} for the listener
     * to ensure that we can remove the same listener again, when invoking remove.
     * 
     * @param listener listener
     */
    public void addTimerListener(TimerListener listener) {
        listeners.add(listener);
        LOG.debug("Added TimerListener: {}", listener);
    }

    /**
     * Removes the listener.
     * <p/>
     * It may be important to implement {@link #equals(Object)} and {@link #hashCode()} for the listener
     * to ensure that we can remove the same listener again, when invoking remove.
     *
     * @param listener listener.
     */
    public void removeTimerListener(TimerListener listener) {
        listeners.remove(listener);
        LOG.debug("Removed TimerListener: {}", listener);
    }

    @Override
    protected void doStart() throws Exception {
        ObjectHelper.notNull(camelContext, "camelContext", this);

        // create scheduled thread pool to trigger the task to run every interval
        executorService = camelContext.getExecutorServiceManager().newSingleThreadScheduledExecutor(this, "ManagementLoadTask");
        task = executorService.scheduleAtFixedRate(this, interval, interval, TimeUnit.MILLISECONDS);
        LOG.debug("Started scheduled TimerListener task to run with interval {} ms", interval);
    }

    @Override
    protected void doStop() throws Exception {
        // executor service will be shutdown by CamelContext
        if (task != null) {
            task.cancel(true);
            task = null;
        }
    }

    @Override
    protected void doShutdown() throws Exception {
        super.doShutdown();
        // shutdown thread pool when we are shutting down
        camelContext.getExecutorServiceManager().shutdownNow(executorService);
        executorService = null;
        listeners.clear();
    }
}

