/*
 * Copyright 2000-2013 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.ide;

import com.intellij.AppTopics;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.GeneratedSourcesFilter;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.EditorNotifications;
import com.intellij.util.Alarm;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.Update;
import org.jetbrains.annotations.NotNull;

import java.util.*;

/**
 * @author nik
 */
public class GeneratedSourceFileChangeTrackerImpl extends GeneratedSourceFileChangeTracker {
  private final FileDocumentManager myDocumentManager;
  private final EditorNotifications myEditorNotifications;
  private final MergingUpdateQueue myCheckingQueue;
  private final Set<VirtualFile> myFilesToCheck = Collections.synchronizedSet(new HashSet<VirtualFile>());
  private final Set<VirtualFile> myEditedGeneratedFiles = Collections.synchronizedSet(new HashSet<VirtualFile>());

  public GeneratedSourceFileChangeTrackerImpl(Project project, FileDocumentManager documentManager, EditorNotifications editorNotifications) {
    super(project);
    myDocumentManager = documentManager;
    myEditorNotifications = editorNotifications;
    myCheckingQueue = new MergingUpdateQueue("Checking for changes in generated sources", 500, false, null, project, null, Alarm.ThreadToUse.SHARED_THREAD);
  }

  @Override
  public boolean isEditedGeneratedFile(@NotNull VirtualFile file) {
    return myEditedGeneratedFiles.contains(file);
  }

  @Override
  public void projectOpened() {
    final Update check = new Update("check for changes in generated files") {
      @Override
      public void run() {
        checkFiles();
      }
    };
    EditorFactory.getInstance().getEventMulticaster().addDocumentListener(new DocumentAdapter() {
      @Override
      public void documentChanged(DocumentEvent e) {
        VirtualFile file = myDocumentManager.getFile(e.getDocument());
        if (file != null) {
          myFilesToCheck.add(file);
          myCheckingQueue.queue(check);
        }
      }
    }, myProject);
    MessageBusConnection connection = myProject.getMessageBus().connect();
    connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() {
      @Override
      public void fileContentReloaded(@NotNull VirtualFile file, @NotNull Document document) {
        myFilesToCheck.remove(file);
        if (myEditedGeneratedFiles.remove(file)) {
          myEditorNotifications.updateNotifications(file);
        }
      }
    });
    connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() {
      @Override
      public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
        myEditedGeneratedFiles.remove(file);
      }
    });
    myCheckingQueue.activate();
  }

  @Override
  public void projectClosed() {
    myCheckingQueue.deactivate();
  }

  private void checkFiles() {
    final VirtualFile[] files;
    synchronized (myFilesToCheck) {
      files = myFilesToCheck.toArray(new VirtualFile[myFilesToCheck.size()]);
      myFilesToCheck.clear();
    }
    final List<VirtualFile> newEditedGeneratedFiles = new ArrayList<VirtualFile>();
    new ReadAction() {
      protected void run(final @NotNull Result result) {
        if (myProject.isDisposed()) return;
        for (VirtualFile file : files) {
          if (isGenerated(file)) {
            newEditedGeneratedFiles.add(file);
          }
        }
      }
    }.execute();

    if (!newEditedGeneratedFiles.isEmpty()) {
      List<VirtualFile> toNotify = new ArrayList<VirtualFile>();
      synchronized (myEditedGeneratedFiles) {
        for (VirtualFile file : newEditedGeneratedFiles) {
          if (myEditedGeneratedFiles.add(file)) {
            toNotify.add(file);
          }
        }
      }
      for (VirtualFile file : toNotify) {
        myEditorNotifications.updateNotifications(file);
      }
    }
  }

  private boolean isGenerated(VirtualFile file) {
    for (GeneratedSourcesFilter filter : GeneratedSourcesFilter.EP_NAME.getExtensions()) {
      if (filter.isGeneratedSource(file, myProject)) {
        return true;
      }
    }
    return false;
  }
}

@@ -652,16 +652,51 @@
 Topics;%0A
+import com.intellij.ProjectTopics;%0A
 import c
@@ -1137,24 +1137,128 @@
 rcesFilter;%0A
+import com.intellij.openapi.roots.ModuleRootAdapter;%0Aimport com.intellij.openapi.roots.ModuleRootEvent;%0A
 import com.i
@@ -1418,24 +1418,65 @@
 Connection;%0A
+import com.intellij.util.messages.Topic;%0A
 import com.i
@@ -3980,24 +3980,325 @@
   %7D%0A    %7D);%0A
+    connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() %7B%0A      @Override%0A      public void rootsChanged(ModuleRootEvent event) %7B%0A        myFilesToCheck.addAll(myEditedGeneratedFiles);%0A        myEditedGeneratedFiles.clear();%0A        myCheckingQueue.queue(check);%0A      %7D%0A    %7D);%0A
     myChecki
@@ -5051,328 +5051,64 @@
     
-List%3CVirtualFile%3E toNotify = new ArrayList%3CVirtualFile%3E();%0A      synchronized (myEditedGeneratedFiles) %7B%0A        for (VirtualFile file : newEditedGeneratedFiles) %7B%0A          if (myEditedGeneratedFiles.add(file)) %7B%0A            toNotify.add(file);%0A          %7D%0A        %7D%0A      %7D%0A      for (VirtualFile file : toNotify) %7B%0A  
+myEditedGeneratedFiles.addAll(newEditedGeneratedFiles);%0A
     
@@ -5129,32 +5129,35 @@
 fications.update
+All
 Notifications(fi
@@ -5154,30 +5154,18 @@
 cations(
-file);%0A      %7D
+);
 %0A    %7D%0A 

update editor notifications on roots changed