package lukazitnik.jshint;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;
import org.netbeans.api.editor.EditorRegistry;
import org.netbeans.editor.Utilities;
import org.netbeans.modules.editor.NbEditorDocument;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.openide.modules.ModuleInstall;
import org.openide.text.Line;

public class Installer extends ModuleInstall {

    @Override
    public void restored() {
        PropertyChangeListener pcl = new PropertyChangeListener() {

            class JSHintAnnotator implements DocumentListener {

                private final LinkedList<JSHintAnnotation> attachedAnnotations = new LinkedList<>();
                private final NbEditorDocument d;

                public JSHintAnnotator(NbEditorDocument d) {
                    this.d = d;
                }

                @Override
                public void insertUpdate(DocumentEvent de) {
                    updateAnnotations();
                }

                @Override
                public void removeUpdate(DocumentEvent de) {
                    updateAnnotations();
                }

                @Override
                public void changedUpdate(DocumentEvent de) {
                    updateAnnotations();
                }

                private void updateAnnotations() {
                    Thread thread = new Thread() {

                        @Override
                        public void run() {
                            JSHint jshint = JSHint.instance;
                            final List<JSHintError> errors = jshint.lint(d);

                            SwingUtilities.invokeLater(new Runnable() {
                                public void run() {
                                    detachAnnotations();
                                    attachAnnotations(errors);
                                }
                            });
                        }

                        private void detachAnnotations() {
                            for (JSHintAnnotation annotation : attachedAnnotations) {
                                annotation.detach();
                            }
                            attachedAnnotations.clear();
                        }

                        private void attachAnnotations(List<JSHintError> errors) {
                            for (JSHintError error : errors) {

                                // Line indexes start from 0, while line numbers start from 1
                                Integer offset = Utilities.getRowStartFromLineOffset(d, error.getLine() - 1);

                                Line line = NbEditorUtilities.getLine(d, offset, false);
                                JSHintAnnotation annotation = new JSHintAnnotation(error);

                                annotation.attach(line);
                                attachedAnnotations.add(annotation);
                            }
                        }
                    };

                    thread.start();
                }
            }

            HashMap<NbEditorDocument, JSHintAnnotator> history = new HashMap<>();

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                JTextComponent jtc = EditorRegistry.lastFocusedComponent();

                if (jtc == null) {
                    return;
                }

                NbEditorDocument focusedDocument = (NbEditorDocument) jtc.getDocument();

                if (!NbEditorUtilities.getFileObject(focusedDocument).getMIMEType().equals("text/javascript")) {
                    return;
                }

                JSHintAnnotator annotator;

                switch (evt.getPropertyName()) {
                    case EditorRegistry.FOCUS_GAINED_PROPERTY:
                        annotator = new JSHintAnnotator(focusedDocument);
                        if (!history.containsKey(focusedDocument)) {
                            history.put(focusedDocument, annotator);
                            annotator.updateAnnotations();
                        }
                        focusedDocument.addDocumentListener(annotator);
                        break;
                    case EditorRegistry.FOCUS_LOST_PROPERTY:
                        annotator = history.get(focusedDocument);
                        focusedDocument.removeDocumentListener(annotator);
                        break;
                    case EditorRegistry.COMPONENT_REMOVED_PROPERTY:
                        List<NbEditorDocument> openedDocuments = new ArrayList<>();
                        for (JTextComponent component : EditorRegistry.componentList()) {
                            openedDocuments.add((NbEditorDocument) component.getDocument());
                        }
                        Iterator<NbEditorDocument> it = history.keySet().iterator();
                        while (it.hasNext()) {
                            NbEditorDocument historicalDocument = it.next();
                            if (!openedDocuments.contains(historicalDocument)) {
                                annotator = history.get(historicalDocument);
                                annotator.updateAnnotations();
                                historicalDocument.removeDocumentListener(annotator);
                                it.remove();
                            }
                        }
                        break;
                }
            }
        };

        EditorRegistry.addPropertyChangeListener(pcl);
    }

}

@@ -187,37 +187,8 @@
 or;%0A
-import java.util.LinkedList;%0A
 impo
@@ -316,32 +316,78 @@
 cumentListener;%0A
+import javax.swing.text.BadLocationException;%0A
 import javax.swi
@@ -457,16 +457,99 @@
 gistry;%0A
+import org.netbeans.editor.AnnotationDesc;%0Aimport org.netbeans.editor.Annotations;%0A
 import o
@@ -750,17 +750,23 @@
 ide.
-text.Line
+util.Exceptions
 ;%0A%0Ap
@@ -993,109 +993,8 @@
  %7B%0A%0A
-                private final LinkedList%3CJSHintAnnotation%3E attachedAnnotations = new LinkedList%3C%3E();%0A
     
@@ -2074,22 +2074,22 @@
         
-detach
+remove
 Annotati
@@ -2132,21 +2132,18 @@
        a
-ttach
+dd
 Annotati
@@ -2285,22 +2285,97 @@
 te void 
-detach
+removeAnnotations() %7B%0A                            Annotations annotations = d.get
 Annotati
@@ -2371,34 +2371,33 @@
 getAnnotations()
- %7B
+;
 %0A               
@@ -2418,60 +2418,210 @@
 or (
-JSHintAnnotation annotation : attachedAnnotations) %7B
+int line = 0; annotations.getNextLineWithAnnotation(line) != -1;) %7B%0A                                AnnotationDesc annotationDesc = annotations.getAnnotation(line, %22lukazitnik-jshint-jshintannotation%22);
 %0A   
@@ -2641,32 +2641,36 @@
                 
+if (
 annotation.detac
@@ -2667,105 +2667,256 @@
 tion
-.detach();%0A                            %7D%0A                            attachedAnnotations.clear();
+Desc == null) %7B%0A                                    ++line;%0A                                    continue;%0A                                %7D%0A%0A                                annotations.removeAnnotation(annotationDesc);%0A                            %7D
 %0A   
@@ -2977,21 +2977,18 @@
 e void a
-ttach
+dd
 Annotati
@@ -3292,97 +3292,8 @@
 );%0A%0A
-                                Line line = NbEditorUtilities.getLine(d, offset, false);%0A
     
@@ -3416,101 +3416,277 @@
     
-annotation.attach(line);%0A                                attachedAnnotations.add(annotation);
+try %7B%0A                                    d.addAnnotation(d.createPosition(offset), 0, annotation);%0A                                %7D catch (BadLocationException ex) %7B%0A                                    Exceptions.printStackTrace(ex);%0A                                %7D
 %0A   

stop keeping track of attached annotations