// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.asset.monitor;

import com.hypixel.hytale.common.util.SystemUtil;
import java.util.stream.Stream;
import com.hypixel.hytale.sneakythrow.SneakyThrow;
import com.hypixel.hytale.server.core.util.io.FileUtil;
import com.sun.nio.file.ExtendedWatchEventModifier;
import com.sun.nio.file.SensitivityWatchEventModifier;
import java.util.Iterator;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.util.logging.Level;
import java.nio.file.StandardWatchEventKinds;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.util.concurrent.ConcurrentHashMap;
import java.nio.file.WatchKey;
import java.util.Map;
import java.nio.file.WatchService;
import javax.annotation.Nonnull;
import java.nio.file.Path;
import java.util.function.BiConsumer;
import java.nio.file.WatchEvent;
import com.hypixel.hytale.logger.HytaleLogger;

public class PathWatcherThread implements Runnable
{
    public static final HytaleLogger LOGGER;
    public static final boolean HAS_FILE_TREE_SUPPORT;
    public static final WatchEvent.Kind<?>[] WATCH_EVENT_KINDS;
    private final BiConsumer<Path, EventKind> consumer;
    @Nonnull
    private final Thread thread;
    private final WatchService service;
    private final Map<Path, WatchKey> registered;
    
    public PathWatcherThread(final BiConsumer<Path, EventKind> consumer) throws IOException {
        this.registered = new ConcurrentHashMap<Path, WatchKey>();
        this.consumer = consumer;
        (this.thread = new Thread(this, "PathWatcher")).setDaemon(true);
        this.service = FileSystems.getDefault().newWatchService();
    }
    
    @Override
    public final void run() {
        try {
            while (!Thread.interrupted()) {
                final WatchKey key = this.service.take();
                final Path directory = (Path)key.watchable();
                for (final WatchEvent<?> event : key.pollEvents()) {
                    final WatchEvent.Kind<?> kind = event.kind();
                    if (kind == StandardWatchEventKinds.OVERFLOW) {
                        PathWatcherThread.LOGGER.at(Level.WARNING).log("Event Overflow, Unable to detect all file changed! This may cause server instability!! More than AbstractWatchKey.MAX_EVENT_LIST_SIZE queued events (512)");
                    }
                    else {
                        final WatchEvent<Path> pathEvent = (WatchEvent<Path>)event;
                        final Path path = directory.resolve(pathEvent.context());
                        if (!PathWatcherThread.HAS_FILE_TREE_SUPPORT && pathEvent.kind() == StandardWatchEventKinds.ENTRY_CREATE && Files.isDirectory(path, new LinkOption[0])) {
                            this.addPath(path);
                        }
                        this.consumer.accept(path, EventKind.parse(pathEvent.kind()));
                    }
                }
                if (!key.reset()) {
                    break;
                }
            }
        }
        catch (final InterruptedException ignored) {
            Thread.currentThread().interrupt();
        }
        catch (final Throwable t) {
            PathWatcherThread.LOGGER.at(Level.SEVERE).withCause(t).log("Exception occurred when polling:");
        }
        PathWatcherThread.LOGGER.at(Level.INFO).log("Stopped polling for changes in assets. Server will need to be rebooted to load changes!");
        try {
            this.service.close();
        }
        catch (final IOException ex) {}
        this.registered.clear();
    }
    
    public void start() {
        this.thread.start();
    }
    
    public void shutdown() {
        this.thread.interrupt();
        try {
            this.thread.join(1000L);
            try {
                this.service.close();
            }
            catch (final IOException ex) {}
            this.registered.clear();
        }
        catch (final InterruptedException ignored) {
            Thread.currentThread().interrupt();
        }
    }
    
    public void addPath(Path path) throws IOException {
        path = path.toAbsolutePath();
        if (Files.isRegularFile(path, new LinkOption[0])) {
            path = path.getParent();
        }
        Path parent = path;
        do {
            final WatchKey keys = this.registered.get(parent);
            if (keys != null) {
                if (!PathWatcherThread.HAS_FILE_TREE_SUPPORT) {
                    this.watchPath(path);
                }
                return;
            }
        } while ((parent = parent.getParent()) != null);
        this.watchPath(path);
    }
    
    private void watchPath(@Nonnull final Path path) throws IOException {
        if (PathWatcherThread.HAS_FILE_TREE_SUPPORT) {
            this.registered.put(path, path.register(this.service, PathWatcherThread.WATCH_EVENT_KINDS, SensitivityWatchEventModifier.HIGH, ExtendedWatchEventModifier.FILE_TREE));
            PathWatcherThread.LOGGER.at(Level.FINEST).log("Register path: %s", path);
        }
        else {
            final WatchEvent.Modifier[] modifiers = { SensitivityWatchEventModifier.HIGH };
            try (final Stream<Path> stream = Files.walk(path, FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY)) {
                stream.forEach(SneakyThrow.sneakyConsumer(childPath -> {
                    if (Files.isDirectory(childPath, new LinkOption[0])) {
                        this.registered.put(childPath, childPath.register(this.service, PathWatcherThread.WATCH_EVENT_KINDS, modifiers));
                        PathWatcherThread.LOGGER.at(Level.FINEST).log("Register path: %s", childPath);
                    }
                    return;
                }));
            }
        }
        PathWatcherThread.LOGGER.at(Level.FINER).log("Watching path: %s", path);
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
        HAS_FILE_TREE_SUPPORT = (SystemUtil.TYPE == SystemUtil.SystemType.WINDOWS);
        WATCH_EVENT_KINDS = new WatchEvent.Kind[] { StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY };
    }
}
