package fr.adrienbrault.idea.symfony2plugin.form.util;

import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.ResolveResult;
import com.intellij.psi.util.PsiElementFilter;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.php.PhpIndex;
import com.jetbrains.php.lang.psi.elements.*;
import com.jetbrains.php.lang.psi.elements.impl.PhpTypedElementImpl;
import fr.adrienbrault.idea.symfony2plugin.Symfony2InterfacesUtil;
import fr.adrienbrault.idea.symfony2plugin.dic.XmlServiceParser;
import fr.adrienbrault.idea.symfony2plugin.form.dict.FormTypeServiceParser;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils;
import fr.adrienbrault.idea.symfony2plugin.util.service.ServiceXmlParserFactory;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;


public class FormUtil {

    @Nullable
    public static PhpClass getFormTypeToClass(Project project,@Nullable String formType) {

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

        // formtype can also be a direct class name
        if(formType.contains("\\")) {
            PhpClass phpClass = PhpElementsUtil.getClass(PhpIndex.getInstance(project), formType);
            if(phpClass != null) {
                return phpClass;
            }
        }

        // find on registered formtype aliases
        FormTypeServiceParser formTypeServiceParser = ServiceXmlParserFactory.getInstance(project, FormTypeServiceParser.class);
        String serviceName = formTypeServiceParser.getFormTypeMap().getServiceName(formType);
        if(serviceName == null) {
            return null;
        }

        String serviceClass = ServiceXmlParserFactory.getInstance(project, XmlServiceParser.class).getServiceMap().getMap().get(serviceName);
        if (null == serviceClass) {
            return null;
        }

        List<ResolveResult> resolveResults = PhpElementsUtil.getClassInterfaceResolveResult(project, serviceClass);
        if(resolveResults.size() == 0) {
            return null;
        }

        PsiElement psiElement = resolveResults.iterator().next().getElement();

        if(psiElement instanceof PhpClass) {
           return (PhpClass) resolveResults.iterator().next().getElement();
        }

        return null;

    }

    public static MethodReference[] getFormBuilderTypes(Method method) {

        final ArrayList<MethodReference> methodReferences = new ArrayList<MethodReference>();

        final Symfony2InterfacesUtil symfony2InterfacesUtil = new Symfony2InterfacesUtil();
        PsiTreeUtil.collectElements(method, new PsiElementFilter() {
            @Override
            public boolean isAccepted(PsiElement psiElement) {

                if (psiElement instanceof MethodReference) {
                    String methodName = ((MethodReference) psiElement).getName();
                    if (methodName != null && (methodName.equals("add") || methodName.equals("create"))) {
                        if(symfony2InterfacesUtil.isFormBuilderFormTypeCall(psiElement)) {
                            methodReferences.add((MethodReference) psiElement);
                            return true;
                        }
                    }
                }

                return false;
            }
        });

        return methodReferences.toArray(new MethodReference[methodReferences.size()]);

    }

    /**
     * $form->get ..
     */
    @Nullable
    public static String resolveFormGetterCall(MethodReference methodReference) {

        // "$form"->get('field_name');
        PhpPsiElement variable = methodReference.getFirstPsiChild();
        if(!(variable instanceof Variable)) {
            return null;
        }

        // find "$form = $this->createForm" createView call
        PsiElement variableDecl = ((Variable) variable).resolve();
        if(variableDecl == null) {
            return null;
        }

        // $form = "$this->createForm(new Type(), $entity)";
        PsiElement assignmentExpression = variableDecl.getParent();
        if(!(assignmentExpression instanceof AssignmentExpression)) {
            return null;
        }

        // $form = "$this->"createForm(new Type(), $entity)";
        PhpPsiElement calledMethodReference = ((AssignmentExpression) assignmentExpression).getValue();
        if(!(calledMethodReference instanceof MethodReference)) {
            return null;
        }

        if(new Symfony2InterfacesUtil().isCallTo(calledMethodReference, "\\Symfony\\Component\\Form\\FormFactory", "create")) {
            return null;
        }

        // $form = "$this->createForm("new Type()", $entity)";
        PsiElement formType = PsiElementUtils.getMethodParameterPsiElementAt((MethodReference) calledMethodReference, 0);
        if(!(formType instanceof PhpTypedElementImpl)) {
            return null;
        }

        return ((PhpTypedElementImpl) formType).getType().toString();

    }

    /**
     * Get form builder field for
     * $form->get('field');
     */
    @Nullable
    public static Method resolveFormGetterCallMethod(MethodReference methodReference) {
        String typeName = FormUtil.resolveFormGetterCall(methodReference);
        if(typeName == null) {
            return null;
        }

        Method method = PhpElementsUtil.getClassMethod(methodReference.getProject(), typeName, "buildForm");
        if(method == null) {
            return null;
        }

        return method;
    }

}

@@ -3596,22 +3596,24 @@
  static 
-String
+PhpClass
  resolve
@@ -4907,168 +4907,60 @@
 0);%0A
-        if(!(formType instanceof PhpTypedElementImpl)) %7B%0A            return null;%0A        %7D%0A%0A        return ((PhpTypedElementImpl) formType).getType().toString(
+%0A        return getFormTypeClassOnParameter(formType
 );%0A%0A
@@ -5158,23 +5158,29 @@
     
-String typeName
+PhpClass formPhpClass
  = F
@@ -5238,24 +5238,28 @@
      if(
-typeName
+formPhpClass
  == null
@@ -5357,114 +5357,630 @@
 hod(
-methodReference.getProject(), typeName, %22buildForm%22);%0A        if(method == null) %7B%0A            return null
+formPhpClass, %22buildForm%22);%0A        if(method == null) %7B%0A            return null;%0A        %7D%0A%0A        return method;%0A    %7D%0A%0A    @Nullable%0A    public static PhpClass getFormTypeClassOnParameter(PsiElement psiElement) %7B%0A%0A        if(psiElement instanceof StringLiteralExpression) %7B%0A            return getFormTypeToClass(psiElement.getProject(), ((StringLiteralExpression) psiElement).getContents());%0A        %7D%0A%0A        if(psiElement instanceof PhpTypedElementImpl) %7B%0A            String typeName = ((PhpTypedElementImpl) psiElement).getType().toString();%0A            return getFormTypeToClass(psiElement.getProject(), typeName)
 ;%0A  
@@ -5995,33 +5995,31 @@
 %0A        return 
-method
+null
 ;%0A    %7D%0A%0A%7D%0A

support form registered as service in form field reference