/*
 * Copyright 2013-2017 Grzegorz Ligas <[email protected]> and other contributors
 * (see the CONTRIBUTORS file).
 *
 * 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.intellij.xquery.psi.impl;

import com.intellij.lang.ASTNode;
import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.ResolveState;
import com.intellij.psi.TokenType;
import com.intellij.psi.impl.ResolveScopeManager;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiElementFilter;
import com.intellij.psi.util.PsiTreeUtil;
import org.intellij.xquery.icons.XQueryIcons;
import org.intellij.xquery.psi.XQueryAnnotation;
import org.intellij.xquery.psi.XQueryArrowFunctionReference;
import org.intellij.xquery.psi.XQueryArrowFunctionSpecifier;
import org.intellij.xquery.psi.XQueryAttrLocalName;
import org.intellij.xquery.psi.XQueryAttrNamespace;
import org.intellij.xquery.psi.XQueryElementFactory;
import org.intellij.xquery.psi.XQueryExprSingle;
import org.intellij.xquery.psi.XQueryFile;
import org.intellij.xquery.psi.XQueryForBinding;
import org.intellij.xquery.psi.XQueryFunctionCall;
import org.intellij.xquery.psi.XQueryFunctionDecl;
import org.intellij.xquery.psi.XQueryFunctionInvocation;
import org.intellij.xquery.psi.XQueryFunctionLocalName;
import org.intellij.xquery.psi.XQueryFunctionName;
import org.intellij.xquery.psi.XQueryLetBinding;
import org.intellij.xquery.psi.XQueryMarklogicAnnotation;
import org.intellij.xquery.psi.XQueryModuleDecl;
import org.intellij.xquery.psi.XQueryModuleImport;
import org.intellij.xquery.psi.XQueryModuleImportPath;
import org.intellij.xquery.psi.XQueryNamedElement;
import org.intellij.xquery.psi.XQueryNamedFunctionRef;
import org.intellij.xquery.psi.XQueryNamespaceDecl;
import org.intellij.xquery.psi.XQueryNamespacePrefix;
import org.intellij.xquery.psi.XQueryPrefix;
import org.intellij.xquery.psi.XQueryPsiElement;
import org.intellij.xquery.psi.XQueryQueryBody;
import org.intellij.xquery.psi.XQueryTypes;
import org.intellij.xquery.psi.XQueryVarDecl;
import org.intellij.xquery.psi.XQueryVarLocalName;
import org.intellij.xquery.psi.XQueryVarName;
import org.intellij.xquery.psi.XQueryVarRef;
import org.intellij.xquery.psi.XQueryVersion;
import org.intellij.xquery.psi.XQueryXmlTagName;
import org.intellij.xquery.psi.XQueryXmlTagNamespace;
import org.intellij.xquery.reference.function.XQueryFunctionReference;
import org.intellij.xquery.reference.module.XQueryModuleReference;
import org.intellij.xquery.reference.namespace.XQueryAttrNamespaceReference;
import org.intellij.xquery.reference.namespace.XQueryNamespacePrefixReference;
import org.intellij.xquery.reference.namespace.XQueryXmlTagNamespaceReference;
import org.intellij.xquery.reference.variable.XQueryVariableReference;
import org.intellij.xquery.reference.xml.XQueryXmlTagNameReference;
import org.intellij.xquery.util.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.Icon;

import java.util.Collection;

import static org.intellij.xquery.util.StringUtils.EMPTY;
import static org.intellij.xquery.util.StringUtils.removeQuotOrApos;

/**
 * User: ligasgr
 * Date: 10/02/13
 * Time: 18:59
 */
public class XQueryPsiImplUtil {

    private static final int DOLLAR_CHAR_LENGTH = 1;
    private static final int SEPARATOR_LENGTH = 1;

    public static String getName(XQueryNamespacePrefix element) {
        return element.getNameIdentifier().getText();
    }

    public static PsiElement setName(XQueryNamespacePrefix element, String newName) {
        XQueryNamespacePrefix name = element;
        if (name != null) {
            name.replace(XQueryElementFactory.createModuleDeclarationName(element.getProject(), newName));
        }
        return element;
    }

    public static PsiElement getNameIdentifier(XQueryNamespacePrefix element) {
        return element;
    }

    public static PsiReference getReference(XQueryVarRef element) {
        int localNameOffset = DOLLAR_CHAR_LENGTH;
        if (element.getVarName() != null) {
            if (element.getVarName().getPrefix() != null) {
                localNameOffset += element.getVarName().getPrefix().getTextLength() + SEPARATOR_LENGTH;
            }
            return new XQueryVariableReference(element, new TextRange(localNameOffset, element.getTextLength()));
        }
        return null;
    }

    public static String getName(XQueryVarName element) {
        if (element.getNameIdentifier() != null) {
            return element.getNameIdentifier().getText();
        } else {
            return null;
        }
    }

    public static PsiElement setName(XQueryVarName element, String newName) {
        XQueryVarName name = element;
        if (name != null) {
            XQueryVarLocalName localName = name.getVarLocalName();
            if (localName != null) {
                XQueryVarName newNameElement = XQueryElementFactory.createVariableReference(element.getProject(),
                        newName);
                localName.replace(newNameElement.getVarLocalName());
            }
        }
        return element;
    }

    public static PsiElement getNameIdentifier(XQueryVarName element) {
        if (element == null) return null;
        return element.getVarLocalName();
    }

    public static int getTextOffset(XQueryVarName element) {
        if (element == null) return 0;
        PsiElement nameIdentifier = getNameIdentifier(element);
        if (nameIdentifier == null) return endOfColon(element);
        return nameIdentifier.getTextOffset();
    }

    private static int endOfColon(XQueryVarName varName) {
        if (varName.getPrefix() != null) {
            String colon = XQueryTypes.COLON.toString();
            int offset = varName.getText().indexOf(colon) + colon.length();
            return varName.getNode().getStartOffset() + offset;
        }
        return 0;
    }

    public static PsiReference getReference(XQueryModuleImportPath element) {
        String filename = stripApostrophes(element.getURILiteral().getText());
        if (!StringUtil.isEmptyOrSpaces(filename)) {
            return new XQueryModuleReference(element, filename, new TextRange(1,
                    element.getURILiteral().getTextLength() - 1));
        }
        return null;
    }

    public static String stripApostrophes(String text) {
        return text.replaceAll("\"", "").replaceAll("'", "");
    }

    public static PsiReference getReference(XQueryFunctionInvocation element) {
        if (element.getFunctionName() == null) return null;
        int localNameOffset = 0;
        if (element.getFunctionName().getPrefix() != null) {
            localNameOffset += element.getFunctionName().getPrefix().getTextLength() + SEPARATOR_LENGTH;
        }
        return new XQueryFunctionReference(element, new TextRange(localNameOffset,
                element.getFunctionName().getTextLength()));
    }

    public static String getName(XQueryFunctionName element) {
        if (element.getNameIdentifier() != null) {
            return element.getNameIdentifier().getText();
        } else {
            return null;
        }
    }

    public static String getPrefixText(XQueryFunctionName element) {
        if (element.getPrefix() != null) {
            return element.getPrefix().getText();
        } else {
            return null;
        }
    }

    public static String getLocalNameText(XQueryFunctionName element) {
        if (element.getFunctionLocalName() != null) {
            return element.getFunctionLocalName().getText();
        } else {
            return null;
        }
    }

    public static PsiElement setName(XQueryFunctionName element, String newName) {
        XQueryFunctionName name = element;
        if (name != null) {
            XQueryFunctionLocalName localName = name.getFunctionLocalName();
            if (localName != null) {
                XQueryFunctionName newNameElement = XQueryElementFactory.createFunctionReference(element.getProject()
                        , "dummy", newName);
                localName.replace(newNameElement.getFunctionLocalName());
            }
        }
        return element;
    }

    public static PsiElement getNameIdentifier(XQueryFunctionName element) {
        if (element == null) return null;
        return element.getFunctionLocalName();
    }

    public static int getTextOffset(XQueryFunctionName element) {
        if (element == null) return 0;
        PsiElement nameIdentifier = getNameIdentifier(element);
        if (nameIdentifier == null) return endOfColon(element);
        return nameIdentifier.getTextOffset();
    }

    private static int endOfColon(XQueryFunctionName functionName) {
        if (functionName.getPrefix() != null) {
            String colon = XQueryTypes.COLON.toString();
            int offset = functionName.getText().indexOf(colon) + colon.length();
            return functionName.getNode().getStartOffset() + offset;
        }
        return 0;
    }

    public static SearchScope getUseScope(XQueryVarName element) {
        XQueryFunctionDecl function = PsiTreeUtil.getParentOfType(element, XQueryFunctionDecl.class, true);
        if (function != null) {
            return new LocalSearchScope(function);
        }
        XQueryQueryBody queryBody = PsiTreeUtil.getParentOfType(element, XQueryQueryBody.class, true);
        if (queryBody != null) {
            return new LocalSearchScope(queryBody);
        }
        return ResolveScopeManager.getElementUseScope(element);
    }

    public static ItemPresentation getPresentation(final XQueryFunctionDecl element) {
        return new ItemPresentation() {
            @Nullable
            @Override
            public String getPresentableText() {
                String name = element.getFunctionName() != null ? element.getFunctionName().getText() : "";
                String tailText = (element.getParamList() != null ? element.getParamList().getText() : "") + " as ";
                String typeText = element.getSequenceType() != null ? element.getSequenceType()
                        .getText() : "item()*";
                return StringUtils.compressWhitespaces(name + tailText + typeText);
            }

            @Nullable
            @Override
            public String getLocationString() {
                return null;
            }

            @Nullable
            @Override
            public Icon getIcon(boolean unused) {
                return element.isPublic() ? XQueryIcons.FUNCTION_PUBLIC_ICON : XQueryIcons.FUNCTION_PRIVATE_ICON;
            }
        };
    }

    public static ItemPresentation getPresentation(final XQueryVarDecl element) {
        return new ItemPresentation() {
            @Nullable
            @Override
            public String getPresentableText() {
                String name = element.getVarName() != null ? element.getVarName().getText() : "";
                String typeText = "item()*";
                if (element.getTypeDeclaration() != null) {
                    typeText = element.getTypeDeclaration().getText();
                }
                return StringUtils.compressWhitespaces(name + " as " + typeText);
            }

            @Nullable
            @Override
            public String getLocationString() {
                return null;
            }

            @Nullable
            @Override
            public Icon getIcon(boolean unused) {
                return isPublic(element) ? XQueryIcons.VARIABLE_PUBLIC_ICON : XQueryIcons.VARIABLE_PRIVATE_ICON;
            }
        };
    }

    public static ItemPresentation getPresentation(final XQueryVarName element) {
        if (element.getParent() instanceof XQueryVarDecl) {
            return ((XQueryVarDecl) element.getParent()).getPresentation();
        }
        return null;
    }


    public static ItemPresentation getPresentation(final XQueryQueryBody element) {
        return new ItemPresentation() {
            @Nullable
            @Override
            public String getPresentableText() {
                return "Query Body";
            }

            @Nullable
            @Override
            public String getLocationString() {
                return null;
            }

            @Nullable
            @Override
            public Icon getIcon(boolean unused) {
                return XQueryIcons.QUERY_BODY;
            }
        };
    }

    public static ItemPresentation getPresentation(final XQueryFunctionName element) {
        if (element.getParent() instanceof XQueryFunctionDecl) {
            return ((XQueryFunctionDecl) element.getParent()).getPresentation();
        }
        return null;
    }

    public static int getArity(XQueryArrowFunctionReference functionCall) {
        return functionCall.getArgumentList().getArgumentList().size() + 1;
    }

    public static int getArity(XQueryFunctionCall functionCall) {
        return functionCall.getArgumentList().getArgumentList().size();
    }

    public static int getArity(XQueryNamedFunctionRef functionCall) {
        return StringUtil.parseInt(functionCall.getFunctionArity().getText(), 0);
    }

    public static int getArity(XQueryFunctionDecl functionDeclaration) {
        if (functionDeclaration.getParamList() != null)
            return functionDeclaration.getParamList().getParamList().size();
        else
            return 0;
    }

    public static boolean hasValidFunctionName(XQueryFunctionDecl functionDecl) {
        return functionDecl.getFunctionName() != null && functionDecl.getFunctionName().getTextLength() > 0;
    }

    public static boolean isPublic(XQueryFunctionDecl functionDecl) {
        for (XQueryAnnotation annotation : functionDecl.getAnnotationList()) {
            if ("private".equals(annotation.getAnnotationName().getText()))
                return false;
        }
        for (XQueryMarklogicAnnotation annotation : functionDecl.getMarklogicAnnotationList()) {
            if ("private".equals(annotation.getText()))
                return false;
        }
        return true;
    }

    public static boolean isPublic(XQueryVarDecl varDecl) {
        for (XQueryAnnotation annotation : varDecl.getAnnotationList()) {
            if ("private".equals(annotation.getAnnotationName().getText()))
                return false;
        }
        for (XQueryMarklogicAnnotation annotation : varDecl.getMarklogicAnnotationList()) {
            if ("private".equals(annotation.getText()))
                return false;
        }
        return true;
    }

    public static void delete(XQueryAttrLocalName namedElement) {
        PsiElement dirAttribute = namedElement.getParent().getParent();
        deleteElement(dirAttribute);
    }

    public static void delete(XQueryVarName element) {
        deleteDeclaration(element);
    }

    public static void delete(XQueryFunctionName element) {
        deleteDeclaration(element);
    }

    public static void delete(XQueryNamespacePrefix element) {
        deleteDeclaration(element);
    }

    private static void deleteDeclaration(XQueryNamedElement namedElement) {
        PsiElement declarationElement = namedElement.getParent();
        deleteElement(declarationElement);
    }

    private static void deleteElement(PsiElement element) {
        final ASTNode parentNode = element.getParent().getNode();
        assert parentNode != null;

        ASTNode node = element.getNode();
        ASTNode prev = node.getTreePrev();
        ASTNode next = node.getTreeNext();
        parentNode.removeChild(node);
        if (prev == null || prev.getElementType() == TokenType.WHITE_SPACE) {
            while (next != null && (next.getElementType() == TokenType.WHITE_SPACE || next.getElementType() ==
                    XQueryTypes.SEPARATOR)) {
                parentNode.removeChild(next);
                next = node.getTreeNext();
            }
        }
    }

    public static boolean isExternal(XQueryVarDecl variableDeclaration) {
        return variableDeclaration.getExternalVarPart() != null;
    }

    public static String getNamespace(XQueryModuleDecl moduleDecl) {
        if (moduleDecl.getURILiteral() != null) {
            return removeQuotOrApos(moduleDecl.getURILiteral().getText());
        }
        return EMPTY;
    }

    public static String getNamespace(XQueryModuleImport moduleImport) {
        if (moduleImport.getModuleImportNamespace() != null) {
            return removeQuotOrApos(moduleImport.getModuleImportNamespace().getModuleImportPath().getURILiteral()
                    .getText());
        }
        return EMPTY;
    }

    public static String getNamespace(XQueryNamespaceDecl namespaceDecl) {
        if (namespaceDecl.getURILiteral() != null) {
            return removeQuotOrApos(namespaceDecl.getURILiteral().getText());
        }
        return EMPTY;
    }

    public static PsiReference getReference(XQueryPrefix prefix) {
        return new XQueryNamespacePrefixReference(prefix, new TextRange(0, prefix.getTextLength()));
    }

    public static PsiReference getReference(XQueryXmlTagNamespace prefix) {
        return new XQueryXmlTagNamespaceReference(prefix, new TextRange(0, prefix.getTextLength()));
    }

    public static PsiReference getReference(XQueryAttrNamespace prefix) {
        return new XQueryAttrNamespaceReference(prefix, new TextRange(0, prefix.getTextLength()));
    }

    public static PsiReference getReference(XQueryXmlTagName element) {
        int offset = 0;
        if (element.getXmlTagNamespace() != null) {
            offset += element.getXmlTagNamespace().getTextLength() + SEPARATOR_LENGTH;
        }
        return new XQueryXmlTagNameReference(element, new TextRange(offset, element.getTextLength()));
    }

    public static String getName(XQueryPrefix element) {
        return element.getNameIdentifier().getText();
    }

    public static PsiElement setName(XQueryPrefix element, String newName) {
        XQueryPrefix name = element;
        if (name != null) {
            name.replace(XQueryElementFactory.createFunctionReference(element.getProject(), newName, "any").getPrefix());
        }
        return element;
    }

    public static String getName(XQueryXmlTagNamespace element) {
        return element.getNameIdentifier().getText();
    }

    public static PsiElement setName(XQueryXmlTagNamespace element, String newName) {
        XQueryXmlTagNamespace name = element;
        if (name != null) {
            name.replace(XQueryElementFactory.createXmlTag(element.getProject(), newName, "any").getXmlTagName().getXmlTagNamespace());
        }
        return element;
    }

    public static String getName(XQueryAttrLocalName element) {
        return element.getNameIdentifier().getText();
    }

    public static PsiElement setName(XQueryAttrLocalName element, String newName) {
        XQueryAttrLocalName name = element;
        if (name != null) {
            name.replace(XQueryElementFactory.createAttributeName(element.getProject(), "any", newName).getAttrLocalName());
        }
        return element;
    }

    public static String getName(XQueryAttrNamespace element) {
        return element.getNameIdentifier().getText();
    }

    public static PsiElement setName(XQueryAttrNamespace element, String newName) {
        XQueryAttrNamespace name = element;
        if (name != null) {
            name.replace(XQueryElementFactory.createAttributeName(element.getProject(), newName, "any").getAttrNamespace());
        }
        return element;
    }

    public static String getName(XQueryXmlTagName element) {
        return element.getNameIdentifier().getText();
    }

    public static PsiElement setName(XQueryXmlTagName element, String newName) {
        XQueryXmlTagName name = element;
        if (name != null) {
            name.getXmlTagLocalName().replace(XQueryElementFactory.createXmlTag(element.getProject(), "any", newName).getXmlTagName().getXmlTagLocalName());
        }
        return element;
    }

    public static PsiElement getNameIdentifier(XQueryPrefix element) {
        return element;
    }

    public static PsiElement getNameIdentifier(XQueryXmlTagNamespace element) {
        return element;
    }

    public static PsiElement getNameIdentifier(XQueryAttrLocalName element) {
        return element;
    }

    public static PsiElement getNameIdentifier(XQueryAttrNamespace element) {
        return element;
    }

    public static PsiElement getNameIdentifier(XQueryXmlTagName element) {
        if (element == null) return null;
        return element.getXmlTagLocalName();
    }

    public static int getTextOffset(XQueryXmlTagName element) {
        if (element == null) return 0;
        PsiElement nameIdentifier = getNameIdentifier(element);
        if (nameIdentifier == null) return endOfColon(element);
        return nameIdentifier.getTextOffset();
    }

    private static int endOfColon(XQueryXmlTagName tagName) {
        if (tagName.getXmlTagNamespace() != null) {
            String colon = XQueryTypes.COLON.toString();
            int offset = tagName.getText().indexOf(colon) + colon.length();
            return tagName.getNode().getStartOffset() + offset;
        }
        return 0;
    }

    public static boolean isEquivalentTo(XQueryPrefix element, PsiElement another) {
        return isEquivalentPrefix(element, another);
    }

    public static boolean isEquivalentTo(XQueryXmlTagNamespace element, PsiElement another) {
        return isEquivalentPrefix(element, another);
    }

    public static boolean isEquivalentTo(XQueryAttrNamespace element, PsiElement another) {
        return isEquivalentPrefix(element, another);
    }

    private static boolean isEquivalentPrefix(XQueryPsiElement element, PsiElement another) {
        boolean isElementContainingNamespace = another instanceof XQueryPrefix
                || another instanceof XQueryXmlTagNamespace
                || another instanceof XQueryAttrNamespace;
        if (!isElementContainingNamespace) {
            return false;
        }
        if (element.getContainingFile() instanceof XQueryFile && another.getContainingFile() instanceof XQueryFile) {
            XQueryFile elementFile = (XQueryFile) element.getContainingFile();
            XQueryFile anotherFile = (XQueryFile) another.getContainingFile();
            String elementNamespace = elementFile.mapFunctionPrefixToNamespace(element.getText());
            String anotherNamespace = anotherFile.mapFunctionPrefixToNamespace(another.getText());
            return elementFile.equals(anotherFile) && elementNamespace.equals(anotherNamespace);
        }
        return false;
    }

    public static PsiElement getTopmostElementWithTheSameOffset(PsiElement element) {
        if (element == null) return null;
        PsiElement lastElement = element;
        int offset = element.getTextOffset();

        while (element != null) {
            if (offset == element.getTextOffset() && !(element instanceof PsiFile)) {
                lastElement = element;
                element = element.getParent();
            } else {
                return lastElement;
            }
        }

        return lastElement;
    }

    public static PsiElement getNextNonWhiteSpaceElement(PsiElement element) {
        if (isNotWhitespace(element)) {
            return element;
        }
        PsiElement nextSibling = element.getNextSibling();
        while (nextSibling != null) {
            if (isNotWhitespace(nextSibling)) {
                return nextSibling;
            }
            nextSibling = element.getNextSibling();
        }
        return null;
    }

    public static PsiElement getPrevNonWhiteSpaceElement(PsiElement element) {
        PsiElement previousLeaf = previousLeaf(element);
        while (previousLeaf != null && (previousLeaf.getTextLength() == 0 || isWhitespace(previousLeaf))) {
            previousLeaf = previousLeaf(previousLeaf);
        }
        return previousLeaf;
    }

    public static PsiElement previousLeaf(@NotNull PsiElement current) {
        final PsiElement prevSibling = current.getPrevSibling();
        if (prevSibling != null) return PsiTreeUtil.lastChild(prevSibling);
        final PsiElement parent = current.getParent();
        if (parent == null || parent instanceof PsiFile) return null;
        PsiElement previousOfParent = previousLeaf(parent);
        if (previousOfParent == null) return null;
        return PsiTreeUtil.lastChild(previousOfParent);
    }

    private static boolean isNotWhitespace(PsiElement element) {
        return !isWhitespace(element);
    }

    private static boolean isWhitespace(PsiElement element) {
        return element instanceof PsiWhiteSpace;
    }

    public static PsiElement[] findChildrenOfType(PsiElement startingElement, final IElementType elementType) {
        return PsiTreeUtil.collectElements(startingElement, new PsiElementFilter() {
            @Override
            public boolean isAccepted(PsiElement element) {
                return element.getNode() != null && element.getNode().getElementType() == elementType;
            }
        });
    }

    public static int getTextOffset(XQueryFunctionDecl functionDecl) {
        if (functionDecl.getFunctionName() == null) return endOfFunctionKeyword(functionDecl);
        return functionDecl.getFunctionName().getTextOffset();
    }

    private static int endOfFunctionKeyword(XQueryFunctionDecl functionDecl) {
        String keyword = XQueryTypes.K_FUNCTION.toString();
        int namePositionOffset = functionDecl.getText().indexOf(keyword) + keyword.length();
        return functionDecl.getNode().getStartOffset() + namePositionOffset;
    }

    public static int getTextOffset(XQueryVarDecl varDecl) {
        if (varDecl.getVarName() == null) return endOfDollarOrVariableKeyword(varDecl);
        ;
        return varDecl.getVarName().getTextOffset();
    }

    private static int endOfDollarOrVariableKeyword(XQueryVarDecl varDecl) {
        String dollarSign = XQueryTypes.DOLLAR_SIGN.toString();
        int indexOfDollar = varDecl.getText().indexOf(dollarSign);
        int dollarOffset = varDecl.getNode().getStartOffset() + indexOfDollar + dollarSign.length();
        String keyword = XQueryTypes.K_VARIABLE.toString();
        int namePositionOffset = varDecl.getText().indexOf(keyword) + keyword.length();
        int variableOffset = varDecl.getNode().getStartOffset() + namePositionOffset;
        return indexOfDollar > -1 ? dollarOffset : variableOffset;
    }

    public static String getVersionString(XQueryVersion version) {
        return stripApostrophes(version.getText());
    }

    public static boolean isEquivalentTo(XQueryFunctionName functionName, PsiElement another) {
        if (another instanceof XQueryFunctionName) {
            if (functionName.getContainingFile() instanceof XQueryFile && another.getContainingFile() instanceof XQueryFile) {
                XQueryFunctionName anotherFunctionName = (XQueryFunctionName) another;
                XQueryFile elementFile = (XQueryFile) functionName.getContainingFile();
                XQueryFile anotherFile = (XQueryFile) another.getContainingFile();
                String elementNamespace = elementFile.mapFunctionPrefixToNamespace(functionName.getPrefixText());
                String anotherNamespace = anotherFile.mapFunctionPrefixToNamespace(anotherFunctionName.getPrefixText());
                String elementName = functionName.getLocalNameText();
                String anotherName = anotherFunctionName.getLocalNameText();
                return elementFile.equals(anotherFile)
                        && elementNamespace.equals(anotherNamespace)
                        && elementName != null && elementName.equals(anotherName);
            }
        }
        return false;
    }

    public static boolean processDeclarations(XQueryForBinding module, @NotNull PsiScopeProcessor processor, @NotNull ResolveState state, PsiElement lastParent, @NotNull PsiElement place) {
        return processChildrenIfPlaceIsNotPartOfSameBinding(module, processor, state, lastParent, place, XQueryForBinding.class, XQueryExprSingle.class);
    }

    public static boolean processDeclarations(XQueryLetBinding module, @NotNull PsiScopeProcessor processor, @NotNull ResolveState state, PsiElement lastParent, @NotNull PsiElement place) {
        return processChildrenIfPlaceIsNotPartOfSameBinding(module, processor, state, lastParent, place, XQueryLetBinding.class, XQueryExprSingle.class);
    }

    public static XQueryFunctionName getFunctionName(XQueryArrowFunctionReference functionReference) {
        if (functionReference.getArrowFunctionSpecifier().getFunctionName() != null) {
            return functionReference.getArrowFunctionSpecifier().getFunctionName();
        }
        return null;
    }

    private static <T extends XQueryPsiElement, U extends XQueryPsiElement>boolean processChildrenIfPlaceIsNotPartOfSameBinding(T module,
            @NotNull PsiScopeProcessor processor, @NotNull ResolveState state, PsiElement lastParent,
            @NotNull PsiElement place, Class<T> bindingClass, Class<U>... childClassesToSkip) {
        PsiElement commonParent = PsiTreeUtil.findCommonParent(place, module);
        T parentBinding = PsiTreeUtil.getParentOfType(place, bindingClass, true);
        XQueryExprSingle expressionInModule = PsiTreeUtil.getChildOfType(module, XQueryExprSingle.class);
        boolean inSameBinding = commonParent != null && commonParent.equals(module) && parentBinding != null && parentBinding.equals(module);
        Collection<T> childBindings = PsiTreeUtil.findChildrenOfType(expressionInModule, bindingClass);
        boolean partOfExpressionInBinding = childBindings.contains(parentBinding);
        if (inSameBinding || partOfExpressionInBinding) {
            return processor.execute(module, state);
        } else {
            if (!processor.execute(module, state)) {
                return false;
            } else {
                return ResolveUtil.processChildren(module, processor, state, lastParent, place, childClassesToSkip);
            }
        }
    }
}