/*
 * Copyright 2003-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 jetbrains.mps.idea.java.refactoring;

import com.intellij.facet.FacetManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.impl.DirectoryIndex;
import com.intellij.openapi.roots.impl.DirectoryInfo;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiPackage;
import com.intellij.refactoring.listeners.RefactoringElementListener;
import com.intellij.refactoring.listeners.RefactoringElementListenerProvider;
import jetbrains.mps.ide.findusages.model.scopes.ProjectScope;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.idea.core.facet.MPSFacet;
import jetbrains.mps.idea.core.facet.MPSFacetType;
import jetbrains.mps.java.stub.JavaPackageNameStub;
import jetbrains.mps.progress.EmptyProgressMonitor;
import jetbrains.mps.smodel.ModelImports;
import jetbrains.mps.smodel.StaticReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeUtil;
import org.jetbrains.mps.openapi.model.SReference;
import org.jetbrains.mps.openapi.module.FindUsagesFacade;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SRepository;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * This class listens to package rename refactorings and updates the references to stub models that
 * were built from those packages. Package name changes => model reference changes. Both model imports and SReferences
 * must changed accordingly.
 * <p>
 * Created by danilla on 2/16/15.
 */
public class PackageRenameListener implements RefactoringElementListenerProvider {
  @Nullable
  @Override
  public RefactoringElementListener getListener(PsiElement element) {

    if (!(element instanceof PsiPackage)) {
      return null;
    }

    final PsiPackage pkg = (PsiPackage) element;
    final Project project = pkg.getProject();
    DirectoryIndex dirIndex = DirectoryIndex.getInstance(project);

    final Set<SModelReference> modelRefs = new HashSet<>();

    for (PsiDirectory dir : pkg.getDirectories()) {
      DirectoryInfo dirInfo = dirIndex.getInfoForFile(dir.getVirtualFile());
      Module module = dirInfo.getModule();
      if (module == null) continue;
      MPSFacet facet = FacetManager.getInstance(module).getFacetByType(MPSFacetType.ID);
      if (facet == null) continue;
      modelRefs.add(new JavaPackageNameStub(pkg.getQualifiedName()).asModelReference(facet.getSolution().getModuleReference()));
    }

    if (modelRefs.isEmpty()) return null;

    final Set<SModel> models = new HashSet<>();
    ProjectHelper.getModelAccess(project).runReadAction(() -> models.addAll(FindUsagesFacade.getInstance().findModelUsages(
      new ProjectScope(ProjectHelper.fromIdeaProject(project)),
      modelRefs,
      new EmptyProgressMonitor()
    )));

    if (models.isEmpty()) return null;

    return new RefactoringElementListener() {
      @Override
      public void elementMoved(@NotNull PsiElement newElement) {
      }

      @Override
      public void elementRenamed(@NotNull PsiElement newElement) {
        final String newPkgName = ((PsiPackage) newElement).getQualifiedName();
        final SRepository repository = ProjectHelper.getProjectRepository(project);
        repository.getModelAccess().executeCommand(() -> {
          for (SModel model : models) {
            ModelImports modelImports = new ModelImports(model);

            Map<SModelReference, SModelReference> changes = new HashMap<>();

            for (SModelReference mref : modelImports.getImportedModels()) {
              if (!modelRefs.contains(mref)) {
                continue;
              }
              SModule module = mref.getModuleReference().resolve(repository);
              SModelReference newModelRef = new JavaPackageNameStub(newPkgName).asModelReference(module.getModuleReference());
              modelImports.removeModelImport(mref);
              modelImports.addModelImport(newModelRef);
              changes.put(mref, newModelRef);
            }

            updateSReferences(model, changes);
          }
        });

      }
    };
  }

  private void updateSReferences(SModel model, Map<SModelReference, SModelReference> modelRefChange) {
    for (SNode node : SNodeUtil.getDescendants(model)) {
      for (SReference ref : node.getReferences()) {
        if (!(ref instanceof StaticReference)) {
          continue;
        }
        SModelReference modelRef = ref.getTargetSModelReference();
        if (modelRefChange.containsKey(modelRef)) {
          ((StaticReference) ref).setTargetSModelReference(modelRefChange.get(modelRef));
        }
      }
    }
  }
}
