package de.espend.idea.php.annotation.util;

import com.intellij.openapi.project.Project;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.php.PhpIndex;
import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment;
import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag;
import com.jetbrains.php.lang.parser.PhpElementTypes;
import com.jetbrains.php.lang.psi.elements.*;
import de.espend.idea.php.annotation.dict.AnnotationTarget;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.Map;

public class PhpElementsUtil {

    @Nullable
    static public PhpClass getClass(Project project, String className) {
        Collection<PhpClass> classes = PhpIndex.getInstance(project).getClassesByFQN(className);
        return classes.isEmpty() ? null : classes.iterator().next();
    }

    @Nullable
    static public AnnotationTarget findAnnotationTarget(@Nullable PhpDocComment phpDocComment) {

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

        if(phpDocComment.getNextPsiSibling() instanceof Method) {
            return AnnotationTarget.METHOD;
        }

        if(PlatformPatterns.psiElement(PhpElementTypes.CLASS_FIELDS).accepts(phpDocComment.getNextPsiSibling())) {
            return AnnotationTarget.PROPERTY;
        }

        if(phpDocComment.getNextPsiSibling() instanceof PhpClass) {
            return AnnotationTarget.CLASS;
        }

        // workaround: if file has no use statements all is wrapped inside a group
        if(phpDocComment.getNextPsiSibling() instanceof GroupStatement) {
            PsiElement groupStatement =  phpDocComment.getNextPsiSibling();
            if(groupStatement != null && groupStatement.getFirstChild() instanceof PhpClass) {
                return AnnotationTarget.CLASS;
            }
        }

        return null;
    }

    @Nullable
    public static <T extends PsiElement> T getPrevSiblingOfPatternMatch(@Nullable PsiElement sibling, ElementPattern<T> pattern) {
        if (sibling == null) return null;
        for (PsiElement child = sibling.getPrevSibling(); child != null; child = child.getPrevSibling()) {
            if (pattern.accepts(child)) {
                //noinspection unchecked
                return (T)child;
            }
        }
        return null;
    }

    @Nullable
    static public PhpClass getClassInterface(Project project, @NotNull String className) {

        // api workaround for at least interfaces
        if(!className.startsWith("\\")) {
            className = "\\" + className;
        }

        Collection<PhpClass> phpClasses = PhpIndex.getInstance(project).getAnyByFQN(className);
        return phpClasses.size() == 0 ? null : phpClasses.iterator().next();
    }

    @Nullable
    public static <T extends PsiElement> T getChildrenOnPatternMatch(@Nullable PsiElement element, ElementPattern<T> pattern) {
        if (element == null) return null;

        for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
            if (pattern.accepts(child)) {
                //noinspection unchecked
                return (T)child;
            }
        }

        return null;
    }

    public static PhpClass getClassInsideAnnotation(StringLiteralExpression phpDocString) {
        return getClassInsideAnnotation(phpDocString, phpDocString.getContents());
    }

    public static PhpClass getClassInsideAnnotation(StringLiteralExpression phpDocString, String modelName) {

        // \ns\Class fine we dont need to resolve classname we are in global context
        if(modelName.startsWith("\\")) {
            return PhpElementsUtil.getClassInterface(phpDocString.getProject(), modelName);
        }

        // try class shortcut: ns\Class
        PhpClass phpClass = PhpElementsUtil.getClassInterface(phpDocString.getProject(), modelName);
        if(phpClass != null) {
            return phpClass;
        }

        PhpDocComment inClass = PsiTreeUtil.getParentOfType(phpDocString, PhpDocComment.class);
        if(inClass == null) {
            return null;
        }

        // doc before class
        PhpPsiElement phpClassElement = inClass.getNextPsiSibling();
        if(phpClassElement instanceof PhpClass) {
            String className = ((PhpClass) phpClassElement).getNamespaceName() + modelName;
            return PhpElementsUtil.getClassInterface(phpDocString.getProject(), className);
        }

        // eg property, method
        PhpClass insidePhpClass = PsiTreeUtil.getParentOfType(phpClassElement, PhpClass.class);
        if(insidePhpClass != null) {
            String className = insidePhpClass.getNamespaceName() + modelName;
            return PhpElementsUtil.getClassInterface(phpDocString.getProject(), className);
        }

        return null;

    }

    @Nullable
    public static PhpClass getClassByContext(PsiElement psiElement, String className) {

        PhpDocTag phpDocTag = PsiTreeUtil.getParentOfType(psiElement, PhpDocTag.class);
        if(phpDocTag == null) {
            return null;
        }

        Map<String, String> map = AnnotationUtil.getUseImportMap(phpDocTag);
        if(map.containsKey(className)) {
            return PhpElementsUtil.getClass(psiElement.getProject(), map.get(className));
        }

        return null;
    }
}

@@ -5003,32 +5003,174 @@
 n null;%0A%0A    %7D%0A%0A
+    /**%0A     * Get a PhpClass on some possible context like namespace or use imports%0A     * TODO there must be some core utils%0A     *%0A     */%0A
     @Nullable%0A  
@@ -5635,33 +5635,344 @@
  %7D%0A%0A        
-return null
+PhpNamespace phpNamespace = PsiTreeUtil.getParentOfType(psiElement, PhpNamespace.class);%0A        if(phpNamespace != null) %7B%0A            return PhpElementsUtil.getClass(psiElement.getProject(), phpNamespace.getFQN() + %22%5C%5C%22 + className);%0A        %7D%0A%0A        return PhpElementsUtil.getClass(psiElement.getProject(), className)
 ;%0A    %7D%0A%7D%0A

add current namespace and class resolving for class name context