/*
 * 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.ide.projectView;

import com.intellij.ide.scratch.RootType;
import com.intellij.ide.scratch.ScratchProjectViewPane;
import com.intellij.ide.util.treeView.AbstractTreeUpdater;
import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiModificationTracker;
import org.jetbrains.annotations.NotNull;

import javax.swing.tree.DefaultMutableTreeNode;

public abstract class ProjectViewPsiTreeChangeListener extends PsiTreeChangeAdapter {
  private final PsiModificationTracker myModificationTracker;
  private long myOutOfCodeBlockModificationCount;

  protected ProjectViewPsiTreeChangeListener(@NotNull Project project) {
    myModificationTracker = PsiManager.getInstance(project).getModificationTracker();
    myOutOfCodeBlockModificationCount = myModificationTracker.getOutOfCodeBlockModificationCount();
  }

  protected abstract AbstractTreeUpdater getUpdater();

  protected abstract boolean isFlattenPackages();

  protected abstract DefaultMutableTreeNode getRootNode();

  @Override
  public final void childRemoved(@NotNull PsiTreeChangeEvent event) {
    PsiElement child = event.getOldChild();
    if (child instanceof PsiWhiteSpace) return; //optimization
    childrenChanged(event.getParent(), true);
  }

  @Override
  public final void childAdded(@NotNull PsiTreeChangeEvent event) {
    PsiElement child = event.getNewChild();
    if (child instanceof PsiWhiteSpace) return; //optimization
    childrenChanged(event.getParent(), true);
  }

  @Override
  public final void childReplaced(@NotNull PsiTreeChangeEvent event) {
    PsiElement oldChild = event.getOldChild();
    PsiElement newChild = event.getNewChild();
    if (oldChild instanceof PsiWhiteSpace && newChild instanceof PsiWhiteSpace) return; //optimization
    childrenChanged(event.getParent(), true);
  }

  @Override
  public final void childMoved(@NotNull PsiTreeChangeEvent event) {
    childrenChanged(event.getOldParent(), false);
    childrenChanged(event.getNewParent(), true);
  }

  @Override
  public final void childrenChanged(@NotNull PsiTreeChangeEvent event) {
    childrenChanged(event.getParent(), true);
  }

  protected void childrenChanged(PsiElement parent, final boolean stopProcessingForThisModificationCount) {
    if (parent instanceof PsiDirectory && isFlattenPackages()){
      addSubtreeToUpdateByRoot();
      return;
    }

    long newModificationCount = myModificationTracker.getOutOfCodeBlockModificationCount();
    if (newModificationCount == myOutOfCodeBlockModificationCount) return;
    if (stopProcessingForThisModificationCount) {
      myOutOfCodeBlockModificationCount = newModificationCount;
    }

    while (true) {
      if (parent == null) break;
      if (parent instanceof PsiFile) {
        VirtualFile virtualFile = ((PsiFile)parent).getVirtualFile();
        if (virtualFile != null && virtualFile.getFileType() != FileTypes.PLAIN_TEXT) {
          // adding a class within a file causes a new node to appear in project view => entire dir should be updated
          parent = ((PsiFile)parent).getContainingDirectory();
          if (parent == null) break;
        }
      }
      else if (parent instanceof PsiDirectory &&
               ScratchProjectViewPane.isScratchesMergedIntoProjectTab() &&
               RootType.forFile(((PsiDirectory)parent).getVirtualFile()) != null) {
        addSubtreeToUpdateByRoot();
        break;
      }

      if (addSubtreeToUpdateByElement(parent)) {
        break;
      }

      if (parent instanceof PsiFile || parent instanceof PsiDirectory) break;
      parent = parent.getParent();
    }
  }

  @Override
  public void propertyChanged(@NotNull PsiTreeChangeEvent event) {
    String propertyName = event.getPropertyName();
    PsiElement element = event.getElement();
    if (propertyName.equals(PsiTreeChangeEvent.PROP_ROOTS)) {
      addSubtreeToUpdateByRoot();
    }
    else if (propertyName.equals(PsiTreeChangeEvent.PROP_WRITABLE)){
      if (!addSubtreeToUpdateByElement(element) && element instanceof PsiFile) {
        addSubtreeToUpdateByElement(((PsiFile)element).getContainingDirectory());
      }
    }
    else if (propertyName.equals(PsiTreeChangeEvent.PROP_FILE_NAME) || propertyName.equals(PsiTreeChangeEvent.PROP_DIRECTORY_NAME)){
      if (element instanceof PsiDirectory && isFlattenPackages()){
        addSubtreeToUpdateByRoot();
        return;
      }
      final PsiElement parent = element.getParent();
      if (parent == null || !addSubtreeToUpdateByElement(parent)) {
        addSubtreeToUpdateByElement(element);
      }
    }
    else if (propertyName.equals(PsiTreeChangeEvent.PROP_FILE_TYPES)){
      addSubtreeToUpdateByRoot();
    }
  }

  protected void addSubtreeToUpdateByRoot() {
    AbstractTreeUpdater updater = getUpdater();
    DefaultMutableTreeNode root = getRootNode();
    if (updater != null && root != null) updater.addSubtreeToUpdate(root);
  }

  protected boolean addSubtreeToUpdateByElement(PsiElement element) {
    AbstractTreeUpdater updater = getUpdater();
    return updater != null && updater.addSubtreeToUpdateByElement(element);
  }
}

@@ -1102,16 +1102,70 @@
 eNode;%0A%0A
+import static com.intellij.util.ObjectUtils.notNull;%0A%0A
 public a
@@ -4212,32 +4212,36 @@
 oUpdateByElement
+File
 (parent)) %7B%0A    
@@ -4760,32 +4760,36 @@
 oUpdateByElement
+File
 (element) && ele
@@ -4850,16 +4850,20 @@
 yElement
+File
 (((PsiFi
@@ -5284,16 +5284,20 @@
 yElement
+File
 (parent)
@@ -5327,32 +5327,36 @@
 oUpdateByElement
+File
 (element);%0A     
@@ -5432,17 +5432,79 @@
 E_TYPES)
-)
+ %7C%7C propertyName.equals(PsiTreeChangeEvent.PROP_UNLOADED_PSI)) 
 %7B%0A      
@@ -5962,11 +5962,194 @@
 nt);%0A  %7D
+%0A%0A  private boolean addSubtreeToUpdateByElementFile(PsiElement element) %7B%0A    return element != null && addSubtreeToUpdateByElement(notNull(element.getContainingFile(), element));%0A  %7D
 %0A%7D%0A

use containing file instead of element to update Project View