package yukihane.inqubus.filewatch;

import static java.nio.file.StandardWatchEventKinds.*;

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author yuki
 */
public final class FileWatch implements Runnable {

    private static final Logger logger = LoggerFactory.getLogger(FileWatch.class);
    private final Set<Path> files;
    private final Set<Path> directories;

    public FileWatch(Collection<Path> dirs) {
        FileDir res = new FileDir();
        getFileDir(dirs, res);

        this.files = new HashSet<>(res.files);
        this.directories = new HashSet<>(res.dirs);
        if (logger.isDebugEnabled()) {
            logger.debug("search dirs: {}", directories);
            logger.debug("exist files: {}", files);
        }
    }

    public Set<Path> getFiles() {
        synchronized (files) {
            return new HashSet<>(files);
        }
    }

    private void getFileDir(Iterable<Path> paths, FileDir result) {
        for (Path p : paths) {
            getFileDir(p, result);
        }
    }

    private void getFileDir(Path path, FileDir result) {
        try {
            if (Files.isDirectory(path)) {
                result.dirs.add(path);
                DirectoryStream<Path> dir = Files.newDirectoryStream(path);
                getFileDir(dir, result);
            } else if (Files.isRegularFile(path)) {
                result.files.add(path);
            }
        } catch (IOException ex) {
            logger.warn("パスの処理中にエラー: {}", path);
        }
    }

    private static class FileDir {

        private final Set<Path> files = new HashSet<>();
        private final Set<Path> dirs = new HashSet<>();
    }

    @Override
    public void run() {
        logger.debug("FileWatch start");
        final Map<WatchKey, Path> keys = new HashMap<>();
        try {
            final WatchService ws = FileSystems.getDefault().newWatchService();
            for (Path p : directories) {
                final WatchKey key = p.register(ws, ENTRY_CREATE, ENTRY_DELETE);
                keys.put(key, p);
            }

            while (true) {
                final WatchKey wk = ws.take();

                final Path dir = keys.get(wk);
                if (dir == null) {
                    logger.warn("WatchKey not recognized!!");
                    continue;
                }

                for (final WatchEvent<?> event : wk.pollEvents()) {
                    final Kind<?> kind = event.kind();

                    if (kind == OVERFLOW) {
                        continue;
                    }

                    if (kind == ENTRY_CREATE) {
                        final Path p = (Path) event.context();
                        final Path target = dir.resolve(p);
                        if (Files.isRegularFile(target)) {
                            logger.debug("ファイル追加: {}", target);
                            synchronized (files) {
                                files.add(target);
                            }
                        }
                    } else if (kind == ENTRY_DELETE) {
                        final Path p = (Path) event.context();
                        final Path target = dir.resolve(p);
                        logger.debug("ファイル削除: {}", target);
                        synchronized (files) {
                            files.remove(target);
                        }
                    }
                }

                if (!wk.reset()) {
                    logger.warn("No longer valid");
                    wk.cancel();
                    ws.close();
                    break;
                }

            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}
