/*
 * Copyright 2009-2017 Aconex
 *
 * 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.pcp.parfait;

import static tec.uom.se.AbstractUnit.ONE;

import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.measure.Unit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;

/**
 * Monitors the value returned by calls at the provided interval to the provided
 * {@link Supplier}.
 */
public class PollingMonitoredValue<T> extends SettableValue<T> {
    private static final Logger LOG = LoggerFactory.getLogger("parfait.polling");

	/**
	 * The minimum time in ms that may be specified as an updateInterval.
	 */
	private static final int MIN_UPDATE_INTERVAL = 250;

	private static final Timer POLLING_TIMER = new Timer(
			"PollingMonitoredValue-poller", true);
	private static Scheduler SHARED_TIMER_SCHEDULER = new TimerScheduler(
			POLLING_TIMER);

    private final Supplier<T> poller;

	/**
	 * All timer tasks that have been scheduled in PollingMonitoredValues;
	 * useful only for testing.
	 */
	private static final List<TimerTask> SCHEDULED_TASKS = new CopyOnWriteArrayList<TimerTask>();

    /**
     * Creates a new {@link PollingMonitoredValue} with the specified polling
     * interval.
     *
     * @param updateInterval
     *            how frequently the Poller should be checked for updates (may
     *            not be less than {@link #MIN_UPDATE_INTERVAL}
     */
    public PollingMonitoredValue(String name, String description,
            MonitorableRegistry registry, int updateInterval, Supplier<T> poller, ValueSemantics semantics) {
        this(name, description, registry, updateInterval, poller, semantics, ONE);
    }

    /**
     * Creates a new {@link PollingMonitoredValue} with the specified polling
     * interval.
     *
     * @param updateInterval
     *            how frequently the Poller should be checked for updates (may
     *            not be less than {@link #MIN_UPDATE_INTERVAL}
     */
    public PollingMonitoredValue(String name, String description, MonitorableRegistry registry, int updateInterval,
            Supplier<T> poller, ValueSemantics semantics, Unit<?> unit) {
    	this(name, description, registry, updateInterval, poller, semantics, unit, SHARED_TIMER_SCHEDULER);
    }

	/**
	 * Creates a new {@link PollingMonitoredValue} with the specified polling
	 * interval.
	 * 
	 * @param updateInterval
	 *            how frequently the Poller should be checked for updates (may
	 *            not be less than {@link #MIN_UPDATE_INTERVAL}
	 */
	public PollingMonitoredValue(String name, String description,
			MonitorableRegistry registry, int updateInterval, Supplier<T> poller,
			ValueSemantics semantics, Unit<?> unit, Scheduler scheduler) {
		super(name, description, registry, poller.get(), unit, semantics);
		this.poller = poller;
		Preconditions.checkState(updateInterval >= MIN_UPDATE_INTERVAL,
				"updateInterval is too short.");
		TimerTask task = new PollerTask();
		SCHEDULED_TASKS.add(task);
		scheduler.schedule(new PollerTask(), updateInterval);
	}

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this).add("name", getName()).add("description", getDescription()).add("poller", poller).toString();
    }


    private class PollerTask extends TimerTask {
        @Override
        public void run() {
            try {
                set(poller.get());
            } catch (Throwable t) {
                LOG.error("Error running poller " + this + "; will rerun next cycle", t);
            }
        }
    }
    
	@VisibleForTesting
	static void runAllTasks() {
		for (TimerTask task : SCHEDULED_TASKS) {
			task.run();
		}
	}

	/**
	 * Convenient factory method to create pollers you don't care about keeping
	 * – that is, pollers which should be registered and start updating their
	 * value, but which you don't need to hold a reference to (because you will
	 * presumably just be modifying the polled source).
	 */
	public static <T> void poll(String name, String description,
			MonitorableRegistry registry, int updateInterval, Supplier<T> poller,
			ValueSemantics semantics, Unit<?> unit) {
		new PollingMonitoredValue<T>(name, description, registry,
				updateInterval, poller, semantics, unit);
	}
}
