package org.plantuml.idea.lang.annotator;

import com.google.common.base.Joiner;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.lang.annotation.Annotation;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.ExternalAnnotator;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.util.IncorrectOperationException;
import net.sourceforge.plantuml.syntax.SyntaxChecker;
import net.sourceforge.plantuml.syntax.SyntaxResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.plantuml.idea.lang.settings.PlantUmlSettings;
import org.plantuml.idea.plantuml.PlantUml;

import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Author: Eugene Steinberg
 * Date: 9/13/14
 */
public class PlantUmlExternalAnnotator extends ExternalAnnotator<PsiFile, Map<Integer, SyntaxResult>> {
    @Nullable
    @Override
    public PsiFile collectInformation(@NotNull PsiFile file) {
        return file;
    }

    @Nullable
    @Override
    public Map<Integer, SyntaxResult> doAnnotate(PsiFile collectedInfo) {
        Map<Integer, SyntaxResult> result = new LinkedHashMap<Integer, SyntaxResult>();
        if (PlantUmlSettings.getInstance().isErrorAnnotationEnabled()) {
            String text = collectedInfo.getFirstChild().getText();
            Map<Integer, String> sources = PlantUml.extractSources(text);
            for (Map.Entry<Integer, String> sourceData : sources.entrySet()) {
                SyntaxResult annotationResult = getAnnotationResult(sourceData.getValue());
                if (annotationResult.isError()) {
                    result.put(sourceData.getKey(), annotationResult);
                }
            }


        }
        return result;
    }

    private SyntaxResult getAnnotationResult(String source) {
        return SyntaxChecker.checkSyntax(source);
    }

    @Override
    public void apply(@NotNull PsiFile file, Map<Integer, SyntaxResult> annotationResult, @NotNull AnnotationHolder holder) {
        if (null != annotationResult) {
            Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
            if (document != null) {
                for (Map.Entry<Integer, SyntaxResult> arEntry : annotationResult.entrySet()) {
                    annotateSource(arEntry.getValue(), holder, document, arEntry.getKey());
                }

            }
        }
    }

    private void annotateSource(SyntaxResult annotationResult, AnnotationHolder holder, Document document, int baseOffset) {
        int sourceStartLineNumber = document.getLineNumber(baseOffset);
        int startoffset = document.getLineStartOffset(annotationResult.getErrorLinePosition() + sourceStartLineNumber);
        int endoffset = document.getLineEndOffset(annotationResult.getErrorLinePosition() + sourceStartLineNumber);
        TextRange range = TextRange.create(startoffset, endoffset);
        String errorMessage = Joiner.on("\n").join(annotationResult.getErrors());
        Annotation errorAnnotation = holder.createErrorAnnotation(range,
                errorMessage);
        for (String s : cleanupSuggestions(annotationResult.getSuggest())) {
            errorAnnotation.registerFix(new PlantUmlIntentionAction(startoffset, endoffset, s));
        }


    }

    private List<String> cleanupSuggestions(List<String> suggest) {
        LinkedList<String> suggestions = new LinkedList<String>(suggest);
        suggestions.remove("Did you mean:");
        return suggestions;
    }

    private class PlantUmlIntentionAction implements IntentionAction {
        private int startOffset;
        private int endOffset;
        private String suggestion;

        protected PlantUmlIntentionAction(int startOffset, int endOffset, String suggestion) {
            this.startOffset = startOffset;
            this.endOffset = endOffset;
            this.suggestion = suggestion;
        }

        @NotNull
        @Override
        public String getText() {
            return "change to '" + suggestion + "'";
        }

        @NotNull
        @Override
        public String getFamilyName() {
            return "PLANTUML_INTENTIONS";
        }

        @Override
        public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
            return true;
        }

        @Override
        public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
            editor.getDocument().replaceString(startOffset, endOffset, suggestion);
        }

        @Override
        public boolean startInWriteAction() {
            return true;
        }
    }
}

@@ -2931,50 +2931,20 @@
 int 
-startoffset = document.getLineStartOffset(
+errorLine = 
 anno
@@ -2994,33 +2994,32 @@
 eStartLineNumber
-)
 ;%0A        int en
@@ -3013,22 +3013,79 @@
        i
-nt end
+f (errorLine %3C document.getLineCount()) %7B%0A            int start
 offset =
@@ -3105,84 +3105,102 @@
 Line
-End
+Start
 Offset(
-annotationResult.getErrorLinePosition() + sourceStartLineNumber);%0A
+errorLine);%0A            int endoffset = document.getLineEndOffset(errorLine);%0A    
     
@@ -3255,32 +3255,36 @@
 et, endoffset);%0A
+    
         String e
@@ -3357,16 +3357,20 @@
         
+    
 Annotati
@@ -3426,16 +3426,20 @@
 (range,%0A
+    
         
@@ -3465,24 +3465,28 @@
 e);%0A        
+    
 for (String 
@@ -3550,24 +3550,28 @@
             
+   
+ 
 errorAnnotat
@@ -3647,26 +3647,39 @@
 ));%0A        
+    %7D%0A        
 %7D
-%0A
 %0A%0A    %7D%0A%0A   

patch for issue #69