package com.cedricziel.idea.typo3.extbase.persistence.codeInsight;

import com.cedricziel.idea.typo3.TYPO3CMSProjectSettings;
import com.cedricziel.idea.typo3.extbase.ExtbaseUtils;
import com.cedricziel.idea.typo3.util.ExtbaseUtility;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.openapi.project.Project;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.util.ProcessingContext;
import com.jetbrains.php.PhpIcons;
import com.jetbrains.php.PhpIndex;
import com.jetbrains.php.lang.lexer.PhpTokenTypes;
import com.jetbrains.php.lang.psi.elements.*;
import com.jetbrains.php.lang.psi.resolve.types.PhpType;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.util.Collection;

public class RepositoryMagicMethodsCompletionContributor extends CompletionContributor {
    public RepositoryMagicMethodsCompletionContributor() {
        this.extend(CompletionType.BASIC, PlatformPatterns.psiElement(PhpTokenTypes.IDENTIFIER), new ExtbaseRepositoryMagicMethodsCompletionProvider());
    }

    public static class ExtbaseRepositoryMagicMethodsCompletionProvider extends CompletionProvider<CompletionParameters> {

        protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) {
            if (!TYPO3CMSProjectSettings.getInstance(parameters.getPosition().getProject()).pluginEnabled) {

                return;
            }

            PsiElement position = parameters.getPosition().getOriginalElement();

            if (!(position.getParent() instanceof FieldReference) && !(position.getParent() instanceof MethodReference)) {
                return;
            }

            if (!(position.getParent().getFirstChild() instanceof Variable) && !(position.getParent().getFirstChild() instanceof FieldReference)) {
                return;
            }

            PhpTypedElement variable = (PhpTypedElement) position.getParent().getFirstChild();

            PhpType type = variable.getType();
            Project project = position.getProject();
            type.getTypes().forEach(t -> {
                Collection<PhpClass> classesByFQN = PhpIndex.getInstance(project).getClassesByFQN(t);
                classesByFQN
                        .stream()
                        .filter(ExtbaseUtils::isRepositoryClass)
                        .forEach(c -> createLookupElementsForRepository(project, c, result));
            });
        }

        private void createLookupElementsForRepository(@NotNull Project project,  @NotNull PhpClass repositoryClass, @NotNull CompletionResultSet result) {
            String potentialModelClass = ExtbaseUtility.convertRepositoryFQNToEntityFQN(repositoryClass.getFQN());

            Collection<PhpClass> classesByFQN = PhpIndex.getInstance(project).getClassesByFQN(potentialModelClass);
            classesByFQN.forEach(c -> {
                c.getFields().forEach(f -> {
                    if (!ExtbaseUtils.fieldHasMagicFinders(f)) {
                        return;
                    }

                    // findBy* matches on single fields only and not on collections
                    result.addElement(new LookupElement() {
                        @NotNull
                        @Override
                        public String getLookupString() {
                            return "findBy" + StringUtils.capitalize(f.getName()) + "($" + f.getName() + ")";
                        }

                        @Override
                        public void renderElement(LookupElementPresentation presentation) {
                            super.renderElement(presentation);

                            presentation.setItemText("findBy" + StringUtils.capitalize(f.getName()));
                            presentation.setTypeText("findBy" + StringUtils.capitalize(f.getName()));
                            presentation.setIcon(PhpIcons.METHOD_ICON);

                            presentation.setTailText("(" + f.getName() + " : " + f.getInferredType() + ")", true);
                            presentation.setTypeText(c.getName() + "[]|" + ExtbaseUtils.QUERY_RESULT_INTERFACE);
                        }
                    });

                    // countBy* matches on single fields only and not on collections
                    result.addElement(new LookupElement() {
                        @NotNull
                        @Override
                        public String getLookupString() {
                            return "countBy" + StringUtils.capitalize(f.getName()) + "($" + f.getName() + ")";
                        }

                        @Override
                        public void renderElement(LookupElementPresentation presentation) {
                            super.renderElement(presentation);

                            presentation.setItemText("countBy" + StringUtils.capitalize(f.getName()));
                            presentation.setTypeText("countBy" + StringUtils.capitalize(f.getName()));
                            presentation.setIcon(PhpIcons.METHOD_ICON);
                            presentation.setTailText("(" + f.getName() + " : " + f.getInferredType() + ")", true);
                            presentation.setTypeText("int");
                        }
                    });

                    // findOneBy* matches on single fields only and not on collections
                    result.addElement(new LookupElement() {
                        @NotNull
                        @Override
                        public String getLookupString() {
                            return "findOneBy" + StringUtils.capitalize(f.getName()) + "($" + f.getName() + ")";
                        }

                        @Override
                        public void renderElement(LookupElementPresentation presentation) {
                            super.renderElement(presentation);

                            presentation.setItemText("findOneBy" + StringUtils.capitalize(f.getName()));
                            presentation.setTypeText("findOneBy" + StringUtils.capitalize(f.getName()));
                            presentation.setIcon(PhpIcons.METHOD_ICON);
                            presentation.setTailText("(" + f.getName() + " : " + f.getInferredType() + ")", true);
                            presentation.setTypeText("null|" + c.getName());
                        }
                    });
                });
            });
        }
    }
}