/*
 * Copyright 2000-2015 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.refactoring.invertBoolean;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.rename.RenameProcessor;
import com.intellij.refactoring.rename.RenameUtil;
import com.intellij.refactoring.ui.ConflictsDialog;
import com.intellij.refactoring.util.MoveRenameUsageInfo;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

/**
 * @author ven
 */
public class InvertBooleanProcessor extends BaseRefactoringProcessor {
  private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.invertBoolean.InvertBooleanMethodProcessor");
  private final InvertBooleanDelegate myDelegate;

  private PsiElement myElement;
  private final String myNewName;
  private final RenameProcessor myRenameProcessor;
  private final Map<UsageInfo, SmartPsiElementPointer> myToInvert = new HashMap<>();
  private final SmartPointerManager mySmartPointerManager;

  public InvertBooleanProcessor(final PsiElement namedElement, final String newName) {
    super(namedElement.getProject());
    myElement = namedElement;
    myNewName = newName;
    final Project project = namedElement.getProject();
    final boolean canRename =
      namedElement instanceof PsiNamedElement && !Comparing.equal(((PsiNamedElement)namedElement).getName(), myNewName);
    myRenameProcessor = canRename ? new RenameProcessor(project, namedElement, newName, false, false) {
      @NotNull
      @Override
      protected ConflictsDialog createConflictsDialog(@NotNull MultiMap<PsiElement, String> conflicts, @Nullable final UsageInfo[] usages) {
        return new ConflictsDialog(myProject, conflicts, usages == null ? null : (Runnable)() -> InvertBooleanProcessor.this.execute(usages), false, true);
      }

      @Override
      protected void prepareSuccessful() {
        InvertBooleanProcessor.this.prepareSuccessful();
      }
    } : null;
    mySmartPointerManager = SmartPointerManager.getInstance(project);
    myDelegate = InvertBooleanDelegate.findInvertBooleanDelegate(myElement);
    LOG.assertTrue(myDelegate != null);
  }

  @Override
  @NotNull
  protected UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) {
    return new InvertBooleanUsageViewDescriptor(myElement);
  }

  @Override
  protected boolean preprocessUsages(@NotNull Ref<UsageInfo[]> refUsages) {
    final MultiMap<PsiElement, String> conflicts = new MultiMap<>();
    final UsageInfo[] usageInfos = refUsages.get();
    myDelegate.findConflicts(usageInfos, conflicts);
    
    if (!conflicts.isEmpty())  {
      return showConflicts(conflicts, usageInfos);
    }

    if (myRenameProcessor == null || myRenameProcessor.preprocessUsages(refUsages)) {
      prepareSuccessful();
      return true;
    }
    return false;
  }

  @Override
  @NotNull
  protected UsageInfo[] findUsages() {
    final List<SmartPsiElementPointer> toInvert = new ArrayList<>();

    final LinkedHashSet<PsiElement> elementsToInvert = new LinkedHashSet<>();
    myDelegate.collectRefElements(myElement, myRenameProcessor, myNewName, elementsToInvert);
    for (PsiElement element : elementsToInvert) {
      toInvert.add(mySmartPointerManager.createSmartPsiElementPointer(element));
    }

    final UsageInfo[] renameUsages = myRenameProcessor != null ? myRenameProcessor.findUsages() : UsageInfo.EMPTY_ARRAY;

    final SmartPsiElementPointer[] usagesToInvert = toInvert.toArray(new SmartPsiElementPointer[toInvert.size()]);

    //merge rename and invert usages
    Map<PsiElement, UsageInfo> expressionsToUsages = new HashMap<>();
    List<UsageInfo> result = new ArrayList<>();
    for (UsageInfo renameUsage : renameUsages) {
      expressionsToUsages.put(renameUsage.getElement(), renameUsage);
      result.add(renameUsage);
    }

    for (SmartPsiElementPointer pointer : usagesToInvert) {
      final PsiElement expression = pointer.getElement();
      if (!expressionsToUsages.containsKey(expression)) {
        final UsageInfo usageInfo = new UsageInfo(expression);
        expressionsToUsages.put(expression, usageInfo);
        result.add(usageInfo); //fake UsageInfo
        myToInvert.put(usageInfo, pointer);
      } else {
        myToInvert.put(expressionsToUsages.get(expression), pointer);
      }
    }

    return result.toArray(new UsageInfo[result.size()]);
  }

  @Override
  protected void refreshElements(@NotNull PsiElement[] elements) {
    myElement = elements[0];
  }

  private static UsageInfo[] extractUsagesForElement(PsiElement element, UsageInfo[] usages) {
    final ArrayList<UsageInfo> extractedUsages = new ArrayList<>(usages.length);
    for (UsageInfo usage : usages) {
      if (usage instanceof MoveRenameUsageInfo) {
        MoveRenameUsageInfo usageInfo = (MoveRenameUsageInfo)usage;
        if (element.equals(usageInfo.getReferencedElement())) {
          extractedUsages.add(usageInfo);
        }
      }
    }
    return extractedUsages.toArray(new UsageInfo[extractedUsages.size()]);
  }

  @Override
  protected void performRefactoring(@NotNull UsageInfo[] usages) {
    if (myRenameProcessor != null) {
      for (final PsiElement element : myRenameProcessor.getElements()) {
        try {
          RenameUtil.doRename(element, myRenameProcessor.getNewName(element), extractUsagesForElement(element, usages), myProject, null);
        }
        catch (final IncorrectOperationException e) {
          RenameUtil.showErrorMessage(e, element, myProject);
          return;
        }
      }
    }


    for (UsageInfo usage : usages) {
      final SmartPsiElementPointer pointerToInvert = myToInvert.get(usage);
      if (pointerToInvert != null) {
        PsiElement element = pointerToInvert.getElement();
        LOG.assertTrue(element != null);
        InvertBooleanDelegate delegate = InvertBooleanDelegate.findInvertBooleanDelegate(element);
        try {
          (delegate != null ? delegate : myDelegate).replaceWithNegatedExpression(element);
        }
        catch (IncorrectOperationException e) {
          LOG.error(e);
        }
      }
    }

    myDelegate.invertElementInitializer(myElement);
  }

  @Override
  protected String getCommandName() {
    return InvertBooleanHandler.REFACTORING_NAME;
  }
}
