/*
 * Copyright 2000-2016 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.util;

import com.intellij.ide.IdeBundle;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.roots.TestSourcesFilter;
import com.intellij.openapi.roots.impl.DirectoryIndex;
import com.intellij.openapi.roots.ui.configuration.CommonContentEntriesEditor;
import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.impl.source.resolve.FileContextUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.GlobalSearchScopesCore;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.util.List;

// TODO this should eventually replace PackageUtil from java-impl
public class PlatformPackageUtil {

  private static final Logger LOG = Logger.getInstance("com.intellij.ide.util.PlatformPackageUtil");

  @Nullable
  private static String findLongestExistingPackage(Project project, String packageName, GlobalSearchScope scope) {
    final PsiManager manager = PsiManager.getInstance(project);
    DirectoryIndex index = DirectoryIndex.getInstance(project);

    String nameToMatch = packageName;
    while (true) {
      Query<VirtualFile> vFiles = index.getDirectoriesByPackageName(nameToMatch, false);
      PsiDirectory directory = getWritableModuleDirectory(vFiles, scope, manager);
      if (directory != null) return index.getPackageName(directory.getVirtualFile());

      int lastDotIndex = nameToMatch.lastIndexOf('.');
      if (lastDotIndex >= 0) {
        nameToMatch = nameToMatch.substring(0, lastDotIndex);
      }
      else {
        return null;
      }
    }
  }

  @Nullable
  private static PsiDirectory getWritableModuleDirectory(@NotNull Query<VirtualFile> vFiles,
                                                         GlobalSearchScope scope,
                                                         PsiManager manager) {
    for (VirtualFile vFile : vFiles) {
      if (!scope.contains(vFile)) continue;
      PsiDirectory directory = manager.findDirectory(vFile);
      if (directory != null && directory.isValid() && directory.isWritable()) {
        return directory;
      }
    }
    return null;
  }

  @Nullable
  public static PsiDirectory findOrCreateDirectoryForPackage(@NotNull final Project project,
                                                             @Nullable Module module,
                                                             GlobalSearchScope scope,
                                                             String packageName,
                                                             PsiDirectory baseDir,
                                                             boolean askUserToCreate,
                                                             ThreeState chooseFlag) throws IncorrectOperationException {
    PsiDirectory psiDirectory = null;
    if (chooseFlag == ThreeState.UNSURE && StringUtil.isNotEmpty(packageName)) {
      String rootPackage = findLongestExistingPackage(project, packageName, scope);
      if (rootPackage != null) {
        int beginIndex = rootPackage.length() + 1;
        packageName = beginIndex < packageName.length() ? packageName.substring(beginIndex) : "";
        String postfixToShow = packageName.replace('.', File.separatorChar);
        if (packageName.length() > 0) {
          postfixToShow = File.separatorChar + postfixToShow;
        }
        psiDirectory =
          DirectoryChooserUtil.selectDirectory(project, getPackageDirectories(project, rootPackage, scope), baseDir, postfixToShow);
        if (psiDirectory == null) return null;
      }
    }

    if (psiDirectory == null) {
      if (chooseFlag == ThreeState.NO && baseDir != null) {
        VirtualFile sourceRoot = ProjectRootManager.getInstance(project).getFileIndex().getSourceRootForFile(baseDir.getVirtualFile());
        psiDirectory = sourceRoot != null ? PsiManager.getInstance(project).findDirectory(sourceRoot) : null;
      }
      else {
        if (module != null && !checkSourceRootsConfigured(module)) return null;
        final GlobalSearchScope scope_ = scope;
        List<PsiDirectory> dirs =
          ContainerUtil
            .mapNotNull(ProjectRootManager.getInstance(project).getContentSourceRoots(),
                        virtualFile -> scope_.contains(virtualFile) ? PsiManager.getInstance(project).findDirectory(virtualFile) : null);
        psiDirectory = DirectoryChooserUtil.selectDirectory(project, dirs.toArray(new PsiDirectory[dirs.size()]), baseDir,
                                                            File.separatorChar + packageName.replace('.', File.separatorChar));
        if (psiDirectory == null) return null;
        final VirtualFile sourceRoot = ProjectRootManager.getInstance(project).getFileIndex().getSourceRootForFile(psiDirectory.getVirtualFile());
        psiDirectory = sourceRoot != null ? PsiManager.getInstance(project).findDirectory(sourceRoot) : null;
      }
    }

    String restOfName = packageName;
    boolean askedToCreate = false;
    while (restOfName.length() > 0) {
      final String name = getLeftPart(restOfName);
      PsiDirectory foundExistingDirectory = psiDirectory != null ? psiDirectory.findSubdirectory(name) : null;
      if (foundExistingDirectory == null) {
        if (!askedToCreate && askUserToCreate) {
          if (!ApplicationManager.getApplication().isUnitTestMode()) {
            int toCreate = Messages.showYesNoDialog(project,
                                                    IdeBundle.message("prompt.create.non.existing.package", packageName),
                                                    IdeBundle.message("title.package.not.found"),
                                                    Messages.getQuestionIcon());
            if (toCreate != Messages.YES) {
              return null;
            }
          }
          askedToCreate = true;
        }

        final PsiDirectory psiDirectory_ = psiDirectory;
        try {
          psiDirectory = ActionRunner.runInsideWriteAction(new ActionRunner.InterruptibleRunnableWithResult<PsiDirectory>() {
            @Override
            public PsiDirectory run() throws Exception {
              return psiDirectory_ != null ? psiDirectory_.createSubdirectory(name) : null;
            }
          });
        }
        catch (IncorrectOperationException e) {
          throw e;
        }
        catch (IOException e) {
          throw new IncorrectOperationException(e);
        }
        catch (Exception e) {
          LOG.error(e);
        }
      }
      else {
        psiDirectory = foundExistingDirectory;
      }
      restOfName = cutLeftPart(restOfName);
    }
    return psiDirectory;
  }

  public static GlobalSearchScope adjustScope(PsiDirectory baseDir,
                                              GlobalSearchScope scope,
                                              boolean skipSourceDirsForBaseTestDirectory,
                                              boolean skipTestDirsForBaseSourceDirectory) {
    if (baseDir != null) {
      if (TestSourcesFilter.isTestSources(baseDir.getVirtualFile(), baseDir.getProject())) {
        if (skipSourceDirsForBaseTestDirectory) {
          return scope.intersectWith(GlobalSearchScopesCore.projectTestScope(baseDir.getProject()));
        }
      }
      else {
        if (skipTestDirsForBaseSourceDirectory) {
          return scope.intersectWith(GlobalSearchScopesCore.projectProductionScope(baseDir.getProject()));
        }
      }
    }
    return scope;
  }

  private static PsiDirectory[] getPackageDirectories(Project project, String rootPackage, final GlobalSearchScope scope) {
    final PsiManager manager = PsiManager.getInstance(project);

    Query<VirtualFile> query = DirectoryIndex.getInstance(scope.getProject()).getDirectoriesByPackageName(rootPackage, true);
    query = new FilteredQuery<>(query, scope::contains);

    List<PsiDirectory> directories = ContainerUtil.mapNotNull(query.findAll(), manager::findDirectory);
    return directories.toArray(new PsiDirectory[directories.size()]);
  }

  private static boolean checkSourceRootsConfigured(final Module module) {
    VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots();
    if (sourceRoots.length == 0) {
      Messages.showErrorDialog(
        module.getProject(),
        ProjectBundle.message("module.source.roots.not.configured.error", module.getName()),
        ProjectBundle.message("module.source.roots.not.configured.title")
      );

      ProjectSettingsService
        .getInstance(module.getProject()).showModuleConfigurationDialog(module.getName(), CommonContentEntriesEditor.NAME);

      sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots();
      if (sourceRoots.length == 0) {
        return false;
      }
    }
    return true;
  }

  private static String getLeftPart(String packageName) {
    int index = packageName.indexOf('.');
    return index > -1 ? packageName.substring(0, index) : packageName;
  }

  private static String cutLeftPart(String packageName) {
    int index = packageName.indexOf('.');
    return index > -1 ? packageName.substring(index + 1) : "";
  }

  @Nullable
  public static PsiDirectory getDirectory(@Nullable PsiElement element) {
    if (element == null) return null;
    // handle injection and fragment editor
    PsiFile file = FileContextUtil.getContextFile(element);
    return file == null ? null : file.getContainingDirectory();
  }
}
