package com.metacodestudio.hotsuploader.files;

import com.google.gson.Gson;
import com.metacodestudio.hotsuploader.providers.Provider;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Paths;
import java.nio.file.WatchService;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;

public class FileHandler extends Service<ReplayFile> {

    public static final String ACCOUNT_FOLDER_FILTER = "(\\d+[^A-Za-z,.\\-()\\s])";
    public static final String HOTS_ACCOUNT_FILTER = "(\\d-Hero-\\d-\\d{6})";
    private final List<File> watchDirectories;
    private WatchService watchService;
    private Map<Status, ObservableList<ReplayFile>> fileMap;
    private List<Provider> providers = Provider.getAll();
    private BlockingQueue<ReplayFile> uploadQueue;
    private final Gson gson;

    public FileHandler(final File root) throws IOException {
        gson = new Gson();
        uploadQueue = new ArrayBlockingQueue<>(2500);
        fileMap = new HashMap<>();
        watchDirectories = getWatchDirectories(root);
        watchDirectories.forEach(System.out::println);

        watchService = FileSystems.getDefault().newWatchService();
        watchDirectories.stream().map(file -> Paths.get(file.toString())).forEach(path -> {
            try {
                path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
        registerInitial();
    }

    @SuppressWarnings("unchecked")
    private void registerInitial() {
        List<ReplayFile> fileList = watchDirectories.stream()
                .map(ReplayFile::fromDirectory)
                .flatMap(List::stream)
                .collect(Collectors.toList());
        fileMap = fileList.stream()
                .map(file -> {
                    File propertiesFile = getPropertiesFile(file.getFile());
                    try {
                        if (propertiesFile.exists()) {
                            file.addStatuses(ReplayUtils.fromJson(gson.fromJson(FileUtils.readFileToString(propertiesFile), List.class)));
                        } else {
                            file.addStatuses(providers.stream()
                                    .map(UploadStatus::new)
                                    .collect(Collectors.toList()));
                            FileUtils.write(propertiesFile, gson.toJson(file.getUploadStatuses()));
                        }
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                    if (file.getStatus() == Status.NEW) {
                        uploadQueue.add(file);
                    }

                    return file;
                }).collect(Collectors.groupingBy(ReplayFile::getStatus,
                ConcurrentHashMap::new,
                Collectors.toCollection(FXCollections::observableArrayList)));;

        System.out.println(fileMap);
    }

    private File getPropertiesFile(final File file) {
        String propertiesFileName = file.toString().replaceAll(".StormReplay", "") + ".json";
        return new File(propertiesFileName);
    }

    public void updateFile(ReplayFile file) throws IOException {
        File propertiesFile = getPropertiesFile(file.getFile());
        Gson gson = new Gson();
        String data = gson.toJson(file.getUploadStatuses());
        FileUtils.write(propertiesFile, data);
    }

    private List<File> getWatchDirectories(final File root) {
        List<File> hotsAccounts = new ArrayList<>();
        File[] files = root.listFiles((dir, name) -> name.matches(ACCOUNT_FOLDER_FILTER));

        for (final File file : files) {
            File[] hotsFolders = file.listFiles((dir, name) -> name.matches(HOTS_ACCOUNT_FILTER));
            Arrays.stream(hotsFolders)
                    .map(folder -> new File(folder, "Replays"))
                    .map(folder -> new File(folder, "Multiplayer"))
                    .forEach(hotsAccounts::add);
        }
        return hotsAccounts;
    }

    @Override
    protected Task<ReplayFile> createTask() {
        try {
            ReplayFile take = uploadQueue.take();
            UploadTask uploadTask = new UploadTask(providers, take);
            final Status oldStatus = take.getStatus();

            uploadTask.setOnSucceeded(event -> {
                try {
                    ReplayFile replayFile = uploadTask.get();
                    Status status = replayFile.getStatus();
                    ObservableList<ReplayFile> replayFiles = fileMap.get(oldStatus);
                    if(replayFiles != null) {
                        replayFiles.remove(replayFile);
                    }
                    replayFiles = fileMap.get(replayFile.getStatus());
                    if(replayFiles == null) {
                        replayFiles = FXCollections.observableArrayList();
                        fileMap.put(status, replayFiles);
                        replayFiles.add(replayFile);
                    }
                    replayFiles.add(replayFile);
                    updateFile(replayFile);
                } catch (InterruptedException | ExecutionException | IOException e) {
                    e.printStackTrace();
                }
            });
            return uploadTask;
        } catch (InterruptedException e) {
            return null;
        }
    }

    public Map<Status, ObservableList<ReplayFile>> getFileMap() {
        return fileMap;
    }
}

@@ -2015,24 +2015,156 @@
 rInitial();%0A
+        WatchHandler watchHandler = new WatchHandler(watchService, fileMap, uploadQueue);%0A        new Thread(watchHandler).start();%0A
     %7D%0A%0A    @
@@ -4807,24 +4807,95 @@
 ateTask() %7B%0A
+        if(uploadQueue.isEmpty()) %7B%0A            return null;%0A        %7D%0A
         try 
@@ -6263,11 +6263,86 @@
 p;%0A    %7D
+%0A%0A    public boolean isIdle() %7B%0A        return uploadQueue.isEmpty();%0A    %7D
 %0A%7D%0A

enable idle check and watch handler