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

import java.io.IOException;
import java.nio.file.*;

public class FileWatcher {
	private static final Logger log = LoggerFactory.getLogger(FileWatcher.class);

	private Thread thread;
	private WatchService watchService;

	public interface Callback {
		void run() throws Exception;
	}

	/**
	 * Starts watching a file and the given path and calls the callback when it is changed.
	 * A shutdown hook is registered to stop watching. To control this yourself, create an
	 * instance and use the start/stop methods.
	 */
	public static void onFileChange(Path file, Callback callback) throws IOException {
		FileWatcher fileWatcher = new FileWatcher();
		fileWatcher.start(file, callback);
		Runtime.getRuntime().addShutdownHook(new Thread(fileWatcher::stop));
	}

	public void start(Path file, Callback callback) throws IOException {
		watchService = FileSystems.getDefault().newWatchService();
		Path parent = file.getParent();
		parent.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE);
		log.info("Going to watch " + file);

		thread = new Thread(() -> {
			while (true) {
				WatchKey wk = null;
				try {
					wk = watchService.take();
					Thread.sleep(500); // give a chance for duplicate events to pile up
					for (WatchEvent<?> event : wk.pollEvents()) {
						Path changed = parent.resolve((Path) event.context());
						if (Files.exists(changed) && Files.isSameFile(changed, file)) {
							log.info("File change event: " + changed);
							callback.run();
							break;
						}
					}
				} catch (InterruptedException e) {
					log.info("Ending my watch");
					Thread.currentThread().interrupt();
					break;
				} catch (Exception e) {
					log.error("Error while reloading cert", e);
				} finally {
					if (wk != null) {
						wk.reset();
					}
				}
			}
		});
		thread.start();
	}

	public void stop() {
		thread.interrupt();
		try {
			watchService.close();
		} catch (IOException e) {
			log.info("Error closing watch service", e);
		}
	}

}