package org.elixir_lang.structure_view.element;
import com.intellij.ide.util.treeView.smartTree.TreeElement;
import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import org.apache.commons.lang.math.IntRange;
import org.elixir_lang.navigation.item_presentation.NameArity;
import org.elixir_lang.psi.*;
import org.elixir_lang.psi.call.Call;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.openapi.util.Pair.pair;
import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.stripAccessExpression;
import static org.elixir_lang.psi.operation.Normalized.operatorIndex;
public class CallDefinitionHead extends Element<Call> implements Presentable, Visible {
/*
* Fields
*/
@NotNull
private final CallDefinition callDefinition;
@NotNull
private final Visibility visibility;
/*
* Static Methods
*/
@Nullable
public static Call argumentEnclosingDelegationCall(@NotNull PsiElement arguments) {
Call delegationCall = null;
if (arguments instanceof ElixirNoParenthesesOneArgument) {
PsiElement argumentsParent = arguments.getParent();
if (argumentsParent instanceof Call) {
Call argumentsParentCall = (Call) argumentsParent;
if (Delegation.is(argumentsParentCall)) {
delegationCall = argumentsParentCall;
}
}
}
return delegationCall;
}
@Nullable
public static Call enclosingDelegationCall(@NotNull Call call) {
// reverse of {@link org.elixir_lang.structure_view.element.Delegation.filterCallDefinitionHeadCallList()}
PsiElement parent = call.getParent();
Call delegationCall = null;
if (parent instanceof ElixirList) {
PsiElement grandParent = parent.getParent();
if (grandParent instanceof ElixirAccessExpression) {
PsiElement greatGrandParent = parent.getParent();
delegationCall = argumentEnclosingDelegationCall(greatGrandParent);
}
} else {
delegationCall = argumentEnclosingDelegationCall(parent);
}
return delegationCall;
}
public static boolean is(Call call) {
return call instanceof UnqualifiedParenthesesCall;
}
@NotNull
public static PsiElement nameIdentifier(PsiElement head) {
PsiElement nameIdentifier;
if (head instanceof ElixirMatchedAtNonNumericOperation) {
AtNonNumericOperation atNonNumericOperation = (AtNonNumericOperation) head;
nameIdentifier = atNonNumericOperation.operator();
} else {
PsiElement stripped = strip(head);
if (stripped instanceof Call) {
Call strippedCall = (Call) stripped;
nameIdentifier = strippedCall.functionNameElement();
} else {
nameIdentifier = stripped;
}
}
return nameIdentifier;
}
@Nullable
public static Pair<String, IntRange> nameArityRange(PsiElement head) {
String name;
IntRange arityRange;
Pair<String, IntRange> pair = null;
if (head instanceof ElixirMatchedAtNonNumericOperation) {
ElixirMatchedAtNonNumericOperation matchedAtNonNumericOperation = (ElixirMatchedAtNonNumericOperation) head;
name = matchedAtNonNumericOperation.operator().getText().trim();
arityRange = new IntRange(1);
pair = pair(name, arityRange);
} else {
PsiElement stripped = strip(head);
if (stripped instanceof Call) {
Call strippedCall = (Call) stripped;
name = strippedCall.functionName();
arityRange = strippedCall.resolvedFinalArityRange();
pair = pair(name, arityRange);
}
}
return pair;
}
/**
* Head without parentheses around the guard or guarded head
*
* @param head {@code ((((name(arg, ...))) when ...))}
* @return {@code name(arg, ...)}
*/
@NotNull
public static PsiElement strip(@NotNull final PsiElement head) {
PsiElement strippedGuarded = stripAllOuterParentheses(head);
PsiElement unguarded = stripGuard(strippedGuarded);
return stripAllOuterParentheses(unguarded);
}
/**
* The head without the guard clause
*
* @param head {@code name(arg, ...) when ...}
* @return {@code name(arg, ...)}. {@code head} if no guard clause.
*/
@NotNull
public static PsiElement stripGuard(@NotNull final PsiElement head) {
PsiElement stripped = head;
if (head instanceof ElixirMatchedWhenOperation) {
ElixirMatchedWhenOperation whenOperation = (ElixirMatchedWhenOperation) head;
PsiElement[] children = whenOperation.getChildren();
int operatorIndex = operatorIndex(children);
int onlyNonErrorIndex = -1;
for (int i = 0; i < operatorIndex; i++) {
if (!(children[i] instanceof PsiErrorElement)) {
if (onlyNonErrorIndex == -1) {
// first
onlyNonErrorIndex = i;
} else {
// more than one
onlyNonErrorIndex = -1;
break;
}
}
}
if (onlyNonErrorIndex != -1) {
stripped = stripGuard(children[onlyNonErrorIndex]);
}
}
return stripped;
}
/**
* Strips each set of outer parentheses from {@code head} until there aren't anymore.
*
* @param head {@code ((value))}
* @return {@code value}. {@code head} if no outer parentheses
*/
@NotNull
public static PsiElement stripAllOuterParentheses(@NotNull final PsiElement head) {
PsiElement stripped = head;
PsiElement previousStripped;
do {
previousStripped = stripped;
stripped = stripOuterParentheses(previousStripped);
} while (previousStripped != stripped);
return stripped;
}
/**
* Strips outer parentheses from {@code head}.
*
* @param head {@code (value)}
* @return {@code value}. {@code head} if no outer parentheses
*/
@NotNull
public static PsiElement stripOuterParentheses(PsiElement head) {
PsiElement stripped = stripAccessExpression(head);
if (stripped instanceof ElixirParentheticalStab) {
ElixirParentheticalStab parentheticalStab = (ElixirParentheticalStab) stripped;
PsiElement[] parentheticalStabChildren = parentheticalStab.getChildren();
if (parentheticalStabChildren.length == 1) {
PsiElement parentheticalStabChild = parentheticalStabChildren[0];
if (parentheticalStabChild instanceof ElixirStab) {
ElixirStab stab = (ElixirStab) parentheticalStabChild;
PsiElement[] stabChildren = stab.getChildren();
if (stabChildren.length == 1) {
PsiElement stabChild = stabChildren[0];
if (stabChild instanceof ElixirStabBody) {
ElixirStabBody stabBody = (ElixirStabBody) stabChild;
PsiElement[] stabBodyChildren = stabBody.getChildren();
if (stabBodyChildren.length == 1) {
stripped = stabBodyChildren[0];
}
}
}
}
}
}
return stripped;
}
/*
* Constructors
*/
public CallDefinitionHead(@NotNull CallDefinition callDefinition,
@NotNull Visibility visibility,
@NotNull Call call) {
super(call);
this.callDefinition = callDefinition;
this.visibility = visibility;
}
/*
* Instance Methods
*/
/**
* Heads have no children since they have no body.
*
* @return empty array
*/
@NotNull
@Override
public TreeElement[] getChildren() {
return new TreeElement[0];
}
/**
* Returns the presentation of the tree element.
*
* @return the element presentation.
*/
@NotNull
@Override
public ItemPresentation getPresentation() {
return new org.elixir_lang.navigation.item_presentation.CallDefinitionHead(
(NameArity) callDefinition.getPresentation(),
visibility,
navigationItem
);
}
/**
* The visibility of the element.
*
* @return {@link Visibility#PUBLIC}.
*/
@NotNull
@Override
public Visibility visibility() {
return visibility;
}
}