/*
 * Copyright 2000-2014 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.psi.impl.source.resolve.reference.impl.providers;

import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;

import java.util.*;

/**
 * @author cdr
 */
public class FilePathReferenceProvider extends PsiReferenceProvider {

  private final boolean myEndingSlashNotAllowed;

  public FilePathReferenceProvider() {
    this(true);
  }

  public FilePathReferenceProvider(boolean endingSlashNotAllowed) {
    myEndingSlashNotAllowed = endingSlashNotAllowed;
  }

  @NotNull
  public PsiReference[] getReferencesByElement(@NotNull PsiElement element, String text, int offset, final boolean soft) {
    return getReferencesByElement(element, text, offset, soft, Module.EMPTY_ARRAY);
  }

  @NotNull
  public PsiReference[] getReferencesByElement(@NotNull PsiElement element,
                                               String text,
                                               int offset,
                                               final boolean soft,
                                               @NotNull final Module... forModules) {
    return new FileReferenceSet(text, element, offset, this, true, myEndingSlashNotAllowed) {


      @Override
      protected boolean isSoft() {
        return soft;
      }

      @Override
      public boolean isAbsolutePathReference() {
        return true;
      }

      @Override
      public boolean couldBeConvertedTo(boolean relative) {
        return !relative;
      }

      @Override
      public boolean absoluteUrlNeedsStartSlash() {
        final String s = getPathString();
        return s != null && !s.isEmpty() && s.charAt(0) == '/';
      }

      @Override
      @NotNull
      public Collection<PsiFileSystemItem> computeDefaultContexts() {
        Set<PsiFileSystemItem> systemItems = new HashSet<PsiFileSystemItem>();
        if (forModules.length > 0) {
          for (Module forModule : forModules) {
            systemItems.addAll(getRoots(forModule, true));
          }
        } else {
          systemItems.addAll(getRoots(ModuleUtilCore.findModuleForPsiElement(getElement()), true));
        }
        return systemItems;
      }

      @Override
      public FileReference createFileReference(final TextRange range, final int index, final String text) {
        return FilePathReferenceProvider.this.createFileReference(this, range, index, text);
      }

      @Override
      protected Condition<PsiFileSystemItem> getReferenceCompletionFilter() {
        return new Condition<PsiFileSystemItem>() {
          @Override
          public boolean value(final PsiFileSystemItem element) {
            return isPsiElementAccepted(element);
          }
        };
      }
    }.getAllReferences();
  }

  @Override
  public boolean acceptsTarget(@NotNull PsiElement target) {
    return target instanceof PsiFileSystemItem;
  }

  protected boolean isPsiElementAccepted(PsiElement element) {
    return !(element instanceof PsiJavaFile && element instanceof PsiCompiledElement);
  }

  protected FileReference createFileReference(FileReferenceSet referenceSet, final TextRange range, final int index, final String text) {
    return new FileReference(referenceSet, range, index, text);
  }

  @Override
  @NotNull
  public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull final ProcessingContext context) {
    String text = null;
    if (element instanceof PsiLiteralExpression) {
      Object value = ((PsiLiteralExpression)element).getValue();
      if (value instanceof String) {
        text = (String)value;
      }
    }
    //else if (element instanceof XmlAttributeValue) {
    //  text = ((XmlAttributeValue)element).getValue();
    //}
    if (text == null) return PsiReference.EMPTY_ARRAY;
    return getReferencesByElement(element, text, 1, true);
  }

  @NotNull
  public static Collection<PsiFileSystemItem> getRoots(final Module thisModule, boolean includingClasses) {
    if (thisModule == null) return Collections.emptyList();
    ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(thisModule);
    List<PsiFileSystemItem> result = new ArrayList<PsiFileSystemItem>();
    final PsiManager psiManager = PsiManager.getInstance(thisModule.getProject());
    if (includingClasses) {
      VirtualFile[] libraryUrls = moduleRootManager.orderEntries().getAllLibrariesAndSdkClassesRoots();
      for (VirtualFile file : libraryUrls) {
        PsiDirectory directory = psiManager.findDirectory(file);
        if (directory != null) {
          result.add(directory);
        }
      }
    }

    VirtualFile[] sourceRoots = moduleRootManager.orderEntries().recursively().withoutSdk().withoutLibraries().getSourceRoots();
    for (VirtualFile root : sourceRoots) {
      final PsiDirectory directory = psiManager.findDirectory(root);
      if (directory != null) {
        final PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage(directory);
        if (aPackage != null && aPackage.getName() != null) {
          // package prefix
          result.add(PackagePrefixFileSystemItemImpl.create(directory));
        }
        else {
          result.add(directory);
        }
      }
    }
    return result;
  }
}

@@ -1053,23 +1053,29 @@
 tainers.
-HashSet
+ContainerUtil
 ;%0Aimport
@@ -1114,27 +1114,131 @@
 ll;%0A
-%0Aimport java.util.*
+import org.jetbrains.annotations.Nullable;%0A%0Aimport java.util.Collection;%0Aimport java.util.Collections;%0Aimport java.util.Set
 ;%0A%0A/
@@ -2827,115 +2827,121 @@
     
-Set%3CPsiFileSystemItem%3E systemItems = new HashSet%3CPsiFileSystemItem%3E();%0A        if (forModules.length %3E 0) %7B
+if (forModules.length %3E 0) %7B%0A          Set%3CPsiFileSystemItem%3E rootsForModules = ContainerUtil.newLinkedHashSet();
 %0A   
@@ -2993,34 +2993,38 @@
             
-systemItem
+rootsForModule
 s.addAll(get
@@ -3072,46 +3072,60 @@
     
-%7D else %7B%0A          systemItems.addAll(
+  return rootsForModules;%0A        %7D%0A%0A        return 
 getR
@@ -3192,47 +3192,8 @@
 rue)
-);%0A        %7D%0A        return systemItems
 ;%0A  
@@ -4925,16 +4925,26 @@
 etRoots(
+@Nullable 
 final Mo
@@ -5044,24 +5044,25 @@
 mptyList();%0A
+%0A
     ModuleRo
@@ -5138,19 +5138,18 @@
 e);%0A    
-Lis
+Se
 t%3CPsiFil
@@ -5174,40 +5174,38 @@
 t = 
-new ArrayList%3CPsiFileSystemItem%3E
+ContainerUtil.newLinkedHashSet
 ();%0A
@@ -5698,16 +5698,23 @@
 sively()
+%0A      
 .without
@@ -5741,18 +5741,42 @@
 es()
-.getSource
+%0A      .sources().usingCache().get
 Root

use OrderEnumerator caching, cleanup CR-IC-6914