/*
 * Copyright 2000-2009 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.intellij.codeInsight.daemon.impl;

import com.intellij.codeHighlighting.TextEditorHighlightingPass;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightingLevelManager;
import com.intellij.lang.ExternalLanguageAnnotators;
import com.intellij.lang.Language;
import com.intellij.lang.annotation.Annotation;
import com.intellij.lang.annotation.AnnotationSession;
import com.intellij.lang.annotation.ExternalAnnotator;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiFile;
import java.util.HashMap;
import com.intellij.util.ui.update.Update;
import javax.annotation.Nonnull;
import consulo.annotation.access.RequiredReadAction;

import java.util.*;

/**
 * @author ven
 */
public class ExternalToolPass extends TextEditorHighlightingPass {
  private final PsiFile myFile;
  private final int myStartOffset;
  private final int myEndOffset;
  private final AnnotationHolderImpl myAnnotationHolder;
  private final Editor myEditor;

  private volatile DocumentListener myDocumentListener;
  private volatile boolean myDocumentChanged;

  private final Map<ExternalAnnotator, MyData> myAnnotator2DataMap;

  private final ExternalToolPassFactory myExternalToolPassFactory;

  private static class MyData {
    final PsiFile myPsiRoot;
    final Object myCollectedInfo;
    volatile Object myAnnotationResult;

    private MyData(PsiFile psiRoot, Object collectedInfo) {
      myPsiRoot = psiRoot;
      myCollectedInfo = collectedInfo;
    }
  }

  public ExternalToolPass(@Nonnull ExternalToolPassFactory externalToolPassFactory,
                          @Nonnull PsiFile file,
                          @Nonnull Editor editor,
                          int startOffset,
                          int endOffset) {
    super(file.getProject(), editor.getDocument(), false);
    myEditor = editor;
    myFile = file;
    myStartOffset = startOffset;
    myEndOffset = endOffset;
    myAnnotationHolder = new AnnotationHolderImpl(new AnnotationSession(file));

    myAnnotator2DataMap = new HashMap<ExternalAnnotator, MyData>();
    myExternalToolPassFactory = externalToolPassFactory;
  }

  @RequiredReadAction
  @Override
  public void doCollectInformation(@Nonnull ProgressIndicator progress) {
    myDocumentChanged = false;

    final FileViewProvider viewProvider = myFile.getViewProvider();
    final Set<Language> relevantLanguages = viewProvider.getLanguages();
    for (Language language : relevantLanguages) {
      PsiFile psiRoot = viewProvider.getPsi(language);
      if (!HighlightingLevelManager.getInstance(myProject).shouldInspect(psiRoot)) continue;
      final List<ExternalAnnotator> externalAnnotators = ExternalLanguageAnnotators.allForFile(language, psiRoot);

      if (!externalAnnotators.isEmpty()) {
        DaemonCodeAnalyzerEx daemonCodeAnalyzer = DaemonCodeAnalyzerEx.getInstanceEx(myProject);
        boolean errorFound = daemonCodeAnalyzer.getFileStatusMap().wasErrorFound(myDocument);

        for(ExternalAnnotator externalAnnotator: externalAnnotators) {
          final Object collectedInfo = externalAnnotator.collectInformation(psiRoot, myEditor, errorFound);
          if (collectedInfo != null) {
            myAnnotator2DataMap.put(externalAnnotator, new MyData(psiRoot, collectedInfo));
          }
        }
      }
    }
  }

  @Override
  public void doApplyInformationToEditor() {
    DaemonCodeAnalyzerEx daemonCodeAnalyzer = DaemonCodeAnalyzerEx.getInstanceEx(myProject);
    daemonCodeAnalyzer.getFileStatusMap().markFileUpToDate(myDocument, getId());

    myDocumentListener = new DocumentListener() {
      @Override
      public void beforeDocumentChange(DocumentEvent event) {
      }

      @Override
      public void documentChanged(DocumentEvent event) {
        myDocumentChanged = true;
      }
    };
    myDocument.addDocumentListener(myDocumentListener);

    final Runnable r = new Runnable() {
      @Override
      public void run() {
        if (myDocumentChanged || myProject.isDisposed()) {
          doFinish();
          return;
        }
        doAnnotate();

        ApplicationManager.getApplication().runReadAction(new Runnable() {
          @Override
          public void run() {
            if (myDocumentChanged || myProject.isDisposed()) {
              doFinish();
              return;
            }
            collectHighlighters();

            ApplicationManager.getApplication().invokeLater(new Runnable() {
              @Override
              public void run() {
                if (myDocumentChanged || myProject.isDisposed()) {
                  doFinish();
                  return;
                }

                myDocument.removeDocumentListener(myDocumentListener);
                final List<HighlightInfo> infos = getHighlights();
                UpdateHighlightersUtil
                  .setHighlightersToEditor(myProject, myDocument, myStartOffset, myEndOffset, infos, getColorsScheme(), getId());
              }
            }, ModalityState.stateForComponent(myEditor.getComponent()));
          }
        });
      }
    };

    if (ApplicationManager.getApplication().isUnitTestMode()) {
      r.run();
    }
    else {
      myExternalToolPassFactory.scheduleExternalActivity(new Update(myFile) {
        @Override
        public void run() {
          r.run();
        }

        @Override
        public void setRejected() {
          super.setRejected();
          doFinish();
        }
      });
    }
  }

  private List<HighlightInfo> getHighlights() {
    List<HighlightInfo> infos = new ArrayList<HighlightInfo>();
    for (Annotation annotation : myAnnotationHolder) {
      infos.add(HighlightInfo.fromAnnotation(annotation));
    }
    return infos;
  }

  private void collectHighlighters() {
    for (ExternalAnnotator annotator : myAnnotator2DataMap.keySet()) {
      final MyData data = myAnnotator2DataMap.get(annotator);
      if (data != null) {
        annotator.apply(data.myPsiRoot, data.myAnnotationResult, myAnnotationHolder);
      }
    }
  }

  private void doFinish() {
    myDocument.removeDocumentListener(myDocumentListener);
    final Runnable r = new Runnable() {
      @Override
      public void run() {
        if (!myProject.isDisposed()) {
          UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, myStartOffset, myEndOffset, Collections.<HighlightInfo>emptyList(),
                                                         getColorsScheme(), getId());
        }
      }
    };
    if (ApplicationManager.getApplication().isDispatchThread()) {
      r.run();
    }
    else {
      ApplicationManager.getApplication().invokeLater(r);
    }
  }

  private void doAnnotate() {
    for (ExternalAnnotator annotator : myAnnotator2DataMap.keySet()) {
      final MyData data = myAnnotator2DataMap.get(annotator);
      if (data != null) {
        data.myAnnotationResult = annotator.doAnnotate(data.myCollectedInfo);
      }
    }
  }
}

@@ -1440,34 +1440,8 @@
 le;%0A
-import java.util.HashMap;%0A
 impo
@@ -1483,41 +1483,8 @@
 te;%0A
-import javax.annotation.Nonnull;%0A
 impo
@@ -1533,16 +1533,49 @@
 ction;%0A%0A
+import javax.annotation.Nonnull;%0A
 import j
@@ -2890,16 +2890,23 @@
 on(file)
+, false
 );%0A%0A    

drop usage of deprecated method