package org.angularjs.codeInsight.router;
import com.intellij.lang.javascript.psi.*;
import com.intellij.lang.javascript.psi.stubs.JSImplicitElement;
import com.intellij.lang.javascript.refactoring.JSDefaultRenameProcessor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.util.Processor;
import org.angularjs.index.AngularIndexUtil;
import org.angularjs.index.AngularUiRouterGenericStatesIndex;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* @author Irina.Chernushina on 6/29/2016.
*/
public class AngularRouterStateLoader {
public static final String STATE_PROVIDER = "$stateProvider";
@NotNull private final Project myProject;
private String myStateName;
private final static Set<String> STATE_FIELDS = new HashSet<>();
private final static Set<String> ARRAY_ITERATE_METHODS = new HashSet<>();
static {
STATE_FIELDS.addAll(Arrays.asList("template", "templateUrl", "templateProvider", "views", "url", "controller", "controllerAs"));
ARRAY_ITERATE_METHODS.addAll(Arrays.asList("forEach", "map", "reduce", "reduceRight", "every", "filter", "some"));
}
public AngularRouterStateLoader(final @NotNull Project project) {
myProject = project;
}
public AngularRouterStateLoader setStateName(String stateName) {
myStateName = stateName;
return this;
}
public List<JSObjectLiteralExpression> loadFreelyDefinedStates() {
final List<JSObjectLiteralExpression> states = new ArrayList<>();
final Collection<String> allKeys = AngularIndexUtil.getAllKeys(AngularUiRouterGenericStatesIndex.KEY, myProject);
for (String key : allKeys) {
final List<JSImplicitElement> list = new ArrayList<>();
AngularIndexUtil.multiResolve(myProject, AngularUiRouterGenericStatesIndex.KEY, key, list::add);
for (JSImplicitElement element : list) {
final JSCallExpression callExpression = AngularUiRouterDiagramBuilder.findWrappingCallExpression(element);
if (callExpression != null) {
findPossibleReferences(callExpression, object -> {
final JSProperty name = object.findProperty("name");
if (name != null && name.getValue() instanceof JSLiteralExpression && ((JSLiteralExpression)name.getValue()).isQuotedLiteral()) {
for (String field : STATE_FIELDS) {
if (object.findProperty(field) != null && (myStateName == null || myStateName.endsWith(StringUtil.unquoteString(name.getValue().getText())))) {
states.add(object);
return true;
}
}
}
return false;
});
if (myStateName != null && !states.isEmpty()) return states;
}
}
}
return states;
}
private static void findPossibleReferences(@NotNull final JSCallExpression callExpression,
@NotNull final Processor<JSObjectLiteralExpression> processor) {
final JSExpression[] arguments = callExpression.getArguments();
if (arguments.length == 1 && arguments[0] instanceof JSReferenceExpression) {
processReference(processor, arguments[0], 0);
}
}
private static void processReference(@NotNull Processor<JSObjectLiteralExpression> processor, JSExpression argument, int deepness) {
if (deepness > 3) return;
final JSReferenceExpression reference = (JSReferenceExpression)argument;
final PsiElement resolved = reference.resolve();
if (resolved != null) {
if (resolved instanceof JSVariable && ((JSVariable)resolved).getInitializer() != null) {
final JSExpression initializer = ((JSVariable)resolved).getInitializer();
if (initializer instanceof JSObjectLiteralExpression) {
if (processor.process((JSObjectLiteralExpression)initializer)) return;
}
}
//find possible assignments
final Collection<PsiReference> references = JSDefaultRenameProcessor.findReferencesForScope(resolved, false, resolved.getUseScope());
for (PsiReference psiReference : references) {
if (!(psiReference instanceof JSElement)) continue;
final JSElement element = (JSElement)psiReference;
if (element.getParent() instanceof JSDefinitionExpression &&
((JSDefinitionExpression)element.getParent()).getExpression() == element &&
element.getParent().getParent() instanceof JSAssignmentExpression) {
final JSAssignmentExpression assignment = (JSAssignmentExpression)element.getParent().getParent();
if (assignment.getDefinitionExpression() != null) {
final JSExpression initializer = assignment.getDefinitionExpression().getInitializer();
if (initializer instanceof JSObjectLiteralExpression) {
processor.process((JSObjectLiteralExpression)initializer);
}
}
}
}
//find possible wrapping array iteration where this was a callback
if (resolved.getParent() instanceof JSParameterList && resolved.getParent().getParent() instanceof JSFunction) {
final JSFunction function = (JSFunction)resolved.getParent().getParent();
if (function.getParent() instanceof JSArgumentList && function.getParent().getParent() instanceof JSCallExpression) {
final JSCallExpression call = (JSCallExpression)function.getParent().getParent();
final JSExpression methodExpression = call.getMethodExpression();
if (methodExpression instanceof JSReferenceExpression) {
if (ARRAY_ITERATE_METHODS.contains(((JSReferenceExpression)methodExpression).getReferenceName())) {
final JSExpression qualifier = ((JSReferenceExpression)methodExpression).getQualifier();
if (qualifier instanceof JSArrayLiteralExpression) {
processArrayWithIterateCallback(processor, deepness, (JSArrayLiteralExpression)qualifier);
} else if (qualifier instanceof JSReferenceExpression) {
final PsiElement resolve = ((JSReferenceExpression)qualifier).resolve();
if (resolve != null && resolve.isValid() && resolve instanceof JSVariable) {
if(((JSVariable)resolve).getInitializer() instanceof JSArrayLiteralExpression) {
processArrayWithIterateCallback(processor, deepness, (JSArrayLiteralExpression)((JSVariable)resolve).getInitializer());
}
} else {
processReference(processor, qualifier, deepness + 1);
}
}
}
}
}
}
}
}
private static void processArrayWithIterateCallback(@NotNull Processor<JSObjectLiteralExpression> processor,
int deepness,
JSArrayLiteralExpression qualifier) {
final JSExpression[] expressions = qualifier.getExpressions();
for (JSExpression expression : expressions) {
if (expression instanceof JSObjectLiteralExpression) {
processor.process((JSObjectLiteralExpression)expression);
} else if (expression instanceof JSReferenceExpression) {
processReference(processor, expression, deepness + 1);
}
}
}
}