/*
 * 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 org.jetbrains.idea.devkit.util;

import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.GlobalSearchScopesCore;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.ui.components.JBList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

/**
 * @author Konstantin Bulenkov
 */
public class PsiUtil {
  private static final Key<Boolean> IDEA_PROJECT = Key.create("idea.internal.inspections.enabled");
  private static final String IDE_PROJECT_MARKER_CLASS = JBList.class.getName();

  private PsiUtil() { }

  public static boolean isInstantiable(@NotNull PsiClass cls) {
    PsiModifierList modList = cls.getModifierList();
    if (modList == null || cls.isInterface() || modList.hasModifierProperty(PsiModifier.ABSTRACT) || !isPublicOrStaticInnerClass(cls)) {
      return false;
    }

    PsiMethod[] constructors = cls.getConstructors();
    if (constructors.length == 0) return true;

    for (PsiMethod constructor : constructors) {
      if (constructor.getParameterList().getParameters().length == 0
          && constructor.hasModifierProperty(PsiModifier.PUBLIC)) {
        return true;
      }
    }
    return false;
  }

  public static boolean isPublicOrStaticInnerClass(@NotNull PsiClass cls) {
    PsiModifierList modifiers = cls.getModifierList();
    if (modifiers == null) return false;

    return modifiers.hasModifierProperty(PsiModifier.PUBLIC) &&
           (cls.getParent() instanceof PsiFile || modifiers.hasModifierProperty(PsiModifier.STATIC));
  }

  @Nullable
  public static String getReturnedLiteral(PsiMethod method, PsiClass cls) {
    PsiExpression value = getReturnedExpression(method);
    if (value instanceof PsiLiteralExpression) {
      Object str = ((PsiLiteralExpression)value).getValue();
      return str == null ? null : str.toString();
    }
    else if (value instanceof PsiMethodCallExpression) {
      if (isSimpleClassNameExpression((PsiMethodCallExpression)value)) {
        return cls.getName();
      }
    }
    return null;
  }

  private static boolean isSimpleClassNameExpression(PsiMethodCallExpression expr) {
    String text = expr.getText();
    if (text == null) return false;
    text = text.replaceAll(" ", "")
      .replaceAll("\n", "")
      .replaceAll("\t", "")
      .replaceAll("\r", "");
    return "getClass().getSimpleName()".equals(text) || "this.getClass().getSimpleName()".equals(text);
  }

  @Nullable
  public static PsiExpression getReturnedExpression(PsiMethod method) {
    PsiCodeBlock body = method.getBody();
    if (body != null) {
      PsiStatement[] statements = body.getStatements();
      if (statements.length == 1 && statements[0] instanceof PsiReturnStatement) {
        PsiExpression value = ((PsiReturnStatement)statements[0]).getReturnValue();
        if (value instanceof PsiReferenceExpression) {
          PsiElement element = ((PsiReferenceExpression)value).resolve();
          if (element instanceof PsiField) {
            PsiField field = (PsiField)element;
            if (field.hasModifierProperty(PsiModifier.FINAL)) {
              return field.getInitializer();
            }
          }
        }
        return value;
      }
    }

    return null;
  }

  @Nullable
  public static PsiMethod findNearestMethod(String name, @Nullable PsiClass cls) {
    if (cls == null) return null;
    for (PsiMethod method : cls.getMethods()) {
      if (method.getParameterList().getParametersCount() == 0 && method.getName().equals(name)) {
        return method.getModifierList().hasModifierProperty(PsiModifier.ABSTRACT) ? null : method;
      }
    }
    return findNearestMethod(name, cls.getSuperClass());
  }


  public static boolean isIdeaProject(@Nullable Project project) {
    if (project == null) return false;

    Boolean flag = project.getUserData(IDEA_PROJECT);
    if (flag == null) {
      flag = checkIdeaProject(project);
      project.putUserData(IDEA_PROJECT, flag);
    }

    return flag;
  }

  public static boolean isPluginProject(@NotNull final Project project) {
    return CachedValuesManager.getManager(project).getCachedValue(project, () -> {
      boolean foundMarkerClass =
        JavaPsiFacade.getInstance(project).findClass(IDE_PROJECT_MARKER_CLASS,
                                                     GlobalSearchScope.allScope(project)) != null;
      return CachedValueProvider.Result.createSingleDependency(foundMarkerClass, ProjectRootManager.getInstance(project));
    });
  }

  public static boolean isPluginModule(@NotNull final Module module) {
    return CachedValuesManager.getManager(module.getProject()).getCachedValue(module, () -> {
      boolean foundMarkerClass = JavaPsiFacade.getInstance(module.getProject())
                                   .findClass(IDE_PROJECT_MARKER_CLASS,
                                              GlobalSearchScope.moduleRuntimeScope(module, false)) != null;
      return CachedValueProvider.Result.createSingleDependency(foundMarkerClass, ProjectRootManager.getInstance(module.getProject()));
    });
  }

  private static boolean isIntelliJBasedDir(VirtualFile baseDir) {
    if (baseDir == null) {
      return false;
    }

    for (VirtualFile dir : new VirtualFile[]{baseDir, baseDir.findChild("community")}) {
      if (dir == null || !dir.isDirectory()) {
        continue;
      }
      if (dir.findChild("idea.iml") != null || dir.findChild("community-main.iml") != null) {
        return true;
      }
    }

    return false;
  }

  @TestOnly
  public static void markAsIdeaProject(@NotNull Project project, boolean value) {
    project.putUserData(IDEA_PROJECT, value);
  }

  private static boolean checkIdeaProject(@NotNull Project project) {
    if (!isIntelliJBasedDir(project.getBaseDir())) {
      return false;
    }

    GlobalSearchScope scope = GlobalSearchScopesCore.projectProductionScope(project);
    if (JavaPsiFacade.getInstance(project).findClass(IDE_PROJECT_MARKER_CLASS, scope) == null) {
      return false;
    }

    return true;
  }

  @NotNull
  public static <E extends PsiElement> SmartPsiElementPointer<E> createPointer(@NotNull E e) {
    return SmartPointerManager.getInstance(e.getProject()).createSmartPsiElementPointer(e);
  }
}

@@ -21,9 +21,9 @@
 -201
-5
+7
  Jet
@@ -6228,16 +6228,47 @@
 munity%22)
+, baseDir.findChild(%22ultimate%22)
 %7D) %7B%0A   

support custom IDE layout