package org.jabref.gui.entryeditor.fileannotationtab;

import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Collectors;

import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;

import org.jabref.gui.AbstractViewModel;
import org.jabref.gui.ClipBoardManager;
import org.jabref.gui.util.DefaultTaskExecutor;
import org.jabref.gui.util.FileUpdateListener;
import org.jabref.gui.util.FileUpdateMonitor;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.pdf.FileAnnotationCache;
import org.jabref.logic.util.OS;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.pdf.FileAnnotation;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class FileAnnotationTabViewModel extends AbstractViewModel {
    private static final Log LOGGER = LogFactory.getLog(FileAnnotationTabViewModel.class);

    private final ListProperty<FileAnnotationViewModel> annotations = new SimpleListProperty<>(FXCollections.observableArrayList());
    private final ListProperty<Path> files = new SimpleListProperty<>(FXCollections.observableArrayList());
    private final ObjectProperty<FileAnnotationViewModel> currentAnnotation = new SimpleObjectProperty<>();
    private ReadOnlyBooleanProperty annotationEmpty;

    private final FileAnnotationCache cache;
    private final BibEntry entry;
    private Map<Path, List<FileAnnotation>> fileAnnotations;
    private Path currentFile;
    private final FileUpdateMonitor fileMonitor;
    private final FileUpdateListener fileListener = this::reloadAnnotations;

    public FileAnnotationTabViewModel(FileAnnotationCache cache, BibEntry entry, FileUpdateMonitor fileMonitor) {
        this.cache = cache;
        this.entry = entry;
        this.fileMonitor = fileMonitor;
        initialize();
    }

    private void initialize() {
        fileAnnotations = cache.getFromCache(entry);
        files.setAll(fileAnnotations.keySet());
    }

    public ObjectProperty<FileAnnotationViewModel> currentAnnotationProperty() {
        return currentAnnotation;
    }

    public ReadOnlyBooleanProperty isAnnotationsEmpty() {
        return annotationEmpty;
    }

    public ListProperty<FileAnnotationViewModel> annotationsProperty() {
        return annotations;
    }

    public ListProperty<Path> filesProperty() {
        return files;
    }

    public void notifyNewSelectedAnnotation(FileAnnotationViewModel newAnnotation) {
        currentAnnotation.set(newAnnotation);
    }

    public void notifyNewSelectedFile(Path newFile) {
        fileMonitor.removeListener(currentFile, fileListener);
        currentFile = newFile;

        Comparator<FileAnnotation> byPage = Comparator.comparingInt(FileAnnotation::getPage);

        List<FileAnnotationViewModel> newAnnotations = fileAnnotations.getOrDefault(currentFile, new ArrayList<>())
                .stream()
                .filter(annotation -> (null != annotation.getContent()))
                .sorted(byPage)
                .map(FileAnnotationViewModel::new)
                .collect(Collectors.toList());
        annotations.setAll(newAnnotations);
        annotationEmpty = annotations.emptyProperty();
        try {
            fileMonitor.addListenerForFile(currentFile, fileListener);
        } catch (IOException e) {
            LOGGER.error("Problem while watching file for changes " + currentFile, e);
        }
    }

    private void reloadAnnotations() {
        // Make sure to always run this in the JavaFX thread as the file monitor (and its notifications) live in a different thread
        DefaultTaskExecutor.runInJavaFXThread(() -> {
            // Remove annotations for the current entry and reinitialize annotation/cache
            cache.remove(entry);
            initialize();

            // Pretend that we just switched to the current file in order to refresh the display
            notifyNewSelectedFile(currentFile);
        });
    }

    /**
     * Copies the meta and content information of the pdf annotation to the clipboard
     */
    public void copyCurrentAnnotation() {
        if (null == getCurrentAnnotation()) {
            return;
        }
        StringJoiner sj = new StringJoiner("," + OS.NEWLINE);
        sj.add(Localization.lang("Author") + ": " + getCurrentAnnotation().getAuthor());
        sj.add(Localization.lang("Date") + ": " + getCurrentAnnotation().getDate());
        sj.add(Localization.lang("Page") + ": " + getCurrentAnnotation().getPage());
        sj.add(Localization.lang("Content") + ": " + getCurrentAnnotation().getContent());
        sj.add(Localization.lang("Marking") + ": " + getCurrentAnnotation().markingProperty().get());

        new ClipBoardManager().setClipboardContents(sj.toString());
    }

    private FileAnnotationViewModel getCurrentAnnotation() {
        return currentAnnotation.get();
    }
}

@@ -1596,16 +1596,22 @@
 private 
+final 
 ReadOnly
@@ -1641,16 +1641,46 @@
 ionEmpty
+ = annotations.emptyProperty()
 ;%0A%0A    p
@@ -2185,69 +2185,8 @@
 or;%0A
-        initialize();%0A    %7D%0A%0A    private void initialize() %7B%0A
     
@@ -2207,16 +2207,21 @@
 tions = 
+this.
 cache.ge
@@ -2231,16 +2231,21 @@
 omCache(
+this.
 entry);%0A
@@ -3478,63 +3478,9 @@
 s);%0A
-        annotationEmpty = annotations.emptyProperty();
 %0A
+
     
@@ -4062,19 +4062,102 @@
     
-initialize(
+fileAnnotations = cache.getFromCache(entry);%0A            files.setAll(fileAnnotations.keySet()
 );%0A%0A

Fix NPE