package net.king2500.plugins.PhpAdvancedAutoComplete.utils;

import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.ResolveResult;
import com.intellij.util.ObjectUtils;
import com.jetbrains.php.lang.psi.PhpPsiUtil;
import com.jetbrains.php.lang.psi.elements.*;
import net.king2500.plugins.PhpAdvancedAutoComplete.PhpCompletionTokens;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nullable;

/**
 * @author Thomas Schulz <[email protected]>
 */
public class PhpElementsUtil {

    public static int getParameterIndex(PsiElement paramElement) {
        int index = 0;
        PsiElement element = paramElement;

        while (element != null && element.getPrevSibling() != null) {
            String elementClass = element.getPrevSibling().getClass().getSimpleName();

            if (elementClass.equals("LeafPsiElement")) {
                index++;
            }

            element = element.getPrevSibling();
        }

        return index;
    }

    public static String getCanonicalFuncName(PsiElement caller) {
        if (caller.getReference() instanceof MethodReference) {
            return getMethodName(caller);
        }
        else if (caller.getReference() instanceof FunctionReference) {
            return getFuncName(caller);
        }
        else if (caller instanceof NewExpression) {
            return getClassConstructName(caller);
        }

        return null;
    }

    private static String getFuncName(PsiElement caller) {

        PsiReference psiReference = caller.getReference();

        if (psiReference == null) {
            return null;
        }

        PsiElement resolvedReference = psiReference.resolve();

        if (!(resolvedReference instanceof Function)) {
            return null;
        }

        Function function = (Function)resolvedReference;

        return function.getName();
    }

    private static String getMethodName(PsiElement caller) {

        PsiReference psiReference = caller.getReference();

        if (psiReference == null) {
            return null;
        }

        PsiElement resolvedReference = psiReference.resolve();

        if (!(resolvedReference instanceof Method)) {
            return null;
        }

        Method method = (Method)resolvedReference;
        PhpClass methodClass = method.getContainingClass();

        if (methodClass == null) {
            return null;
        }

        String className = methodClass.getName();
        String methodName = method.getName();

        return className + "::" + methodName;
    }

    private static String getClassConstructName(PsiElement caller) {

        PsiElement[] children = caller.getChildren();

        if (children.length == 0) {
            return null;
        }

        PsiReference psiReference = children[0].getReference();

        if (!(psiReference instanceof ClassReference)) {
            return null;
        }

        PsiElement resolvedReference = psiReference.resolve();

        if (!(resolvedReference instanceof Method)) {
            return null;
        }

        Method method = (Method)resolvedReference;

        if (!method.getName().equals("__construct")) {
            return null;
        }

        PhpClass methodClass = method.getContainingClass();

        if (methodClass == null) {
            return null;
        }

        String className = methodClass.getName();
        return className + "::__construct";
    }


    public static boolean isFormatFunction(String fqn) {
        return StreamEx.of(PhpCompletionTokens.formatFuncFqns).has(fqn);
    }

    @Nullable
    public static String resolveFqn(@Nullable FunctionReference reference) {
        Function function = reference != null ? ObjectUtils.tryCast(reference.resolve(), Function.class) : null;
        return function != null ? function.getFQN() : null;
    }

    @Nullable
    public static Function getFunction(PsiElement position) {
        FunctionReference functionReference = PhpPsiUtil.getParentByCondition(position, true, FunctionReference.INSTANCEOF, Statement.INSTANCEOF);
        if (functionReference != null) {
            return getFunction(functionReference);
        } else {
            NewExpression newExpression = PhpPsiUtil.getParentByCondition(position, true, NewExpression.INSTANCEOF, Statement.INSTANCEOF);
            if (newExpression != null) {
                ClassReference classReference = newExpression.getClassReference();
                if (classReference != null) {
                    return getFunction(classReference);
                }
            }

            return null;
        }
    }

    @Nullable
    private static Function getFunction(PhpReference reference) {
        return StreamEx.of(reference.multiResolve(false)).map(ResolveResult::getElement).select(Function.class).findFirst().orElse(null);
    }
}