/*
 * Copyright 2000-2010 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.plugins.haxe.ide.refactoring.extractSuperclass;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.turnRefsToSuper.TurnRefsToSuperProcessorBase;
import com.intellij.refactoring.util.DocCommentPolicy;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.refactoring.util.classMembers.MemberInfo;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.usageView.UsageViewUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @author dsl
 */
public abstract class ExtractSuperBaseProcessor extends TurnRefsToSuperProcessorBase {
  private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.extractSuperclass.ExtractSuperClassProcessor");
  protected PsiDirectory myTargetDirectory;
  protected final String myNewClassName;
  protected final MemberInfo[] myMemberInfos;
  protected final DocCommentPolicy myJavaDocPolicy;


  public ExtractSuperBaseProcessor(Project project,
                                   boolean replaceInstanceOf,
                                   PsiDirectory targetDirectory,
                                   String newClassName,
                                   PsiClass aClass, MemberInfo[] memberInfos, DocCommentPolicy javaDocPolicy) {
    super(project, replaceInstanceOf, newClassName);
    myTargetDirectory = targetDirectory;
    myNewClassName = newClassName;
    myClass = aClass;
    myMemberInfos = memberInfos;
    myJavaDocPolicy = javaDocPolicy;
  }

  @NotNull
  protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
    return new ExtractSuperClassViewDescriptor(myTargetDirectory, myClass, myMemberInfos);
  }

  protected boolean doesAnyExtractedInterfaceExtends(PsiClass aClass) {
    for (final MemberInfo memberInfo : myMemberInfos) {
      final PsiElement member = memberInfo.getMember();
      if (member instanceof PsiClass && memberInfo.getOverrides() != null) {
        if (InheritanceUtil.isInheritorOrSelf((PsiClass)member, aClass, true)) {
          return true;
        }
      }
    }
    return false;
  }

  protected boolean doMemberInfosContain(PsiMethod method) {
    for (final MemberInfo info : myMemberInfos) {
      if (info.getMember() instanceof PsiMethod) {
        if (MethodSignatureUtil.areSignaturesEqual(method, (PsiMethod)info.getMember())) return true;
      }
      else if (info.getMember() instanceof PsiClass && info.getOverrides() != null) {
        final PsiMethod methodBySignature = ((PsiClass)info.getMember()).findMethodBySignature(method, true);
        if (methodBySignature != null) {
          return true;
        }
      }
    }
    return false;
  }

  protected boolean doMemberInfosContain(final PsiField field) {
    for (final MemberInfo info : myMemberInfos) {
      if (myManager.areElementsEquivalent(field, info.getMember())) return true;
    }
    return false;
  }

  @NotNull
  protected UsageInfo[] findUsages() {
    PsiReference[] refs = ReferencesSearch.search(myClass, GlobalSearchScope.projectScope(myProject), false).toArray(new PsiReference[0]);
    final ArrayList<UsageInfo> result = new ArrayList<UsageInfo>();
    detectTurnToSuperRefs(refs, result);
    final PsiPackage originalPackage = JavaDirectoryService.getInstance().getPackage(myClass.getContainingFile().getContainingDirectory());
    if (Comparing.equal(JavaDirectoryService.getInstance().getPackage(myTargetDirectory), originalPackage)) {
      result.clear();
    }
    for (final PsiReference ref : refs) {
      final PsiElement element = ref.getElement();
      if (!canTurnToSuper(element) && !RefactoringUtil.inImportStatement(ref, element)) {
        result.add(new BindToOldUsageInfo(element, ref, myClass));
      }
    }
    UsageInfo[] usageInfos = result.toArray(new UsageInfo[result.size()]);
    return UsageViewUtil.removeDuplicatedUsages(usageInfos);
  }

  protected void performRefactoring(UsageInfo[] usages) {
    try {
      final String superClassName = myClass.getName();
      final String oldQualifiedName = myClass.getQualifiedName();
      myClass.setName(myNewClassName);
      PsiClass superClass = extractSuper(superClassName);
      final PsiDirectory initialDirectory = myClass.getContainingFile().getContainingDirectory();
      try {
        if (myTargetDirectory != initialDirectory) {
          myTargetDirectory.add(myClass.getContainingFile().copy());
          myClass.getContainingFile().delete();
        }
      }
      catch (IncorrectOperationException e) {
        RefactoringUIUtil.processIncorrectOperation(myProject, e);
      }
      for (final UsageInfo usage : usages) {
        if (usage instanceof BindToOldUsageInfo) {
          final PsiReference reference = usage.getReference();
          if (reference != null && reference.getElement().isValid()) {
            reference.bindToElement(myClass);
          }
        }
      }
      if (!Comparing.equal(oldQualifiedName, superClass.getQualifiedName())) {
        processTurnToSuperRefs(usages, superClass);
      }
      final PsiFile containingFile = myClass.getContainingFile();
      if (containingFile instanceof PsiJavaFile) {
        JavaCodeStyleManager.getInstance(myProject).removeRedundantImports((PsiJavaFile) containingFile);
      }
    }
    catch (IncorrectOperationException e) {
      LOG.error(e);
    }

    performVariablesRenaming();
  }

  protected abstract PsiClass extractSuper(String superClassName) throws IncorrectOperationException;

  protected void refreshElements(PsiElement[] elements) {
    myClass = (PsiClass)elements[0];
    myTargetDirectory = (PsiDirectory)elements[1];
    for (int i = 0; i < myMemberInfos.length; i++) {
      final MemberInfo info = myMemberInfos[i];
      info.updateMember((PsiMember)elements[i + 2]);
    }
  }

  protected String getCommandName() {
    return RefactoringBundle.message("extract.subclass.command");
  }

  @NotNull
  @Override
  protected Collection<? extends PsiElement> getElementsToWrite(@NotNull final UsageViewDescriptor descriptor) {
    return ((ExtractSuperClassViewDescriptor) descriptor).getMembersToMakeWritable();
  }
}