/*******************************************************************************
 * Copyright (c) 2012-2017 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.api.core.util;

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

import java.util.concurrent.TimeUnit;

/**
 * It controls the time of {@code Cancellable} invocation and if time if greater than timeout it terminates such {@code Cancellable}.
 *
 * @author <a href="mailto:andrew00x@gmail.com">Andrey Parfonov</a>
 */
public final class Watchdog implements Runnable {
    private static final Logger LOG = LoggerFactory.getLogger(Watchdog.class);

    private final String name;
    private final long   timeout;

    private boolean     watch;
    private Cancellable cancellable;

    /**
     * Create new {@code Watchdog}.
     *
     * @param name
     *         name for background {@code Thread}. It helps to identify out threads. This parameter is optional and may be {@code null}.
     * @param timeout
     *         timeout
     * @param unit
     *         timeout unit
     */
    public Watchdog(String name, long timeout, TimeUnit unit) {
        this.name = name;
        if (timeout < 1) {
            throw new IllegalArgumentException(String.format("Invalid timeout: %d", timeout));
        }
        this.timeout = unit.toMillis(timeout);
    }

    public Watchdog(long timeout, TimeUnit unit) {
        this(null, timeout, unit);
    }

    /**
     * Start watching {@code Cancellable}.
     *
     * @param cancellable
     *         Cancellable
     */
    public synchronized void start(Cancellable cancellable) {
        this.cancellable = cancellable;
        this.watch = true;
        final Thread t = name == null ? new Thread(this) : new Thread(this, name);
        t.setDaemon(true);
        t.start();
    }

    /** Stop watching. */
    public synchronized void stop() {
        watch = false;
        notify();
    }

    /** NOTE: Not expected to call directly by regular users of this class. */
    public synchronized void run() {
        final long end = System.currentTimeMillis() + timeout;
        long now;
        while (watch && (end > (now = System.currentTimeMillis()))) {
            try {
                wait(end - now);
            } catch (InterruptedException ignored) {
                // Not expected to be thrown
            }
        }
        if (watch) {
            try {
                cancellable.cancel();
            } catch (Exception e) {
                LOG.error(e.getMessage(), e);
            }
            watch = false;
        }
    }
}
