package org.jetbrains.plugins.ruby.motion;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.ruby.motion.bridgesupport.BridgeSupportLoader;
import org.jetbrains.plugins.ruby.motion.bridgesupport.Constant;
import org.jetbrains.plugins.ruby.motion.bridgesupport.Framework;
import org.jetbrains.plugins.ruby.motion.bridgesupport.Function;
import org.jetbrains.plugins.ruby.motion.symbols.ConstantSymbol;
import org.jetbrains.plugins.ruby.motion.symbols.FunctionSymbol;
import org.jetbrains.plugins.ruby.motion.symbols.MotionClassSymbol;
import org.jetbrains.plugins.ruby.motion.symbols.MotionSymbolUtil;
import org.jetbrains.plugins.ruby.ruby.codeInsight.AbstractRubyTypeProvider;
import org.jetbrains.plugins.ruby.ruby.codeInsight.resolve.ResolveUtil;
import org.jetbrains.plugins.ruby.ruby.codeInsight.resolve.scope.ScopeVariable;
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure.Symbol;
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure.SymbolUtil;
import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.v2.ClassModuleSymbol;
import org.jetbrains.plugins.ruby.ruby.codeInsight.types.Context;
import org.jetbrains.plugins.ruby.ruby.codeInsight.types.RType;
import org.jetbrains.plugins.ruby.ruby.codeInsight.types.RTypeUtil;
import org.jetbrains.plugins.ruby.ruby.lang.psi.RPossibleCall;
import org.jetbrains.plugins.ruby.ruby.lang.psi.RPsiElement;
import org.jetbrains.plugins.ruby.ruby.lang.psi.controlStructures.methods.RArgument;
import org.jetbrains.plugins.ruby.ruby.lang.psi.controlStructures.methods.RMethod;
import org.jetbrains.plugins.ruby.ruby.lang.psi.controlStructures.methods.RNamedArgument;
import org.jetbrains.plugins.ruby.ruby.lang.psi.expressions.RExpression;
import org.jetbrains.plugins.ruby.ruby.lang.psi.methodCall.RCall;
import org.jetbrains.plugins.ruby.ruby.lang.psi.references.RReference;
import org.jetbrains.plugins.ruby.ruby.lang.psi.variables.RConstant;
import org.jetbrains.plugins.ruby.ruby.lang.psi.variables.RIdentifier;
import java.util.Collection;
import java.util.List;
/**
* @author Dennis.Ushakov
*/
public class RubyMotionTypeProvider extends AbstractRubyTypeProvider {
@Nullable
@Override
public RType createTypeBySymbol(Symbol symbol, Context context) {
if (symbol instanceof ConstantSymbol) {
final Constant constant = ((ConstantSymbol)symbol).getConstant();
return MotionSymbolUtil.getTypeByName(symbol.getModule(), constant.getDeclaredType());
}
return null;
}
@Nullable
@Override
public RType createTypeByRExpression(@NotNull RExpression expression) {
if (!RubyMotionUtil.getInstance().hasMacRubySupport(expression)) return null;
Module module = ModuleUtilCore.findModuleForPsiElement(expression.getContainingFile());
if (module == null) return null;
if (expression instanceof RReference) {
return createTypeForRef((RReference)expression, module);
}
else if (expression instanceof RCall) {
final PsiElement psiCommand = ((RCall)expression).getPsiCommand();
if (psiCommand instanceof RReference) {
return createTypeForRef((RReference)psiCommand, module);
}
}
else if (expression instanceof RConstant) {
return createTypeForConstant(expression, module);
}
else if (expression instanceof RIdentifier) {
final ScopeVariable variable = ((RIdentifier)expression).getScopeVariable();
if (variable != null) {
for (RPsiElement element : variable.getDeclarations()) {
if (element instanceof RIdentifier && ((RIdentifier)element).isParameterDeclaration()) {
return createTypeForMethodParameter((RIdentifier)element, module);
}
}
}
}
return null;
}
private static RType createTypeForMethodParameter(RExpression expression, @Nullable Module module) {
final RMethod method = PsiTreeUtil.getParentOfType(expression, RMethod.class);
final Symbol symbol = SymbolUtil.getSymbolByContainer(method);
if (symbol == null) return null;
List<RArgument> arguments = method.getArguments();
final String rubyName = method.getName();
final String objCName = calculateObjCName(rubyName, arguments);
final String sdkVersion = RubyMotionUtil.getInstance().getSdkVersion(module);
final String[] frameworks = RubyMotionUtil.getInstance().getRequiredFrameworks(module);
boolean isSelector = false;
for (String framework : frameworks) {
if (BridgeSupportLoader.getInstance().isSelector(objCName, sdkVersion, framework)) {
isSelector = true;
break;
}
}
if (!isSelector) return null;
Symbol classSymbol = symbol.getParentSymbol();
while (classSymbol != null && classSymbol instanceof ClassModuleSymbol) {
classSymbol = ((ClassModuleSymbol)classSymbol).getSuperClassSymbol(expression);
}
if (classSymbol instanceof MotionClassSymbol) {
final List<Symbol> candidates = classSymbol.getChildren().getSymbolsByNameAndTypes(rubyName, symbol.getType().asSet(), expression);
for (Symbol candidate : candidates) {
final Function function = ((FunctionSymbol)candidate).getFunction();
if (objCName.equals(function.getName())) {
int argIndex = 0;
while (argIndex < arguments.size() && !arguments.get(argIndex).getName().equals(expression.getName())) argIndex++;
if (argIndex < arguments.size()) {
return MotionSymbolUtil.getTypeByName(module, function.getArguments().get(argIndex).second);
}
}
}
}
return null;
}
private static String calculateObjCName(final String methodName, final List<RArgument> arguments) {
final StringBuilder objCNameBuilder = new StringBuilder(methodName).append(":");
for (RArgument argument : arguments) {
if (argument instanceof RNamedArgument) {
final RPsiElement nameIdentifier = ((RNamedArgument)argument).getNameIdentifier();
assert nameIdentifier != null;
objCNameBuilder.append(nameIdentifier.getName()).append(":");
}
}
return objCNameBuilder.toString();
}
@Nullable
private static RType createTypeForConstant(@NotNull RExpression expression, @NotNull Module module) {
final String name = expression.getName();
if (name == null) return null;
final Collection<Framework> frameworks = ((RubyMotionUtilImpl)RubyMotionUtil.getInstance()).getFrameworks(module);
for (Framework framework : frameworks) {
final Constant constant = RubyMotionSymbolProvider.findConstant(name, framework);
if (constant != null) {
return MotionSymbolUtil.getTypeByName(module, constant.getDeclaredType());
}
}
return null;
}
@Nullable
private static RType createTypeForRef(@NotNull RReference ref, @NotNull Module module) {
final RPsiElement value = ref.getValue();
// method name may be identifier on constant
if (value instanceof RPossibleCall) {
final String shortName = ((RPossibleCall)value).getPossibleCommand();
final String sdkVersion = RubyMotionUtil.getInstance().getSdkVersion(module);
final String[] frameworks = RubyMotionUtil.getInstance().getRequiredFrameworks(module);
boolean isIdSelector = false;
for (String framework : frameworks) {
if (BridgeSupportLoader.getInstance().isIdSelector(shortName, sdkVersion, framework)) {
isIdSelector = true;
break;
}
}
if (isIdSelector) {
final Symbol callSymbol = ResolveUtil.resolveToSymbolWithCaching(ref.getReference());
if (callSymbol instanceof FunctionSymbol) {
final Function function = ((FunctionSymbol)callSymbol).getFunction();
if (function.isId()) {
return RTypeUtil.createTypeSameAsReceiverInstance(ref);
}
}
}
}
return null;
}
}